mirror of
https://github.com/tenrok/vue-select.git
synced 2026-06-19 09:50:33 +03:00
separate logic into mixin, complete test coverage with mocks and spies
This commit is contained in:
@@ -168,7 +168,11 @@
|
|||||||
|
|
||||||
|
|
||||||
<script type="text/babel">
|
<script type="text/babel">
|
||||||
|
import pointerScroll from '../mixins/pointerScroll'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [pointerScroll],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
/**
|
/**
|
||||||
* Contains the currently selected value. Very similar to a
|
* Contains the currently selected value. Very similar to a
|
||||||
@@ -325,6 +329,7 @@
|
|||||||
},
|
},
|
||||||
filteredOptions() {
|
filteredOptions() {
|
||||||
this.typeAheadPointer = 0
|
this.typeAheadPointer = 0
|
||||||
|
this.maybeAdjustScroll()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -442,7 +447,7 @@
|
|||||||
typeAheadUp() {
|
typeAheadUp() {
|
||||||
if (this.typeAheadPointer > 0) {
|
if (this.typeAheadPointer > 0) {
|
||||||
this.typeAheadPointer--
|
this.typeAheadPointer--
|
||||||
this.maybeAdjustScrollPosition()
|
this.maybeAdjustScroll()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -454,7 +459,7 @@
|
|||||||
typeAheadDown() {
|
typeAheadDown() {
|
||||||
if (this.typeAheadPointer < this.filteredOptions.length - 1) {
|
if (this.typeAheadPointer < this.filteredOptions.length - 1) {
|
||||||
this.typeAheadPointer++
|
this.typeAheadPointer++
|
||||||
this.maybeAdjustScrollPosition()
|
this.maybeAdjustScroll()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -475,55 +480,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
maybeAdjustScrollPosition() {
|
|
||||||
let bounds = this.viewport()
|
|
||||||
let pixelsToPointerTop = this.pixelsToPointerTop()
|
|
||||||
let pixelsToPointerBottom = this.pixelsToPointerBottom()
|
|
||||||
let pointerHeight = this.pointerHeight()
|
|
||||||
|
|
||||||
if (pixelsToPointerTop <= bounds.top) {
|
|
||||||
return this.scrollTo(pixelsToPointerTop)
|
|
||||||
} else if (pixelsToPointerBottom >= bounds.bottom) {
|
|
||||||
// return this.scrollTo(( this.pixelsToPointerCenter() - this.$els.dropdownMenu.offsetHeight ) + ( pointerHeight / 2))
|
|
||||||
return this.scrollTo(( this.pixelsToPointerCenter() - this.$els.dropdownMenu.offsetHeight ) + pointerHeight)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
pixelsToPointerTop() {
|
|
||||||
let pixelsToPointerTop = 0
|
|
||||||
for (let i = 0; i < this.typeAheadPointer; i++) {
|
|
||||||
pixelsToPointerTop += this.$els.dropdownMenu.children[i].offsetHeight
|
|
||||||
}
|
|
||||||
return pixelsToPointerTop
|
|
||||||
},
|
|
||||||
|
|
||||||
pixelsToPointerBottom() {
|
|
||||||
return this.pixelsToPointerTop() + this.pointerHeight()
|
|
||||||
},
|
|
||||||
|
|
||||||
pixelsToPointerCenter() {
|
|
||||||
return this.pixelsToPointerTop() + ( this.pointerHeight() / 2 )
|
|
||||||
},
|
|
||||||
|
|
||||||
pointerHeight() {
|
|
||||||
return this.$els.dropdownMenu.children[this.typeAheadPointer].offsetHeight
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The viewport is the currently viewable
|
|
||||||
* portion of the dropdown menu
|
|
||||||
*/
|
|
||||||
viewport() {
|
|
||||||
return {
|
|
||||||
top: this.$els.dropdownMenu.scrollTop,
|
|
||||||
bottom: this.$els.dropdownMenu.offsetHeight + this.$els.dropdownMenu.scrollTop
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
scrollTo(position) {
|
|
||||||
return this.$els.dropdownMenu.scrollTop = position
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is any text in the search input, remove it.
|
* If there is any text in the search input, remove it.
|
||||||
* Otherwise, blur the search input to close the dropdown.
|
* Otherwise, blur the search input to close the dropdown.
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Adjust the scroll position of the dropdown list
|
||||||
|
* if the current pointer is outside of the
|
||||||
|
* overflow bounds.
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
maybeAdjustScroll() {
|
||||||
|
let pixelsToPointerTop = this.pixelsToPointerTop()
|
||||||
|
let pixelsToPointerBottom = this.pixelsToPointerBottom()
|
||||||
|
|
||||||
|
if ( pixelsToPointerTop <= this.viewport().top) {
|
||||||
|
return this.scrollTo( pixelsToPointerTop )
|
||||||
|
} else if (pixelsToPointerBottom >= this.viewport().bottom) {
|
||||||
|
return this.scrollTo( this.viewport().top + this.pointerHeight() )
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The distance in pixels from the top of the dropdown
|
||||||
|
* list to the top of the current pointer element.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
pixelsToPointerTop() {
|
||||||
|
let pixelsToPointerTop = 0
|
||||||
|
for (let i = 0; i < this.typeAheadPointer; i++) {
|
||||||
|
pixelsToPointerTop += this.$els.dropdownMenu.children[i].offsetHeight
|
||||||
|
}
|
||||||
|
return pixelsToPointerTop
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The distance in pixels from the top of the dropdown
|
||||||
|
* list to the bottom of the current pointer element.
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
pixelsToPointerBottom() {
|
||||||
|
return this.pixelsToPointerTop() + this.pointerHeight()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The offsetHeight of the current pointer element.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
pointerHeight() {
|
||||||
|
let element = this.$els.dropdownMenu.children[this.typeAheadPointer]
|
||||||
|
return element ? element.offsetHeight : 0
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently viewable portion of the dropdownMenu.
|
||||||
|
* @returns {{top: (string|*|number), bottom: *}}
|
||||||
|
*/
|
||||||
|
viewport() {
|
||||||
|
return {
|
||||||
|
top: this.$els.dropdownMenu.scrollTop,
|
||||||
|
bottom: this.$els.dropdownMenu.offsetHeight + this.$els.dropdownMenu.scrollTop
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll the dropdownMenu to a given position.
|
||||||
|
* @param position
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
scrollTo(position) {
|
||||||
|
return this.$els.dropdownMenu.scrollTop = position
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
+100
-58
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
import Vue from 'vue'
|
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'
|
||||||
|
|
||||||
|
// http://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html
|
||||||
|
const Mock = require('!!vue?inject!src/components/Select.vue')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulate a DOM event.
|
* Simulate a DOM event.
|
||||||
@@ -323,7 +327,7 @@ describe('Select.vue', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should move the pointer visually up the list on up arrow keyup', () => {
|
it('should move the pointer visually up the list on up arrow keyUp', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: '<div><v-select :options="options"></v-select></div>',
|
template: '<div><v-select :options="options"></v-select></div>',
|
||||||
components: {vSelect},
|
components: {vSelect},
|
||||||
@@ -338,7 +342,7 @@ describe('Select.vue', () => {
|
|||||||
expect(vm.$children[0].typeAheadPointer).toEqual(0)
|
expect(vm.$children[0].typeAheadPointer).toEqual(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should move the pointer visually down the list on down arrow keyup', () => {
|
it('should move the pointer visually down the list on down arrow keyUp', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: '<div><v-select :options="options"></v-select></div>',
|
template: '<div><v-select :options="options"></v-select></div>',
|
||||||
components: {vSelect},
|
components: {vSelect},
|
||||||
@@ -366,76 +370,117 @@ describe('Select.vue', () => {
|
|||||||
expect(vm.$children[0].typeAheadPointer).toEqual(2)
|
expect(vm.$children[0].typeAheadPointer).toEqual(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should check if the scroll position needs to be adjusted on up arrow keyup', () => {
|
describe('Automatic Scrolling', () => {
|
||||||
const vm = new Vue({
|
it('should check if the scroll position needs to be adjusted on up arrow keyUp', () => {
|
||||||
template: '<div><v-select :options="options"></v-select></div>',
|
const vm = new Vue({
|
||||||
components: {vSelect},
|
template: '<div><v-select :options="options"></v-select></div>',
|
||||||
data: {
|
components: {vSelect},
|
||||||
options: ['one', 'two', 'three']
|
data: {
|
||||||
}
|
options: ['one', 'two', 'three']
|
||||||
}).$mount()
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
vm.$children[0].typeAheadPointer = 1
|
vm.$children[0].typeAheadPointer = 1
|
||||||
spyOn(vm.$children[0], 'maybeAdjustScrollPosition')
|
spyOn(vm.$children[0], 'maybeAdjustScroll')
|
||||||
trigger(vm.$children[0].$els.search, 'keyup', (e) => e.keyCode = 38)
|
trigger(vm.$children[0].$els.search, 'keyup', (e) => e.keyCode = 38)
|
||||||
expect(vm.$children[0].maybeAdjustScrollPosition).toHaveBeenCalled()
|
expect(vm.$children[0].maybeAdjustScroll).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should check if the scroll position needs to be adjusted on down arrow keyup', () => {
|
it('should check if the scroll position needs to be adjusted on down arrow keyUp', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: '<div><v-select :options="options" max-height="10"></v-select></div>',
|
template: '<div><v-select :options="options"></v-select></div>',
|
||||||
components: {vSelect},
|
components: {vSelect},
|
||||||
data: {
|
data: {
|
||||||
options: ['one', 'two', 'three']
|
options: ['one', 'two', 'three']
|
||||||
}
|
}
|
||||||
}).$mount()
|
}).$mount()
|
||||||
|
|
||||||
spyOn(vm.$children[0], 'maybeAdjustScrollPosition')
|
spyOn(vm.$children[0], 'maybeAdjustScroll')
|
||||||
|
trigger(vm.$children[0].$els.search, 'keyup', (e) => e.keyCode = 40)
|
||||||
|
expect(vm.$children[0].maybeAdjustScroll).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
vm.$children[0].$els.search.focus()
|
it('should check if the scroll position needs to be adjusted when filtered options changes', (done) => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: '<div><v-select :options="options"></v-select></div>',
|
||||||
|
components: {vSelect},
|
||||||
|
data: {
|
||||||
|
options: ['one', 'two', 'three']
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
trigger(vm.$children[0].$els.search, 'keyup', (e) => e.keyCode = 40)
|
spyOn(vm.$children[0], 'maybeAdjustScroll')
|
||||||
expect(vm.$children[0].maybeAdjustScrollPosition).toHaveBeenCalled()
|
vm.$children[0].search = 'two'
|
||||||
})
|
|
||||||
|
|
||||||
it('should scroll up if the new pointer is above the current viewport bounds', () => {
|
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: '<div><v-select :options="[\'one\', \'two\', \'three\']"></v-select></div>',
|
||||||
|
components: {
|
||||||
|
'v-select': Mock({
|
||||||
|
'../mixins/pointerScroll': {methods}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
spyOn(vm.$children[0], 'scrollTo')
|
||||||
|
vm.$children[0].maybeAdjustScroll()
|
||||||
|
expect(vm.$children[0].scrollTo).toHaveBeenCalledWith(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should scroll down if the pointer is below the current viewport bounds', () => {
|
||||||
|
let methods = Object.assign(pointerScroll.methods, {
|
||||||
|
pixelsToPointerBottom() {
|
||||||
|
return 2
|
||||||
|
},
|
||||||
|
viewport() {
|
||||||
|
return { top: 0, bottom: 1 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const vm = new Vue({
|
||||||
|
template: '<div><v-select :options="[\'one\', \'two\', \'three\']"></v-select></div>',
|
||||||
|
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', () => {
|
describe('Measuring pixel distances', () => {
|
||||||
it('should calculate pixelsToPointerTop as the sum of the height all options above the pointer', () => {
|
it('should calculate pointerHeight as the offsetHeight of the pointer element if it exists', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: '<div><v-select :options="options"></v-select></div>',
|
template: '<div><v-select :options="[\'one\', \'two\', \'three\']""></v-select></div>',
|
||||||
components: {vSelect},
|
components: {vSelect},
|
||||||
data: {
|
|
||||||
options: ['one', 'two', 'three']
|
|
||||||
}
|
|
||||||
}).$mount()
|
}).$mount()
|
||||||
|
|
||||||
vm.$children[0].typeAheadPointer = 1
|
// Fresh instances start with the pointer at -1
|
||||||
let lineHeight = vm.$children[0].$els.dropdownMenu.children[0].offsetHeight
|
vm.$children[0].typeAheadPointer = -1
|
||||||
expect(vm.$children[0].pixelsToPointerTop()).toEqual( lineHeight * 2 )
|
expect(vm.$children[0].pointerHeight()).toEqual(0)
|
||||||
})
|
|
||||||
it('should calculate pixelsToPointerBottom as the sum of the height all options above the pointer plus the height of the pointer', () => {
|
vm.$children[0].typeAheadPointer = 100
|
||||||
const vm = new Vue({
|
expect(vm.$children[0].pointerHeight()).toEqual(0)
|
||||||
template: '<div><v-select :options="options"></v-select></div>',
|
|
||||||
components: {vSelect},
|
|
||||||
data: {
|
|
||||||
options: ['one', 'two', 'three']
|
|
||||||
}
|
|
||||||
}).$mount()
|
|
||||||
|
|
||||||
vm.$children[0].typeAheadPointer = 1
|
vm.$children[0].typeAheadPointer = 1
|
||||||
let lineHeight = vm.$children[0].$els.dropdownMenu.children[0].offsetHeight
|
expect(vm.$children[0].pointerHeight()).toEqual(vm.$children[0].$els.dropdownMenu.children[1].offsetHeight)
|
||||||
console.log(lineHeight)
|
|
||||||
expect(vm.$children[0].pixelsToPointerTop()).toEqual( lineHeight * 10 + lineHeight)
|
|
||||||
})
|
|
||||||
it('should calculate pixelsToPointerCenter', () => {
|
|
||||||
|
|
||||||
})
|
|
||||||
it('should calculate pointerHeight', () => {
|
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -682,6 +727,3 @@ describe('Select.vue', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// also see example testing a component with mocks at
|
|
||||||
// http://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html
|
|
||||||
|
|||||||
Reference in New Issue
Block a user