@@ -43,12 +44,9 @@
@@ -79,7 +77,8 @@
-
+
+
+```
+
+## Browser Globals
+
+`v1.3.0+` no longer requires any toolchain to use the component:
+
+Just include `vue` & `vue-select.js` - I recommend using [unpkg.com](https://unpkg.com/#/).
+
+```html
+
+
+
+
+```
+Then register the component in your javascript:
+
+```js
+Vue.component('v-select', VueSelect.VueSelect);
+```
+
+From there you can use as normal. Here's an [example on JSBin](http://jsbin.com/saxaru/5/edit?html,js,output).
diff --git a/docs/md/OnChange.md b/docs/md/OnChange.md
new file mode 100644
index 0000000..4b3ad49
--- /dev/null
+++ b/docs/md/OnChange.md
@@ -0,0 +1,26 @@
+### Change Event
Vuex Compatibility
+
+vue-select provides a `change` event. This function is passed the currently selected value(s) as it's only parameter.
+
+This is very useful when integrating with Vuex, as it will allow your to trigger an action to update your vuex state object. Choose a callback and see it in action.
+
+
+
+```html
+
+```
+
+```js
+methods: {
+ consoleCallback(val) {
+ console.dir(JSON.stringify(val))
+ },
+
+ alertCallback(val) {
+ alert(JSON.stringify(val))
+ }
+}
+```
diff --git a/docs/md/ReactiveOptions.md b/docs/md/ReactiveOptions.md
new file mode 100644
index 0000000..31de63b
--- /dev/null
+++ b/docs/md/ReactiveOptions.md
@@ -0,0 +1,19 @@
+### Reactive Options
+
+When the list of options provided by the parent changes, vue-select will react as you'd expect.
+
+
+
+
+ ` `
+
+
+
+
+
+
+ ` `
+
+
+
+
diff --git a/docs/md/SingleMultiple.md b/docs/md/SingleMultiple.md
new file mode 100644
index 0000000..4257901
--- /dev/null
+++ b/docs/md/SingleMultiple.md
@@ -0,0 +1,29 @@
+
+
+### Single/Multiple Selection
+
+
+
+
+
+#### Single Option Select
+
+```html
+
+```
+
+
+
+
+
+#### Multiple Option Select
+
+```html
+
+```
+
+
+
+
+
+
diff --git a/docs/md/VModel.md b/docs/md/VModel.md
new file mode 100644
index 0000000..e0af80a
--- /dev/null
+++ b/docs/md/VModel.md
@@ -0,0 +1,15 @@
+### Two-Way Value Syncing
+
+The most common use case for vue-select is being able to sync the components value with a parent component. The `value` property supports two-way data binding to accomplish this. The `.sync` data-binding modifier is completely optional. You may use `value` without a two-way binding to preselect options. Here we have preselected 'Canada' by setting `syncedVal: 'Canada'` on the parent component. The buttons below demonstrate how you can set the `value` from the parent. Current value:
{{ syncedVal }}
+
+
+` `
+
+
+
+
+
+
+Set to United States
+Set to Canada
+
diff --git a/docs/vuex/actions.js b/docs/vuex/actions.js
deleted file mode 100644
index 799cbfd..0000000
--- a/docs/vuex/actions.js
+++ /dev/null
@@ -1,15 +0,0 @@
-export const setSelected = ({ dispatch }, selected) => {
- dispatch('SET_SELECTED', selected)
-}
-
-export const toggleOptionType = ({ dispatch }) => {
- dispatch('TOGGLE_OPTION_TYPE')
-}
-
-export const setPlaceholder = ({ dispatch }, placeholder) => {
- dispatch('SET_PLACEHOLDER', placeholder)
-}
-
-export const toggleMultiple = ({ dispatch }) => {
- dispatch('TOGGLE_MULTIPLE')
-}
\ No newline at end of file
diff --git a/docs/vuex/store.js b/docs/vuex/store.js
deleted file mode 100644
index 6dd9207..0000000
--- a/docs/vuex/store.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import Vue from 'vue'
-import Vuex from 'vuex'
-
-Vue.use(Vuex)
-Vue.config.debug = true
-
-const state = {
- selected: null,
- placeholder: 'Select a Country',
- multiple: true,
- maxHeight: '400px',
- options: {
- advanced: require('../data/advanced.js'),
- simple: require('../data/simple.js'),
- },
- optionType: 'advanced'
-}
-
-const mutations = {
- SET_SELECTED (state, selected) {
- state.selected = selected
- },
-
- TOGGLE_OPTION_TYPE (state) {
- if( state.optionType === 'advanced' ) {
- state.optionType = 'simple'
- } else {
- state.optionType = 'advanced'
- }
- },
-
- SET_PLACEHOLDER (state, placeholder) {
- state.placeholder = placeholder
- },
-
- TOGGLE_MULTIPLE (state) {
- state.multiple = ! state.multiple
- },
-
- SET_MAX_HEIGHT (state, maxHeight) {
- state.maxHeight = maxHeight
- }
-}
-
-export default new Vuex.Store({
- state,
- mutations
-})
diff --git a/package.json b/package.json
index 0a1031a..6ad3f0d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "vue-select",
- "version": "1.3.3",
+ "version": "2.0.0",
"description": "A native Vue.js component that provides similar functionality to Select2 without the overhead of jQuery.",
"author": "Jeff Sagal
",
"private": false,
@@ -8,6 +8,7 @@
"license": "MIT",
"scripts": {
"dev": "node build/dev-server.js",
+ "dev:docs": "node build/dev-server.js --docs",
"build": "node build/build.js",
"lint": "eslint --ext .js,.vue src test/unit/specs",
"test": "karma start test/unit/karma.conf.js --single-run",
@@ -23,6 +24,9 @@
"type": "git",
"url": "https://github.com/sagalbot/vue-select.git"
},
+ "peerDependencies": {
+ "vue": "2.x"
+ },
"devDependencies": {
"babel-core": "^6.0.0",
"babel-loader": "^6.0.0",
@@ -38,6 +42,8 @@
"file-loader": "^0.8.4",
"function-bind": "^1.0.2",
"gh-pages": "^0.11.0",
+ "highlight.js": "^9.9.0",
+ "html-loader": "^0.4.4",
"html-webpack-plugin": "^2.8.1",
"http-proxy-middleware": "^0.15.2",
"inject-loader": "^2.0.1",
@@ -52,6 +58,7 @@
"karma-spec-reporter": "0.0.26",
"karma-webpack": "^1.7.0",
"lolex": "^1.4.0",
+ "markdown-loader": "^0.1.7",
"node-sass": "^3.7.0",
"ora": "^0.2.0",
"phantomjs-prebuilt": "^2.1.3",
@@ -59,13 +66,15 @@
"sass-loader": "^3.2.0",
"shelljs": "^0.7.0",
"url-loader": "^0.5.7",
- "vue": "^1.0.24",
- "vue-hot-reload-api": "^1.2.0",
- "vue-html-loader": "^1.0.0",
- "vue-loader": "^8.3.0",
- "vue-resource": "^0.8.0",
+ "vue": "^2.1.8",
+ "vue-hot-reload-api": "^2.0.7",
+ "vue-html-loader": "^1.2.3",
+ "vue-loader": "^10.0.2",
+ "vue-markdown-loader": "^0.6.1",
+ "vue-resource": "^1.0.3",
"vue-style-loader": "^1.0.0",
- "vuex": "^0.6.3",
+ "vue-template-compiler": "^2.1.8",
+ "vuex": "^2.1.1",
"webpack": "^1.12.2",
"webpack-dev-middleware": "^1.4.0",
"webpack-hot-middleware": "^2.6.0",
diff --git a/src/components/Select.vue b/src/components/Select.vue
index 442c1f1..1b1b753 100644
--- a/src/components/Select.vue
+++ b/src/components/Select.vue
@@ -1,293 +1,268 @@
-
diff --git a/src/dev.js b/src/dev.js
index 6716b32..4a619b4 100644
--- a/src/dev.js
+++ b/src/dev.js
@@ -1,12 +1,17 @@
import Vue from 'vue'
-import vSelect from '../src/components/Select.vue'
+import vSelect from './components/Select.vue'
+import countries from 'docs/data/advanced.js'
Vue.component('v-select', vSelect)
-Vue.config.debug = true
Vue.config.devtools = true
/* eslint-disable no-new */
new Vue({
- el: 'body'
+ el: '#app',
+ data: {
+ placeholder: "placeholder",
+ value: null,
+ options: countries
+ }
})
diff --git a/src/mixins/ajax.js b/src/mixins/ajax.js
index a62842c..69ebe1a 100644
--- a/src/mixins/ajax.js
+++ b/src/mixins/ajax.js
@@ -23,7 +23,7 @@ module.exports = {
*/
onSearch: {
type: Function,
- default: false
+ default: function(search, loading){}
},
/**
@@ -59,9 +59,9 @@ module.exports = {
*/
toggleLoading(toggle = null) {
if (toggle == null) {
- return this.loading = !this.loading
+ return this.showLoading = !this.showLoading
}
- return this.loading = toggle
+ return this.showLoading = toggle
}
}
-}
\ No newline at end of file
+}
diff --git a/src/mixins/pointerScroll.js b/src/mixins/pointerScroll.js
index de7d6e8..5950959 100644
--- a/src/mixins/pointerScroll.js
+++ b/src/mixins/pointerScroll.js
@@ -1,3 +1,5 @@
+// flow
+
module.exports = {
watch: {
typeAheadPointer() {
@@ -30,8 +32,10 @@ module.exports = {
*/
pixelsToPointerTop() {
let pixelsToPointerTop = 0
- for (let i = 0; i < this.typeAheadPointer; i++) {
- pixelsToPointerTop += this.$els.dropdownMenu.children[i].offsetHeight
+ if( this.$refs.dropdownMenu ) {
+ for (let i = 0; i < this.typeAheadPointer; i++) {
+ pixelsToPointerTop += this.$refs.dropdownMenu.children[i].offsetHeight
+ }
}
return pixelsToPointerTop
},
@@ -50,7 +54,7 @@ module.exports = {
* @returns {number}
*/
pointerHeight() {
- let element = this.$els.dropdownMenu.children[this.typeAheadPointer]
+ let element = this.$refs.dropdownMenu ? this.$refs.dropdownMenu.children[this.typeAheadPointer] : false
return element ? element.offsetHeight : 0
},
@@ -60,8 +64,8 @@ module.exports = {
*/
viewport() {
return {
- top: this.$els.dropdownMenu.scrollTop,
- bottom: this.$els.dropdownMenu.offsetHeight + this.$els.dropdownMenu.scrollTop
+ top: this.$refs.dropdownMenu ? this.$refs.dropdownMenu.scrollTop: 0,
+ bottom: this.$refs.dropdownMenu ? this.$refs.dropdownMenu.offsetHeight + this.$refs.dropdownMenu.scrollTop : 0
}
},
@@ -71,7 +75,7 @@ module.exports = {
* @returns {*}
*/
scrollTo(position) {
- return this.$els.dropdownMenu.scrollTop = position
+ return this.$refs.dropdownMenu ? this.$refs.dropdownMenu.scrollTop = position : null
},
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js
index f79deff..304072f 100644
--- a/test/unit/karma.conf.js
+++ b/test/unit/karma.conf.js
@@ -64,6 +64,9 @@ module.exports = function (config) {
webpackMiddleware: {
noInfo: true
},
+ specReporter: {
+ suppressSkipped: true
+ },
coverageReporter: {
dir: './coverage',
reporters: [
diff --git a/test/unit/specs/Select.spec.js b/test/unit/specs/Select.spec.js
index 7b88915..bfc4579 100644
--- a/test/unit/specs/Select.spec.js
+++ b/test/unit/specs/Select.spec.js
@@ -1,15 +1,15 @@
+// flow
/* global describe, it, expect */
import Vue from 'vue'
import vSelect from 'src/components/Select.vue'
-// import vSelect from '../../../dist/vue-select'
import pointerScroll from 'src/mixins/pointerScroll.js'
-Vue.component('v-select', vSelect)
-
// http://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html
const Mock = require('!!vue?inject!src/components/Select.vue')
+Vue.component('v-select', vSelect)
+
/**
* Simulate a DOM event.
* @param target
@@ -25,6 +25,13 @@ function trigger(target, event, process) {
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)
@@ -33,6 +40,13 @@ function triggerMouse(target, event, process) {
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)
@@ -51,7 +65,7 @@ function searchSubmit(vm, search = false) {
vm.$children[0].search = search
}
- trigger(vm.$children[0].$els.search, 'keyup', function (e) {
+ trigger(vm.$children[0].$refs.search, 'keyup', function (e) {
e.keyCode = 13
})
}
@@ -61,67 +75,75 @@ describe('Select.vue', () => {
describe('Selecting values', () => {
it('can accept an array with pre-selected values', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
- value: ['one'],
+ value: 'one',
options: ['one', 'two', 'three']
}
}).$mount()
- expect(vm.$children[0].value).toEqual(vm.value)
+ 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: '
',
+ 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].value).toEqual(vm.value)
+ 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: '
',
+ 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].value).toEqual(vm.value)
+ expect(vm.$children[0].mutableValue).toEqual(vm.value)
})
it('can deselect a pre-selected object', () => {
const vm = new Vue({
- template: '
',
+ 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].value.length).toEqual(1)
+ expect(vm.$children[0].mutableValue.length).toEqual(1)
})
it('can deselect a pre-selected string', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
data: {
value: ['foo', 'bar'],
options: ['foo','bar']
}
}).$mount()
vm.$children[0].select('foo')
- expect(vm.$children[0].value.length).toEqual(1)
+ 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: '
',
+ template: '
',
components: {vSelect},
data: {
value: [],
@@ -131,28 +153,28 @@ describe('Select.vue', () => {
var select = vm.$children[0]
expect(select.isValueEmpty).toEqual(true)
- select.$set('value', ['one'])
+ select.select(['one'])
expect(select.isValueEmpty).toEqual(false)
- select.$set('value', [{l: 'f'}])
+ select.select([{l: 'f'}])
expect(select.isValueEmpty).toEqual(false)
- select.$set('value', 'one')
+ select.select('one')
expect(select.isValueEmpty).toEqual(false)
- select.$set('value', {label: 'foo', value: 'foo'})
+ select.select({label: 'foo', value: 'foo'})
expect(select.isValueEmpty).toEqual(false)
- select.$set('value', '')
+ select.select('')
expect(select.isValueEmpty).toEqual(true)
- select.$set('value', null)
+ 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: '
',
+ template: '
',
components: {vSelect},
data: {
value: ['one'],
@@ -164,10 +186,10 @@ describe('Select.vue', () => {
vm.multiple = false
Vue.nextTick(() => {
- expect(vm.$children[0].value).toEqual(null)
+ expect(vm.$children[0].mutableValue).toEqual(null)
vm.multiple = true
Vue.nextTick(() => {
- expect(vm.$children[0].value).toEqual([])
+ expect(vm.$children[0].mutableValue).toEqual([])
done()
})
})
@@ -175,20 +197,20 @@ describe('Select.vue', () => {
it('can retain values present in a new array of options', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
value: ['one'],
options: ['one', 'two', 'three']
}
}).$mount()
- vm.$children[0].$set('options', ['one', 'five', 'six'])
- expect(vm.$children[0].value).toEqual(['one'])
+ 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: '
',
+ template: '
',
components: {vSelect},
data: {
value: [{label: 'one'}],
@@ -199,59 +221,99 @@ describe('Select.vue', () => {
expect(vm.$children[0].isOptionSelected({label: 'one'})).toEqual(true)
})
- describe('onChange Callback', () => {
- it('can run a callback when the selection changes', (done) => {
+ 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: `
`,
- components: {vSelect},
- methods: {
- cb(val) {
- }
+ template: `
`,
+ data: {
+ foo: ''
}
}).$mount()
- spyOn(vm.$children[0], 'onChange')
-
- vm.$children[0].select('bar')
+ vm.$refs.select.select('bar')
Vue.nextTick(() => {
- expect(vm.$children[0].onChange).toHaveBeenCalledWith('bar')
- vm.$children[0].select('baz')
-
- Vue.nextTick(() => {
- expect(vm.$children[0].onChange).toHaveBeenCalledWith('baz')
- done()
- })
+ expect(vm.foo).toEqual('bar')
+ done()
})
})
- it('should run onChange when multiple is true and the value changes', (done) => {
+ it('should run change when multiple is true and the value changes', (done) => {
const vm = new Vue({
- template: `
`,
- methods: {
- cb(val) {
- }
+ template: `
`,
+ data: {
+ foo: ''
}
}).$mount()
- spyOn(vm.$children[0], 'onChange')
-
- vm.$children[0].select('bar')
+ vm.$refs.select.select('bar')
Vue.nextTick(() => {
- expect(vm.$children[0].onChange).toHaveBeenCalledWith(['foo','bar'])
- vm.$children[0].select('baz')
-
- Vue.nextTick(() => {
- expect(vm.$children[0].onChange).toHaveBeenCalledWith(['foo','bar','baz'])
- done()
- })
+ 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 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({
@@ -274,7 +336,7 @@ describe('Select.vue', () => {
it('should open the dropdown when the el is clicked', (done) => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
value: [{label: 'one'}],
@@ -282,7 +344,7 @@ describe('Select.vue', () => {
}
}).$mount()
- vm.$children[0].toggleDropdown({target: vm.$children[0].$els.search})
+ vm.$children[0].toggleDropdown({target: vm.$children[0].$refs.search})
Vue.nextTick(() => {
Vue.nextTick(() => {
expect(vm.$children[0].open).toEqual(true)
@@ -297,20 +359,20 @@ describe('Select.vue', () => {
components: {vSelect},
}).$mount()
- spyOn(vm.$children[0].$els.search, 'blur')
+ 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].$els.search.blur).toHaveBeenCalled()
+ expect(vm.$children[0].$refs.search.blur).toHaveBeenCalled()
done()
})
})
it('should close the dropdown on search blur', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
value: [{label: 'one'}],
@@ -319,30 +381,55 @@ describe('Select.vue', () => {
}).$mount()
vm.$children[0].open = true
- triggerFocusEvent(vm.$children[0].$els.toggle, 'blur')
+ 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].$els.search, 'blur')
+ spyOn(vm.$children[0].$refs.search, 'blur')
vm.$children[0].open = true
vm.$children[0].onEscape()
Vue.nextTick(() => {
- expect(vm.$children[0].$els.search.blur).toHaveBeenCalled()
+ expect(vm.$children[0].$refs.search.blur).toHaveBeenCalled()
done()
})
})
it('should remove existing search text on escape keyup', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
value: [{label: 'one'}],
@@ -393,7 +480,7 @@ describe('Select.vue', () => {
vm.$children[0].typeAheadPointer = 1
- trigger(vm.$children[0].$els.search, 'keydown', (e) => e.keyCode = 38)
+ trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 38)
expect(vm.$children[0].typeAheadPointer).toEqual(0)
})
@@ -407,7 +494,7 @@ describe('Select.vue', () => {
}).$mount()
vm.$children[0].typeAheadPointer = 1
- trigger(vm.$children[0].$els.search, 'keydown', (e) => e.keyCode = 40)
+ trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 40)
expect(vm.$children[0].typeAheadPointer).toEqual(2)
})
@@ -437,7 +524,7 @@ describe('Select.vue', () => {
vm.$children[0].typeAheadPointer = 1
spyOn(vm.$children[0], 'maybeAdjustScroll')
- trigger(vm.$children[0].$els.search, 'keydown', (e) => e.keyCode = 38)
+ trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 38)
expect(vm.$children[0].maybeAdjustScroll).toHaveBeenCalled()
})
@@ -451,7 +538,7 @@ describe('Select.vue', () => {
}).$mount()
spyOn(vm.$children[0], 'maybeAdjustScroll')
- trigger(vm.$children[0].$els.search, 'keydown', (e) => e.keyCode = 40)
+ trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 40)
expect(vm.$children[0].maybeAdjustScroll).toHaveBeenCalled()
})
@@ -496,21 +583,26 @@ describe('Select.vue', () => {
expect(vm.$children[0].scrollTo).toHaveBeenCalledWith(1)
})
- it('should scroll down if the pointer is below the current viewport bounds', () => {
+ /**
+ * @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}
- }
+ pixelsToPointerBottom() {
+ return 2
+ },
+ viewport() {
+ return {top: 0, bottom: 1}
+ }
})
const vm = new Vue({
- template: '
',
+ template: `
`,
components: {
- 'v-select': Mock({
- '../mixins/pointerScroll': {methods}
- })
+ 'v-select': Mock({
+ '../mixins/pointerScroll': {methods}
+ })
},
}).$mount()
@@ -523,19 +615,23 @@ describe('Select.vue', () => {
describe('Measuring pixel distances', () => {
it('should calculate pointerHeight as the offsetHeight of the pointer element if it exists', () => {
const vm = new Vue({
- template: '
',
- components: {vSelect},
+ template: `
`,
}).$mount()
- // Fresh instances start with the pointer at -1
- vm.$children[0].typeAheadPointer = -1
- expect(vm.$children[0].pointerHeight()).toEqual(0)
+ // dropdown must be open for $refs to exist
+ vm.$children[0].open = true
- vm.$children[0].typeAheadPointer = 100
- expect(vm.$children[0].pointerHeight()).toEqual(0)
+ 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 = 1
- expect(vm.$children[0].pointerHeight()).toEqual(vm.$children[0].$els.dropdownMenu.children[1].offsetHeight)
+ 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)
+ })
})
})
})
@@ -543,16 +639,16 @@ describe('Select.vue', () => {
describe('Removing values', () => {
it('can remove the given tag when its close icon is clicked', (done) => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
value: ['one'],
options: ['one', 'two', 'three']
}
}).$mount()
- vm.$children[0].$els.toggle.querySelector('.close').click()
+ vm.$children[0].$refs.toggle.querySelector('.close').click()
Vue.nextTick(() => {
- expect(vm.$children[0].value).toEqual([])
+ expect(vm.$children[0].mutableValue).toEqual([])
done()
})
})
@@ -560,7 +656,7 @@ describe('Select.vue', () => {
it('should remove the last item in the value array on delete keypress when multiple is true', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
value: ['one', 'two'],
@@ -569,13 +665,13 @@ describe('Select.vue', () => {
}).$mount()
vm.$children[0].maybeDeleteValue()
Vue.nextTick(() => {
- expect(vm.$children[0].value).toEqual(['one'])
+ 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: '
',
+ template: '
',
components: {vSelect},
data: {
value: 'one',
@@ -584,7 +680,7 @@ describe('Select.vue', () => {
}).$mount()
vm.$children[0].maybeDeleteValue()
Vue.nextTick(() => {
- expect(vm.$children[0].value).toEqual(null)
+ expect(vm.$children[0].mutableValue).toEqual(null)
})
})
})
@@ -592,14 +688,14 @@ describe('Select.vue', () => {
describe('Labels', () => {
it('can generate labels using a custom label key', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
value: [{name: 'Baz'}],
options: [{name: 'Foo'}, {name: 'Baz'}]
}
}).$mount()
- expect(vm.$children[0].$els.toggle.querySelector('.selected-tag').textContent).toContain('Baz')
+ expect(vm.$children[0].$refs.toggle.querySelector('.selected-tag').textContent).toContain('Baz')
})
it('should display a placeholder if the value is empty', (done) => {
@@ -612,20 +708,18 @@ describe('Select.vue', () => {
}).$mount()
expect(vm.$children[0].searchPlaceholder).toEqual('foo')
- vm.$children[0].value = {label: 'one'}
+ vm.$children[0].mutableValue = {label: 'one'}
Vue.nextTick(() => {
expect(vm.$children[0].searchPlaceholder).not.toBeDefined()
done()
})
-
- // expect(vm.$children[0].searchPlaceholder()).toEqual('foo')
})
})
describe('When Tagging Is Enabled', () => {
it('can determine if a given option string already exists', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
options: ['one', 'two']
@@ -638,7 +732,7 @@ describe('Select.vue', () => {
it('can determine if a given option object already exists', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
options: [{label: 'one'}, {label: 'two'}]
@@ -651,7 +745,7 @@ describe('Select.vue', () => {
it('can determine if a given option object already exists when using custom labels', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
options: [{foo: 'one'}, {foo: 'two'}]
@@ -664,7 +758,7 @@ describe('Select.vue', () => {
it('can add the current search text as the first item in the options list', () => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
value: ['one'],
@@ -678,7 +772,7 @@ describe('Select.vue', () => {
it('can select the current search text as a string', (done) => {
const vm = new Vue({
- template: '
',
+ template: '
',
components: {vSelect},
data: {
value: ['one'],
@@ -688,14 +782,14 @@ describe('Select.vue', () => {
searchSubmit(vm, 'three')
Vue.nextTick(() => {
- expect(vm.$children[0].value).toEqual(['one', 'three'])
+ 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: '
',
+ template: '
',
components: {vSelect},
data: {
value: [{label: 'one'}],
@@ -705,14 +799,14 @@ describe('Select.vue', () => {
searchSubmit(vm, 'two')
Vue.nextTick(() => {
- expect(vm.$children[0].value).toEqual([{label: 'one'}, {label: 'two'}])
+ 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: '
',
+ template: '
',
components: {vSelect},
data: {
value: ['one'],
@@ -721,12 +815,12 @@ describe('Select.vue', () => {
}).$mount()
searchSubmit(vm, 'three')
- expect(vm.$children[0].options).toEqual(['one', 'two', 'three'])
+ expect(vm.$children[0].mutableOptions).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: '
',
+ template: '
',
components: {vSelect},
data: {
value: ['one'],
@@ -735,14 +829,13 @@ describe('Select.vue', () => {
}).$mount()
searchSubmit(vm, 'three')
- expect(vm.$children[0].options).toEqual(['one', 'two'])
+ expect(vm.$children[0].mutableOptions).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: '
',
- components: {vSelect},
+ template: '
',
data: {
value: null,
options: ['one', two]
@@ -753,7 +846,7 @@ describe('Select.vue', () => {
searchSubmit(vm)
Vue.nextTick(() => {
- expect(vm.$children[0].value[0]).toBe(two)
+ expect(vm.$children[0].mutableValue[0]).toBe(two)
done()
})
})
@@ -762,7 +855,6 @@ describe('Select.vue', () => {
let two = {label: 'two'}
const vm = new Vue({
template: '
',
- components: {vSelect},
data: {
options: [{label: 'one'}, two]
}
@@ -775,80 +867,127 @@ describe('Select.vue', () => {
// 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].value.label).toBe(two.label)
+ 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: '
',
+ template: '
',
components: {vSelect},
data: {
value: [{label: 'one'}],
options: [{label: 'one'}]
}
}).$mount()
- vm.$children[0].options = [{label: 'two'}]
+ vm.$children[0].mutableOptions = [{label: 'two'}]
Vue.nextTick(() => {
- expect(vm.$children[0].value).toEqual([{label: 'one'}])
+ 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: '
',
+ template: '
',
}).$mount()
vm.$refs.select.toggleLoading()
- expect(vm.$refs.select.loading).toEqual(true)
+ expect(vm.$refs.select.showLoading).toEqual(true)
vm.$refs.select.toggleLoading(true)
- expect(vm.$refs.select.loading).toEqual(true)
+ expect(vm.$refs.select.showLoading).toEqual(true)
})
it('should trigger the onSearch callback when the search text changes', (done) => {
const vm = new Vue({
- template: '
',
+ template: '
',
+ data: {
+ called: false
+ },
methods: {
- foo() {
+ foo(val) {
+ this.called = val
}
}
}).$mount()
- spyOn(vm.$refs.select, 'onSearch')
vm.$refs.select.search = 'foo'
Vue.nextTick(() => {
- expect(vm.$refs.select.onSearch).toHaveBeenCalledWith('foo', vm.$refs.select.toggleLoading)
+ 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: '
',
+ template: '
',
+ data: { called: false },
methods: {
- foo() {
+ foo(val) {
+ this.called = ! this.called
}
}
}).$mount()
- spyOn(vm.$refs.select, 'onSearch')
- vm.$refs.select.search = ''
-
+ vm.$refs.select.search = 'foo'
Vue.nextTick(() => {
- expect(vm.$refs.select.onSearch).not.toHaveBeenCalled()
- done()
+ 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: '
',
+ template: '
',
methods: {
foo(search, loading) {
loading(false)
@@ -858,14 +997,14 @@ describe('Select.vue', () => {
vm.$refs.select.search = 'foo'
Vue.nextTick(() => {
- expect(vm.$refs.select.loading).toEqual(false)
+ expect(vm.$refs.select.showLoading).toEqual(false)
done()
})
})
it('can set loading to true from the onSearch callback', (done) => {
const vm = new Vue({
- template: '
',
+ template: '
',
methods: {
foo(search, loading) {
loading(true)
@@ -877,7 +1016,7 @@ describe('Select.vue', () => {
select.onSearch(select.search, select.toggleLoading)
Vue.nextTick(() => {
- expect(vm.$refs.select.loading).toEqual(true)
+ expect(vm.$refs.select.showLoading).toEqual(true)
done()
})
})
@@ -886,31 +1025,31 @@ describe('Select.vue', () => {
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: '
',
+ template: '
',
data: {
value: 'one',
options: ['one', 'two', 'three']
}
}).$mount()
- vm.$children[0].options = ['four', 'five', 'six']
+ vm.$children[0].mutableOptions = ['four', 'five', 'six']
Vue.nextTick(() => {
- expect(vm.$children[0].value).toEqual('one')
+ 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: '
',
+ template: '
',
components: {vSelect},
data: {
value: 'one',
options: ['one', 'two', 'three']
}
}).$mount()
- vm.$children[0].options = ['four', 'five', 'six']
+ vm.$children[0].mutableOptions = ['four', 'five', 'six']
Vue.nextTick(() => {
- expect(vm.$children[0].value).toEqual(null)
+ expect(vm.$children[0].mutableValue).toEqual(null)
done()
})
})