mirror of
https://github.com/tenrok/vue-select.git
synced 2026-06-22 10:30:34 +03:00
merge master, resolve upstream conflicts
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
github: [sagalbot]
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
*
|
||||||
|
!src/**/*
|
||||||
|
!dist/**/*
|
||||||
|
.DS_Store
|
||||||
+30
-4
@@ -1,8 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app" class="font-sans px-5 py-5">
|
<div id="app" class="font-sans px-5 py-5">
|
||||||
<v-select :options="countries"/>
|
<v-select v-bind="jest" />
|
||||||
<!-- <ListSelect :options="countries" />-->
|
<!-- <ListSelect :options="countries" />-->
|
||||||
<!-- <paginated />-->
|
<!-- <paginated />-->
|
||||||
|
|
||||||
|
<!-- <v-select multiple :options="books" label="title">-->
|
||||||
|
<!-- <template #selected-option="selected">-->
|
||||||
|
<!-- <span :class="selected.bindings.class">-->
|
||||||
|
<!-- {{ selected.title }} {{ selected.author.firstName }} {{ selected.author.lastName }}-->
|
||||||
|
<!-- <component-->
|
||||||
|
<!-- ref="deselectButtons"-->
|
||||||
|
<!-- :is="selected.deselect.component"-->
|
||||||
|
<!-- v-if="selected.deselect.bindings.multiple"-->
|
||||||
|
<!-- v-bind="selected.deselect.bindings"-->
|
||||||
|
<!-- v-on="selected.deselect.events"-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- </span>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- <template #option="{ title, author, attributes, events }">-->
|
||||||
|
<!-- <li v-bind="attributes" v-on="events">{{ title }} {{ author.firstName }} {{-->
|
||||||
|
<!-- author.lastName }}-->
|
||||||
|
<!-- </li>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- </v-select>-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -15,11 +35,17 @@ import Sandbox from '../docs/.vuepress/components/Sandbox';
|
|||||||
import countries from '../docs/.vuepress/data/countryCodes';
|
import countries from '../docs/.vuepress/data/countryCodes';
|
||||||
import books from '../docs/.vuepress/data/books';
|
import books from '../docs/.vuepress/data/books';
|
||||||
|
|
||||||
|
const randomBook = (id) => {
|
||||||
|
const rand = Math.floor(Math.random() * Math.floor(books.length - 1));
|
||||||
|
return {...books[rand], id};
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {vSelect, Sandbox, ListSelect, Paginated},
|
components: {vSelect, Sandbox, ListSelect, Paginated},
|
||||||
computed: {
|
computed: {
|
||||||
countries: () => countries,
|
countries: () => countries,
|
||||||
books: () => books,
|
books: () => Array(100).fill(0).map((num, index) => randomBook(index)),
|
||||||
|
jest: () => ({value: [{label: "one"}], options: [{label: "one"}]}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<v-select
|
||||||
|
multiple
|
||||||
|
placeholder="Choose up to 3 books!"
|
||||||
|
label="title"
|
||||||
|
v-model="selected"
|
||||||
|
:options="books"
|
||||||
|
:selectable="() => selected.length < 3"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import books from '../data/books';
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return { selected: [] }
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
books: () => books,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<v-select
|
||||||
|
placeholder="Choose a book to read"
|
||||||
|
label="title"
|
||||||
|
:options="books"
|
||||||
|
:selectable="option => ! option.author.lastName.includes('Woodhouse')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import books from '../data/books';
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
books: () => books,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -118,6 +118,7 @@ module.exports = {
|
|||||||
collapsable: false,
|
collapsable: false,
|
||||||
children: [
|
children: [
|
||||||
['guide/validation', 'Validation'],
|
['guide/validation', 'Validation'],
|
||||||
|
['guide/selectable', 'Limiting Selections'],
|
||||||
['guide/vuex', 'Vuex'],
|
['guide/vuex', 'Vuex'],
|
||||||
['guide/ajax', 'AJAX'],
|
['guide/ajax', 'AJAX'],
|
||||||
['guide/loops', 'Using in Loops'],
|
['guide/loops', 'Using in Loops'],
|
||||||
|
|||||||
+13
-3
@@ -339,12 +339,22 @@ createOption: {
|
|||||||
|
|
||||||
## resetOnOptionsChange
|
## resetOnOptionsChange
|
||||||
|
|
||||||
When false, updating the options will not reset the select value
|
When false, updating the options will not reset the selected value.
|
||||||
|
|
||||||
|
Since `v3.4+` the prop accepts either a `boolean` or `function` that returns a `boolean`.
|
||||||
|
|
||||||
|
If defined as a function, it will receive the params listed below.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
/**
|
||||||
|
* @type {Boolean|Function}
|
||||||
|
* @param {Array} newOptions
|
||||||
|
* @param {Array} oldOptions
|
||||||
|
* @param {Array} selectedValue
|
||||||
|
*/
|
||||||
resetOnOptionsChange: {
|
resetOnOptionsChange: {
|
||||||
type: Boolean,
|
default: false,
|
||||||
default: false
|
validator: (value) => ['function', 'boolean'].includes(typeof value)
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
## Selectable Prop <Badge text="v3.3.0+" />
|
||||||
|
|
||||||
|
The `selectable` prop determines if an option is selectable or not. If `selectable` returns false
|
||||||
|
for a given option, it will be displayed with a `vs__dropdown-option--disabled` class. The option
|
||||||
|
will be disabled and unable to be selected.
|
||||||
|
|
||||||
|
```js
|
||||||
|
selectable: {
|
||||||
|
type: Function,
|
||||||
|
/**
|
||||||
|
* @param {Object|String} option
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
default: option => true,
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
Here `selectable` is used to prevent books by a certain author from being chosen. In this case,
|
||||||
|
the options passed to the component are objects:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
title: "Right Ho Jeeves",
|
||||||
|
author: { firstName: "P.D", lastName: "Woodhouse" },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This object will be passed to `selectable`, so we can check if the author should be selectable or not.
|
||||||
|
|
||||||
|
<UnselectableExample />
|
||||||
|
|
||||||
|
<<< @/.vuepress/components/UnselectableExample.vue{6}
|
||||||
|
|
||||||
|
## Limiting the Number of Selections
|
||||||
|
|
||||||
|
`selectable` can also be used a bit more creatively to limit the number selections that can be made
|
||||||
|
within the component. In this case, the user can select any author, but may only select a maximum
|
||||||
|
of three books.
|
||||||
|
|
||||||
|
<LimitSelectionQuantity />
|
||||||
|
|
||||||
|
<<< @/.vuepress/components/LimitSelectionQuantity.vue{8}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-select",
|
"name": "vue-select",
|
||||||
"version": "3.2.0",
|
"version": "3.4.0",
|
||||||
"description": "Everything you wish the HTML <select> element could do, wrapped up into a lightweight, extensible Vue component.",
|
"description": "Everything you wish the HTML <select> element could do, wrapped up into a lightweight, extensible Vue component.",
|
||||||
"author": "Jeff Sagal <sagalbot@gmail.com>",
|
"author": "Jeff Sagal <sagalbot@gmail.com>",
|
||||||
"homepage": "https://vue-select.org",
|
"homepage": "https://vue-select.org",
|
||||||
|
|||||||
+47
-39
@@ -7,10 +7,11 @@
|
|||||||
<span :class="selected.bindings.class">
|
<span :class="selected.bindings.class">
|
||||||
{{ selected.label }}
|
{{ selected.label }}
|
||||||
<component
|
<component
|
||||||
:is="selected.deselect.component"
|
ref="deselectButtons"
|
||||||
v-if="selected.deselect.bindings.multiple"
|
:is="selected.deselect.component"
|
||||||
v-bind="selected.deselect.bindings"
|
v-if="selected.deselect.bindings.multiple"
|
||||||
v-on="selected.deselect.events"
|
v-bind="selected.deselect.bindings"
|
||||||
|
v-on="selected.deselect.events"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
<div class="vs__actions" ref="actions">
|
<div class="vs__actions" ref="actions">
|
||||||
<slot name="clear">
|
<slot name="clear">
|
||||||
<component
|
<component
|
||||||
|
ref="clearButton"
|
||||||
:is="scope.clear.component"
|
:is="scope.clear.component"
|
||||||
v-bind="scope.clear.bindings"
|
v-bind="scope.clear.bindings"
|
||||||
v-on="scope.clear.events"
|
v-on="scope.clear.events"
|
||||||
@@ -66,6 +68,8 @@
|
|||||||
import ajax from '../mixins/ajax'
|
import ajax from '../mixins/ajax'
|
||||||
import childComponents from './childComponents';
|
import childComponents from './childComponents';
|
||||||
|
|
||||||
|
const spreadableOptionProperties = (option) => typeof option === 'object' ? {...option} : {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name VueSelect
|
* @name VueSelect
|
||||||
*/
|
*/
|
||||||
@@ -215,10 +219,11 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decides wether an option is selectable or not. Not selectable options
|
* Decides whether an option is selectable or not. Not selectable options
|
||||||
* are displayed but disabled and cannot be selected.
|
* are displayed but disabled and cannot be selected.
|
||||||
*
|
*
|
||||||
* @type {Function}
|
* @type {Function}
|
||||||
|
* @since 3.3.0
|
||||||
* @param {Object|String} option
|
* @param {Object|String} option
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
@@ -292,10 +297,8 @@
|
|||||||
getOptionScope: {
|
getOptionScope: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default (option, index) {
|
default (option, index) {
|
||||||
const optionProperties = typeof option === 'object' ? {...option} : {};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...optionProperties,
|
...spreadableOptionProperties(option),
|
||||||
label: this.getOptionLabel(option),
|
label: this.getOptionLabel(option),
|
||||||
attributes: {
|
attributes: {
|
||||||
key: this.getOptionKey(option),
|
key: this.getOptionKey(option),
|
||||||
@@ -322,6 +325,7 @@
|
|||||||
type: Function,
|
type: Function,
|
||||||
default (option, index) {
|
default (option, index) {
|
||||||
return {
|
return {
|
||||||
|
...spreadableOptionProperties(option),
|
||||||
label: this.getOptionLabel(option),
|
label: this.getOptionLabel(option),
|
||||||
deselect: this.getOptionDeselectScope(option),
|
deselect: this.getOptionDeselectScope(option),
|
||||||
bindings: {
|
bindings: {
|
||||||
@@ -354,6 +358,7 @@
|
|||||||
'aria-label': `Deselect ${this.getOptionLabel(option)}`,
|
'aria-label': `Deselect ${this.getOptionLabel(option)}`,
|
||||||
'disabled': this.disabled,
|
'disabled': this.disabled,
|
||||||
'multiple': this.multiple,
|
'multiple': this.multiple,
|
||||||
|
'ref': 'deselectButtons'
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
'click': () => this.deselect(option),
|
'click': () => this.deselect(option),
|
||||||
@@ -467,12 +472,20 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When false, updating the options will not reset the select value
|
* When false, updating the options will not reset the selected value. Accepts
|
||||||
* @type {Boolean}
|
* a `boolean` or `function` that returns a `boolean`. If defined as a function,
|
||||||
|
* it will receive the params listed below.
|
||||||
|
*
|
||||||
|
* @since 3.4 - Type changed to {Boolean|Function}
|
||||||
|
*
|
||||||
|
* @type {Boolean|Function}
|
||||||
|
* @param {Array} newOptions
|
||||||
|
* @param {Array} oldOptions
|
||||||
|
* @param {Array} selectedValue
|
||||||
*/
|
*/
|
||||||
resetOnOptionsChange: {
|
resetOnOptionsChange: {
|
||||||
type: Boolean,
|
default: false,
|
||||||
default: false
|
validator: (value) => ['function', 'boolean'].includes(typeof value)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -571,13 +584,17 @@
|
|||||||
* is correct.
|
* is correct.
|
||||||
* @return {[type]} [description]
|
* @return {[type]} [description]
|
||||||
*/
|
*/
|
||||||
options(val) {
|
options (newOptions, oldOptions) {
|
||||||
if (!this.taggable && this.resetOnOptionsChange) {
|
let shouldReset = () => typeof this.resetOnOptionsChange === 'function'
|
||||||
this.clearSelection()
|
? this.resetOnOptionsChange(newOptions, oldOptions, this.selectedValue)
|
||||||
|
: this.resetOnOptionsChange;
|
||||||
|
|
||||||
|
if (!this.taggable && shouldReset()) {
|
||||||
|
this.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.value && this.isTrackingValues) {
|
if (this.value && this.isTrackingValues) {
|
||||||
this.setInternalValueFromOptions(this.value)
|
this.setInternalValueFromOptions(this.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -712,33 +729,23 @@
|
|||||||
* @param {Event} e
|
* @param {Event} e
|
||||||
* @return {void}
|
* @return {void}
|
||||||
*/
|
*/
|
||||||
toggleDropdown (e) {
|
toggleDropdown ({target}) {
|
||||||
const target = e.target;
|
// don't react to click on deselect/clear buttons,
|
||||||
const toggleTargets = [
|
// they dropdown state will be set in their click handlers
|
||||||
this.$el,
|
const ignoredButtons = [
|
||||||
this.searchEl,
|
...(this.$refs['deselectButtons'] || []),
|
||||||
this.$refs.toggle,
|
...([this.$refs['clearButton']] || [])
|
||||||
this.$refs.actions,
|
|
||||||
this.$refs.selectedOptions,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (typeof this.$refs.openIndicator !== 'undefined') {
|
if (ignoredButtons.some(ref => ref.contains(target) || ref === target)) {
|
||||||
toggleTargets.push(
|
return;
|
||||||
this.$refs.openIndicator.$el,
|
|
||||||
// the line below is a bit gross, but required to support IE11 without adding polyfills
|
|
||||||
...Array.prototype.slice.call(this.$refs.openIndicator.$el.childNodes),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toggleTargets.indexOf(target) > -1 || target.classList.contains('vs__selected')) {
|
if (this.open) {
|
||||||
if (this.open) {
|
this.searchEl.blur();
|
||||||
this.searchEl.blur(); // dropdown will close on blur
|
} else if (!this.disabled) {
|
||||||
} else {
|
this.open = true;
|
||||||
if (!this.disabled) {
|
this.searchEl.focus();
|
||||||
this.open = true;
|
|
||||||
this.searchEl.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1061,6 +1068,7 @@
|
|||||||
clear: {
|
clear: {
|
||||||
component: this.childComponents.Deselect,
|
component: this.childComponents.Deselect,
|
||||||
bindings: {
|
bindings: {
|
||||||
|
'ref': 'clearButton',
|
||||||
'disabled': this.disabled,
|
'disabled': this.disabled,
|
||||||
'type': 'button',
|
'type': 'button',
|
||||||
'class': 'vs__clear',
|
'class': 'vs__clear',
|
||||||
|
|||||||
+9
-8
@@ -1,5 +1,5 @@
|
|||||||
import { shallowMount } from "@vue/test-utils";
|
import { mount, shallowMount } from '@vue/test-utils';
|
||||||
import VueSelect from "../src/components/Select.vue";
|
import VueSelect from '../src/components/Select.vue';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,7 +13,7 @@ export const searchSubmit = (Wrapper, searchText = false) => {
|
|||||||
if (searchText) {
|
if (searchText) {
|
||||||
Wrapper.vm.search = searchText;
|
Wrapper.vm.search = searchText;
|
||||||
}
|
}
|
||||||
Wrapper.find({ ref: "search" }).trigger("keydown.enter")
|
Wrapper.find({ref: 'search'}).trigger('keydown.enter');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,9 +22,11 @@ export const searchSubmit = (Wrapper, searchText = false) => {
|
|||||||
* @param propsData
|
* @param propsData
|
||||||
* @returns {Wrapper<Vue>}
|
* @returns {Wrapper<Vue>}
|
||||||
*/
|
*/
|
||||||
export const selectWithProps = (propsData = {}) => {
|
export const selectWithProps = (propsData = {}) =>
|
||||||
return shallowMount(VueSelect, { propsData });
|
shallowMount(VueSelect, {propsData});
|
||||||
};
|
|
||||||
|
export const mountWithProps = (propsData = {}) =>
|
||||||
|
mount(VueSelect, {propsData});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Wrapper with a v-select component.
|
* Returns a Wrapper with a v-select component.
|
||||||
@@ -42,7 +44,6 @@ export const mountDefault = (props = {}, options = {}) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a v-select component directly.
|
* Returns a v-select component directly.
|
||||||
* @param props
|
* @param props
|
||||||
@@ -54,7 +55,7 @@ export const mountWithoutTestUtils = (props = {}, options = {}) => {
|
|||||||
render: createEl => createEl('vue-select', {
|
render: createEl => createEl('vue-select', {
|
||||||
ref: 'select',
|
ref: 'select',
|
||||||
props: {options: ['one', 'two', 'three'], ...props},
|
props: {options: ['one', 'two', 'three'], ...props},
|
||||||
...options
|
...options,
|
||||||
}),
|
}),
|
||||||
components: {VueSelect},
|
components: {VueSelect},
|
||||||
}).$mount().$refs.select;
|
}).$mount().$refs.select;
|
||||||
|
|||||||
+36
-24
@@ -1,25 +1,37 @@
|
|||||||
import { selectWithProps } from "../helpers";
|
import { mountWithProps } from "../helpers";
|
||||||
import OpenIndicator from "../../src/components/OpenIndicator";
|
import OpenIndicator from "../../src/components/OpenIndicator";
|
||||||
|
|
||||||
describe("Toggling Dropdown", () => {
|
describe("Toggling Dropdown", () => {
|
||||||
it("should not open the dropdown when the el is clicked but the component is disabled", () => {
|
it("should open the dropdown when the el is clicked", async () => {
|
||||||
const Select = selectWithProps({ disabled: true });
|
const Select = mountWithProps({
|
||||||
Select.vm.toggleDropdown({ target: Select.vm.$refs.search });
|
|
||||||
expect(Select.vm.open).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should open the dropdown when the el is clicked", () => {
|
|
||||||
const Select = selectWithProps({
|
|
||||||
value: [{ label: "one" }],
|
value: [{ label: "one" }],
|
||||||
options: [{ label: "one" }]
|
options: [{ label: "one" }]
|
||||||
});
|
});
|
||||||
|
const spy = jest.spyOn(Select.vm, 'toggleDropdown');
|
||||||
|
|
||||||
Select.vm.toggleDropdown({ target: Select.vm.$refs.search });
|
Select.find({ref: 'toggle'}).trigger('mousedown');
|
||||||
|
|
||||||
|
await Select.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
expect(Select.vm.open).toEqual(true);
|
expect(Select.vm.open).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not open the dropdown when the el is clicked but the component is disabled", async () => {
|
||||||
|
const Select = mountWithProps({ disabled: true });
|
||||||
|
|
||||||
|
const spy = jest.spyOn(Select.vm, 'toggleDropdown');
|
||||||
|
|
||||||
|
Select.find({ref: 'toggle'}).trigger('mousedown');
|
||||||
|
|
||||||
|
await Select.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
expect(Select.vm.open).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("should open the dropdown when the selected tag is clicked", () => {
|
it("should open the dropdown when the selected tag is clicked", () => {
|
||||||
const Select = selectWithProps({
|
const Select = mountWithProps({
|
||||||
value: [{ label: "one" }],
|
value: [{ label: "one" }],
|
||||||
options: [{ label: "one" }]
|
options: [{ label: "one" }]
|
||||||
});
|
});
|
||||||
@@ -31,7 +43,7 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("can close the dropdown when the el is clicked", () => {
|
it("can close the dropdown when the el is clicked", () => {
|
||||||
const Select = selectWithProps();
|
const Select = mountWithProps();
|
||||||
const spy = jest.spyOn(Select.vm.$refs.search, "blur");
|
const spy = jest.spyOn(Select.vm.$refs.search, "blur");
|
||||||
|
|
||||||
Select.vm.open = true;
|
Select.vm.open = true;
|
||||||
@@ -41,7 +53,7 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("closes the dropdown when an option is selected, multiple is true, and closeOnSelect option is true", () => {
|
it("closes the dropdown when an option is selected, multiple is true, and closeOnSelect option is true", () => {
|
||||||
const Select = selectWithProps({
|
const Select = mountWithProps({
|
||||||
value: [],
|
value: [],
|
||||||
options: ["one", "two", "three"],
|
options: ["one", "two", "three"],
|
||||||
multiple: true
|
multiple: true
|
||||||
@@ -54,7 +66,7 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not close the dropdown when the el is clicked, multiple is true, and closeOnSelect option is false", () => {
|
it("does not close the dropdown when the el is clicked, multiple is true, and closeOnSelect option is false", () => {
|
||||||
const Select = selectWithProps({
|
const Select = mountWithProps({
|
||||||
value: [],
|
value: [],
|
||||||
options: ["one", "two", "three"],
|
options: ["one", "two", "three"],
|
||||||
multiple: true,
|
multiple: true,
|
||||||
@@ -68,7 +80,7 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should close the dropdown on search blur", () => {
|
it("should close the dropdown on search blur", () => {
|
||||||
const Select = selectWithProps({
|
const Select = mountWithProps({
|
||||||
options: [{ label: "one" }]
|
options: [{ label: "one" }]
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,7 +91,7 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("will close the dropdown and emit the search:blur event from onSearchBlur", () => {
|
it("will close the dropdown and emit the search:blur event from onSearchBlur", () => {
|
||||||
const Select = selectWithProps();
|
const Select = mountWithProps();
|
||||||
const spy = jest.spyOn(Select.vm, "$emit");
|
const spy = jest.spyOn(Select.vm, "$emit");
|
||||||
|
|
||||||
Select.vm.open = true;
|
Select.vm.open = true;
|
||||||
@@ -90,7 +102,7 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("will open the dropdown and emit the search:focus event from onSearchFocus", () => {
|
it("will open the dropdown and emit the search:focus event from onSearchFocus", () => {
|
||||||
const Select = selectWithProps();
|
const Select = mountWithProps();
|
||||||
const spy = jest.spyOn(Select.vm, "$emit");
|
const spy = jest.spyOn(Select.vm, "$emit");
|
||||||
|
|
||||||
Select.vm.onSearchFocus();
|
Select.vm.onSearchFocus();
|
||||||
@@ -100,7 +112,7 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("will close the dropdown on escape, if search is empty", () => {
|
it("will close the dropdown on escape, if search is empty", () => {
|
||||||
const Select = selectWithProps();
|
const Select = mountWithProps();
|
||||||
const spy = jest.spyOn(Select.vm.$refs.search, "blur");
|
const spy = jest.spyOn(Select.vm.$refs.search, "blur");
|
||||||
|
|
||||||
Select.vm.open = true;
|
Select.vm.open = true;
|
||||||
@@ -110,7 +122,7 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should remove existing search text on escape keydown", () => {
|
it("should remove existing search text on escape keydown", () => {
|
||||||
const Select = selectWithProps({
|
const Select = mountWithProps({
|
||||||
value: [{ label: "one" }],
|
value: [{ label: "one" }],
|
||||||
options: [{ label: "one" }]
|
options: [{ label: "one" }]
|
||||||
});
|
});
|
||||||
@@ -121,7 +133,7 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should have an open class when dropdown is active", () => {
|
it("should have an open class when dropdown is active", () => {
|
||||||
const Select = selectWithProps();
|
const Select = mountWithProps();
|
||||||
|
|
||||||
expect(Select.vm.stateClasses['vs--open']).toEqual(false);
|
expect(Select.vm.stateClasses['vs--open']).toEqual(false);
|
||||||
|
|
||||||
@@ -130,7 +142,7 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not display the dropdown if noDrop is true", () => {
|
it("should not display the dropdown if noDrop is true", () => {
|
||||||
const Select = selectWithProps({
|
const Select = mountWithProps({
|
||||||
noDrop: true,
|
noDrop: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,21 +153,21 @@ describe("Toggling Dropdown", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should hide the open indicator if noDrop is true", () => {
|
it("should hide the open indicator if noDrop is true", () => {
|
||||||
const Select = selectWithProps({
|
const Select = mountWithProps({
|
||||||
noDrop: true,
|
noDrop: true,
|
||||||
});
|
});
|
||||||
expect(Select.contains(OpenIndicator)).toBeFalsy();
|
expect(Select.contains(OpenIndicator)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not add the searchable state class when noDrop is true", () => {
|
it("should not add the searchable state class when noDrop is true", () => {
|
||||||
const Select = selectWithProps({
|
const Select = mountWithProps({
|
||||||
noDrop: true,
|
noDrop: true,
|
||||||
});
|
});
|
||||||
expect(Select.classes('vs--searchable')).toBeFalsy();
|
expect(Select.classes('vs--searchable')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not add the searching state class when noDrop is true", () => {
|
it("should not add the searching state class when noDrop is true", () => {
|
||||||
const Select = selectWithProps({
|
const Select = mountWithProps({
|
||||||
noDrop: true,
|
noDrop: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { shallowMount } from "@vue/test-utils";
|
import { mount, shallowMount } from '@vue/test-utils';
|
||||||
import VueSelect from "../../src/components/Select";
|
import VueSelect from "../../src/components/Select";
|
||||||
|
import { mountDefault } from '../helpers';
|
||||||
|
|
||||||
describe("Reset on options change", () => {
|
describe("Reset on options change", () => {
|
||||||
it("should not reset the selected value by default when the options property changes", () => {
|
it("should not reset the selected value by default when the options property changes", () => {
|
||||||
@@ -13,6 +14,73 @@ describe("Reset on options change", () => {
|
|||||||
expect(Select.vm.selectedValue).toEqual(["one"]);
|
expect(Select.vm.selectedValue).toEqual(["one"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('resetOnOptionsChange as a function', () => {
|
||||||
|
it('will yell at you if resetOnOptionsChange is not a function or boolean', () => {
|
||||||
|
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
|
mountDefault({resetOnOptionsChange: 1});
|
||||||
|
expect(spy.mock.calls[0][0]).toContain('Invalid prop: custom validator check failed for prop "resetOnOptionsChange"')
|
||||||
|
|
||||||
|
mountDefault({resetOnOptionsChange: 'one'});
|
||||||
|
expect(spy.mock.calls[1][0]).toContain('Invalid prop: custom validator check failed for prop "resetOnOptionsChange"')
|
||||||
|
|
||||||
|
mountDefault({resetOnOptionsChange: []});
|
||||||
|
expect(spy.mock.calls[2][0]).toContain('Invalid prop: custom validator check failed for prop "resetOnOptionsChange"')
|
||||||
|
|
||||||
|
mountDefault({resetOnOptionsChange: {}});
|
||||||
|
expect(spy.mock.calls[3][0]).toContain('Invalid prop: custom validator check failed for prop "resetOnOptionsChange"')
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should receive the new options, old options, and current value', () => {
|
||||||
|
let resetOnOptionsChange = jest.fn(option => option);
|
||||||
|
const Select = mountDefault(
|
||||||
|
{resetOnOptionsChange, options: ['bear'], value: 'selected'},
|
||||||
|
);
|
||||||
|
|
||||||
|
Select.setProps({options: ['lake', 'kite']});
|
||||||
|
|
||||||
|
expect(resetOnOptionsChange).toHaveBeenCalledTimes(1);
|
||||||
|
expect(resetOnOptionsChange)
|
||||||
|
.toHaveBeenCalledWith(['lake', 'kite'], ['bear'], ['selected']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow resetOnOptionsChange to be a function that returns true', () => {
|
||||||
|
let resetOnOptionsChange = () => true;
|
||||||
|
const Select = shallowMount(VueSelect, {
|
||||||
|
propsData: {resetOnOptionsChange, options: ['one'], value: 'one'},
|
||||||
|
});
|
||||||
|
const spy = jest.spyOn(Select.vm, 'clearSelection');
|
||||||
|
|
||||||
|
Select.setProps({options: ['one', 'two']});
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow resetOnOptionsChange to be a function that returns false', () => {
|
||||||
|
let resetOnOptionsChange = () => false;
|
||||||
|
const Select = shallowMount(VueSelect, {
|
||||||
|
propsData: {resetOnOptionsChange, options: ['one'], value: 'one'},
|
||||||
|
});
|
||||||
|
const spy = jest.spyOn(Select.vm, 'clearSelection');
|
||||||
|
|
||||||
|
Select.setProps({options: ['one', 'two']});
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset the options if the selectedValue does not exist in the new options', async () => {
|
||||||
|
let resetOnOptionsChange = (options, old, val) => val.some(val => options.includes(val));
|
||||||
|
const Select = shallowMount(VueSelect, {
|
||||||
|
propsData: {resetOnOptionsChange, options: ['one'], value: 'one'},
|
||||||
|
});
|
||||||
|
const spy = jest.spyOn(Select.vm, 'clearSelection');
|
||||||
|
|
||||||
|
Select.setProps({options: ['one', 'two']});
|
||||||
|
expect(Select.vm.selectedValue).toEqual(['one']);
|
||||||
|
|
||||||
|
Select.setProps({options: ['two']});
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should reset the selected value when the options property changes", () => {
|
it("should reset the selected value when the options property changes", () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = shallowMount(VueSelect, {
|
||||||
propsData: { resetOnOptionsChange: true, options: ["one"] }
|
propsData: { resetOnOptionsChange: true, options: ["one"] }
|
||||||
|
|||||||
+45
-21
@@ -1,29 +1,53 @@
|
|||||||
import { mountDefault } from '../helpers';
|
import { mountDefault } from '../helpers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Breaking change to the slot signature: {option} is no longer a valid key
|
||||||
|
* Breaking change: removed selected-option-container
|
||||||
|
*/
|
||||||
|
|
||||||
describe('Scoped Slots', () => {
|
describe('Scoped Slots', () => {
|
||||||
it('receives an option object to the selected-option slot', () => {
|
let receiveProps = props => receivedSlotProps = props;
|
||||||
const Select = mountDefault(
|
let receivedSlotProps;
|
||||||
{value: 'one'},
|
|
||||||
{
|
|
||||||
scopedSlots: {
|
|
||||||
'selected-option': `<span class="vs__selected" slot="selected-option" slot-scope="option">{{ option.label }}</span>`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(Select.find('.vs__selected').text()).toEqual('one')
|
beforeEach(() => receivedSlotProps = null);
|
||||||
|
|
||||||
|
describe('Slot: selected-option', () => {
|
||||||
|
it('receives an option object in the selected-option slot', () => {
|
||||||
|
mountDefault(
|
||||||
|
{value: 'one'},
|
||||||
|
{scopedSlots: {'selected-option': receiveProps}},
|
||||||
|
);
|
||||||
|
expect(receivedSlotProps.label).toEqual('one');
|
||||||
|
expect(receivedSlotProps.hasOwnProperty('bindings')).toBeTruthy();
|
||||||
|
expect(receivedSlotProps.hasOwnProperty('events')).toBeTruthy();
|
||||||
|
expect(receivedSlotProps.hasOwnProperty('deselect')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
xit('opens the dropdown when clicked', () => {
|
||||||
|
const Select = mountDefault(
|
||||||
|
{value: 'one'},
|
||||||
|
{
|
||||||
|
scopedSlots: {
|
||||||
|
'selected-option': `<span class="my-option" slot-scope="option">{{ option.label }}</span>`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Select.find('.my-option').trigger('mousedown');
|
||||||
|
expect(Select.vm.open).toEqual(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('receives an option object to the option slot in the dropdown menu', () => {
|
describe('Slot: option', () => {
|
||||||
const Select = mountDefault(
|
it('receives an option object in the option slot', () => {
|
||||||
{value: 'one'},
|
const {vm} = mountDefault(
|
||||||
{
|
{value: 'one', options: ['one']},
|
||||||
scopedSlots: {
|
{scopedSlots: {option: receiveProps}},
|
||||||
'option': `<span slot="option" slot-scope="option">{{ option.label }}</span>`,
|
);
|
||||||
},
|
vm.open = true;
|
||||||
});
|
expect(receivedSlotProps.label).toEqual('one');
|
||||||
|
expect(receivedSlotProps.hasOwnProperty('attributes')).toBeTruthy();
|
||||||
Select.vm.open = true;
|
expect(receivedSlotProps.hasOwnProperty('events')).toBeTruthy();
|
||||||
|
});
|
||||||
expect(Select.find({ref: 'dropdownMenu'}).text()).toEqual('onetwothree')
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user