From cb324e024089376acdce9ab85930d15be787082c Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 4 Jan 2018 16:53:29 -0800 Subject: [PATCH] Revert "Fix linter" --- .eslintrc | 184 ++- package.json | 10 +- src/components/Select.vue | 110 +- src/dev.js | 6 +- src/mixins/ajax.js | 121 +- src/mixins/pointerScroll.js | 35 +- src/mixins/typeAheadPointer.js | 28 +- test/unit/specs/Select.spec.js | 2390 ++++++++++++++++---------------- 8 files changed, 1515 insertions(+), 1369 deletions(-) diff --git a/.eslintrc b/.eslintrc index 6ddab43..7f2ceaf 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,17 +3,183 @@ "html" ], - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - }, - "env": { "browser": true, - "node": true, - "es6": true, - "jasmine": true + "node": true }, - "extends": "standard" + "ecmaFeatures": { + "arrowFunctions": true, + "destructuring": true, + "classes": true, + "defaultParams": true, + "blockBindings": true, + "modules": true, + "objectLiteralComputedProperties": true, + "objectLiteralShorthandMethods": true, + "objectLiteralShorthandProperties": true, + "restParams": true, + "spread": true, + "forOf": true, + "generators": true, + "templateStrings": true, + "superInFunctions": true, + "experimentalObjectRestSpread": true + }, + + "rules": { + "accessor-pairs": 2, + "array-bracket-spacing": 0, + "block-scoped-var": 0, + "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "camelcase": 0, + "comma-dangle": [2, "never"], + "comma-spacing": [2, { "before": false, "after": true }], + "comma-style": [2, "last"], + "complexity": 0, + "computed-property-spacing": 0, + "consistent-return": 0, + "consistent-this": 0, + "constructor-super": 2, + "curly": [2, "multi-line"], + "default-case": 0, + "dot-location": [2, "property"], + "dot-notation": 0, + "eol-last": 2, + "eqeqeq": [2, "allow-null"], + "func-names": 0, + "func-style": 0, + "generator-star-spacing": [2, { "before": true, "after": true }], + "guard-for-in": 0, + "handle-callback-err": [2, "^(err|error)$" ], + "indent": [2, 2, { "SwitchCase": 1 }], + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "linebreak-style": 0, + "lines-around-comment": 0, + "max-nested-callbacks": 0, + "new-cap": [2, { "newIsCap": true, "capIsNew": false }], + "new-parens": 2, + "newline-after-var": 0, + "no-alert": 0, + "no-array-constructor": 2, + "no-caller": 2, + "no-catch-shadow": 0, + "no-cond-assign": 2, + "no-console": 0, + "no-constant-condition": 0, + "no-continue": 0, + "no-control-regex": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-div-regex": 0, + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-else-return": 0, + "no-empty": 0, + "no-empty-character-class": 2, + "no-empty-label": 2, + "no-eq-null": 0, + "no-eval": 2, + "no-ex-assign": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-boolean-cast": 2, + "no-extra-parens": 0, + "no-extra-semi": 0, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-func-assign": 2, + "no-implied-eval": 2, + "no-inline-comments": 0, + "no-inner-declarations": [2, "functions"], + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-lonely-if": 0, + "no-loop-func": 0, + "no-mixed-requires": 0, + "no-mixed-spaces-and-tabs": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [2, { "max": 1 }], + "no-native-reassign": 2, + "no-negated-in-lhs": 2, + "no-nested-ternary": 0, + "no-new": 2, + "no-new-func": 0, + "no-new-object": 2, + "no-new-require": 2, + "no-new-wrappers": 2, + "no-obj-calls": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-param-reassign": 0, + "no-path-concat": 0, + "no-process-env": 0, + "no-process-exit": 0, + "no-proto": 0, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-restricted-modules": 0, + "no-return-assign": 2, + "no-script-url": 0, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow": 0, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-sparse-arrays": 2, + "no-sync": 0, + "no-ternary": 0, + "no-this-before-super": 2, + "no-throw-literal": 2, + "no-trailing-spaces": 2, + "no-undef": 2, + "no-undef-init": 2, + "no-undefined": 0, + "no-underscore-dangle": 0, + "no-unexpected-multiline": 2, + "no-unneeded-ternary": 2, + "no-unreachable": 2, + "no-unused-expressions": 0, + "no-unused-vars": [2, { "vars": "all", "args": "none" }], + "no-use-before-define": 0, + "no-var": 0, + "no-void": 0, + "no-warning-comments": 0, + "no-with": 2, + "object-curly-spacing": 0, + "object-shorthand": 0, + "one-var": [2, { "initialized": "never" }], + "operator-assignment": 0, + "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }], + "padded-blocks": 0, + "prefer-const": 0, + "quote-props": 0, + "quotes": [2, "single", "avoid-escape"], + "radix": 2, + "semi": [2, "never"], + "semi-spacing": 0, + "sort-vars": 0, + "space-after-keywords": [2, "always"], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, "always"], + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-return-throw-case": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }], + "strict": 0, + "use-isnan": 2, + "valid-jsdoc": 0, + "valid-typeof": 2, + "vars-on-top": 0, + "wrap-iife": [2, "any"], + "wrap-regex": 0, + "yoda": [2, "never"] + } } diff --git a/package.json b/package.json index eee278f..ea2ca1a 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,6 @@ "codeclimate-test-reporter": "^0.3.1", "connect-history-api-fallback": "^1.1.0", "css-loader": "^0.23.0", - "eslint": "^4.14.0", - "eslint-config-standard": "^11.0.0-beta.0", - "eslint-plugin-html": "^4.0.1", - "eslint-plugin-import": "^2.8.0", - "eslint-plugin-node": "^5.2.1", - "eslint-plugin-promise": "^3.6.0", - "eslint-plugin-standard": "^3.0.1", "eventsource-polyfill": "^0.9.6", "express": "^4.13.3", "extract-text-webpack-plugin": "^1.0.1", @@ -81,6 +74,5 @@ "webpack-dev-middleware": "^1.4.0", "webpack-hot-middleware": "^2.6.0", "webpack-merge": "^0.13.0" - }, - "dependencies": {} + } } diff --git a/src/components/Select.vue b/src/components/Select.vue index b966671..372662f 100644 --- a/src/components/Select.vue +++ b/src/components/Select.vue @@ -370,9 +370,9 @@ */ options: { type: Array, - default () { + default() { return [] - } + }, }, /** @@ -469,13 +469,13 @@ */ getOptionLabel: { type: Function, - default (option) { + default(option) { if (typeof option === 'object') { if (this.label && option[this.label]) { return option[this.label] } } - return option + return option; } }, @@ -538,7 +538,7 @@ */ createOption: { type: Function, - default (newOption) { + default(newOption) { if (typeof this.mutableOptions[0] === 'object') { newOption = {[this.label]: newOption} } @@ -583,10 +583,10 @@ dir: { type: String, default: 'auto' - } + }, }, - data () { + data() { return { search: '', open: false, @@ -598,12 +598,12 @@ watch: { /** * When the value prop changes, update - * the internal mutableValue. + * the internal mutableValue. * @param {mixed} val * @return {void} */ - value (val) { - this.mutableValue = val + value(val) { + this.mutableValue = val }, /** @@ -612,11 +612,11 @@ * @param {string|object} old * @return {void} */ - mutableValue (val, old) { + mutableValue(val, old) { if (this.multiple) { - if (this.onChange) this.onChange(val) + this.onChange ? this.onChange(val) : null } else { - if (this.onChange && val !== old) this.onChange(val) + this.onChange && val !== old ? this.onChange(val) : null } }, @@ -626,29 +626,29 @@ * @param {array} val * @return {void} */ - options (val) { + options(val) { this.mutableOptions = val }, /** - * Maybe reset the mutableValue + * Maybe reset the mutableValue * when mutableOptions change. * @return {[type]} [description] */ - mutableOptions () { + mutableOptions() { if (!this.taggable && this.resetOnOptionsChange) { - this.mutableValue = this.multiple ? [] : null + this.mutableValue = this.multiple ? [] : null } }, /** - * Always reset the mutableValue when + * Always reset the mutableValue when * the multiple prop changes. * @param {Boolean} val * @return {void} */ - multiple (val) { - this.mutableValue = val ? [] : null + multiple(val) { + this.mutableValue = val ? [] : null } }, @@ -656,10 +656,10 @@ * Clone props into mutable values, * attach any event listeners. */ - created () { - this.mutableValue = this.value + created() { + this.mutableValue = this.value this.mutableOptions = this.options.slice(0) - this.mutableLoading = this.loading + this.mutableLoading = this.loading this.$on('option:created', this.maybePushTag) }, @@ -671,7 +671,7 @@ * @param {Object|String} option * @return {void} */ - select (option) { + select(option) { if (this.isOptionSelected(option)) { this.deselect(option) } else { @@ -696,15 +696,15 @@ * @param {Object|String} option * @return {void} */ - deselect (option) { + deselect(option) { if (this.multiple) { let ref = -1 this.mutableValue.forEach((val) => { - if (val === option || (typeof val === 'object' && val[this.label] === option[this.label])) { + if (val === option || typeof val === 'object' && val[this.label] === option[this.label]) { ref = val } }) - const index = this.mutableValue.indexOf(ref) + var index = this.mutableValue.indexOf(ref) this.mutableValue.splice(index, 1) } else { this.mutableValue = null @@ -716,7 +716,7 @@ * @param {Object|String} option * @return {void} */ - onAfterSelect (option) { + onAfterSelect(option) { if (this.closeOnSelect) { this.open = !this.open this.$refs.search.blur() @@ -732,9 +732,8 @@ * @param {Event} e * @return {void} */ - toggleDropdown (e) { - if (e.target === this.$refs.openIndicator || e.target === this.$refs.search || - e.target === this.$refs.toggle || e.target === this.$el) { + toggleDropdown(e) { + if (e.target === this.$refs.openIndicator || e.target === this.$refs.search || e.target === this.$refs.toggle || e.target === this.$el) { if (this.open) { this.$refs.search.blur() // dropdown will close on blur } else { @@ -751,7 +750,7 @@ * @param {Object|String} option * @return {Boolean} True when selected | False otherwise */ - isOptionSelected (option) { + isOptionSelected(option) { if (this.multiple && this.mutableValue) { let selected = false this.mutableValue.forEach(opt => { @@ -759,7 +758,8 @@ selected = true } else if (typeof opt === 'object' && opt[this.label] === option) { selected = true - } else if (opt === option) { + } + else if (opt === option) { selected = true } }) @@ -774,7 +774,7 @@ * Otherwise, blur the search input to close the dropdown. * @return {void} */ - onEscape () { + onEscape() { if (!this.search.length) { this.$refs.search.blur() } else { @@ -787,7 +787,7 @@ * @emits {search:blur} * @return {void} */ - onSearchBlur () { + onSearchBlur() { if (this.clearSearchOnBlur) { this.search = '' } @@ -800,7 +800,7 @@ * @emits {search:focus} * @return {void} */ - onSearchFocus () { + onSearchFocus() { this.open = true this.$emit('search:focus') }, @@ -810,18 +810,10 @@ * text in the search input, & there's tags to delete * @return {this.value} */ - maybeDeleteValue () { - let value = null - + maybeDeleteValue() { if (!this.$refs.search.value.length && this.mutableValue) { - if (this.multiple) { - value = this.mutableValue.pop() - } else { - value = this.mutableValue = null - } + return this.multiple ? this.mutableValue.pop() : this.mutableValue = null } - - return value }, /** @@ -831,7 +823,7 @@ * @param {Object || String} option * @return {boolean} */ - optionExists (option) { + optionExists(option) { let exists = false this.mutableOptions.forEach(opt => { @@ -852,7 +844,7 @@ * @param {Object || String} option * @return {void} */ - maybePushTag (option) { + maybePushTag(option) { if (this.pushTags) { this.mutableOptions.push(option) } @@ -865,7 +857,7 @@ * Classes to be output on .dropdown * @return {Object} */ - dropdownClasses () { + dropdownClasses() { return { open: this.dropdownOpen, single: !this.multiple, @@ -882,7 +874,7 @@ * If search text should clear on blur * @return {Boolean} True when single and clearSearchOnSelect */ - clearSearchOnBlur () { + clearSearchOnBlur() { return this.clearSearchOnSelect && !this.multiple }, @@ -891,7 +883,7 @@ * search input * @return {Boolean} True if non empty value */ - searching () { + searching() { return !!this.search }, @@ -900,7 +892,7 @@ * dropdown menu. * @return {Boolean} True if open */ - dropdownOpen () { + dropdownOpen() { return this.noDrop ? false : this.open && !this.mutableLoading }, @@ -909,9 +901,9 @@ * & there is no value selected. * @return {String} Placeholder text */ - searchPlaceholder () { + searchPlaceholder() { if (this.isValueEmpty && this.placeholder) { - return this.placeholder + return this.placeholder; } }, @@ -923,7 +915,7 @@ * * @return {array} */ - filteredOptions () { + filteredOptions() { if (!this.filterable && !this.taggable) { return this.mutableOptions.slice() } @@ -945,7 +937,7 @@ * Check if there aren't any options selected. * @return {Boolean} */ - isValueEmpty () { + isValueEmpty() { if (this.mutableValue) { if (typeof this.mutableValue === 'object') { return !Object.keys(this.mutableValue).length @@ -953,14 +945,14 @@ return !this.mutableValue.length } - return true + return true; }, /** * Return the current value in array format. * @return {Array} */ - valueAsArray () { + valueAsArray() { if (this.multiple) { return this.mutableValue } else if (this.mutableValue) { @@ -969,7 +961,7 @@ return [] } - } + }, } diff --git a/src/dev.js b/src/dev.js index f1bd4e7..314174b 100644 --- a/src/dev.js +++ b/src/dev.js @@ -14,18 +14,18 @@ Vue.config.devtools = true new Vue({ el: '#app', data: { - placeholder: 'placeholder', + placeholder: "placeholder", value: null, options: countries, ajaxRes: [], people: [] }, methods: { - search (search, loading) { + search(search, loading) { loading(true) this.getRepositories(search, loading, this) }, - searchPeople (search, loading) { + searchPeople(search, loading) { loading(true) this.getPeople(loading, this) }, diff --git a/src/mixins/ajax.js b/src/mixins/ajax.js index 44dbaba..b897053 100644 --- a/src/mixins/ajax.js +++ b/src/mixins/ajax.js @@ -1,75 +1,72 @@ module.exports = { - props: { - /** - * Toggles the adding of a 'loading' class to the main - * .v-select wrapper. Useful to control UI state when - * results are being processed through AJAX. - */ - loading: { - type: Boolean, - default: false - }, + props: { + /** + * Toggles the adding of a 'loading' class to the main + * .v-select wrapper. Useful to control UI state when + * results are being processed through AJAX. + */ + loading: { + type: Boolean, + default: false + }, - /** - * Accept a callback function that will be - * run when the search text changes. - * - * loading() accepts a boolean value, and can - * be used to toggle a loading class from - * the onSearch callback. - * - * @param {search} String Current search text - * @param {loading} Function(bool) Toggle loading class - */ - onSearch: { - type: Function, - default: function (search, loading) {} - } - }, + /** + * Accept a callback function that will be + * run when the search text changes. + * + * loading() accepts a boolean value, and can + * be used to toggle a loading class from + * the onSearch callback. + * + * @param {search} String Current search text + * @param {loading} Function(bool) Toggle loading class + */ + onSearch: { + type: Function, + default: function(search, loading){} + } + }, - data () { - return { + data() { + return { mutableLoading: false } - }, + }, - watch: { - /** - * If a callback & search text has been provided, - * invoke the onSearch callback. - */ - search () { - if (this.search.length > 0) { - this.onSearch(this.search, this.toggleLoading) + watch: { + /** + * If a callback & search text has been provided, + * invoke the onSearch callback. + */ + search() { + if (this.search.length > 0) { + this.onSearch(this.search, this.toggleLoading) this.$emit('search', this.search, this.toggleLoading) } - }, + }, /** - * Sync the loading prop with the internal - * mutable loading value. + * Sync the loading prop with the internal + * mutable loading value. * @param val */ - loading (val) { - this.mutableLoading = val - } - }, + loading(val) { + this.mutableLoading = val + } + }, - methods: { - /** - * Toggle this.loading. Optionally pass a boolean - * value. If no value is provided, this.loading - * will be set to the opposite of it's current value. - * @param toggle Boolean - * @returns {*} - */ - toggleLoading (toggle = null) { - if (toggle == null) { - this.mutableLoading = !this.mutableLoading - } else { - this.mutableLoading = toggle - } - - return this.mutableLoading - } - } + methods: { + /** + * Toggle this.loading. Optionally pass a boolean + * value. If no value is provided, this.loading + * will be set to the opposite of it's current value. + * @param toggle Boolean + * @returns {*} + */ + toggleLoading(toggle = null) { + if (toggle == null) { + return this.mutableLoading = !this.mutableLoading + } + return this.mutableLoading = toggle + } + } } diff --git a/src/mixins/pointerScroll.js b/src/mixins/pointerScroll.js index 84875d0..5950959 100644 --- a/src/mixins/pointerScroll.js +++ b/src/mixins/pointerScroll.js @@ -2,7 +2,7 @@ module.exports = { watch: { - typeAheadPointer () { + typeAheadPointer() { this.maybeAdjustScroll() } }, @@ -14,14 +14,14 @@ module.exports = { * overflow bounds. * @returns {*} */ - maybeAdjustScroll () { + maybeAdjustScroll() { let pixelsToPointerTop = this.pixelsToPointerTop() let pixelsToPointerBottom = this.pixelsToPointerBottom() - if (pixelsToPointerTop <= this.viewport().top) { - return this.scrollTo(pixelsToPointerTop) + if ( pixelsToPointerTop <= this.viewport().top) { + return this.scrollTo( pixelsToPointerTop ) } else if (pixelsToPointerBottom >= this.viewport().bottom) { - return this.scrollTo(this.viewport().top + this.pointerHeight()) + return this.scrollTo( this.viewport().top + this.pointerHeight() ) } }, @@ -30,10 +30,9 @@ module.exports = { * list to the top of the current pointer element. * @returns {number} */ - pixelsToPointerTop () { + pixelsToPointerTop() { let pixelsToPointerTop = 0 - - if (this.$refs.dropdownMenu) { + if( this.$refs.dropdownMenu ) { for (let i = 0; i < this.typeAheadPointer; i++) { pixelsToPointerTop += this.$refs.dropdownMenu.children[i].offsetHeight } @@ -46,7 +45,7 @@ module.exports = { * list to the bottom of the current pointer element. * @returns {*} */ - pixelsToPointerBottom () { + pixelsToPointerBottom() { return this.pixelsToPointerTop() + this.pointerHeight() }, @@ -54,7 +53,7 @@ module.exports = { * The offsetHeight of the current pointer element. * @returns {number} */ - pointerHeight () { + pointerHeight() { let element = this.$refs.dropdownMenu ? this.$refs.dropdownMenu.children[this.typeAheadPointer] : false return element ? element.offsetHeight : 0 }, @@ -63,9 +62,9 @@ module.exports = { * The currently viewable portion of the dropdownMenu. * @returns {{top: (string|*|number), bottom: *}} */ - viewport () { + viewport() { return { - top: this.$refs.dropdownMenu ? this.$refs.dropdownMenu.scrollTop : 0, + top: this.$refs.dropdownMenu ? this.$refs.dropdownMenu.scrollTop: 0, bottom: this.$refs.dropdownMenu ? this.$refs.dropdownMenu.offsetHeight + this.$refs.dropdownMenu.scrollTop : 0 } }, @@ -75,14 +74,8 @@ module.exports = { * @param position * @returns {*} */ - scrollTo (position) { - let result = null - - if (this.$refs.dropdownMenu) { - result = this.$refs.dropdownMenu.scrollTop = position - } - - return result - } + scrollTo(position) { + return this.$refs.dropdownMenu ? this.$refs.dropdownMenu.scrollTop = position : null + }, } } diff --git a/src/mixins/typeAheadPointer.js b/src/mixins/typeAheadPointer.js index a038aca..26c9374 100644 --- a/src/mixins/typeAheadPointer.js +++ b/src/mixins/typeAheadPointer.js @@ -1,12 +1,12 @@ module.exports = { - data () { + data() { return { typeAheadPointer: -1 } }, watch: { - filteredOptions () { + filteredOptions() { this.typeAheadPointer = 0 } }, @@ -17,10 +17,10 @@ module.exports = { * subtracting the current index by one. * @return {void} */ - typeAheadUp () { + typeAheadUp() { if (this.typeAheadPointer > 0) { this.typeAheadPointer-- - if (this.maybeAdjustScroll) { + if( this.maybeAdjustScroll ) { this.maybeAdjustScroll() } } @@ -31,10 +31,10 @@ module.exports = { * adding the current index by one. * @return {void} */ - typeAheadDown () { + typeAheadDown() { if (this.typeAheadPointer < this.filteredOptions.length - 1) { this.typeAheadPointer++ - if (this.maybeAdjustScroll) { + if( this.maybeAdjustScroll ) { this.maybeAdjustScroll() } } @@ -45,16 +45,16 @@ module.exports = { * Optionally clear the search input on selection. * @return {void} */ - typeAheadSelect () { - if (this.filteredOptions[ this.typeAheadPointer ]) { - this.select(this.filteredOptions[this.typeAheadPointer]) - } else if (this.taggable && this.search.length) { + typeAheadSelect() { + if( this.filteredOptions[ this.typeAheadPointer ] ) { + this.select( this.filteredOptions[ this.typeAheadPointer ] ); + } else if (this.taggable && this.search.length){ this.select(this.search) } - if (this.clearSearchOnSelect) { - this.search = '' + if( this.clearSearchOnSelect ) { + this.search = ""; } - } + }, } -} +} \ No newline at end of file diff --git a/test/unit/specs/Select.spec.js b/test/unit/specs/Select.spec.js index 1bf7805..33d954c 100644 --- a/test/unit/specs/Select.spec.js +++ b/test/unit/specs/Select.spec.js @@ -17,12 +17,12 @@ Vue.component('v-select', vSelect) * @param process * @returns {Event} */ -function trigger (target, event, process) { - const e = document.createEvent('HTMLEvents') - e.initEvent(event, true, true) - if (process) process(e) - target.dispatchEvent(e) - return e +function trigger(target, event, process) { + var e = document.createEvent('HTMLEvents') + e.initEvent(event, true, true) + if (process) process(e) + target.dispatchEvent(e) + return e } /** @@ -32,12 +32,12 @@ function trigger (target, event, process) { * @param process * @returns {Event} */ -function triggerMouse (target, event, process) { - const e = document.createEvent('MouseEvent') - e.initEvent('event', true, true) - if (process) process(e) - target.dispatchEvent(e) - return e +function triggerMouse(target, event, process) { + var e = document.createEvent('MouseEvent') + e.initEvent('event', true, true) + if (process) process(e) + target.dispatchEvent(e) + return e } /** @@ -47,12 +47,12 @@ function triggerMouse (target, event, process) { * @param process * @returns {Event} */ -function triggerFocusEvent (target, event, process) { - const e = document.createEvent('FocusEvent') - e.initEvent('event', true, true) - if (process) process(e) - target.dispatchEvent(e) - return e +function triggerFocusEvent(target, event, process) { + var e = document.createEvent('FocusEvent') + e.initEvent('event', true, true) + if (process) process(e) + target.dispatchEvent(e) + return e } /** @@ -60,1071 +60,1077 @@ function triggerFocusEvent (target, event, process) { * @param vm * @param search */ -function searchSubmit (vm, search = false) { - if (search) { - vm.$children[0].search = 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 - }) + trigger(vm.$children[0].$refs.search, 'keydown', function (e) { + e.keyCode = 13 + }) } 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 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].select({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].select('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].mutableValue = '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'}])) - }) - }) - - 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('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', () => { - let methods = Object.assign(pointerScroll.methods, { - pixelsToPointerTop () { - return 1 - }, - viewport () { - return {top: 2, bottom: 0} - } - }) - const vm = new Vue({ - template: '
', - components: { - 'v-select': Mock({ - '../mixins/pointerScroll': {methods} - }) - } - }).$mount() - - spyOn(vm.$children[0], 'scrollTo') - vm.$children[0].maybeAdjustScroll() - expect(vm.$children[0].scrollTo).toHaveBeenCalledWith(1) - }) - - /** - * @link https://github.com/vuejs/vue-loader/issues/434 - * @todo vue-loader/inject-loader fails when used twice in the same file, - * so the mock here is abastracted to a separate file. - */ - xit('should scroll down if the pointer is below the current viewport bounds', () => { - let methods = Object.assign(pointerScroll.methods, { - pixelsToPointerBottom () { - return 2 - }, - viewport () { - return {top: 0, bottom: 1} - } - }) - const vm = new Vue({ - template: `
`, - components: { - 'v-select': Mock({ - '../mixins/pointerScroll': {methods} - }) - } - }).$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() - 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 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([]) - 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([]) - 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() - }) - }) - }) + + 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 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].select({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].select('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].mutableValue = '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'}])) + }) + }) + + 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('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', () => { + let methods = Object.assign(pointerScroll.methods, { + pixelsToPointerTop() { + return 1 + }, + viewport() { + return {top: 2, bottom: 0} + } + }) + const vm = new Vue({ + template: '
', + components: { + 'v-select': Mock({ + '../mixins/pointerScroll': {methods} + }) + }, + }).$mount() + + spyOn(vm.$children[0], 'scrollTo') + vm.$children[0].maybeAdjustScroll() + expect(vm.$children[0].scrollTo).toHaveBeenCalledWith(1) + }) + + /** + * @link https://github.com/vuejs/vue-loader/issues/434 + * @todo vue-loader/inject-loader fails when used twice in the same file, + * so the mock here is abastracted to a separate file. + */ + xit('should scroll down if the pointer is below the current viewport bounds', () => { + let methods = Object.assign(pointerScroll.methods, { + pixelsToPointerBottom() { + return 2 + }, + viewport() { + return {top: 0, bottom: 1} + } + }) + const vm = new Vue({ + template: `
`, + components: { + 'v-select': Mock({ + '../mixins/pointerScroll': {methods} + }) + }, + }).$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() + 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 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([]) + 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([]) + 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({ @@ -1133,7 +1139,7 @@ describe('Select.vue', () => { called: false }, methods: { - foo (val) { + foo(val) { this.called = val } } @@ -1152,8 +1158,8 @@ describe('Select.vue', () => { template: '
', data: { called: false }, methods: { - foo (val) { - this.called = !this.called + foo(val) { + this.called = ! this.called } } }).$mount() @@ -1169,128 +1175,128 @@ describe('Select.vue', () => { }) }) - it('can set loading to false from the onSearch callback', (done) => { - const vm = new Vue({ - template: '
', - methods: { - foo (search, loading) { - loading(false) - } - } - }).$mount() + 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() - }) - }) + 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() + 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) + let select = vm.$refs.select + select.onSearch(select.search, select.toggleLoading) - Vue.nextTick(() => { - expect(vm.$refs.select.mutableLoading).toEqual(true) - done() - }) - }) + Vue.nextTick(() => { + expect(vm.$refs.select.mutableLoading).toEqual(true) + done() + }) + }) - it('will sync mutable loading with the loading prop', (done) => { + it('will sync mutable loading with the loading prop', (done) => { const vm = new Vue({ template: '
', - data: {loading: false} + data: {loading:false} }).$mount() - vm.loading = true - Vue.nextTick(() => { - expect(vm.$refs.select.mutableLoading).toEqual(true) - done() - }) - }) - }) + 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() - }) - }) + 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() - }) - }) - }) + 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() + 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() + }) + }) - vm.$children[0].open = true - vm.$refs.select.search = 't' - expect(vm.$refs.select.search).toEqual('t') + 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].onSearchBlur() - Vue.nextTick(() => { - expect(vm.$refs.select.search).toEqual('') - done() - }) - }) + vm.$children[0].open = true + vm.$refs.select.search = "t" + expect(vm.$refs.select.search).toEqual('t') - 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() - }) - }) - }) + vm.$children[0].onSearchBlur() + Vue.nextTick(() => { + expect(vm.$refs.select.search).toEqual('t') + done() + }) + }) + }) })