2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-16 09:10:33 +03:00

update tests to use mount where required, start new slot docs

This commit is contained in:
Jeff
2019-11-06 13:10:51 -08:00
parent 83c1c795db
commit 218a9e5c99
7 changed files with 134 additions and 160 deletions
-65
View File
@@ -1,65 +0,0 @@
::: tip
Vue Select leverages scoped slots to allow for total customization of the presentation layer.
Slots can be used to change the look and feel of the UI, or to simply swap out text.
:::
## Selected Option(s)
### `selected-option`
#### Scope:
- `option {Object}` - A selected option
```html
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
```
### `selected-option-container`
#### Scope:
- `option {Object}` - A selected option
- `deselect {Function}` - Method used to deselect a given option when `multiple` is true
- `disabled {Boolean}` - Determine if the component is disabled
- `multiple {Boolean}` - If the component supports the selection of multiple values
```html
<slot v-for="option in valueAsArray" name="selected-option-container"
:option="(typeof option === 'object')?option:{[label]: option}" :deselect="deselect" :multiple="multiple" :disabled="disabled">
<span class="selected-tag" v-bind:key="option.index">
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
<span aria-hidden="true">&times;</span>
</button>
</span>
</slot>
```
## Component Actions
### `spinner`
```html
<slot name="spinner">
<div class="spinner" v-show="mutableLoading">Loading...</div>
</slot>
```
## Dropdown
### `option`
#### Scope:
- `option {Object}` - The currently iterated option from `filteredOptions`
```html
<slot name="option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
```
+9 -18
View File
@@ -1,22 +1,13 @@
::: tip 🚧
This section of the guide is a work in progress! Check back soon for an update.
Vue Select currently offers quite a few scoped slots, and you can check out the
[API Docs for Slots](../api/slots.md) in the meantime while a good guide is put together.
:::
## Scoped Slots
#### Scoped Slot `option`
Vue Select offers a number of scoped slots that allow you to customize many parts of the
component for your app. You can make small adjustments with slots, or you can swap out all elements
of the default UI for your own.
vue-select provides the scoped `option` slot in order to create custom dropdown templates.
All of Vue Selects scoped slots follow a similar pattern. Each slot is scoped with an object with at
least two keys: `bindings` and `events`.
```html
<v-select :options="options" label="title">
<template v-slot:option="option">
<span :class="option.icon"></span>
{{ option.title }}
</template>
</v-select>
```
`bindings {Object}` Data that is bound to an element within the slot (HTML attributes, classes, etc)
`events {Object}` Event handlers for elements within the slot
Using the `option` slot with props `"option"` provides the current option variable to the template.
<CodePen url="NXBwYG" height="500"/>
+10 -10
View File
@@ -1,22 +1,22 @@
<script>
export default {
functional: true,
render (_, {data}) {
const svg = {
render (createElement, {data}) {
const path = createElement('path', {
attrs: {
d: 'M6.895455 5l2.842897-2.842898c.348864-.348863.348864-.914488 0-1.263636L9.106534.261648c-.348864-.348864-.914489-.348864-1.263636 0L5 3.104545 2.157102.261648c-.348863-.348864-.914488-.348864-1.263636 0L.261648.893466c-.348864.348864-.348864.914489 0 1.263636L3.104545 5 .261648 7.842898c-.348864.348863-.348864.914488 0 1.263636l.631818.631818c.348864.348864.914773.348864 1.263636 0L5 6.895455l2.842898 2.842897c.348863.348864.914772.348864 1.263636 0l.631818-.631818c.348864-.348864.348864-.914489 0-1.263636L6.895455 5z,',
},
});
const svg = createElement('svg', {
attrs: {
xmlns: 'http://www.w3.org/2000/svg',
width: 10,
height: 10,
},
};
}, [path]);
const path = {
attrs: {
d: 'M6.895455 5l2.842897-2.842898c.348864-.348863.348864-.914488 0-1.263636L9.106534.261648c-.348864-.348864-.914489-.348864-1.263636 0L5 3.104545 2.157102.261648c-.348863-.348864-.914488-.348864-1.263636 0L.261648.893466c-.348864.348864-.348864.914489 0 1.263636L3.104545 5 .261648 7.842898c-.348864.348863-.348864.914488 0 1.263636l.631818.631818c.348864.348864.914773.348864 1.263636 0L5 6.895455l2.842898 2.842897c.348863.348864.914772.348864 1.263636 0l.631818-.631818c.348864-.348864.348864-.914489 0-1.263636L6.895455 5z,',
},
};
return _('button', data, [_('svg', svg, [_('path', path)])]);
return createElement('button', data, [svg]);
},
};
</script>
+74 -43
View File
@@ -3,13 +3,14 @@
<div ref="toggle" @mousedown.prevent="toggleDropdown" class="vs__dropdown-toggle">
<div class="vs__selected-options" ref="selectedOptions">
<slot name="selected-option" v-for="option in scopedValues" v-bind="option">
<span :class="option.bindings.class">
{{ option.label }}
<slot name="selected-option" v-for="selected in scopedValues" v-bind="selected">
<span :class="selected.bindings.class">
{{ selected.label }}
<component
:is="option.deselect.component"
v-bind="option.deselect.bindings"
v-on="option.deselect.events"
:is="selected.deselect.component"
v-if="selected.deselect.bindings.multiple"
v-bind="selected.deselect.bindings"
v-on="selected.deselect.events"
/>
</span>
</slot>
@@ -20,23 +21,24 @@
</div>
<div class="vs__actions">
<button
v-show="showClearButton"
:disabled="disabled"
@click="clearSelection"
type="button"
class="vs__clear"
title="Clear selection"
>
<component :is="childComponents.Deselect" />
</button>
<slot name="clear">
<component
:is="scope.clear.component"
v-bind="scope.clear.bindings"
v-on="scope.clear.events"
/>
</slot>
<slot name="open-indicator" v-bind="scope.openIndicator">
<component :is="childComponents.OpenIndicator" v-if="!noDrop" v-bind="scope.openIndicator.attributes"/>
<component
v-if="!noDrop"
:is="childComponents.OpenIndicator"
v-bind="scope.openIndicator.attributes"
/>
</slot>
<slot name="spinner" v-bind="scope.spinner">
<div class="vs__spinner" v-show="mutableLoading">Loading...</div>
<div :class="scope.spinner.bindings.class" v-show="mutableLoading">Loading...</div>
</slot>
</div>
</div>
@@ -50,8 +52,8 @@
@mousedown.prevent="onMousedown"
@mouseup="onMouseUp"
>
<slot name="option" v-for="{attributes, events, option} in scopedOptions" v-bind="{attributes, events, option}">
<li v-bind="attributes" v-on="events">{{ getOptionLabel(option) }}</li>
<slot name="option" v-for="option in scopedOptions" v-bind="option">
<li v-bind="option.attributes" v-on="option.events">{{ option.label }}</li>
</slot>
<li v-if="!filteredOptions.length" class="vs__no-options" @mousedown.stop="">
<slot name="no-options">Sorry, no matching options.</slot>
@@ -269,30 +271,32 @@
*/
getOptionKey: {
type: Function,
default(option) {
default (option) {
if (typeof option === 'object' && option.id) {
return option.id
return option.id;
} else {
try {
return JSON.stringify(option)
} catch(e) {
return JSON.stringify(option);
} catch (e) {
return console.warn(
`[vue-select warn]: Could not stringify option ` +
`to generate unique key. Please provide'getOptionKey' prop ` +
`to return a unique key for each option.\n` +
'https://vue-select.org/api/props.html#getoptionkey'
)
return null
'https://vue-select.org/api/props.html#getoptionkey',
);
}
}
}
},
},
getDropdownOptionScope: {
getOptionScope: {
type: Function,
default(option, index) {
default (option, index) {
const optionProperties = typeof option === 'object' ? {...option} : {};
return {
option,
...optionProperties,
label: this.getOptionLabel(option),
attributes: {
key: this.getOptionKey(option),
class: {
@@ -308,7 +312,7 @@
e.preventDefault();
e.stopPropagation();
return this.selectable(option) ? this.select(option) : null;
}
},
},
};
},
@@ -316,7 +320,7 @@
getSelectedOptionScope: {
type: Function,
default(option, index) {
default (option, index) {
return {
label: this.getOptionLabel(option),
deselect: this.getOptionDeselectScope(option),
@@ -325,7 +329,7 @@
option: this.normalizeOptionForSlot(option),
deselect: this.deselect,
multiple: this.multiple,
class: "vs__selected",
class: 'vs__selected',
},
events: {
'mouseover': () => this.selectable(option) ? this.typeAheadPointer = index : null,
@@ -333,7 +337,7 @@
e.preventDefault();
e.stopPropagation();
return this.selectable(option) ? this.select(option) : null;
}
},
},
};
},
@@ -341,7 +345,7 @@
getOptionDeselectScope: {
type: Function,
default(option) {
default (option) {
return {
component: childComponents.Deselect,
bindings: {
@@ -353,9 +357,9 @@
},
events: {
'click': () => this.deselect(option),
}
}
}
},
};
},
},
/**
@@ -1017,11 +1021,38 @@
'keydown': this.onSearchKeyDown,
'blur': this.onSearchBlur,
'focus': this.onSearchFocus,
'input': (e) => this.search = e.target.value
'input': (e) => this.search = e.target.value,
},
},
clear: {
component: this.childComponents.Deselect,
bindings: {
'disabled': this.disabled,
'type': 'button',
'class': 'vs__clear',
'title': 'Clear Selection',
'style': { 'display': this.showClearButton ? 'block': 'none' }
},
events: {
'click': () => this.clearSelection(),
},
},
noOptions: {
text: 'Sorry, no options',
options: this.scopedOptions,
search: this.search,
attributes: {
class: 'vs__no-options',
},
events: {
mousedown: e => e.stopPropagation(),
},
},
spinner: {
loading: this.mutableLoading
loading: this.mutableLoading,
bindings: {
'class': 'vs__spinner',
},
},
openIndicator: {
attributes: {
@@ -1123,7 +1154,7 @@
},
scopedOptions() {
return this.filteredOptions.map((option, index) => this.getDropdownOptionScope(this.normalizeOptionForSlot(option), index));
return this.filteredOptions.map((option, index) => this.getOptionScope(option, index));
},
/**
+1 -1
View File
@@ -6,7 +6,7 @@ describe('Components API', () => {
it('swap the Deselect component', () => {
const Deselect = Vue.component('Deselect', {
render (createElement) {
return createElement('button', 'remove');
return createElement('span', 'remove');
},
});
+39 -22
View File
@@ -1,13 +1,22 @@
import { selectWithProps } from "../helpers";
import { mount } from '@vue/test-utils';
import { selectWithProps } from '../helpers';
import VueSelect from '../../src/components/Select.vue';
describe("Removing values", () => {
it("can remove the given tag when its close icon is clicked", () => {
const Select = selectWithProps({ multiple: true });
Select.vm.$data._value = 'one';
const Select = mount(VueSelect, {
propsData: {
multiple: true,
options: ['foo', 'bar'],
value: 'foo',
},
});
Select.find(".vs__deselect").trigger("click");
expect(Select.emitted().input).toEqual([[[]]]);
expect(Select.vm.selectedValue).toEqual([]);
const deselect = jest.spyOn(Select.vm, 'deselect');
Select.find("button.vs__deselect").trigger("click");
expect(deselect).toHaveBeenCalled();
});
it("should not remove tag when close icon is clicked and component is disabled", () => {
@@ -55,36 +64,44 @@ describe("Removing values", () => {
});
expect(Select.vm.showClearButton).toEqual(true);
expect(Select.find('.vs__clear').isVisible()).toBe(true);
});
it("should not be displayed on multiple select", () => {
const Select = selectWithProps({
options: ["foo", "bar"],
value: "foo",
multiple: true
const Select = mount(VueSelect, {
propsData: {
multiple: true,
options: ['foo', 'bar'],
value: 'foo',
},
});
expect(Select.vm.showClearButton).toEqual(false);
expect(Select.find('.vs__clear').isVisible()).toBe(false);
});
it("should remove selected value when clicked", () => {
const Select = selectWithProps({
options: ["foo", "bar"],
it("should remove selected value when clicked", async () => {
const Select = mount(VueSelect, {
propsData: {
options: ['foo', 'bar'],
value: 'foo',
},
});
Select.vm.$data._value = 'foo';
expect(Select.vm.selectedValue).toEqual(["foo"]);
Select.find("button.vs__clear").trigger("click");
const spy = jest.spyOn(Select.vm, 'clearSelection');
expect(Select.emitted().input).toEqual([[null]]);
expect(Select.vm.selectedValue).toEqual([]);
Select.find('button.vs__clear').trigger("click");
expect(spy).toHaveBeenCalled();
});
it("should be disabled when component is disabled", () => {
const Select = selectWithProps({
options: ["foo", "bar"],
value: "foo",
disabled: true
const Select = mount(VueSelect, {
propsData: {
disabled: true,
options: ['foo', 'bar'],
value: 'foo',
},
});
expect(Select.find("button.vs__clear").attributes().disabled).toEqual(
+1 -1
View File
@@ -6,7 +6,7 @@ describe('Scoped Slots', () => {
{value: 'one'},
{
scopedSlots: {
'selected-option': `<span slot="selected-option" slot-scope="option">{{ option.label }}</span>`,
'selected-option': `<span class="vs__selected" slot="selected-option" slot-scope="option">{{ option.label }}</span>`,
},
});