Skip to content

Commit 7989302

Browse files
author
Jeff
committed
- update getOptionLabel to be consistent when using index
- move index warning from getOptionLabel to `select` method - update isOptionSelected, pull up object comparator to it's own method - add test edge cases
1 parent 33b0e7e commit 7989302

File tree

2 files changed

+194
-73
lines changed

2 files changed

+194
-73
lines changed

src/components/Select.vue

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -346,13 +346,13 @@
346346
aria-label="Search for option"
347347
>
348348

349-
<button
350-
v-show="showClearButton"
351-
:disabled="disabled"
349+
<button
350+
v-show="showClearButton"
351+
:disabled="disabled"
352352
@click="clearSelection"
353-
type="button"
354-
class="clear"
355-
title="Clear selection"
353+
type="button"
354+
class="clear"
355+
title="Clear selection"
356356
>
357357
<span aria-hidden="true">&times;</span>
358358
</button>
@@ -512,13 +512,23 @@
512512
/**
513513
* Callback to generate the label text. If {option}
514514
* is an object, returns option[this.label] by default.
515+
*
516+
* Label text is used for filtering comparison and
517+
* displaying. If you only need to adjust the
518+
* display, you should use the `option` and
519+
* `selected-option` slots.
520+
*
515521
* @type {Function}
516522
* @param {Object || String} option
517523
* @return {String}
518524
*/
519525
getOptionLabel: {
520526
type: Function,
521527
default(option) {
528+
if( this.index ) {
529+
option = this.findOptionByIndexValue(option)
530+
}
531+
522532
if (typeof option === 'object') {
523533
if (!option.hasOwnProperty(this.label)) {
524534
return console.warn(
@@ -527,28 +537,7 @@
527537
'http://sagalbot.github.io/vue-select/#ex-labels'
528538
)
529539
}
530-
531-
if(this.index) {
532-
if (!option.hasOwnProperty(this.index)) {
533-
console.warn(
534-
`[vue-select warn]: Index key "option.${this.index}" does not` +
535-
` exist in options object ${JSON.stringify(option)}.`
536-
)
537-
}
538-
}
539-
540-
if (this.label && option[this.label]) {
541-
return option[this.label]
542-
}
543-
}
544-
if(this.index) {
545-
let label = option
546-
this.options.forEach((val) => {
547-
if (val[this.index] == option) {
548-
label = val[this.label]
549-
}
550-
})
551-
return label
540+
return option[this.label]
552541
}
553542
return option;
554543
}
@@ -793,7 +782,13 @@
793782
option = this.createOption(option)
794783
}
795784
if(this.index) {
796-
option = option[this.index]
785+
if (!option.hasOwnProperty(this.index)) {
786+
return console.warn(
787+
`[vue-select warn]: Index key "option.${this.index}" does not` +
788+
` exist in options object ${JSON.stringify(option)}.`
789+
)
790+
}
791+
option = option[this.index]
797792
}
798793
if (this.multiple && !this.mutableValue) {
799794
this.mutableValue = [option]
@@ -875,22 +870,50 @@
875870
* @return {Boolean} True when selected | False otherwise
876871
*/
877872
isOptionSelected(option) {
878-
if (this.multiple && this.mutableValue) {
879873
let selected = false
880-
this.mutableValue.forEach(opt => {
881-
if (typeof opt === 'object' && opt[this.label] === option[this.label]) {
882-
selected = true
883-
} else if (typeof opt === 'object' && opt[this.label] === option) {
884-
selected = true
885-
}
886-
else if (opt === option) {
874+
this.valueAsArray.forEach(value => {
875+
if (typeof value === 'object') {
876+
selected = this.optionObjectComparator(value, option)
877+
} else if (value === option || value === option[this.index]) {
887878
selected = true
888879
}
889880
})
890881
return selected
882+
},
883+
884+
/**
885+
* Determine if two option objects are matching.
886+
*
887+
* @param value {Object}
888+
* @param option {Object}
889+
* @returns {boolean}
890+
*/
891+
optionObjectComparator(value, option) {
892+
if (this.index && value === option[this.index]) {
893+
return true
894+
} else if ((value[this.label] === option[this.label]) || (value[this.label] === option)) {
895+
return true
896+
} else if (this.index && value[this.index] === option[this.index]) {
897+
return true
891898
}
899+
return false;
900+
},
892901
893-
return this.mutableValue === option
902+
/**
903+
* Finds an option from this.options
904+
* where option[this.index] matches
905+
* the passed in value.
906+
*
907+
* @param value {Object}
908+
* @returns {*}
909+
*/
910+
findOptionByIndexValue(value) {
911+
this.options.forEach(_option => {
912+
if (JSON.stringify(_option[this.index]) === JSON.stringify(value)) {
913+
value = _option
914+
}
915+
})
916+
return value
894917
},
895918
896919
/**
@@ -1070,7 +1093,7 @@
10701093
* @return {Array}
10711094
*/
10721095
valueAsArray() {
1073-
if (this.multiple) {
1096+
if (this.multiple && this.mutableValue) {
10741097
return this.mutableValue
10751098
} else if (this.mutableValue) {
10761099
return [].concat(this.mutableValue)

test/unit/specs/Select.spec.js

Lines changed: 132 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,45 @@ describe('Select.vue', () => {
851851
expect(vm.$children[0].mutableValue).toEqual(vm.value)
852852
})
853853

854+
it('can determine if an object is pre-selected', () => {
855+
const vm = new Vue({
856+
template: '<div><v-select :options="options" v-model="value" index="id"></v-select></div>',
857+
components: {vSelect},
858+
data: {
859+
value: 'foo',
860+
options: [{
861+
id: 'foo',
862+
label: 'This is Foo'
863+
}]
864+
}
865+
}).$mount()
866+
867+
expect(vm.$children[0].isOptionSelected({
868+
id: 'foo',
869+
label: 'This is Foo'
870+
})).toEqual(true)
871+
})
872+
873+
it('can determine if an object is selected after it has been chosen', () => {
874+
const vm = new Vue({
875+
template: '<div><v-select :options="options" index="id"></v-select></div>',
876+
components: {vSelect},
877+
data: {
878+
options: [{id: 'foo', label: 'FooBar'}]
879+
}
880+
}).$mount()
881+
882+
vm.$children[0].select({id: 'foo', label: 'FooBar'});
883+
884+
// Vue.nextTick(() => {
885+
expect(vm.$children[0].isOptionSelected({
886+
id: 'foo',
887+
label: 'This is Foo'
888+
})).toEqual(true)
889+
// done()
890+
// })
891+
})
892+
854893
it('can accept an array of objects and pre-selected values (multiple)', () => {
855894
const vm = new Vue({
856895
template: '<div><v-select :index="index" :options="options" :value="value" :multiple="true"></v-select></div>',
@@ -873,8 +912,9 @@ describe('Select.vue', () => {
873912
options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}]
874913
}
875914
}).$mount()
876-
vm.$children[0].select('foo')
915+
vm.$children[0].deselect('foo')
877916
expect(vm.$children[0].mutableValue.length).toEqual(1)
917+
expect(vm.$children[0].mutableValue).toEqual(['bar'])
878918
})
879919

880920
it('can deselect an option when multiple is false', () => {
@@ -886,7 +926,7 @@ describe('Select.vue', () => {
886926
options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}]
887927
}
888928
}).$mount()
889-
vm.$children[0].select('foo')
929+
vm.$children[0].deselect('foo')
890930
expect(vm.$children[0].mutableValue).toEqual(null)
891931
})
892932

@@ -925,39 +965,97 @@ describe('Select.vue', () => {
925965
expect(vm.$children[0].$refs.toggle.querySelector('.selected-tag').textContent).toContain('Baz')
926966
})
927967

928-
it('will console.warn when options contain objects without a valid index key', (done) => {
929-
spyOn(console, 'warn')
930-
const vm = new Vue({
931-
template: '<div><v-select :index="index" :options="options"></v-select></div>',
932-
data: {
933-
index: 'value',
934-
options: [{label: 'Foo'}]
935-
}
936-
}).$mount()
937-
vm.$children[0].open = true
938-
Vue.nextTick(() => {
939-
expect(console.warn).toHaveBeenCalledWith(
940-
`[vue-select warn]: Index key "option.value" does not exist in options object {"label":"Foo"}.`
941-
)
942-
done()
968+
it('will console.warn when attempting to select an option with an undefined index', () => {
969+
spyOn(console, 'warn')
970+
971+
const vm = new Vue({
972+
template: '<div><v-select index="value" :options="options"></v-select></div>',
973+
data: {
974+
options: [{label: 'Foo'}]
975+
}
976+
}).$mount()
977+
vm.$children[0].select({label: 'Foo'})
978+
expect(console.warn).toHaveBeenCalledWith(
979+
`[vue-select warn]: Index key "option.value" does not exist in options object {"label":"Foo"}.`
980+
)
981+
})
982+
983+
it('can find the original option within this.options', () => {
984+
const vm = new Vue({
985+
template: '<div><v-select index="id" :options="options"></v-select></div>',
986+
data: {
987+
options: [{id: 1, label: 'Foo'},{id:2, label: 'Bar'}]
988+
}
989+
}).$mount()
990+
991+
expect(vm.$children[0].findOptionByIndexValue(1)).toEqual({id: 1, label: 'Foo'})
992+
expect(vm.$children[0].findOptionByIndexValue({id: 1, label: 'Foo'})).toEqual({id: 1, label: 'Foo'})
993+
})
994+
995+
describe('And when option[index] is a nested object', () => {
996+
it('can determine if an object is pre-selected', () => {
997+
const nestedOption = {
998+
value: {
999+
nested: true
1000+
},
1001+
label: 'foo'
1002+
};
1003+
const vm = new Vue({
1004+
template: '<div><v-select index="value" :options="options" :value="value"></v-select></div>',
1005+
components: {vSelect},
1006+
data: {
1007+
value: {
1008+
nested: true
1009+
},
1010+
options: [nestedOption]
1011+
}
1012+
}).$mount()
1013+
expect(vm.$children[0].isOptionSelected({
1014+
nested: true
1015+
})).toEqual(true)
9431016
})
944-
})
9451017

946-
it('will not console.warn when options contain objects without an index key', (done) => {
947-
spyOn(console, 'warn')
948-
const vm = new Vue({
949-
template: '<div><v-select :options="options"></v-select></div>',
950-
data: {
951-
options: [{label: 'Foo'}]
952-
}
953-
}).$mount()
954-
vm.$children[0].open = true
955-
Vue.nextTick(() => {
956-
expect(console.warn).not.toHaveBeenCalledWith(
957-
`[vue-select warn]: Index key "option.value" does not exist in options object {"label":"Foo"}.`
958-
)
959-
done()
1018+
it('can determine if an object is selected after it is chosen', () => {
1019+
const nestedOption = {
1020+
value: {
1021+
nested: true
1022+
},
1023+
label: 'foo'
1024+
};
1025+
const vm = new Vue({
1026+
template: '<div><v-select index="value" :options="options"></v-select></div>',
1027+
components: {vSelect},
1028+
data: {
1029+
options: [nestedOption]
1030+
}
1031+
}).$mount()
1032+
vm.$children[0].select(nestedOption)
1033+
expect(vm.$children[0].isOptionSelected(nestedOption)).toEqual(true)
9601034
})
1035+
1036+
it('can determine a selected values label', () => {
1037+
const nestedOption = {
1038+
value: {
1039+
nested: true
1040+
},
1041+
label: 'foo'
1042+
};
1043+
const vm = new Vue({
1044+
template: '<div><v-select index="value" :options="options" :value="value"></v-select></div>',
1045+
components: {vSelect},
1046+
data: {
1047+
value: {
1048+
nested: true
1049+
},
1050+
options: [nestedOption]
1051+
}
1052+
}).$mount()
1053+
1054+
expect(vm.$children[0].getOptionLabel({
1055+
nested: true
1056+
})).toEqual('foo')
1057+
})
1058+
9611059
})
9621060
})
9631061

@@ -1501,7 +1599,7 @@ describe('Select.vue', () => {
15011599
value: 'foo'
15021600
}
15031601
}).$mount()
1504-
1602+
15051603
expect(vm.mutableValue).toEqual('foo')
15061604
vm.$el.querySelector( 'button.clear' ).click()
15071605
expect(vm.mutableValue).toEqual(null)
@@ -1520,6 +1618,6 @@ describe('Select.vue', () => {
15201618
const buttonEl = vm.$el.querySelector( 'button.clear' )
15211619
expect(buttonEl.disabled).toEqual(true);
15221620
})
1523-
1621+
15241622
});
15251623
})

0 commit comments

Comments
 (0)