2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-22 10:30:34 +03:00

ajax mixin 100% coverage, ready for production

This commit is contained in:
Jeff Sagal
2016-06-15 20:34:24 -07:00
parent 7dc28bc07b
commit 480bfc1c5f
3 changed files with 905 additions and 697 deletions
+58 -5
View File
@@ -1,5 +1,5 @@
<style> <style>
.v-select.dropdown { .v-select {
position: relative; position: relative;
} }
@@ -12,6 +12,12 @@
pointer-events: all; pointer-events: all;
transition: all 150ms cubic-bezier(1.000, -0.115, 0.975, 0.855); transition: all 150ms cubic-bezier(1.000, -0.115, 0.975, 0.855);
transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855); transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
opacity: 1;
transition: opacity .1s;
}
.v-select.loading .open-indicator {
opacity: 0;
} }
.v-select .open-indicator:before { .v-select .open-indicator:before {
@@ -116,6 +122,47 @@
background: #f0f0f0; background: #f0f0f0;
color: #333; color: #333;
} }
.v-select .spinner {
opacity: 0;
position: absolute;
top: 5px;
right: 10px;
font-size: 5px;
text-indent: -9999em;
border-top: .9em solid rgba(100,100,100,.1);
border-right: .9em solid rgba(100,100,100,.1);
border-bottom: .9em solid rgba(100,100,100,.1);
border-left: .9em solid rgba(60,60,60,.45);
transform: translateZ(0);
animation: vSelectSpinner 1.1s infinite linear;
transition: opacity .1s;
}
.v-select.loading .spinner {
opacity: 1;
}
.v-select .spinner,
.v-select .spinner:after {
border-radius: 50%;
width: 5em;
height: 5em;
}
@-webkit-keyframes vSelectSpinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes vSelectSpinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style> </style>
<template> <template>
@@ -134,8 +181,9 @@
<input <input
v-el:search v-el:search
v-show="searchable" :debounce="debounce"
v-model="search" v-model="search"
v-show="searchable"
@keydown.delete="maybeDeleteValue" @keydown.delete="maybeDeleteValue"
@keyup.esc="onEscape" @keyup.esc="onEscape"
@keydown.up.prevent="typeAheadUp" @keydown.up.prevent="typeAheadUp"
@@ -150,6 +198,10 @@
> >
<i v-el:open-indicator role="presentation" class="open-indicator"></i> <i v-el:open-indicator role="presentation" class="open-indicator"></i>
<slot name="spinner">
<div class="spinner" v-show="onSearch && loading">Loading...</div>
</slot>
</div> </div>
<ul v-el:dropdown-menu v-show="open" :transition="transition" class="dropdown-menu" :style="{ 'max-height': maxHeight }"> <ul v-el:dropdown-menu v-show="open" :transition="transition" class="dropdown-menu" :style="{ 'max-height': maxHeight }">
@@ -169,9 +221,10 @@
<script type="text/babel"> <script type="text/babel">
import pointerScroll from '../mixins/pointerScroll' import pointerScroll from '../mixins/pointerScroll'
import ajax from '../mixins/ajax.js'
export default { export default {
mixins: [pointerScroll], mixins: [pointerScroll, ajax],
props: { props: {
/** /**
@@ -536,8 +589,8 @@
*/ */
dropdownClasses() { dropdownClasses() {
return { return {
open: this.open, searchable: this.searchable,
searchable: this.searchable loading: this.loading
} }
}, },
+67
View File
@@ -0,0 +1,67 @@
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
},
/**
* 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: false
},
/**
* The number of milliseconds to wait before
* invoking this.onSearch(). Used to prevent
* sending an AJAX request until input is complete.
*/
debounce: {
type: Number,
default: 0
}
},
watch: {
/**
* If a callback & search text has been provided,
* invoke the onSearch callback.
*/
search() {
if (this.search.length > 0 && this.onSearch) {
this.onSearch(this.search, this.toggleLoading)
}
},
},
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.loading = !this.loading
}
return this.loading = toggle
}
}
}
+88
View File
@@ -4,6 +4,8 @@ import Vue from 'vue'
import vSelect from 'src/components/Select.vue' import vSelect from 'src/components/Select.vue'
import pointerScroll from 'src/mixins/pointerScroll.js' import pointerScroll from 'src/mixins/pointerScroll.js'
Vue.component('v-select', vSelect)
// http://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html // http://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html
const Mock = require('!!vue?inject!src/components/Select.vue') const Mock = require('!!vue?inject!src/components/Select.vue')
@@ -726,4 +728,90 @@ describe('Select.vue', () => {
}) })
}) })
}) })
describe('Asynchronous Loading', () => {
it('can toggle the loading class', () => {
const vm = new Vue({
template: '<div><v-select v-ref:select></v-select></div>',
}).$mount()
vm.$refs.select.toggleLoading()
expect(vm.$refs.select.loading).toEqual(true)
vm.$refs.select.toggleLoading(true)
expect(vm.$refs.select.loading).toEqual(true)
})
it('should trigger the onSearch callback when the search text changes', (done) => {
const vm = new Vue({
template: '<div><v-select v-ref:select :on-search="foo"></v-select></div>',
methods: {
foo() {
}
}
}).$mount()
spyOn(vm.$refs.select, 'onSearch')
vm.$refs.select.search = 'foo'
Vue.nextTick(() => {
expect(vm.$refs.select.onSearch).toHaveBeenCalledWith('foo', vm.$refs.select.toggleLoading)
done()
})
})
it('should not trigger the onSearch callback if the search text is empty', (done) => {
const vm = new Vue({
template: '<div><v-select v-ref:select search="foo" :on-search="foo"></v-select></div>',
methods: {
foo() {
}
}
}).$mount()
spyOn(vm.$refs.select, 'onSearch')
vm.$refs.select.search = ''
Vue.nextTick(() => {
expect(vm.$refs.select.onSearch).not.toHaveBeenCalled()
done()
})
})
it('can set loading to false from the onSearch callback', (done) => {
const vm = new Vue({
template: '<div><v-select loading v-ref:select :on-search="foo"></v-select></div>',
methods: {
foo(search, loading) {
loading(false)
}
}
}).$mount()
vm.$refs.select.search = 'foo'
Vue.nextTick(() => {
expect(vm.$refs.select.loading).toEqual(false)
done()
})
})
it('can set loading to true from the onSearch callback', (done) => {
const vm = new Vue({
template: '<div><v-select loading v-ref:select :on-search="foo"></v-select></div>',
methods: {
foo(search, loading) {
loading(true)
}
}
}).$mount()
let select = vm.$refs.select
select.onSearch(select.search, select.toggleLoading)
Vue.nextTick(() => {
expect(vm.$refs.select.loading).toEqual(true)
done()
})
})
})
}) })