diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts
index 256b91559..ee6cfcbab 100644
--- a/packages/runtime-dom/src/directives/vModel.ts
+++ b/packages/runtime-dom/src/directives/vModel.ts
@@ -90,7 +90,7 @@ export const vModelTextInit = (
if (trim) {
domValue = domValue.trim()
}
- if (number) {
+ if (number || el.type === 'number') {
domValue = looseToNumber(domValue)
}
;(set || (el as any)[assignKey])(domValue)
@@ -359,7 +359,14 @@ function getCheckboxValue(
checked: boolean,
) {
const key = checked ? '_trueValue' : '_falseValue'
- return key in el ? el[key] : checked
+ if (key in el) {
+ return el[key]
+ }
+ const attr = checked ? 'true-value' : 'false-value'
+ if (el.hasAttribute(attr)) {
+ return el.getAttribute(attr)
+ }
+ return checked
}
export const vModelDynamic: ObjectDirective<
diff --git a/packages/runtime-vapor/__tests__/directives/vModel.spec.ts b/packages/runtime-vapor/__tests__/directives/vModel.spec.ts
index 4a744a5f3..5429cc1b3 100644
--- a/packages/runtime-vapor/__tests__/directives/vModel.spec.ts
+++ b/packages/runtime-vapor/__tests__/directives/vModel.spec.ts
@@ -1,17 +1,16 @@
import { reactive, ref } from '@vue/reactivity'
import {
+ applyCheckboxModel,
+ applyRadioModel,
+ applySelectModel,
+ applyTextModel,
delegate,
delegateEvents,
on,
setClass,
- setDOMProp,
+ setProp,
+ setValue,
template,
- // @ts-expect-error
- vModelDynamic,
- // @ts-expect-error
- vModelSelect,
- // @ts-expect-error
- withDirectives,
} from '../../src'
import { makeRender } from '../_utils'
import { nextTick } from '@vue/runtime-dom'
@@ -26,11 +25,11 @@ const triggerEvent = (type: string, el: Element) => {
const setDOMProps = (el: any, props: Array<[key: string, value: any]>) => {
props.forEach(prop => {
const [key, value] = prop
- key === 'class' ? setClass(el, value) : setDOMProp(el, key, value)
+ key === 'class' ? setClass(el, value) : setProp(el, key, value)
})
}
-describe.todo('directive: v-model', () => {
+describe('directive: v-model', () => {
test('should work with text input', async () => {
const spy = vi.fn()
@@ -39,8 +38,11 @@ describe.todo('directive: v-model', () => {
const t0 = template('')
delegateEvents('input')
const n0 = t0() as HTMLInputElement
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
+ applyTextModel(
+ n0,
+ () => data.value,
+ val => (data.value = val),
+ )
delegate(n0, 'input', () => () => spy(data.value))
return n0
}).render()
@@ -70,9 +72,12 @@ describe.todo('directive: v-model', () => {
const t0 = template(
'',
)
- const n0 = t0() as HTMLInputElement
- withDirectives(n0, [[vModelSelect, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
+ const n0 = t0() as HTMLSelectElement
+ applySelectModel(
+ n0,
+ () => data.value,
+ val => (data.value = val),
+ )
on(n0, 'change', () => () => spy(data.value))
return n0
}).render()
@@ -96,10 +101,12 @@ describe.todo('directive: v-model', () => {
const { host } = define(() => {
const t0 = template('')
const n0 = t0() as HTMLInputElement
-
- setDOMProp(n0, 'type', 'number')
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
+ applyTextModel(
+ n0,
+ () => data.value,
+ val => (data.value = val),
+ )
+ n0.type = 'number'
return n0
}).render()
@@ -115,66 +122,16 @@ describe.todo('directive: v-model', () => {
expect(data.value).toEqual(1)
})
- test('should work with multiple listeners', async () => {
- const spy = vi.fn()
-
- const data = ref('')
- const { host } = define(() => {
- const t0 = template('')
- const n0 = t0() as HTMLInputElement
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
- delegate(n0, 'update:modelValue', () => spy)
- return n0
- }).render()
-
- const input = host.querySelector('input')!
-
- input.value = 'foo'
- triggerEvent('input', input)
- await nextTick()
- expect(data.value).toEqual('foo')
- expect(spy).toHaveBeenCalledWith('foo')
- })
-
- test('should work with updated listeners', async () => {
- const spy1 = vi.fn()
- const spy2 = vi.fn()
- const toggle = ref(true)
-
- const data = ref('')
- const { host } = define(() => {
- const t0 = template('')
- const n0 = t0() as HTMLInputElement
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => (toggle.value ? spy1 : spy2))
- return n0
- }).render()
-
- const input = host.querySelector('input')!
- input.value = 'foo'
- triggerEvent('input', input)
- await nextTick()
- expect(spy1).toHaveBeenCalledWith('foo')
-
- // update listener
- toggle.value = false
- await nextTick()
-
- input.value = 'bar'
- triggerEvent('input', input)
- await nextTick()
- expect(spy1).not.toHaveBeenCalledWith('bar')
- expect(spy2).toHaveBeenCalledWith('bar')
- })
-
test('should work with textarea', async () => {
const data = ref('')
const { host } = define(() => {
const t0 = template('')
const n0 = t0() as HTMLInputElement
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
+ applyTextModel(
+ n0,
+ () => data.value,
+ val => (data.value = val),
+ )
return n0
}).render()
@@ -207,40 +164,39 @@ describe.todo('directive: v-model', () => {
// number
setClass(input1, 'number')
- withDirectives(input1, [
- [vModelDynamic, () => data.number, '', { number: true }],
- ])
- delegate(input1, 'update:modelValue', () => val => (data.number = val))
+ applyTextModel(
+ input1,
+ () => data.number,
+ val => (data.number = val),
+ { number: true },
+ )
// trim
setClass(input2, 'trim')
- withDirectives(input2, [
- [vModelDynamic, () => data.trim, '', { trim: true }],
- ])
- delegate(input2, 'update:modelValue', () => val => (data.trim = val))
+ applyTextModel(
+ input2,
+ () => data.trim,
+ val => (data.trim = val),
+ { trim: true },
+ )
// trim & number
setClass(input3, 'trim-number')
- withDirectives(input3, [
- [
- vModelDynamic,
- () => data.trimNumber,
- '',
- { trim: true, number: true },
- ],
- ])
- delegate(
+ applyTextModel(
input3,
- 'update:modelValue',
- () => val => (data.trimNumber = val),
+ () => data.trimNumber,
+ val => (data.trimNumber = val),
+ { trim: true, number: true },
)
// lazy
setClass(input4, 'lazy')
- withDirectives(input4, [
- [vModelDynamic, () => data.lazy, '', { lazy: true }],
- ])
- delegate(input4, 'update:modelValue', () => val => (data.lazy = val))
+ applyTextModel(
+ input4,
+ () => data.lazy,
+ val => (data.lazy = val),
+ { lazy: true },
+ )
return n0
}).render()
@@ -278,99 +234,98 @@ describe.todo('directive: v-model', () => {
test('should work with range', async () => {
const data = ref(25)
- const { host } = define(() => {
- const t0 = template(`${''.repeat(2)}
`)
+ let n1: HTMLInputElement, n2: HTMLInputElement
+ define(() => {
+ const t0 = template(
+ `` +
+ `` +
+ `` +
+ `
`,
+ )
const n0 = t0() as HTMLInputElement
- const [n1, n2] = Array.from(n0.children) as Array
+ ;[n1, n2] = Array.from(n0.children) as Array
- setDOMProps(n1, [
- ['class', 'foo'],
- ['type', 'range'],
- ['min', 1],
- ['max', 100],
- ])
- withDirectives(n1, [
- [vModelDynamic, () => data.value, '', { number: true }],
- ])
- delegate(n1, 'update:modelValue', () => val => (data.value = val))
+ applyTextModel(
+ n1,
+ () => data.value,
+ val => (data.value = val),
+ { number: true },
+ )
- setDOMProps(n2, [
- ['class', 'bar'],
- ['type', 'range'],
- ['min', 1],
- ['max', 100],
- ])
+ applyTextModel(
+ n2,
+ () => data.value,
+ val => (data.value = val),
+ {
+ lazy: true,
+ },
+ )
- withDirectives(n2, [
- [vModelDynamic, () => data.value, '', { lazy: true }],
- ])
- delegate(n2, 'update:modelValue', () => val => (data.value = val))
return n0
}).render()
- const foo = host.querySelector('.foo') as HTMLInputElement
- const bar = host.querySelector('.bar') as HTMLInputElement
-
// @ts-expect-error
- foo.value = 20
- triggerEvent('input', foo)
+ n1.value = 20
+ triggerEvent('input', n1!)
await nextTick()
expect(data.value).toEqual(20)
// @ts-expect-error
- foo.value = 200
- triggerEvent('input', foo)
+ n1.value = 200
+ triggerEvent('input', n1!)
await nextTick()
expect(data.value).toEqual(100)
// @ts-expect-error
- foo.value = -1
- triggerEvent('input', foo)
+ n1.value = -1
+ triggerEvent('input', n1!)
await nextTick()
expect(data.value).toEqual(1)
// @ts-expect-error
- bar.value = 30
- triggerEvent('change', bar)
+ n2.value = 30
+ triggerEvent('change', n2!)
await nextTick()
expect(data.value).toEqual('30')
// @ts-expect-error
- bar.value = 200
- triggerEvent('change', bar)
+ n2.value = 200
+ triggerEvent('change', n2!)
await nextTick()
expect(data.value).toEqual('100')
// @ts-expect-error
- bar.value = -1
- triggerEvent('change', bar)
+ n2.value = -1
+ triggerEvent('change', n2!)
await nextTick()
expect(data.value).toEqual('1')
data.value = 60
await nextTick()
- expect(foo.value).toEqual('60')
- expect(bar.value).toEqual('60')
+ expect(n1!.value).toEqual('60')
+ expect(n2!.value).toEqual('60')
data.value = -1
await nextTick()
- expect(foo.value).toEqual('1')
- expect(bar.value).toEqual('1')
+ expect(n1!.value).toEqual('1')
+ expect(n2!.value).toEqual('1')
data.value = 200
await nextTick()
- expect(foo.value).toEqual('100')
- expect(bar.value).toEqual('100')
+ expect(n1!.value).toEqual('100')
+ expect(n2!.value).toEqual('100')
})
test('should work with checkbox', async () => {
const data = ref(null)
const { host } = define(() => {
- const t0 = template('')
+ const t0 = template('')
const n0 = t0() as HTMLInputElement
- setDOMProp(n0, 'type', 'checkbox')
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
+ applyCheckboxModel(
+ n0,
+ () => data.value,
+ val => (data.value = val),
+ )
return n0
}).render()
@@ -398,15 +353,15 @@ describe.todo('directive: v-model', () => {
test('should work with checkbox and true-value/false-value', async () => {
const data = ref('yes')
const { host } = define(() => {
- const t0 = template('')
+ const t0 = template(
+ '',
+ )
const n0 = t0() as HTMLInputElement
- setDOMProps(n0, [
- ['type', 'checkbox'],
- ['true-value', 'yes'],
- ['false-value', 'no'],
- ])
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
+ applyCheckboxModel(
+ n0,
+ () => data.value,
+ val => (data.value = val),
+ )
return n0
}).render()
@@ -436,15 +391,17 @@ describe.todo('directive: v-model', () => {
test('should work with checkbox and true-value/false-value with object values', async () => {
const data = ref<{ yes?: 'yes'; no?: 'no' } | null>(null)
const { host } = define(() => {
- const t0 = template('')
+ const t0 = template('')
const n0 = t0() as HTMLInputElement
setDOMProps(n0, [
- ['type', 'checkbox'],
['true-value', { yes: 'yes' }],
['false-value', { no: 'no' }],
])
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
+ applyCheckboxModel(
+ n0,
+ () => data.value,
+ val => (data.value = val),
+ )
return n0
}).render()
@@ -470,310 +427,300 @@ describe.todo('directive: v-model', () => {
test(`should support array as a checkbox model`, async () => {
const data = ref>([])
- const { host } = define(() => {
- const t0 = template(`${''.repeat(2)}
`)
+ let n1: HTMLInputElement, n2: HTMLInputElement
+ define(() => {
+ const t0 = template(
+ `` +
+ `` +
+ `` +
+ `
`,
+ )
const n0 = t0() as HTMLInputElement
- const [n1, n2] = Array.from(n0.children) as Array
- setDOMProps(n1, [
- ['class', 'foo'],
- ['type', 'checkbox'],
- ['value', 'foo'],
- ])
- withDirectives(n1, [[vModelDynamic, () => data.value]])
- delegate(n1, 'update:modelValue', () => val => (data.value = val))
-
- setDOMProps(n2, [
- ['class', 'bar'],
- ['type', 'checkbox'],
- ['value', 'bar'],
- ])
- withDirectives(n2, [[vModelDynamic, () => data.value]])
- delegate(n2, 'update:modelValue', () => val => (data.value = val))
+ ;[n1, n2] = Array.from(n0.children) as Array
+ applyCheckboxModel(
+ n1,
+ () => data.value,
+ val => (data.value = val),
+ )
+ applyCheckboxModel(
+ n2,
+ () => data.value,
+ val => (data.value = val),
+ )
return n0
}).render()
- const foo = host.querySelector('.foo') as HTMLInputElement
- const bar = host.querySelector('.bar') as HTMLInputElement
-
- foo.checked = true
- triggerEvent('change', foo)
+ n1!.checked = true
+ triggerEvent('change', n1!)
await nextTick()
expect(data.value).toMatchObject(['foo'])
- bar.checked = true
- triggerEvent('change', bar)
+ n2!.checked = true
+ triggerEvent('change', n2!)
await nextTick()
expect(data.value).toMatchObject(['foo', 'bar'])
- bar.checked = false
- triggerEvent('change', bar)
+ n2!.checked = false
+ triggerEvent('change', n2!)
await nextTick()
expect(data.value).toMatchObject(['foo'])
- foo.checked = false
- triggerEvent('change', foo)
+ n1!.checked = false
+ triggerEvent('change', n1!)
await nextTick()
expect(data.value).toMatchObject([])
data.value = ['foo']
await nextTick()
- expect(bar.checked).toEqual(false)
- expect(foo.checked).toEqual(true)
+ expect(n2!.checked).toEqual(false)
+ expect(n1!.checked).toEqual(true)
data.value = ['bar']
await nextTick()
- expect(foo.checked).toEqual(false)
- expect(bar.checked).toEqual(true)
+ expect(n1!.checked).toEqual(false)
+ expect(n2!.checked).toEqual(true)
data.value = []
await nextTick()
- expect(foo.checked).toEqual(false)
- expect(bar.checked).toEqual(false)
+ expect(n1!.checked).toEqual(false)
+ expect(n2!.checked).toEqual(false)
})
test(`should support Set as a checkbox model`, async () => {
const data = ref>(new Set())
- const { host } = define(() => {
- const t0 = template(`${''.repeat(2)}
`)
+ let n1: HTMLInputElement, n2: HTMLInputElement
+ define(() => {
+ const t0 = template(
+ `` +
+ `` +
+ `` +
+ `
`,
+ )
const n0 = t0() as HTMLInputElement
- const [n1, n2] = Array.from(n0.children) as Array
- setDOMProps(n1, [
- ['class', 'foo'],
- ['type', 'checkbox'],
- ['value', 'foo'],
- ])
- withDirectives(n1, [[vModelDynamic, () => data.value]])
- delegate(n1, 'update:modelValue', () => val => (data.value = val))
+ ;[n1, n2] = Array.from(n0.children) as Array
- setDOMProps(n2, [
- ['class', 'bar'],
- ['type', 'checkbox'],
- ['value', 'bar'],
- ])
- withDirectives(n2, [[vModelDynamic, () => data.value]])
- delegate(n2, 'update:modelValue', () => val => (data.value = val))
+ applyCheckboxModel(
+ n1,
+ () => data.value,
+ val => (data.value = val),
+ )
+ applyCheckboxModel(
+ n2,
+ () => data.value,
+ val => (data.value = val),
+ )
return n0
}).render()
- const foo = host.querySelector('.foo') as HTMLInputElement
- const bar = host.querySelector('.bar') as HTMLInputElement
-
- foo.checked = true
- triggerEvent('change', foo)
+ n1!.checked = true
+ triggerEvent('change', n1!)
await nextTick()
expect(data.value).toMatchObject(new Set(['foo']))
- bar.checked = true
- triggerEvent('change', bar)
+ n2!.checked = true
+ triggerEvent('change', n2!)
await nextTick()
expect(data.value).toMatchObject(new Set(['foo', 'bar']))
- bar.checked = false
- triggerEvent('change', bar)
+ n2!.checked = false
+ triggerEvent('change', n2!)
await nextTick()
expect(data.value).toMatchObject(new Set(['foo']))
- foo.checked = false
- triggerEvent('change', foo)
+ n1!.checked = false
+ triggerEvent('change', n1!)
await nextTick()
expect(data.value).toMatchObject(new Set())
data.value = new Set(['foo'])
await nextTick()
- expect(bar.checked).toEqual(false)
- expect(foo.checked).toEqual(true)
+ expect(n2!.checked).toEqual(false)
+ expect(n1!.checked).toEqual(true)
data.value = new Set(['bar'])
await nextTick()
- expect(foo.checked).toEqual(false)
- expect(bar.checked).toEqual(true)
+ expect(n1!.checked).toEqual(false)
+ expect(n2!.checked).toEqual(true)
data.value = new Set()
await nextTick()
- expect(foo.checked).toEqual(false)
- expect(bar.checked).toEqual(false)
+ expect(n1!.checked).toEqual(false)
+ expect(n2!.checked).toEqual(false)
})
test('should work with radio', async () => {
const data = ref(null)
- const { host } = define(() => {
- const t0 = template(`${''.repeat(2)}
`)
+ let n1: HTMLInputElement, n2: HTMLInputElement
+ define(() => {
+ const t0 = template(
+ `` +
+ `` +
+ `` +
+ `
`,
+ )
const n0 = t0() as HTMLInputElement
- const [n1, n2] = Array.from(n0.children) as Array
- setDOMProps(n1, [
- ['class', 'foo'],
- ['type', 'radio'],
- ['value', 'foo'],
- ])
- withDirectives(n1, [[vModelDynamic, () => data.value]])
- delegate(n1, 'update:modelValue', () => val => (data.value = val))
-
- setDOMProps(n2, [
- ['class', 'bar'],
- ['type', 'radio'],
- ['value', 'bar'],
- ])
- withDirectives(n2, [[vModelDynamic, () => data.value]])
- delegate(n2, 'update:modelValue', () => val => (data.value = val))
+ ;[n1, n2] = Array.from(n0.children) as Array
+ applyRadioModel(
+ n1,
+ () => data.value,
+ val => (data.value = val),
+ )
+ applyRadioModel(
+ n2,
+ () => data.value,
+ val => (data.value = val),
+ )
return n0
}).render()
- const foo = host.querySelector('.foo') as HTMLInputElement
- const bar = host.querySelector('.bar') as HTMLInputElement
-
- foo.checked = true
- triggerEvent('change', foo)
+ n1!.checked = true
+ triggerEvent('change', n1!)
await nextTick()
expect(data.value).toEqual('foo')
- bar.checked = true
- triggerEvent('change', bar)
+ n2!.checked = true
+ triggerEvent('change', n2!)
await nextTick()
expect(data.value).toEqual('bar')
data.value = null
await nextTick()
- expect(foo.checked).toEqual(false)
- expect(bar.checked).toEqual(false)
+ expect(n1!.checked).toEqual(false)
+ expect(n2!.checked).toEqual(false)
data.value = 'foo'
await nextTick()
- expect(foo.checked).toEqual(true)
- expect(bar.checked).toEqual(false)
+ expect(n1!.checked).toEqual(true)
+ expect(n2!.checked).toEqual(false)
data.value = 'bar'
await nextTick()
- expect(foo.checked).toEqual(false)
- expect(bar.checked).toEqual(true)
+ expect(n1!.checked).toEqual(false)
+ expect(n2!.checked).toEqual(true)
})
test('should work with single select', async () => {
const data = ref(null)
- const { host } = define(() => {
- const t0 = template('')
- const n0 = t0() as HTMLSelectElement
- const [n1, n2] = Array.from(n0.childNodes) as Array
- setDOMProp(n1, 'value', 'foo')
- setDOMProp(n2, 'value', 'bar')
+ let select: HTMLSelectElement, n1: HTMLOptionElement, n2: HTMLOptionElement
+ define(() => {
+ const t0 = template(
+ '',
+ )
+ select = t0() as HTMLSelectElement
+ ;[n1, n2] = Array.from(select.childNodes) as Array
- setDOMProp(n0, 'value', null)
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
- return n0
+ applySelectModel(
+ select,
+ () => data.value,
+ val => (data.value = val),
+ )
+ return select
}).render()
- const select = host.querySelector('select') as HTMLSelectElement
- const foo = host.querySelector('option[value=foo]') as HTMLOptionElement
- const bar = host.querySelector('option[value=bar]') as HTMLOptionElement
-
- foo.selected = true
- triggerEvent('change', select)
+ n1!.selected = true
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toEqual('foo')
- foo.selected = false
- bar.selected = true
- triggerEvent('change', select)
+ n1!.selected = false
+ n2!.selected = true
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toEqual('bar')
- foo.selected = false
- bar.selected = false
+ n1!.selected = false
+ n2!.selected = false
data.value = 'foo'
await nextTick()
- expect(select.value).toEqual('foo')
- expect(foo.selected).toEqual(true)
- expect(bar.selected).toEqual(false)
+ expect(select!.value).toEqual('foo')
+ expect(n1!.selected).toEqual(true)
+ expect(n2!.selected).toEqual(false)
- foo.selected = true
- bar.selected = false
+ n1!.selected = true
+ n2!.selected = false
data.value = 'bar'
await nextTick()
- expect(select.value).toEqual('bar')
- expect(foo.selected).toEqual(false)
- expect(bar.selected).toEqual(true)
+ expect(select!.value).toEqual('bar')
+ expect(n1!.selected).toEqual(false)
+ expect(n2!.selected).toEqual(true)
})
- test('should work wiht multiple select (model is Array)', async () => {
+ test('should work with multiple select (model is Array)', async () => {
const data = ref>([])
- const { host } = define(() => {
- const t0 = template('')
- const n0 = t0() as HTMLSelectElement
- const [n1, n2] = Array.from(n0.childNodes) as Array
- setDOMProp(n1, 'value', 'foo')
- setDOMProp(n2, 'value', 'bar')
+ let select: HTMLSelectElement, n1: HTMLOptionElement, n2: HTMLOptionElement
+ define(() => {
+ const t0 = template(
+ '',
+ )
+ select = t0() as HTMLSelectElement
+ ;[n1, n2] = Array.from(select.childNodes) as Array
- setDOMProps(n0, [
- ['value', null],
- ['multiple', true],
- ])
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
- return n0
+ applySelectModel(
+ select,
+ () => data.value,
+ val => (data.value = val),
+ )
+ return select
}).render()
- const select = host.querySelector('select') as HTMLSelectElement
- const foo = host.querySelector('option[value=foo]') as HTMLOptionElement
- const bar = host.querySelector('option[value=bar]') as HTMLOptionElement
-
- foo.selected = true
- triggerEvent('change', select)
+ n1!.selected = true
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject(['foo'])
- foo.selected = false
- bar.selected = true
- triggerEvent('change', select)
+ n1!.selected = false
+ n2!.selected = true
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject(['bar'])
- foo.selected = true
- bar.selected = true
- triggerEvent('change', select)
+ n1!.selected = true
+ n2!.selected = true
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject(['foo', 'bar'])
- foo.selected = false
- bar.selected = false
+ n1!.selected = false
+ n2!.selected = false
data.value = ['foo']
await nextTick()
- expect(select.value).toEqual('foo')
- expect(foo.selected).toEqual(true)
- expect(bar.selected).toEqual(false)
+ expect(select!.value).toEqual('foo')
+ expect(n1!.selected).toEqual(true)
+ expect(n2!.selected).toEqual(false)
- foo.selected = false
- bar.selected = false
+ n1!.selected = false
+ n2!.selected = false
data.value = ['foo', 'bar']
await nextTick()
- expect(foo.selected).toEqual(true)
- expect(bar.selected).toEqual(true)
+ expect(n1!.selected).toEqual(true)
+ expect(n2!.selected).toEqual(true)
})
test('v-model.number should work with single select', async () => {
const data = ref(null)
- const { host } = define(() => {
- const t0 = template('')
- const n0 = t0() as HTMLSelectElement
- const [n1, n2] = Array.from(n0.childNodes) as Array
- setDOMProp(n1, 'value', '1')
- setDOMProp(n2, 'value', '2')
-
- setDOMProp(n0, 'value', null)
- withDirectives(n0, [
- [vModelDynamic, () => data.value, '', { number: true }],
- ])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
- return n0
+ let select: HTMLSelectElement, n1: HTMLOptionElement
+ define(() => {
+ const t0 = template(
+ '',
+ )
+ select = t0() as HTMLSelectElement
+ n1 = select.childNodes[0] as HTMLOptionElement
+ applySelectModel(
+ select,
+ () => data.value,
+ val => (data.value = val),
+ { number: true },
+ )
+ return select
}).render()
- const select = host.querySelector('select') as HTMLSelectElement
- const one = host.querySelector('option[value="1"]') as HTMLOptionElement
-
- one.selected = true
- triggerEvent('change', select)
+ n1!.selected = true
+ triggerEvent('change', select!)
await nextTick()
expect(typeof data.value).toEqual('number')
expect(data.value).toEqual(1)
@@ -781,43 +728,41 @@ describe.todo('directive: v-model', () => {
test('v-model.number should work with multiple select', async () => {
const data = ref>([])
+ let select: HTMLSelectElement
const { host } = define(() => {
- const t0 = template('')
- const n0 = t0() as HTMLSelectElement
- const [n1, n2] = Array.from(n0.childNodes) as Array
- setDOMProp(n1, 'value', '1')
- setDOMProp(n2, 'value', '2')
-
- setDOMProps(n0, [
- ['value', null],
- ['multiple', true],
- ])
- withDirectives(n0, [
- [vModelDynamic, () => data.value, '', { number: true }],
- ])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
- return n0
+ const t0 = template(
+ '',
+ )
+ select = t0() as HTMLSelectElement
+ applySelectModel(
+ select,
+ () => data.value,
+ val => (data.value = val),
+ { number: true },
+ )
+ return select
}).render()
- const select = host.querySelector('select') as HTMLSelectElement
const one = host.querySelector('option[value="1"]') as HTMLOptionElement
const two = host.querySelector('option[value="2"]') as HTMLOptionElement
one.selected = true
two.selected = false
- triggerEvent('change', select)
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject([1])
one.selected = false
two.selected = true
- triggerEvent('change', select)
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject([2])
one.selected = true
two.selected = true
- triggerEvent('change', select)
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject([1, 2])
@@ -841,50 +786,49 @@ describe.todo('directive: v-model', () => {
const barValue = { bar: 1 }
const data = ref>([])
- const { host } = define(() => {
- const t0 = template('')
- const n0 = t0() as HTMLSelectElement
- const [n1, n2] = Array.from(n0.childNodes) as Array
- setDOMProp(n1, 'value', fooValue)
- setDOMProp(n2, 'value', barValue)
- setDOMProps(n0, [
- ['value', null],
- ['multiple', true],
- ])
- withDirectives(n0, [
- [vModelDynamic, () => data.value, '', { number: true }],
- ])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
- return n0
+ let select: HTMLSelectElement
+ const { host } = define(() => {
+ const t0 = template(
+ '',
+ )
+ select = t0() as HTMLSelectElement
+ const [n1, n2] = Array.from(select.childNodes) as Array
+ setValue(n1, fooValue)
+ setValue(n2, barValue)
+ applySelectModel(
+ select,
+ () => data.value,
+ val => (data.value = val),
+ )
+ return select
}).render()
- const select = host.querySelector('select') as HTMLSelectElement
const [foo, bar] = Array.from(
host.querySelectorAll('option'),
) as Array
foo.selected = true
- triggerEvent('change', select)
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject([fooValue])
foo.selected = false
bar.selected = true
- triggerEvent('change', select)
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject([barValue])
foo.selected = true
bar.selected = true
- triggerEvent('change', select)
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject([fooValue, barValue])
// reset
foo.selected = false
bar.selected = false
- triggerEvent('change', select)
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject([])
@@ -897,7 +841,7 @@ describe.todo('directive: v-model', () => {
// reset
foo.selected = false
bar.selected = false
- triggerEvent('change', select)
+ triggerEvent('change', select!)
await nextTick()
expect(data.value).toMatchObject([])
@@ -912,18 +856,15 @@ describe.todo('directive: v-model', () => {
test('multiple select (model is Set)', async () => {
const data = ref>(new Set())
const { host } = define(() => {
- const t0 = template('')
+ const t0 = template(
+ '',
+ )
const n0 = t0() as HTMLSelectElement
- const [n1, n2] = Array.from(n0.childNodes) as Array
- setDOMProp(n1, 'value', 'foo')
- setDOMProp(n2, 'value', 'bar')
-
- setDOMProps(n0, [
- ['value', null],
- ['multiple', true],
- ])
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
+ applySelectModel(
+ n0,
+ () => data.value,
+ val => (data.value = val),
+ )
return n0
}).render()
@@ -973,20 +914,18 @@ describe.todo('directive: v-model', () => {
const data = ref>(new Set())
const { host } = define(() => {
- const t0 = template('')
+ const t0 = template(
+ '',
+ )
const n0 = t0() as HTMLSelectElement
const [n1, n2] = Array.from(n0.childNodes) as Array
- setDOMProp(n1, 'value', fooValue)
- setDOMProp(n2, 'value', barValue)
-
- setDOMProps(n0, [
- ['value', null],
- ['multiple', true],
- ])
- withDirectives(n0, [
- [vModelDynamic, () => data.value, '', { number: true }],
- ])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
+ setValue(n1, fooValue)
+ setValue(n2, barValue)
+ applySelectModel(
+ n0,
+ () => data.value,
+ val => (data.value = val),
+ )
return n0
}).render()
@@ -1035,8 +974,11 @@ describe.todo('directive: v-model', () => {
const { host } = define(() => {
const t0 = template('')
const n0 = t0() as HTMLInputElement
- withDirectives(n0, [[vModelDynamic, () => data.value]])
- delegate(n0, 'update:modelValue', () => val => (data.value = val))
+ applyTextModel(
+ n0,
+ () => data.value,
+ val => (data.value = val),
+ )
return n0
}).render()
diff --git a/packages/runtime-vapor/src/dom/prop.ts b/packages/runtime-vapor/src/dom/prop.ts
index 922e7d38d..2ae558bbb 100644
--- a/packages/runtime-vapor/src/dom/prop.ts
+++ b/packages/runtime-vapor/src/dom/prop.ts
@@ -47,6 +47,16 @@ export function setAttr(el: any, key: string, value: any): void {
return
}
+ // special case for with
+ // :true-value & :false-value
+ // store value as dom properties since non-string values will be
+ // stringified.
+ if (key === 'true-value') {
+ ;(el as any)._trueValue = value
+ } else if (key === 'false-value') {
+ ;(el as any)._falseValue = value
+ }
+
if (value !== el[`$${key}`]) {
el[`$${key}`] = value
if (value != null) {