diff --git a/packages/runtime-dom/__tests__/directives/vModel.spec.ts b/packages/runtime-dom/__tests__/directives/vModel.spec.ts index 33d48e5fc6c..140ef08d8cd 100644 --- a/packages/runtime-dom/__tests__/directives/vModel.spec.ts +++ b/packages/runtime-dom/__tests__/directives/vModel.spec.ts @@ -681,6 +681,108 @@ describe('vModel', () => { expect(bar.selected).toEqual(true) }) + it('v-model.number should work with select tag', async () => { + const component = defineComponent({ + data() { + return { value: null } + }, + render() { + return [ + withVModel( + h( + 'select', + { + value: null, + 'onUpdate:modelValue': setValue.bind(this) + }, + [h('option', { value: '1' }), h('option', { value: '2' })] + ), + this.value, + { + number: true + } + ) + ] + } + }) + render(h(component), root) + + const input = root.querySelector('select') + const one = root.querySelector('option[value="1"]') + const data = root._vnode.component.data + + one.selected = true + triggerEvent('change', input) + await nextTick() + expect(typeof data.value).toEqual('number') + expect(data.value).toEqual(1) + }) + + it('v-model.number should work with multiple select', async () => { + const component = defineComponent({ + data() { + return { value: [] } + }, + render() { + return [ + withVModel( + h( + 'select', + { + value: null, + multiple: true, + 'onUpdate:modelValue': setValue.bind(this) + }, + [h('option', { value: '1' }), h('option', { value: '2' })] + ), + this.value, + { + number: true + } + ) + ] + } + }) + render(h(component), root) + + const input = root.querySelector('select') + const one = root.querySelector('option[value="1"]') + const two = root.querySelector('option[value="2"]') + const data = root._vnode.component.data + + one.selected = true + two.selected = false + triggerEvent('change', input) + await nextTick() + expect(data.value).toMatchObject([1]) + + one.selected = false + two.selected = true + triggerEvent('change', input) + await nextTick() + expect(data.value).toMatchObject([2]) + + one.selected = true + two.selected = true + triggerEvent('change', input) + await nextTick() + expect(data.value).toMatchObject([1, 2]) + + one.selected = false + two.selected = false + data.value = [1] + await nextTick() + expect(one.selected).toEqual(true) + expect(two.selected).toEqual(false) + + one.selected = false + two.selected = false + data.value = [1, 2] + await nextTick() + expect(one.selected).toEqual(true) + expect(two.selected).toEqual(true) + }) + it('should work with composition session', async () => { const component = defineComponent({ data() { diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index ea1098dd19b..c7dfa40c205 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -166,11 +166,14 @@ export const vModelRadio: ModelDirective = { } export const vModelSelect: ModelDirective = { - created(el, binding, vnode) { + created(el, { modifiers: { number } }, vnode) { addEventListener(el, 'change', () => { const selectedVal = Array.prototype.filter .call(el.options, (o: HTMLOptionElement) => o.selected) - .map(getValue) + .map( + (o: HTMLOptionElement) => + number ? toNumber(getValue(o)) : getValue(o) + ) el._assign(el.multiple ? selectedVal : selectedVal[0]) }) el._assign = getModelAssigner(vnode)