]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(runtime-dom): vModel tests (#255)
authorStanislav Lashmanov <stasvarenkin@gmail.com>
Mon, 14 Oct 2019 21:07:34 +0000 (00:07 +0300)
committerEvan You <yyx990803@gmail.com>
Mon, 14 Oct 2019 21:07:34 +0000 (17:07 -0400)
packages/runtime-dom/__tests__/vModel.spec.ts [new file with mode: 0644]
packages/runtime-dom/src/directives/vModel.ts

diff --git a/packages/runtime-dom/__tests__/vModel.spec.ts b/packages/runtime-dom/__tests__/vModel.spec.ts
new file mode 100644 (file)
index 0000000..8031956
--- /dev/null
@@ -0,0 +1,441 @@
+import {
+  createApp,
+  h,
+  nextTick,
+  createComponent,
+  vModelDynamic,
+  applyDirectives,
+  VNode
+} from '@vue/runtime-dom'
+
+const triggerEvent = (type: string, el: Element) => {
+  const event = new Event(type)
+  el.dispatchEvent(event)
+}
+
+const withVModel = (node: VNode, arg: any, mods?: any) =>
+  applyDirectives(node, [[vModelDynamic, arg, '', mods]])
+
+const setValue = function(this: any, value: any) {
+  this.value = value
+}
+
+let app: any, root: any
+
+beforeEach(() => {
+  app = createApp()
+  root = document.createElement('div') as any
+})
+
+describe('vModel', () => {
+  it('should work with text input', async () => {
+    const component = createComponent({
+      data() {
+        return { value: null }
+      },
+      render() {
+        return [
+          withVModel(
+            h('input', {
+              'onUpdate:modelValue': setValue.bind(this)
+            }),
+            this.value
+          )
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const input = root.querySelector('input')
+    const data = root._vnode.component.data
+
+    input.value = 'foo'
+    triggerEvent('input', input)
+    await nextTick()
+    expect(data.value).toEqual('foo')
+
+    data.value = 'bar'
+    await nextTick()
+    expect(input.value).toEqual('bar')
+  })
+
+  it('should work with textarea', async () => {
+    const component = createComponent({
+      data() {
+        return { value: null }
+      },
+      render() {
+        return [
+          withVModel(
+            h('textarea', {
+              'onUpdate:modelValue': setValue.bind(this)
+            }),
+            this.value
+          )
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const input = root.querySelector('textarea')
+    const data = root._vnode.component.data
+
+    input.value = 'foo'
+    triggerEvent('input', input)
+    await nextTick()
+    expect(data.value).toEqual('foo')
+
+    data.value = 'bar'
+    await nextTick()
+    expect(input.value).toEqual('bar')
+  })
+
+  it('should support modifiers', async () => {
+    const component = createComponent({
+      data() {
+        return { number: null, trim: null, lazy: null }
+      },
+      render() {
+        return [
+          withVModel(
+            h('input', {
+              class: 'number',
+              'onUpdate:modelValue': (val: any) => {
+                this.number = val
+              }
+            }),
+            this.number,
+            {
+              number: true
+            }
+          ),
+          withVModel(
+            h('input', {
+              class: 'trim',
+              'onUpdate:modelValue': (val: any) => {
+                this.trim = val
+              }
+            }),
+            this.trim,
+            {
+              trim: true
+            }
+          ),
+          withVModel(
+            h('input', {
+              class: 'lazy',
+              'onUpdate:modelValue': (val: any) => {
+                this.lazy = val
+              }
+            }),
+            this.lazy,
+            {
+              lazy: true
+            }
+          )
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const number = root.querySelector('.number')
+    const trim = root.querySelector('.trim')
+    const lazy = root.querySelector('.lazy')
+    const data = root._vnode.component.data
+
+    number.value = '+01.2'
+    triggerEvent('input', number)
+    await nextTick()
+    expect(data.number).toEqual(1.2)
+
+    trim.value = '    hello, world    '
+    triggerEvent('input', trim)
+    await nextTick()
+    expect(data.trim).toEqual('hello, world')
+
+    lazy.value = 'foo'
+    triggerEvent('change', lazy)
+    await nextTick()
+    expect(data.lazy).toEqual('foo')
+  })
+
+  it('should work with checkbox', async () => {
+    const component = createComponent({
+      data() {
+        return { value: null }
+      },
+      render() {
+        return [
+          withVModel(
+            h('input', {
+              type: 'checkbox',
+              'onUpdate:modelValue': setValue.bind(this)
+            }),
+            this.value
+          )
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const input = root.querySelector('input')
+    const data = root._vnode.component.data
+
+    input.checked = true
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toEqual(true)
+
+    data.value = false
+    await nextTick()
+    expect(input.checked).toEqual(false)
+  })
+
+  it(`should support array as a checkbox model`, async () => {
+    const component = createComponent({
+      data() {
+        return { value: [] }
+      },
+      render() {
+        return [
+          withVModel(
+            h('input', {
+              type: 'checkbox',
+              class: 'foo',
+              value: 'foo',
+              'onUpdate:modelValue': setValue.bind(this)
+            }),
+            this.value
+          ),
+          withVModel(
+            h('input', {
+              type: 'checkbox',
+              class: 'bar',
+              value: 'bar',
+              'onUpdate:modelValue': setValue.bind(this)
+            }),
+            this.value
+          )
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const foo = root.querySelector('.foo')
+    const bar = root.querySelector('.bar')
+    const data = root._vnode.component.data
+
+    foo.checked = true
+    triggerEvent('change', foo)
+    await nextTick()
+    expect(data.value).toMatchObject(['foo'])
+
+    bar.checked = true
+    triggerEvent('change', bar)
+    await nextTick()
+    expect(data.value).toMatchObject(['foo', 'bar'])
+
+    bar.checked = false
+    triggerEvent('change', bar)
+    await nextTick()
+    expect(data.value).toMatchObject(['foo'])
+
+    foo.checked = false
+    triggerEvent('change', foo)
+    await nextTick()
+    expect(data.value).toMatchObject([])
+
+    data.value = ['foo']
+    await nextTick()
+    expect(bar.checked).toEqual(false)
+    expect(foo.checked).toEqual(true)
+
+    data.value = ['bar']
+    await nextTick()
+    expect(foo.checked).toEqual(false)
+    expect(bar.checked).toEqual(true)
+
+    data.value = []
+    await nextTick()
+    expect(foo.checked).toEqual(false)
+    expect(bar.checked).toEqual(false)
+  })
+
+  it('should work with radio', async () => {
+    const component = createComponent({
+      data() {
+        return { value: null }
+      },
+      render() {
+        return [
+          withVModel(
+            h('input', {
+              type: 'radio',
+              class: 'foo',
+              value: 'foo',
+              'onUpdate:modelValue': setValue.bind(this)
+            }),
+            this.value
+          ),
+          withVModel(
+            h('input', {
+              type: 'radio',
+              class: 'bar',
+              value: 'bar',
+              'onUpdate:modelValue': setValue.bind(this)
+            }),
+            this.value
+          )
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const foo = root.querySelector('.foo')
+    const bar = root.querySelector('.bar')
+    const data = root._vnode.component.data
+
+    foo.checked = true
+    triggerEvent('change', foo)
+    await nextTick()
+    expect(data.value).toEqual('foo')
+
+    bar.checked = true
+    triggerEvent('change', bar)
+    await nextTick()
+    expect(data.value).toEqual('bar')
+
+    data.value = null
+    await nextTick()
+    expect(foo.checked).toEqual(false)
+    expect(bar.checked).toEqual(false)
+
+    data.value = 'foo'
+    await nextTick()
+    expect(foo.checked).toEqual(true)
+    expect(bar.checked).toEqual(false)
+
+    data.value = 'bar'
+    await nextTick()
+    expect(foo.checked).toEqual(false)
+    expect(bar.checked).toEqual(true)
+  })
+
+  it('should work with single select', async () => {
+    const component = createComponent({
+      data() {
+        return { value: null }
+      },
+      render() {
+        return [
+          withVModel(
+            h(
+              'select',
+              {
+                value: null,
+                'onUpdate:modelValue': setValue.bind(this)
+              },
+              [h('option', { value: 'foo' }), h('option', { value: 'bar' })]
+            ),
+            this.value
+          )
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const input = root.querySelector('select')
+    const foo = root.querySelector('option[value=foo]')
+    const bar = root.querySelector('option[value=bar]')
+    const data = root._vnode.component.data
+
+    foo.selected = true
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toEqual('foo')
+
+    foo.selected = false
+    bar.selected = true
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toEqual('bar')
+
+    foo.selected = false
+    bar.selected = false
+    data.value = 'foo'
+    await nextTick()
+    expect(input.value).toEqual('foo')
+    expect(foo.selected).toEqual(true)
+    expect(bar.selected).toEqual(false)
+
+    foo.selected = true
+    bar.selected = false
+    data.value = 'bar'
+    await nextTick()
+    expect(input.value).toEqual('bar')
+    expect(foo.selected).toEqual(false)
+    expect(bar.selected).toEqual(true)
+  })
+
+  it('should work with multiple select', async () => {
+    const component = createComponent({
+      data() {
+        return { value: [] }
+      },
+      render() {
+        return [
+          withVModel(
+            h(
+              'select',
+              {
+                value: null,
+                multiple: true,
+                'onUpdate:modelValue': setValue.bind(this)
+              },
+              [h('option', { value: 'foo' }), h('option', { value: 'bar' })]
+            ),
+            this.value
+          )
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const input = root.querySelector('select')
+    const foo = root.querySelector('option[value=foo]')
+    const bar = root.querySelector('option[value=bar]')
+    const data = root._vnode.component.data
+
+    foo.selected = true
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toMatchObject(['foo'])
+
+    foo.selected = false
+    bar.selected = true
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toMatchObject(['bar'])
+
+    foo.selected = true
+    bar.selected = true
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toMatchObject(['foo', 'bar'])
+
+    foo.selected = false
+    bar.selected = false
+    data.value = ['foo']
+    await nextTick()
+    expect(input.value).toEqual('foo')
+    expect(foo.selected).toEqual(true)
+    expect(bar.selected).toEqual(false)
+
+    foo.selected = false
+    bar.selected = false
+    data.value = ['foo', 'bar']
+    await nextTick()
+    expect(foo.selected).toEqual(true)
+    expect(bar.selected).toEqual(true)
+  })
+})
index b67250d7f9afc4ada1a07ca030b3a16505440189..96acadf2680e342b3c0f8632c8229195de516d7a 100644 (file)
@@ -79,15 +79,19 @@ export const vModelCheckbox: Directive<HTMLInputElement> = {
     addEventListener(el, 'change', () => {
       const modelValue = (el as any)._modelValue
       const elementValue = getValue(el)
+      const checked = el.checked
       if (isArray(modelValue)) {
-        const i = looseIndexOf(modelValue, elementValue)
-        if (i > -1) {
-          assign([...modelValue.slice(0, i), ...modelValue.slice(i + 1)])
-        } else {
+        const index = looseIndexOf(modelValue, elementValue)
+        const found = index !== -1
+        if (checked && !found) {
           assign(modelValue.concat(elementValue))
+        } else if (!checked && found) {
+          const filtered = [...modelValue]
+          filtered.splice(index, 1)
+          assign(filtered)
         }
       } else {
-        assign(el.checked)
+        assign(checked)
       }
     })
   },