diff --git a/.travis.yml b/.travis.yml index 58dc582..cb9fc23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,5 +5,4 @@ node_js: - node script: - - yarn test --coverage --coverageReporters=text-lcov - - codecov + - yarn test --coverage --coverageReporters=text-lcov | coveralls diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js index 8b039fa..ce13428 100644 --- a/build/webpack.prod.conf.js +++ b/build/webpack.prod.conf.js @@ -1,3 +1,4 @@ +const TerserPlugin = require('terser-webpack-plugin'); const merge = require('webpack-merge'); const baseWebpackConfig = require('./webpack.base.conf'); @@ -9,4 +10,16 @@ module.exports = merge(baseWebpackConfig, { libraryTarget: 'umd', globalObject: 'typeof self !== \'undefined\' ? self : this', }, + optimization: { + minimizer: [ + new TerserPlugin({ + sourceMap: true, + terserOptions: { + compress: { + drop_console: true, + }, + }, + }), + ], + } }); diff --git a/dev/Dev.vue b/dev/Dev.vue index 62735dc..d135ad0 100644 --- a/dev/Dev.vue +++ b/dev/Dev.vue @@ -2,7 +2,18 @@
@@ -18,6 +29,30 @@ import Sandbox from '../docs/.vuepress/components/Sandbox'; export default { components: {Sandbox, vSelect}, + data: () => ({ + vModelValue: { + value: 'CA', + label: 'Canada' + }, + valueProp: { + value: 'US', + label: 'United States' + } + }), + methods: { + changeValueProp(value) { + this.valueProp = value; + } + }, + computed: { + vModelValueStringified() { + return JSON.stringify(this.vModelValue, null, 2); + }, + + valuePropStringified() { + return JSON.stringify(this.valueProp, null, 2); + } + } }; @@ -32,4 +67,12 @@ export default { #app { height: 100%; } + + hr { + border: none; + border-bottom: 1px solid #cacaca; + margin-bottom: 1em; + padding-top: 1em; + width: 90%; + } diff --git a/docs/api/props.md b/docs/api/props.md index 2374791..052ed7e 100644 --- a/docs/api/props.md +++ b/docs/api/props.md @@ -189,21 +189,6 @@ getOptionLabel: { }, ``` -## onChange - -An optional callback function that is called each time the selected -value(s) change. When integrating with Vuex, use this callback to trigger -an action, rather than using `v-model` to retrieve the selected value. - -```js -onChange: { - type: Function, - default: function(val) { - this.$emit("input", val); - } -}, -``` - ## onTab Select the current value if `selectOnTab` is enabled @@ -311,7 +296,7 @@ User defined function for adding Options createOption: { type: Function, default(newOption) { - if (typeof this.mutableOptions[0] === "object") { + if (typeof this.optionList[0] === "object") { newOption = { [this.label]: newOption }; } this.$emit("option:created", newOption); diff --git a/package.json b/package.json index 738b021..824bff2 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "postcss-loader": "^3.0.0", "postcss-scss": "^2.0.0", "sass-loader": "^7.1.0", + "terser-webpack-plugin": "^1.2.3", "url-loader": "^1.1.2", "vue": "^2.6.4", "vue-html-loader": "^1.2.4", diff --git a/src/components/Select.vue b/src/components/Select.vue index 88c5a83..df0ad43 100644 --- a/src/components/Select.vue +++ b/src/components/Select.vue @@ -7,7 +7,7 @@
- { - return ! (val === option || (this.index && val === option[this.index]) || (typeof val === 'object' && val[this.label] === option[this.label])); + value = this.selectedValue.filter(val => { + return ! this.optionComparator(val, option) }); - } else { - this.mutableValue = null } - this.onInput(this.mutableValue); + this.updateValue(value); }, /** @@ -569,8 +508,7 @@ * @return {void} */ clearSelection() { - this.mutableValue = this.multiple ? [] : null - this.onInput(this.mutableValue) + this.updateValue(this.multiple ? [] : null) }, /** @@ -589,6 +527,14 @@ } }, + updateValue(value) { + if (typeof this.value === 'undefined') { + // Vue select has to manage value + this.$data._value = value; + } + this.$emit('input', value); + }, + /** * Toggle the visibility of the dropdown menu. * @param {Event} e @@ -614,11 +560,8 @@ * @return {Boolean} True when selected | False otherwise */ isOptionSelected(option) { - return this.valueAsArray.some(value => { - if (typeof value === 'object') { - return this.optionObjectComparator(value, option) - } - return value === option || value === option[this.index] + return this.selectedValue.some(value => { + return this.optionComparator(value, option) }) }, @@ -629,14 +572,26 @@ * @param option {Object} * @returns {boolean} */ - optionObjectComparator(value, option) { - if (this.index && value === option[this.index]) { - return true - } else if ((value[this.label] === option[this.label]) || (value[this.label] === option)) { - return true - } else if (this.index && value[this.index] === option[this.index]) { - return true + optionComparator(value, option) { + // This method will need to be cleaned/replaced when the `reducer` API is added + if (typeof value !== 'object' && typeof option !== 'object') { + // Comparing primitives + if (value === option) { + return true + } + } else { + // Comparing objects + if (this.index && value === option[this.index]) { + return true + } + if ((value[this.label] === option[this.label]) || (value[this.label] === option)) { + return true + } + if (this.index && value[this.index] === option[this.index]) { + return true + } } + return false; }, @@ -686,7 +641,7 @@ return } // Fixed bug where no-options message could not be closed - if(this.search.length === 0 && this.options.length === 0){ + if (this.search.length === 0 && this.options.length === 0){ this.closeSearchOptions() return } @@ -718,42 +673,43 @@ * @return {this.value} */ maybeDeleteValue() { - if (!this.searchEl.value.length && this.mutableValue && this.clearable) { - return this.multiple ? this.mutableValue.pop() : this.mutableValue = null + if (!this.searchEl.value.length && this.selectedValue && this.clearable) { + let value = null; + if (this.multiple) { + value = [...this.selectedValue.slice(0, this.selectedValue.length - 1)] + } + this.updateValue(value) } }, /** * Determine if an option exists - * within this.mutableOptions array. + * within this.optionList array. * * @param {Object || String} option * @return {boolean} */ optionExists(option) { - let exists = false - - this.mutableOptions.forEach(opt => { + return this.optionList.some(opt => { if (typeof opt === 'object' && opt[this.label] === option) { - exists = true + return true } else if (opt === option) { - exists = true + return true } + return false }) - - return exists }, /** * If push-tags is true, push the - * given option to mutableOptions. + * given option to `this.pushedTags`. * * @param {Object || String} option * @return {void} */ maybePushTag(option) { if (this.pushTags) { - this.mutableOptions.push(option) + this.pushedTags.push(option) } }, @@ -812,6 +768,25 @@ computed: { + selectedValue () { + let value = this.value; + + if (typeof this.value === 'undefined') { + // Vue select has to manage value internally + value = this.$data._value; + } + + if (value) { + return [].concat(value); + } + + return []; + }, + + optionList () { + return this.options.concat(this.pushedTags); + }, + /** * Find the search input DOM element. * @returns {HTMLInputElement} @@ -848,7 +823,7 @@ 'keyup': this.onSearchKeyUp, 'blur': this.onSearchBlur, 'focus': this.onSearchFocus, - 'input': (e) => this.search = e.target.value, + 'input': (e) => this.search = e.target.value }, }, spinner: { @@ -919,10 +894,13 @@ * @return {array} */ filteredOptions() { + const optionList = [].concat(this.optionList); + if (!this.filterable && !this.taggable) { - return this.mutableOptions.slice() + return optionList; } - let options = this.search.length ? this.filter(this.mutableOptions, this.search, this) : this.mutableOptions; + + let options = this.search.length ? this.filter(optionList, this.search, this) : optionList; if (this.taggable && this.search.length && !this.optionExists(this.search)) { options.unshift(this.search) } @@ -934,28 +912,7 @@ * @return {Boolean} */ isValueEmpty() { - if (this.mutableValue) { - if (typeof this.mutableValue === 'object') { - return ! Object.keys(this.mutableValue).length - } - return ! this.valueAsArray.length - } - - return true; - }, - - /** - * Return the current value in array format. - * @return {Array} - */ - valueAsArray() { - if (this.multiple && this.mutableValue) { - return this.mutableValue - } else if (this.mutableValue) { - return [].concat(this.mutableValue) - } - - return [] + return this.selectedValue.length === 0; }, /** @@ -963,7 +920,7 @@ * @return {Boolean} */ showClearButton() { - return !this.multiple && this.clearable && !this.open && this.mutableValue != null + return !this.multiple && this.clearable && !this.open && !this.isValueEmpty } }, diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js deleted file mode 100644 index f4e07b3..0000000 --- a/test/unit/karma.conf.js +++ /dev/null @@ -1,42 +0,0 @@ -var merge = require('webpack-merge') - -var baseWebpackConfig = require('../../build/webpack.base.conf') - -module.exports = function(config) { - config.set({ - browsers: ['PhantomJS'], - frameworks: ['jasmine'], - files: [ - '../../node_modules/phantomjs-polyfill-object-assign/object-assign-polyfill.js', - '**/*.js' - ], - reporters: ['spec', 'coverage'], - preprocessors: { - '**/*.js': ['webpack', 'sourcemap'] - }, - captureConsole: true, - browserConsoleLogOptions: { - terminal: true, - level: "" - }, - - webpack: merge(baseWebpackConfig, { - entry: './dev/dev.js' - }), - - webpackMiddleware: { - noInfo: true - }, - - specReporter: { - suppressSkipped: true - }, - - coverageReporter: { - instrumenters: { isparta: require('isparta') }, - instrumenter: { - '**/*.js': 'isparta' - } - } - }) -} \ No newline at end of file diff --git a/test/unit/specs/Select.spec.js b/test/unit/specs/Select.spec.js deleted file mode 100644 index c94fdf9..0000000 --- a/test/unit/specs/Select.spec.js +++ /dev/null @@ -1,1673 +0,0 @@ -// flow -/* global describe, it, expect */ - -import Vue from 'vue' -import vSelect from 'src/components/Select.vue' -import pointerScroll from 'src/mixins/pointerScroll.js' -Vue.config.productionTip = false - -Vue.component('v-select', vSelect) - -/** - * Simulate a DOM event. - * @param target - * @param event - * @param process - * @returns {Event} - */ -function trigger(target, event, process) { - var e = document.createEvent('HTMLEvents') - e.initEvent(event, true, true) - if (process) process(e) - target.dispatchEvent(e) - return e -} - -/** - * Simulate a Mouse event. - * @param target - * @param event - * @param process - * @returns {Event} - */ -function triggerMouse(target, event, process) { - var e = document.createEvent('MouseEvent') - e.initEvent('event', true, true) - if (process) process(e) - target.dispatchEvent(e) - return e -} - -/** - * Simulate a Focus event. - * @param target - * @param event - * @param process - * @returns {Event} - */ -function triggerFocusEvent(target, event, process) { - var e = document.createEvent('FocusEvent') - e.initEvent('event', true, true) - if (process) process(e) - target.dispatchEvent(e) - return e -} - -/** - * Optionally set the search term, then simulate a return keypress. - * @param vm - * @param search - */ -function searchSubmit(vm, search = false) { - if (search) { - vm.$children[0].search = search - } - - trigger(vm.$children[0].$refs.search, 'keydown', function (e) { - e.keyCode = 13 - }) -} - -/** - * Replace component mixins (a simple approach to mocking) - * @param mixins - */ -function OverrideMixin(mixins) { - return { - extends: Object.assign({}, vSelect, { - mixins: [].concat(mixins) - }) - } -} - -describe('Select.vue', () => { - - describe('Selecting values', () => { - it('can accept an array with pre-selected values', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: 'one', - options: ['one', 'two', 'three'] - } - }).$mount() - expect(vm.$children[0].mutableValue).toEqual(vm.value) - }) - - it('can accept an array of objects and pre-selected value (single)', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: {label: 'This is Foo', value: 'foo'}, - options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}] - } - }).$mount() - expect(vm.$children[0].mutableValue).toEqual(vm.value) - }) - - it('can accept an array of objects and pre-selected values (multiple)', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}], - options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}] - } - }).$mount() - expect(vm.$children[0].mutableValue).toEqual(vm.value) - }) - - it('can select an option on tab', (done) => { - const vm = new Vue({ - template: `
`, - components: {vSelect}, - }).$mount() - - vm.$children[0].typeAheadPointer = 0 - - trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 9) - - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual('one'); - done(); - }) - }) - - it('can deselect a pre-selected object', () => { - const vm = new Vue({ - template: '
', - data: { - value: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}], - options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}] - } - }).$mount() - vm.$children[0].deselect({label: 'This is Foo', value: 'foo'}) - expect(vm.$children[0].mutableValue.length).toEqual(1) - }) - - it('can deselect a pre-selected string', () => { - const vm = new Vue({ - template: '
', - data: { - value: ['foo', 'bar'], - options: ['foo','bar'] - } - }).$mount() - vm.$children[0].deselect('foo') - expect(vm.$children[0].mutableValue.length).toEqual(1) - }), - - it('can deselect an option when multiple is false', () => { - const vm = new Vue({ - template: `
`, - }).$mount() - vm.$children[0].deselect('foo') - expect(vm.$children[0].mutableValue).toEqual(null) - }) - - it('can determine if the value prop is empty', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [], - options: ['one', 'two', 'three'] - } - }).$mount() - var select = vm.$children[0] - expect(select.isValueEmpty).toEqual(true) - - select.select(['one']) - expect(select.isValueEmpty).toEqual(false) - - select.select([{l: 'f'}]) - expect(select.isValueEmpty).toEqual(false) - - select.select('one') - expect(select.isValueEmpty).toEqual(false) - - select.select({label: 'foo', value: 'foo'}) - expect(select.isValueEmpty).toEqual(false) - - select.select('') - expect(select.isValueEmpty).toEqual(true) - - select.select(null) - expect(select.isValueEmpty).toEqual(true) - }) - - it('should reset the selected values when the multiple property changes', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one'], - multiple: true, - options: ['one', 'two', 'three'] - } - }).$mount() - - vm.multiple = false - - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual(null) - vm.multiple = true - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual([]) - done() - }) - }) - }) - - it('can retain values present in a new array of options', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one'], - options: ['one', 'two', 'three'] - } - }).$mount() - vm.options = ['one', 'five', 'six'] - expect(vm.$children[0].mutableValue).toEqual(['one']) - }) - - it('can determine if an object is already selected', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{label: 'one'}], - options: [{label: 'one'}] - } - }).$mount() - - expect(vm.$children[0].isOptionSelected({label: 'one'})).toEqual(true) - }) - - it('can use v-model syntax for a two way binding to a parent component', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: 'foo', - options: ['foo','bar','baz'] - } - }).$mount() - - expect(vm.$children[0].value).toEqual('foo') - expect(vm.$children[0].mutableValue).toEqual('foo') - - vm.$children[0].select('bar') - - Vue.nextTick(() => { - expect(vm.value).toEqual('bar') - done() - }) - }), - - it('can check if a string value is selected when the value is an object and multiple is true', () => { - const vm = new Vue({ - template: `
`, - }).$mount() - expect(vm.$children[0].isOptionSelected('foo')).toEqual(true) - }), - - describe('change Event', () => { - it('will trigger the input event when the selection changes', (done) => { - const vm = new Vue({ - template: `
`, - data: { - foo: '' - } - }).$mount() - - vm.$refs.select.select('bar') - - Vue.nextTick(() => { - expect(vm.foo).toEqual('bar') - done() - }) - }) - - it('should run change when multiple is true and the value changes', (done) => { - const vm = new Vue({ - template: `
`, - data: { - foo: '' - } - }).$mount() - - vm.$refs.select.select('bar') - - Vue.nextTick(() => { - expect(vm.foo).toEqual(['foo','bar']) - done() - }) - - }) - }) - }) - - describe('Filtering Options', () => { - it('should filter an array of strings', () => { - const vm = new Vue({ - template: `
`, - data: {value: 'foo'} - }).$mount() - vm.$refs.select.search = 'ba' - expect(vm.$refs.select.filteredOptions).toEqual(['bar','baz']) - }) - - it('should not filter the array of strings if filterable is false', () => { - const vm = new Vue({ - template: `
`, - data: {value: 'foo'} - }).$mount() - vm.$refs.select.search = 'ba' - expect(vm.$refs.select.filteredOptions).toEqual(['foo','bar','baz']) - }) - - it('should filter without case-sensitivity', () => { - const vm = new Vue({ - template: `
`, - data: {value: 'foo'} - }).$mount() - vm.$refs.select.search = 'ba' - expect(vm.$refs.select.filteredOptions).toEqual(['Bar','Baz']) - }) - - it('can filter an array of objects based on the objects label key', () => { - const vm = new Vue({ - template: `
`, - data: {value: 'foo'} - }).$mount() - vm.$refs.select.search = 'ba' - expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify([{label: 'Bar', value: 'bar'}, {label: 'Baz', value: 'baz'}])) - }) - - it('can determine if a given option should match the current search text', () => { - const vm = new Vue({ - template: `
`, - data: {value: 'foo'}, - methods:{ - customFn: (option, label, search) => label.match(new RegExp('^' + search, 'i')) - } - }).$mount() - vm.$refs.select.search = 'a' - expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify([{label: 'Aoo', value: 'foo'}])) - }) - - it('can use a custom filtering method', () => { - const vm = new Vue({ - template: `
`, - data: { - options: ['foo','bar','baz'], - value: 'foo' - }, - methods:{ - customFn(options, search, vm) { - return options.filter(option => option.indexOf(search) > 0) - } - } - }).$mount() - vm.$refs.select.search = 'a' - expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify(['bar','baz'])) - }) - - it('can filter arrays of numbers', () => { - const vm = new Vue({ - template: `
`, - data: { - options: [1,5,10], - value: 'foo' - }, - }).$mount() - vm.$refs.select.search = '5' - expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify([5])) - }) - }) - - describe('Toggling Dropdown', () => { - it('should not open the dropdown when the el is clicked but the component is disabled', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{label: 'one'}], - options: [{label: 'one'}] - } - }).$mount() - - vm.$children[0].toggleDropdown({target: vm.$children[0].$refs.search}) - Vue.nextTick(() => { - Vue.nextTick(() => { - expect(vm.$children[0].open).toEqual(false) - done() - }) - }) - }) - - it('should open the dropdown when the el is clicked', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{label: 'one'}], - options: [{label: 'one'}] - } - }).$mount() - - vm.$children[0].toggleDropdown({target: vm.$children[0].$refs.search}) - Vue.nextTick(() => { - Vue.nextTick(() => { - expect(vm.$children[0].open).toEqual(true) - done() - }) - }) - }) - - it('should open the dropdown when the selected tag is clicked', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{label: 'one'}], - options: [{label: 'one'}] - } - }).$mount() - - const selectedTag = vm.$children[0].$el.getElementsByClassName('selected-tag')[0] - vm.$children[0].toggleDropdown({target: selectedTag}) - Vue.nextTick(() => { - Vue.nextTick(() => { - expect(vm.$children[0].open).toEqual(true) - done() - }) - }) - }) - - it('can close the dropdown when the el is clicked', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - }).$mount() - - spyOn(vm.$children[0].$refs.search, 'blur') - - vm.$children[0].open = true - vm.$children[0].toggleDropdown({target: vm.$children[0].$el}) - - Vue.nextTick(() => { - expect(vm.$children[0].$refs.search.blur).toHaveBeenCalled() - done() - }) - }) - - - it('closes the dropdown when an option is selected, multiple is true, and closeOnSelect option is true', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [], - options: ['one', 'two', 'three'] - } - }).$mount() - - vm.$children[0].open = true - vm.$refs.select.select('one') - - Vue.nextTick(() => { - expect(vm.$children[0].open).toEqual(false) - done() - }) - }) - - it('does not close the dropdown when the el is clicked, multiple is true, and closeOnSelect option is false', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [], - options: ['one', 'two', 'three'] - } - }).$mount() - - vm.$children[0].open = true - vm.$refs.select.select('one') - - Vue.nextTick(() => { - expect(vm.$children[0].open).toEqual(true) - done() - }) - }) - - - it('should close the dropdown on search blur', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{label: 'one'}], - options: [{label: 'one'}] - } - }).$mount() - - vm.$children[0].open = true - triggerFocusEvent(vm.$children[0].$refs.toggle, 'blur') - expect(vm.$children[0].open).toEqual(true) - }) - - it('will close the dropdown and emit the search:blur event from onSearchBlur', () => { - const vm = new Vue({ - template: '
', - }).$mount() - - spyOn(vm.$children[0], '$emit') - vm.$children[0].open = true - vm.$children[0].onSearchBlur() - - expect(vm.$children[0].open).toEqual(false) - expect(vm.$children[0].$emit).toHaveBeenCalledWith('search:blur') - }) - - it('will open the dropdown and emit the search:focus event from onSearchFocus', () => { - const vm = new Vue({ - template: '
', - }).$mount() - - spyOn(vm.$children[0], '$emit') - vm.$children[0].onSearchFocus() - - expect(vm.$children[0].open).toEqual(true) - expect(vm.$children[0].$emit).toHaveBeenCalledWith('search:focus') - }) - - it('will close the dropdown on escape, if search is empty', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - }).$mount() - - spyOn(vm.$children[0].$refs.search, 'blur') - - vm.$children[0].open = true - vm.$children[0].onEscape() - - Vue.nextTick(() => { - expect(vm.$children[0].$refs.search.blur).toHaveBeenCalled() - done() - }) - }) - - it('should remove existing search text on escape keyup', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{label: 'one'}], - options: [{label: 'one'}] - } - }).$mount() - - vm.$children[0].search = 'foo' - vm.$children[0].onEscape() - expect(vm.$children[0].search).toEqual('') - }) - - it('should have an open class when dropdown is active', () => { - const vm = new Vue({ - template: '
', - components: {vSelect} - }).$mount() - - expect(vm.$children[0].dropdownClasses.open).toEqual(false) - }) - }) - - describe('Moving the Typeahead Pointer', () => { - it('should set the pointer to zero when the filteredOptions change', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: ['one', 'two', 'three'] - } - }).$mount() - - vm.$children[0].search = 'two' - Vue.nextTick(() => { - expect(vm.$children[0].typeAheadPointer).toEqual(0) - done() - }) - }) - - it('should move the pointer visually up the list on up arrow keyDown', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: ['one', 'two', 'three'] - } - }).$mount() - - vm.$children[0].typeAheadPointer = 1 - - trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 38) - expect(vm.$children[0].typeAheadPointer).toEqual(0) - }) - - it('should move the pointer visually down the list on down arrow keyDown', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: ['one', 'two', 'three'] - } - }).$mount() - - vm.$children[0].typeAheadPointer = 1 - trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 40) - expect(vm.$children[0].typeAheadPointer).toEqual(2) - }) - - it('should not move the pointer past the end of the list', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: ['one', 'two', 'three'] - } - }).$mount() - - vm.$children[0].typeAheadPointer = 2 - vm.$children[0].typeAheadDown() - expect(vm.$children[0].typeAheadPointer).toEqual(2) - }) - - describe('Automatic Scrolling', () => { - it('should check if the scroll position needs to be adjusted on up arrow keyDown', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: ['one', 'two', 'three'] - } - }).$mount() - - vm.$children[0].typeAheadPointer = 1 - spyOn(vm.$children[0], 'maybeAdjustScroll') - trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 38) - expect(vm.$children[0].maybeAdjustScroll).toHaveBeenCalled() - }) - - it('should check if the scroll position needs to be adjusted on down arrow keyDown', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: ['one', 'two', 'three'] - } - }).$mount() - - spyOn(vm.$children[0], 'maybeAdjustScroll') - trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 40) - expect(vm.$children[0].maybeAdjustScroll).toHaveBeenCalled() - }) - - it('should check if the scroll position needs to be adjusted when filtered options changes', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: ['one', 'two', 'three'] - } - }).$mount() - - spyOn(vm.$children[0], 'maybeAdjustScroll') - vm.$children[0].search = 'two' - - Vue.nextTick(() => { - expect(vm.$children[0].maybeAdjustScroll).toHaveBeenCalled() - done() - }) - }) - - it('should scroll up if the pointer is above the current viewport bounds', () => { - const scrollMixin = Object.assign({}, pointerScroll, {methods: Object.assign({}, pointerScroll.methods, { - pixelsToPointerTop() { - return 1 - }, - viewport() { - return {top: 2, bottom: 0} - } - })}) - const vm = new Vue({ - template: '
', - components: { - 'v-select': OverrideMixin(scrollMixin) - }, - }).$mount() - - spyOn(vm.$children[0], 'scrollTo') - vm.$children[0].maybeAdjustScroll() - expect(vm.$children[0].scrollTo).toHaveBeenCalledWith(1) - }) - - it('should scroll down if the pointer is below the current viewport bounds', () => { - const scrollMixin = Object.assign({}, pointerScroll, {methods: Object.assign({}, pointerScroll.methods, { - pixelsToPointerBottom() { - return 2 - }, - viewport() { - return { top: 0, bottom: 1 } - } - })}) - const vm = new Vue({ - template: `
`, - components: { - 'v-select': OverrideMixin(scrollMixin) - }, - }).$mount() - - spyOn(vm.$children[0], 'scrollTo') - vm.$children[0].maybeAdjustScroll() - expect(vm.$children[0].scrollTo).toHaveBeenCalledWith(vm.$children[0].viewport().top + vm.$children[0].pointerHeight()) - }) - }) - - describe('Measuring pixel distances', () => { - it('should calculate pointerHeight as the offsetHeight of the pointer element if it exists', () => { - const vm = new Vue({ - template: `
`, - }).$mount() - - // dropdown must be open for $refs to exist - vm.$children[0].open = true - - Vue.nextTick(() => { - // Fresh instances start with the pointer at -1 - vm.$children[0].typeAheadPointer = -1 - expect(vm.$children[0].pointerHeight()).toEqual(0) - - vm.$children[0].typeAheadPointer = 100 - expect(vm.$children[0].pointerHeight()).toEqual(0) - - vm.$children[0].typeAheadPointer = 1 - expect(vm.$children[0].pointerHeight()).toEqual(vm.$children[0].$refs.dropdownMenu.children[1].offsetHeight) - }) - }) - }) - }) - - describe('Removing values', () => { - it('can remove the given tag when its close icon is clicked', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one'], - options: ['one', 'two', 'three'] - } - }).$mount() - vm.$children[0].$refs.toggle.querySelector('.close').click() - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual([]) - done() - }) - }) - - it('should not remove tag when close icon is clicked and component is disabled', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one'], - options: ['one', 'two', 'three'] - } - }).$mount() - vm.$children[0].$refs.toggle.querySelector('.close').click() - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual(['one']) - done() - }) - }) - - it('should remove the last item in the value array on delete keypress when multiple is true', () => { - - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one', 'two'], - options: ['one', 'two', 'three'] - } - }).$mount() - vm.$children[0].maybeDeleteValue() - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual(['one']) - }) - }) - - it('should set value to null on delete keypress when multiple is false', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: 'one', - options: ['one', 'two', 'three'] - } - }).$mount() - vm.$children[0].maybeDeleteValue() - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual(null) - }) - }) - }) - - describe('Labels', () => { - it('can generate labels using a custom label key', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{name: 'Baz'}], - options: [{name: 'Foo'}, {name: 'Baz'}] - } - }).$mount() - expect(vm.$children[0].$refs.toggle.querySelector('.selected-tag').textContent).toContain('Baz') - }) - - it('will console.warn when options contain objects without a valid label key', (done) => { - spyOn(console, 'warn') - const vm = new Vue({ - template: '
', - }).$mount() - vm.$children[0].open = true - Vue.nextTick(() => { - expect(console.warn).toHaveBeenCalledWith( - '[vue-select warn]: Label key "option.label" does not exist in options object {}.' + - '\nhttp://sagalbot.github.io/vue-select/#ex-labels' - ) - done() - }) - }) - - it('should display a placeholder if the value is empty', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: [{label: 'one'}] - } - }).$mount() - - expect(vm.$children[0].searchPlaceholder).toEqual('foo') - vm.$children[0].mutableValue = {label: 'one'} - Vue.nextTick(() => { - expect(vm.$children[0].searchPlaceholder).not.toBeDefined() - done() - }) - }) - }) - - describe('When index prop is defined', () => { - it('can accept an array of objects and pre-selected value (single)', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - index: 'value', - value: 'foo', - options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}] - } - }).$mount() - expect(vm.$children[0].mutableValue).toEqual(vm.value) - }) - - it('can determine if an object is pre-selected', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: 'foo', - options: [{ - id: 'foo', - label: 'This is Foo' - }] - } - }).$mount() - - expect(vm.$children[0].isOptionSelected({ - id: 'foo', - label: 'This is Foo' - })).toEqual(true) - }) - - it('can determine if an object is selected after it has been chosen', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: [{id: 'foo', label: 'FooBar'}] - } - }).$mount() - - vm.$children[0].select({id: 'foo', label: 'FooBar'}); - - // Vue.nextTick(() => { - expect(vm.$children[0].isOptionSelected({ - id: 'foo', - label: 'This is Foo' - })).toEqual(true) - // done() - // }) - }) - - it('can accept an array of objects and pre-selected values (multiple)', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - index: 'value', - value: ['foo', 'bar'], - options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}] - } - }).$mount() - expect(vm.$children[0].mutableValue).toEqual(vm.value) - }) - - it('can deselect a pre-selected object', () => { - const vm = new Vue({ - template: '
', - data: { - index: 'value', - value: ['foo', 'bar'], - options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}] - } - }).$mount() - vm.$children[0].deselect('foo') - expect(vm.$children[0].mutableValue.length).toEqual(1) - expect(vm.$children[0].mutableValue).toEqual(['bar']) - }) - - it('can deselect an option when multiple is false', () => { - const vm = new Vue({ - template: `
`, - data: { - index: 'value', - value: 'foo', - options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}] - } - }).$mount() - vm.$children[0].deselect('foo') - expect(vm.$children[0].mutableValue).toEqual(null) - }) - - it('can use v-model syntax for a two way binding to a parent component', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - index: 'value', - value: 'foo', - options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}, {label: 'This is Baz', value: 'baz'}] - } - }).$mount() - - expect(vm.$children[0].value).toEqual('foo') - expect(vm.$children[0].mutableValue).toEqual('foo') - - vm.$children[0].select({label: 'This is Bar', value: 'bar'}) - - Vue.nextTick(() => { - expect(vm.value).toEqual('bar') - done() - }) - }), - - it('can work with an array of integers', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: 5, - } - }).$mount() - - expect(vm.$children[0].isOptionSelected(5)).toEqual(true) - expect(vm.$children[0].isValueEmpty).toEqual(false) - }) - - it('can generate labels using a custom label key', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - index: 'value', - value: ['baz'], - options: [{value: 'foo', name: 'Foo'}, {value: 'baz', name: 'Baz'}] - } - }).$mount() - expect(vm.$children[0].$refs.toggle.querySelector('.selected-tag').textContent).toContain('Baz') - }) - - it('will console.warn when attempting to select an option with an undefined index', () => { - spyOn(console, 'warn') - - const vm = new Vue({ - template: '
', - data: { - options: [{label: 'Foo'}] - } - }).$mount() - vm.$children[0].select({label: 'Foo'}) - expect(console.warn).toHaveBeenCalledWith( - `[vue-select warn]: Index key "option.value" does not exist in options object {"label":"Foo"}.` - ) - }) - - it('can find the original option within this.options', () => { - const vm = new Vue({ - template: '
', - data: { - options: [{id: 1, label: 'Foo'},{id:2, label: 'Bar'}] - } - }).$mount() - - expect(vm.$children[0].findOptionByIndexValue(1)).toEqual({id: 1, label: 'Foo'}) - expect(vm.$children[0].findOptionByIndexValue({id: 1, label: 'Foo'})).toEqual({id: 1, label: 'Foo'}) - }) - - describe('And when option[index] is a nested object', () => { - it('can determine if an object is pre-selected', () => { - const nestedOption = { - value: { - nested: true - }, - label: 'foo' - }; - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: { - nested: true - }, - options: [nestedOption] - } - }).$mount() - expect(vm.$children[0].isOptionSelected({ - nested: true - })).toEqual(true) - }) - - it('can determine if an object is selected after it is chosen', () => { - const nestedOption = { - value: { - nested: true - }, - label: 'foo' - }; - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: [nestedOption] - } - }).$mount() - vm.$children[0].select(nestedOption) - expect(vm.$children[0].isOptionSelected(nestedOption)).toEqual(true) - }) - - it('can determine a selected values label', () => { - const nestedOption = { - value: { - nested: true - }, - label: 'foo' - }; - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: { - nested: true - }, - options: [nestedOption] - } - }).$mount() - - expect(vm.$children[0].getOptionLabel({ - nested: true - })).toEqual('foo') - }) - - }) - }) - - describe('When Tagging Is Enabled', () => { - it('can determine if a given option string already exists', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: ['one', 'two'] - } - }).$mount() - - expect(vm.$refs.select.optionExists('one')).toEqual(true) - expect(vm.$refs.select.optionExists('three')).toEqual(false) - }) - - it('can determine if a given option object already exists', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: [{label: 'one'}, {label: 'two'}] - } - }).$mount() - - expect(vm.$refs.select.optionExists('one')).toEqual(true) - expect(vm.$refs.select.optionExists('three')).toEqual(false) - }) - - it('can determine if a given option object already exists when using custom labels', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - options: [{foo: 'one'}, {foo: 'two'}] - } - }).$mount() - - expect(vm.$refs.select.optionExists('one')).toEqual(true) - expect(vm.$refs.select.optionExists('three')).toEqual(false) - }) - - it('can add the current search text as the first item in the options list', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one'], - options: ['one', 'two'] - } - }).$mount() - - vm.$children[0].search = 'three' - expect(vm.$children[0].filteredOptions).toEqual(['three']) - }) - - it('can select the current search text as a string', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one'], - options: ['one', 'two'] - } - }).$mount() - - searchSubmit(vm, 'three') - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual(['one', 'three']) - done() - }) - }) - - it('can select the current search text as an object', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{label: 'one'}], - options: [{label: 'one'}] - } - }).$mount() - - searchSubmit(vm, 'two') - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual([{label: 'one'}, {label: 'two'}]) - done() - }) - }) - - it('should add a freshly created option/tag to the options list when pushTags is true', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one'], - options: ['one', 'two'] - } - }).$mount() - - searchSubmit(vm, 'three') - expect(vm.$children[0].mutableOptions).toEqual(['one', 'two', 'three']) - }) - - it('should add a freshly created option/tag to the options list when pushTags is true and filterable is false', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one'], - options: ['one', 'two'] - } - }).$mount() - - searchSubmit(vm, 'three') - expect(vm.$children[0].mutableOptions).toEqual(['one', 'two', 'three']) - expect(vm.$children[0].filteredOptions).toEqual(['one', 'two', 'three']) - }) - - it('wont add a freshly created option/tag to the options list when pushTags is false', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one'], - options: ['one', 'two'] - } - }).$mount() - - searchSubmit(vm, 'three') - expect(vm.$children[0].mutableOptions).toEqual(['one', 'two']) - }) - - it('wont add a freshly created option/tag to the options list when pushTags is false and filterable is false', () => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: ['one'], - options: ['one', 'two'] - } - }).$mount() - - searchSubmit(vm, 'three') - expect(vm.$children[0].mutableOptions).toEqual(['one', 'two']) - expect(vm.$children[0].filteredOptions).toEqual(['one', 'two']) - }) - - it('should select an existing option if the search string matches a string from options', (done) => { - let two = 'two' - const vm = new Vue({ - template: '
', - data: { - value: null, - options: ['one', two] - } - }).$mount() - vm.$children[0].search = 'two' - - searchSubmit(vm) - - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue[0]).toBe(two) - done() - }) - }) - - it('should select an existing option if the search string matches an objects label from options', (done) => { - let two = {label: 'two'} - const vm = new Vue({ - template: '
', - data: { - options: [{label: 'one'}, two] - } - }).$mount() - - vm.$children[0].search = 'two' - - Vue.nextTick(() => { - searchSubmit(vm) - // This needs to be wrapped in nextTick() twice so that filteredOptions can - // calculate after setting the search text, and move the typeAheadPointer index to 0. - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue.label).toBe(two.label) - done() - }) - }) - }) - - it('should select an existing option if the search string matches an objects label from options when filter-options is false', (done) => { - let two = {label: 'two'} - const vm = new Vue({ - template: '
', - data: { - options: [{label: 'one'}, two] - } - }).$mount() - - vm.$children[0].search = 'two' - - Vue.nextTick(() => { - searchSubmit(vm) - // This needs to be wrapped in nextTick() twice so that filteredOptions can - // calculate after setting the search text, and move the typeAheadPointer index to 0. - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue.label).toBe(two.label) - done() - }) - }) - }) - - it('should not reset the selected value when the options property changes', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{label: 'one'}], - options: [{label: 'one'}] - } - }).$mount() - vm.$children[0].mutableOptions = [{label: 'two'}] - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual([{label: 'one'}]) - done() - }) - }) - - it('should not reset the selected value when the options property changes when filterable is false', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: [{label: 'one'}], - options: [{label: 'one'}] - } - }).$mount() - vm.$children[0].mutableOptions = [{label: 'two'}] - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual([{label: 'one'}]) - done() - }) - }) - - it('should not allow duplicate tags when using string options', (done) => { - const vm = new Vue({ - template: `
`, - }).$mount() - vm.$refs.select.search = 'one' - searchSubmit(vm) - Vue.nextTick(() => { - expect(vm.$refs.select.mutableValue).toEqual(['one']) - expect(vm.$refs.select.search).toEqual('') - vm.$refs.select.search = 'one' - searchSubmit(vm) - Vue.nextTick(() => { - expect(vm.$refs.select.mutableValue).toEqual(['one']) - expect(vm.$refs.select.search).toEqual('') - done() - }) - }) - }) - - it('should not allow duplicate tags when using object options', (done) => { - const vm = new Vue({ - template: `
`, - }).$mount() - vm.$refs.select.search = 'one' - searchSubmit(vm) - Vue.nextTick(() => { - expect(vm.$refs.select.mutableValue).toEqual(['one']) - expect(vm.$refs.select.search).toEqual('') - vm.$refs.select.search = 'one' - searchSubmit(vm) - Vue.nextTick(() => { - expect(vm.$refs.select.mutableValue).toEqual(['one']) - expect(vm.$refs.select.search).toEqual('') - done() - }) - }) - - }) - }) - - describe('Asynchronous Loading', () => { - it('can toggle the loading class', () => { - const vm = new Vue({ - template: '
', - }).$mount() - - vm.$refs.select.toggleLoading() - expect(vm.$refs.select.mutableLoading).toEqual(true) - - vm.$refs.select.toggleLoading(true) - expect(vm.$refs.select.mutableLoading).toEqual(true) - }) - - it('should trigger the onSearch callback when the search text changes', (done) => { - const vm = new Vue({ - template: '
', - data: { - called: false - }, - methods: { - foo(val) { - this.called = val - } - } - }).$mount() - - vm.$refs.select.search = 'foo' - - Vue.nextTick(() => { - expect(vm.called).toEqual('foo') - done() - }) - }) - - it('should not trigger the onSearch callback if the search text is empty', (done) => { - const vm = new Vue({ - template: '
', - data: { called: false }, - methods: { - foo(val) { - this.called = ! this.called - } - } - }).$mount() - - vm.$refs.select.search = 'foo' - Vue.nextTick(() => { - expect(vm.called).toBe(true) - vm.$refs.select.search = '' - Vue.nextTick(() => { - expect(vm.called).toBe(true) - done() - }) - }) - }) - - it('should trigger the search event when the search text changes', (done) => { - const vm = new Vue({ - template: '
', - data: { - called: false - }, - methods: { - foo(val) { - this.called = val - } - } - }).$mount() - - vm.$refs.select.search = 'foo' - - Vue.nextTick(() => { - expect(vm.called).toEqual('foo') - done() - }) - }) - - it('should not trigger the search event if the search text is empty', (done) => { - const vm = new Vue({ - template: '
', - data: { called: false }, - methods: { - foo(val) { - this.called = ! this.called - } - } - }).$mount() - - vm.$refs.select.search = 'foo' - Vue.nextTick(() => { - expect(vm.called).toBe(true) - vm.$refs.select.search = '' - Vue.nextTick(() => { - expect(vm.called).toBe(true) - done() - }) - }) - }) - - it('can set loading to false from the onSearch callback', (done) => { - const vm = new Vue({ - template: '
', - methods: { - foo(search, loading) { - loading(false) - } - } - }).$mount() - - vm.$refs.select.search = 'foo' - Vue.nextTick(() => { - expect(vm.$refs.select.mutableLoading).toEqual(false) - done() - }) - }) - - it('can set loading to true from the onSearch callback', (done) => { - const vm = new Vue({ - template: '
', - methods: { - foo(search, loading) { - loading(true) - } - } - }).$mount() - - let select = vm.$refs.select - select.onSearch(select.search, select.toggleLoading) - - Vue.nextTick(() => { - expect(vm.$refs.select.mutableLoading).toEqual(true) - done() - }) - }) - - it('will sync mutable loading with the loading prop', (done) => { - const vm = new Vue({ - template: '
', - data: {loading:false} - }).$mount() - vm.loading = true - Vue.nextTick(() => { - expect(vm.$refs.select.mutableLoading).toEqual(true) - done() - }) - }) - }) - - describe('Reset on options change', () => { - it('should not reset the selected value by default when the options property changes', (done) => { - const vm = new Vue({ - template: '
', - data: { - value: 'one', - options: ['one', 'two', 'three'] - } - }).$mount() - vm.$children[0].mutableOptions = ['four', 'five', 'six'] - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual('one') - done() - }) - }) - - it('should reset the selected value when the options property changes', (done) => { - const vm = new Vue({ - template: '
', - components: {vSelect}, - data: { - value: 'one', - options: ['one', 'two', 'three'] - } - }).$mount() - vm.$children[0].mutableOptions = ['four', 'five', 'six'] - Vue.nextTick(() => { - expect(vm.$children[0].mutableValue).toEqual(null) - done() - }) - }) - }) - - describe('Single value options', () => { - it('should reset the search input on focus lost', (done) => { - const vm = new Vue({ - template: '
', - data: { - value: 'one', - options: ['one', 'two', 'three'] - } - }).$mount() - - vm.$children[0].open = true - vm.$refs.select.search = "t" - expect(vm.$refs.select.search).toEqual('t') - - vm.$children[0].onSearchBlur() - Vue.nextTick(() => { - expect(vm.$refs.select.search).toEqual('') - done() - }) - }) - - it ('should not reset the search input on focus lost when clearSearchOnSelect is false', (done) => { - const vm = new Vue({ - template: '
', - data: { - value: 'one', - options: ['one', 'two', 'three'] - } - }).$mount() - expect(vm.$refs.select.clearSearchOnSelect).toEqual(false) - - vm.$children[0].open = true - vm.$refs.select.search = "t" - expect(vm.$refs.select.search).toEqual('t') - - vm.$children[0].onSearchBlur() - Vue.nextTick(() => { - expect(vm.$refs.select.search).toEqual('t') - done() - }) - }) - }) - - describe('Clear button', () => { - - it( 'should be displayed on single select when value is selected', () => { - const VueSelect = Vue.extend( vSelect ) - const vm = new VueSelect({ - propsData: { - options: ['foo','bar'], - value: 'foo' - } - }).$mount() - - expect(vm.showClearButton).toEqual(true) - }) - - it( 'should not be displayed on multiple select', () => { - const VueSelect = Vue.extend( vSelect ) - const vm = new VueSelect({ - propsData: { - options: ['foo','bar'], - value: 'foo', - multiple: true - } - }).$mount() - - expect(vm.showClearButton).toEqual(false) - }) - - it( 'should remove selected value when clicked', () => { - const VueSelect = Vue.extend( vSelect ) - const vm = new VueSelect({ - propsData: { - options: ['foo','bar'], - value: 'foo' - } - }).$mount() - - expect(vm.mutableValue).toEqual('foo') - vm.$el.querySelector( 'button.clear' ).click() - expect(vm.mutableValue).toEqual(null) - }) - - it( 'should be disabled when component is disabled', () => { - const VueSelect = Vue.extend( vSelect ) - const vm = new VueSelect({ - propsData: { - options: ['foo','bar'], - value: 'foo', - disabled: true - } - }).$mount() - - const buttonEl = vm.$el.querySelector( 'button.clear' ) - expect(buttonEl.disabled).toEqual(true); - }) - - }); -}) diff --git a/tests/unit/Deselecting.spec.js b/tests/unit/Deselecting.spec.js index 8f043f3..db14a79 100755 --- a/tests/unit/Deselecting.spec.js +++ b/tests/unit/Deselecting.spec.js @@ -2,10 +2,12 @@ import { selectWithProps } from "../helpers"; describe("Removing values", () => { it("can remove the given tag when its close icon is clicked", () => { - const Select = selectWithProps({ multiple: true, value: ["foo"] }); + const Select = selectWithProps({ multiple: true }); + Select.vm.$data._value = 'one'; Select.find(".vs__deselect").trigger("click"); - expect(Select.vm.mutableValue).toEqual([]); + expect(Select.emitted().input).toEqual([[[]]]); + expect(Select.vm.selectedValue).toEqual([]); }); it("should not remove tag when close icon is clicked and component is disabled", () => { @@ -17,28 +19,32 @@ describe("Removing values", () => { }); Select.find(".vs__deselect").trigger("click"); - expect(Select.vm.mutableValue).toEqual(["one"]); + expect(Select.vm.selectedValue).toEqual(["one"]); }); it("should remove the last item in the value array on delete keypress when multiple is true", () => { const Select = selectWithProps({ multiple: true, - value: ["one", "two"], options: ["one", "two", "three"] }); - Select.vm.maybeDeleteValue(); - expect(Select.vm.mutableValue).toEqual(["one"]); + Select.vm.$data._value = ["one", "two"]; + + Select.find('.vs__search').trigger('keydown.backspace') + + expect(Select.emitted().input).toEqual([[['one']]]); + expect(Select.vm.selectedValue).toEqual(["one"]); }); it("should set value to null on delete keypress when multiple is false", () => { const Select = selectWithProps({ - value: "one", options: ["one", "two", "three"] }); + Select.vm.$data._value = 'one'; + Select.vm.maybeDeleteValue(); - expect(Select.vm.mutableValue).toEqual(null); + expect(Select.vm.selectedValue).toEqual([]); }); describe("Clear button", () => { @@ -64,12 +70,14 @@ describe("Removing values", () => { it("should remove selected value when clicked", () => { const Select = selectWithProps({ options: ["foo", "bar"], - value: "foo" }); + Select.vm.$data._value = 'foo'; - expect(Select.vm.mutableValue).toEqual("foo"); + expect(Select.vm.selectedValue).toEqual(["foo"]); Select.find("button.vs__clear").trigger("click"); - expect(Select.vm.mutableValue).toEqual(null); + + expect(Select.emitted().input).toEqual([[null]]); + expect(Select.vm.selectedValue).toEqual([]); }); it("should be disabled when component is disabled", () => { diff --git a/tests/unit/Dropdown.spec.js b/tests/unit/Dropdown.spec.js index 5d935cd..e755ef2 100755 --- a/tests/unit/Dropdown.spec.js +++ b/tests/unit/Dropdown.spec.js @@ -59,11 +59,6 @@ describe("Toggling Dropdown", () => { multiple: true, closeOnSelect: false }); - // const vm = new Vue({ - // template: - // '
', - // components: { vSelect }, - // }).$mount(); Select.vm.open = true; Select.vm.select("one"); @@ -120,7 +115,7 @@ describe("Toggling Dropdown", () => { }); Select.vm.search = "foo"; - Select.vm.onEscape(); + Select.find('.vs__search').trigger('keyup.esc') expect(Select.vm.search).toEqual(""); }); diff --git a/tests/unit/Filtering.spec.js b/tests/unit/Filtering.spec.js index 933fde8..05bff8b 100755 --- a/tests/unit/Filtering.spec.js +++ b/tests/unit/Filtering.spec.js @@ -2,6 +2,17 @@ import { shallowMount } from "@vue/test-utils"; import VueSelect from "../../src/components/Select"; describe("Filtering Options", () => { + it("should update the search value when the input element receives the 'input' event", () => { + const Select = shallowMount(VueSelect, { + propsData: { options: ["foo", "bar", "baz"] } + }); + + const input = Select.find('.vs__search'); + input.element.value = 'a' + input.trigger('input') + expect(Select.vm.search).toEqual('a'); + }); + it("should filter an array of strings", () => { const Select = shallowMount(VueSelect, { propsData: { options: ["foo", "bar", "baz"] } diff --git a/tests/unit/Labels.spec.js b/tests/unit/Labels.spec.js index f5fd0f0..94787a6 100755 --- a/tests/unit/Labels.spec.js +++ b/tests/unit/Labels.spec.js @@ -36,7 +36,7 @@ describe("Labels", () => { }); expect(Select.vm.searchPlaceholder).toEqual("foo"); - Select.vm.mutableValue = "one"; + Select.vm.$data._value = "one"; expect(Select.vm.searchPlaceholder).not.toBeDefined(); }); }); diff --git a/tests/unit/ObjectIndex.spec.js b/tests/unit/ObjectIndex.spec.js index 2ffa9c8..f735dcc 100755 --- a/tests/unit/ObjectIndex.spec.js +++ b/tests/unit/ObjectIndex.spec.js @@ -10,7 +10,7 @@ describe("When index prop is defined", () => { options: [{ label: "This is Foo", value: "foo" }] } }); - expect(Select.vm.mutableValue).toEqual("foo"); + expect(Select.vm.selectedValue).toEqual(["foo"]); }); it("can determine if an object is pre-selected", () => { @@ -66,7 +66,7 @@ describe("When index prop is defined", () => { } }); - expect(Select.vm.mutableValue).toEqual(["foo", "bar"]); + expect(Select.vm.selectedValue).toEqual(["foo", "bar"]); }); it("can deselect a pre-selected object", () => { @@ -74,7 +74,6 @@ describe("When index prop is defined", () => { propsData: { multiple: true, index: "value", - value: ["foo", "bar"], options: [ { label: "This is Foo", value: "foo" }, { label: "This is Bar", value: "bar" } @@ -82,16 +81,16 @@ describe("When index prop is defined", () => { } }); + Select.vm.$data._value = ['foo', 'bar']; + Select.vm.deselect("foo"); - expect(Select.vm.mutableValue.length).toEqual(1); - expect(Select.vm.mutableValue).toEqual(["bar"]); + expect(Select.vm.selectedValue).toEqual(["bar"]); }); it("can deselect an option when multiple is false", () => { const Select = shallowMount(VueSelect, { propsData: { index: "value", - value: "foo", options: [ { label: "This is Foo", value: "foo" }, { label: "This is Bar", value: "bar" } @@ -100,7 +99,7 @@ describe("When index prop is defined", () => { }); Select.vm.deselect("foo"); - expect(Select.vm.mutableValue).toEqual(null); + expect(Select.vm.selectedValue).toEqual([]); }); it("can use v-model syntax for a two way binding to a parent component", () => { @@ -120,7 +119,7 @@ describe("When index prop is defined", () => { const Select = Parent.vm.$children[0]; expect(Select.value).toEqual("foo"); - expect(Select.mutableValue).toEqual("foo"); + expect(Select.selectedValue).toEqual(["foo"]); Select.select({ label: "This is Bar", value: "bar" }); expect(Parent.vm.value).toEqual("bar"); diff --git a/tests/unit/ReactiveOptions.spec.js b/tests/unit/ReactiveOptions.spec.js index d73b72f..276a29b 100755 --- a/tests/unit/ReactiveOptions.spec.js +++ b/tests/unit/ReactiveOptions.spec.js @@ -4,17 +4,23 @@ import VueSelect from "../../src/components/Select"; describe("Reset on options change", () => { it("should not reset the selected value by default when the options property changes", () => { const Select = shallowMount(VueSelect, { - propsData: { value: "one", options: ["one"] } + propsData: { options: ["one"] } }); - Select.vm.mutableOptions = ["four", "five", "six"]; - expect(Select.vm.mutableValue).toEqual("one"); + + Select.vm.$data._value = 'one'; + + Select.setProps({options: ["four", "five", "six"]}); + expect(Select.vm.selectedValue).toEqual(["one"]); }); it("should reset the selected value when the options property changes", () => { const Select = shallowMount(VueSelect, { - propsData: { resetOnOptionsChange: true, value: "one", options: ["one"] } + propsData: { resetOnOptionsChange: true, options: ["one"] } }); - Select.vm.mutableOptions = ["four", "five", "six"]; - expect(Select.vm.mutableValue).toEqual(null); + + Select.vm.$data._value = 'one'; + + Select.setProps({options: ["four", "five", "six"]}); + expect(Select.vm.selectedValue).toEqual([]); }); }); diff --git a/tests/unit/Selecting.spec.js b/tests/unit/Selecting.spec.js index 1a799c6..f5a522b 100755 --- a/tests/unit/Selecting.spec.js +++ b/tests/unit/Selecting.spec.js @@ -15,7 +15,7 @@ describe("VS - Selecting Values", () => { const Select = shallowMount(VueSelect, { propsData: defaultProps }); - expect(Select.mutableValue).toEqual(Select.value); + expect(Select.selectedValue).toEqual(Select.value); }); it("can accept an array of objects and pre-selected value (single)", () => { @@ -28,7 +28,7 @@ describe("VS - Selecting Values", () => { ] } }); - expect(Select.mutableValue).toEqual(Select.value); + expect(Select.selectedValue).toEqual(Select.value); }); it("can accept an array of objects and pre-selected values (multiple)", () => { @@ -46,7 +46,7 @@ describe("VS - Selecting Values", () => { multiple: true }); - expect(Select.mutableValue).toEqual(Select.value); + expect(Select.selectedValue).toEqual(Select.value); }); it("can select an option on tab", () => { @@ -67,10 +67,6 @@ describe("VS - Selecting Values", () => { const Select = shallowMount(VueSelect, { propsData: { multiple: true, - value: [ - { label: "This is Foo", value: "foo" }, - { label: "This is Bar", value: "bar" } - ], options: [ { label: "This is Foo", value: "foo" }, { label: "This is Bar", value: "bar" } @@ -78,36 +74,41 @@ describe("VS - Selecting Values", () => { } }); + Select.vm.$data._value = [ + { label: "This is Foo", value: "foo" }, + { label: "This is Bar", value: "bar" } + ]; + Select.vm.deselect({ label: "This is Foo", value: "foo" }); - expect(Select.vm.mutableValue.length).toEqual(1); + expect(Select.vm.selectedValue).toEqual([{ label: "This is Bar", value: "bar" }]); }); it("can deselect a pre-selected string", () => { const Select = shallowMount(VueSelect, { propsData: { multiple: true, - value: ["foo", "bar"], options: ["foo", "bar"] } }); + + Select.vm.$data._value = "foo"; + Select.vm.deselect("foo"); - expect(Select.vm.mutableValue.length).toEqual(1); + expect(Select.vm.selectedValue).toEqual([]); }); it("can deselect an option when multiple is false", () => { - const Select = shallowMount(VueSelect, { - propsData: { - value: "foo" - } - }); + const Select = shallowMount(VueSelect); + + Select.vm.$data._value = "foo"; + Select.vm.deselect("foo"); - expect(Select.vm.mutableValue).toEqual(null); + expect(Select.vm.selectedValue).toEqual([]); }); it("can determine if the value prop is empty", () => { const Select = shallowMount(VueSelect, { propsData: { - value: [], options: ["one", "two", "three"] } }); @@ -134,19 +135,16 @@ describe("VS - Selecting Values", () => { it("should reset the selected values when the multiple property changes", () => { const Select = shallowMount(VueSelect, { propsData: { - value: ["one"], multiple: true, options: ["one", "two", "three"] } }); - expect(Select.vm.mutableValue).toEqual(["one"]); - Select.setProps({ multiple: false }); - expect(Select.vm.mutableValue).toEqual(null); + expect(Select.vm.selectedValue).toEqual([]); Select.setProps({ multiple: true }); - expect(Select.vm.mutableValue).toEqual([]); + expect(Select.vm.selectedValue).toEqual([]); }); it("can retain values present in a new array of options", () => { @@ -158,7 +156,7 @@ describe("VS - Selecting Values", () => { }); Select.setProps({ options: ["one", "five", "six"] }); - expect(Select.vm.mutableValue).toEqual(["one"]); + expect(Select.vm.selectedValue).toEqual(["one"]); }); it("can determine if an object is already selected", () => { @@ -181,7 +179,7 @@ describe("VS - Selecting Values", () => { const Select = Parent.vm.$children[0]; expect(Select.value).toEqual("foo"); - expect(Select.mutableValue).toEqual("foo"); + expect(Select.selectedValue).toEqual(["foo"]); Select.select("bar"); expect(Parent.vm.value).toEqual("bar"); diff --git a/tests/unit/Tagging.spec.js b/tests/unit/Tagging.spec.js index e8b2d88..d7c8de8 100755 --- a/tests/unit/Tagging.spec.js +++ b/tests/unit/Tagging.spec.js @@ -44,25 +44,24 @@ describe("When Tagging Is Enabled", () => { const Select = selectWithProps({ taggable: true, multiple: true, - value: ["one"], options: ["one", "two"] }); searchSubmit(Select, "three"); - expect(Select.vm.mutableValue).toEqual(["one", "three"]); + + expect(Select.vm.selectedValue).toEqual(["three"]); }); it("can select the current search text as an object", () => { const Select = selectWithProps({ taggable: true, multiple: true, - value: [{ label: "one" }], options: [{ label: "one" }] }); searchSubmit(Select, "two"); - expect(Select.vm.mutableValue).toEqual([ - { label: "one" }, + + expect(Select.vm.selectedValue).toEqual([ { label: "two" } ]); }); @@ -77,7 +76,8 @@ describe("When Tagging Is Enabled", () => { }); searchSubmit(Select, "three"); - expect(Select.vm.mutableOptions).toEqual(["one", "two", "three"]); + expect(Select.vm.pushedTags).toEqual(["three"]); + expect(Select.vm.optionList).toEqual(["one", "two", "three"]); }); it("should add a freshly created option/tag to the options list when pushTags is true and filterable is false", () => { @@ -91,7 +91,8 @@ describe("When Tagging Is Enabled", () => { }); searchSubmit(Select, "three"); - expect(Select.vm.mutableOptions).toEqual(["one", "two", "three"]); + expect(Select.vm.pushedTags).toEqual(["three"]); + expect(Select.vm.optionList).toEqual(["one", "two", "three"]); expect(Select.vm.filteredOptions).toEqual(["one", "two", "three"]); }); @@ -105,7 +106,7 @@ describe("When Tagging Is Enabled", () => { }); searchSubmit(Select, "three"); - expect(Select.vm.mutableOptions).toEqual(["one", "two"]); + expect(Select.vm.optionList).toEqual(["one", "two"]); }); it("wont add a freshly created option/tag to the options list when pushTags is false and filterable is false", () => { @@ -119,16 +120,15 @@ describe("When Tagging Is Enabled", () => { }); searchSubmit(Select, "three"); - expect(Select.vm.mutableOptions).toEqual(["one", "two"]); + expect(Select.vm.optionList).toEqual(["one", "two"]); expect(Select.vm.filteredOptions).toEqual(["one", "two"]); }); - it("should select an existing option if the search string matches a string from options", () => { + it("should select an existing option if the search string matches a string from options", async () => { let two = "two"; const Select = selectWithProps({ taggable: true, multiple: true, - value: null, options: ["one", two] }); @@ -136,7 +136,7 @@ describe("When Tagging Is Enabled", () => { searchSubmit(Select); - expect(Select.vm.mutableValue[0]).toBe(two); + expect(Select.vm.selectedValue).toEqual([two]); }); it("should select an existing option if the search string matches an objects label from options", () => { @@ -149,7 +149,7 @@ describe("When Tagging Is Enabled", () => { Select.vm.search = "two"; searchSubmit(Select); - expect(Select.vm.mutableValue.label).toBe(two.label); + expect(Select.vm.selectedValue).toEqual([two]); }); it("should select an existing option if the search string matches an objects label from options when filter-options is false", () => { @@ -163,7 +163,7 @@ describe("When Tagging Is Enabled", () => { Select.vm.search = "two"; searchSubmit(Select); - expect(Select.vm.mutableValue.label).toBe(two.label); + expect(Select.vm.selectedValue).toEqual([two]); }); it("should not reset the selected value when the options property changes", () => { @@ -174,8 +174,8 @@ describe("When Tagging Is Enabled", () => { options: [{ label: "one" }] }); - Select.vm.mutableOptions = [{ label: "two" }]; - expect(Select.vm.mutableValue).toEqual([{ label: "one" }]); + Select.setProps({ options: [{ label: "two" }] }); + expect(Select.vm.selectedValue).toEqual([{ label: "one" }]); }); it("should not reset the selected value when the options property changes when filterable is false", () => { @@ -187,8 +187,8 @@ describe("When Tagging Is Enabled", () => { options: [{ label: "one" }] }); - Select.vm.mutableOptions = [{ label: "two" }]; - expect(Select.vm.mutableValue).toEqual([{ label: "one" }]); + Select.setProps({ options: [{ label: "two" }] }); + expect(Select.vm.selectedValue).toEqual([{ label: "one" }]); }); it("should not allow duplicate tags when using string options", () => { @@ -198,11 +198,11 @@ describe("When Tagging Is Enabled", () => { }); searchSubmit(Select, "one"); - expect(Select.vm.mutableValue).toEqual(["one"]); + expect(Select.vm.selectedValue).toEqual(["one"]); expect(Select.vm.search).toEqual(""); searchSubmit(Select, "one"); - expect(Select.vm.mutableValue).toEqual(["one"]); + expect(Select.vm.selectedValue).toEqual(["one"]); expect(Select.vm.search).toEqual(""); }); @@ -214,11 +214,11 @@ describe("When Tagging Is Enabled", () => { }); searchSubmit(Select, "one"); - expect(Select.vm.mutableValue).toEqual([{ label: "one" }]); + expect(Select.vm.selectedValue).toEqual([{ label: "one" }]); expect(Select.vm.search).toEqual(""); searchSubmit(Select, "one"); - expect(Select.vm.mutableValue).toEqual([{ label: "one" }]); + expect(Select.vm.selectedValue).toEqual([{ label: "one" }]); expect(Select.vm.search).toEqual(""); }); });