expect(bar.checked).toEqual(false)
})
+ it('should not update DOM unnecessarily', async () => {
+ const component = defineComponent({
+ data() {
+ return { value: true }
+ },
+ render() {
+ return [
+ withVModel(
+ h('input', {
+ type: 'checkbox',
+ 'onUpdate:modelValue': setValue.bind(this),
+ }),
+ this.value,
+ ),
+ ]
+ },
+ })
+ render(h(component), root)
+
+ const input = root.querySelector('input')
+ const data = root._vnode.component.data
+
+ const setCheckedSpy = vi.spyOn(input, 'checked', 'set')
+
+ // Trigger a change event without actually changing the value
+ triggerEvent('change', input)
+ await nextTick()
+ expect(data.value).toEqual(true)
+ expect(setCheckedSpy).not.toHaveBeenCalled()
+
+ // Change the value and trigger a change event
+ input.checked = false
+ triggerEvent('change', input)
+ await nextTick()
+ expect(data.value).toEqual(false)
+ expect(setCheckedSpy).toHaveBeenCalledTimes(1)
+
+ setCheckedSpy.mockClear()
+
+ data.value = false
+ await nextTick()
+ expect(input.checked).toEqual(false)
+ expect(setCheckedSpy).not.toHaveBeenCalled()
+
+ data.value = true
+ await nextTick()
+ expect(input.checked).toEqual(true)
+ expect(setCheckedSpy).toHaveBeenCalledTimes(1)
+ })
+
+ it('should handle array values correctly without unnecessary updates', async () => {
+ const component = defineComponent({
+ data() {
+ return { value: ['foo'] }
+ },
+ render() {
+ return [
+ withVModel(
+ h('input', {
+ type: 'checkbox',
+ value: 'foo',
+ 'onUpdate:modelValue': setValue.bind(this),
+ }),
+ this.value,
+ ),
+ withVModel(
+ h('input', {
+ type: 'checkbox',
+ value: 'bar',
+ 'onUpdate:modelValue': setValue.bind(this),
+ }),
+ this.value,
+ ),
+ ]
+ },
+ })
+ render(h(component), root)
+
+ const [foo, bar] = root.querySelectorAll('input')
+ const data = root._vnode.component.data
+
+ const setCheckedSpyFoo = vi.spyOn(foo, 'checked', 'set')
+ const setCheckedSpyBar = vi.spyOn(bar, 'checked', 'set')
+
+ expect(foo.checked).toEqual(true)
+ expect(bar.checked).toEqual(false)
+
+ triggerEvent('change', foo)
+ await nextTick()
+ expect(data.value).toEqual(['foo'])
+ expect(setCheckedSpyFoo).not.toHaveBeenCalled()
+
+ bar.checked = true
+ triggerEvent('change', bar)
+ await nextTick()
+ expect(data.value).toEqual(['foo', 'bar'])
+ expect(setCheckedSpyBar).toHaveBeenCalledTimes(1)
+
+ setCheckedSpyFoo.mockClear()
+ setCheckedSpyBar.mockClear()
+
+ data.value = ['foo', 'bar']
+ await nextTick()
+ expect(setCheckedSpyFoo).not.toHaveBeenCalled()
+ expect(setCheckedSpyBar).not.toHaveBeenCalled()
+
+ data.value = ['bar']
+ await nextTick()
+ expect(setCheckedSpyFoo).toHaveBeenCalledTimes(1)
+ expect(setCheckedSpyBar).not.toHaveBeenCalled()
+ expect(foo.checked).toEqual(false)
+ expect(bar.checked).toEqual(true)
+ })
+
it('should work with radio', async () => {
const component = defineComponent({
data() {
// store the v-model value on the element so it can be accessed by the
// change listener.
;(el as any)._modelValue = value
+ let checked: boolean
+
if (isArray(value)) {
- el.checked = looseIndexOf(value, vnode.props!.value) > -1
+ checked = looseIndexOf(value, vnode.props!.value) > -1
} else if (isSet(value)) {
- el.checked = value.has(vnode.props!.value)
- } else if (value !== oldValue) {
- el.checked = looseEqual(value, getCheckboxValue(el, true))
+ checked = value.has(vnode.props!.value)
+ } else {
+ checked = looseEqual(value, getCheckboxValue(el, true))
+ }
+
+ // Only update if the checked state has changed
+ if (el.checked !== checked) {
+ el.checked = checked
}
}