]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: add test for runtime-dom/modules/class (#75)
authorHaoqun Jiang <haoqunjiang@gmail.com>
Mon, 9 Dec 2019 19:52:20 +0000 (03:52 +0800)
committerEvan You <yyx990803@gmail.com>
Mon, 9 Dec 2019 19:52:20 +0000 (14:52 -0500)
packages/runtime-dom/__tests__/modules/class.spec.ts [new file with mode: 0644]
packages/runtime-dom/src/modules/class.ts

diff --git a/packages/runtime-dom/__tests__/modules/class.spec.ts b/packages/runtime-dom/__tests__/modules/class.spec.ts
new file mode 100644 (file)
index 0000000..85f6e0a
--- /dev/null
@@ -0,0 +1,193 @@
+// https://github.com/vuejs/vue/blob/dev/test/unit/features/directives/class.spec.js
+
+import { h, render, createComponent } from '../../src'
+
+type ClassItem = {
+  value: string | object | string[]
+}
+
+function assertClass(assertions: Array<Array<any>>) {
+  const root = document.createElement('div')
+  const dynamic = { value: '' }
+  const wrapper = () => h('div', { class: ['foo', dynamic.value] })
+
+  for (const [input, expected] of assertions) {
+    if (typeof input === 'function') {
+      input(dynamic.value)
+    } else {
+      dynamic.value = input
+    }
+
+    render(wrapper(), root)
+    expect(root.children[0].className).toBe(expected)
+  }
+}
+
+describe('class', () => {
+  test('plain string', () => {
+    assertClass([
+      ['bar', 'foo bar'],
+      ['baz qux', 'foo baz qux'],
+      ['qux', 'foo qux'],
+      [undefined, 'foo']
+    ])
+  })
+
+  test('object value', () => {
+    assertClass([
+      [{ bar: true, baz: false }, 'foo bar'],
+      [{ baz: true }, 'foo baz'],
+      [null, 'foo'],
+      [{ 'bar baz': true, qux: false }, 'foo bar baz'],
+      [{ qux: true }, 'foo qux']
+    ])
+  })
+
+  test('array value', () => {
+    assertClass([
+      [['bar', 'baz'], 'foo bar baz'],
+      [['qux', 'baz'], 'foo qux baz'],
+      [['w', 'x y z'], 'foo w x y z'],
+      [undefined, 'foo'],
+      [['bar'], 'foo bar'],
+      [(val: Array<any>) => val.push('baz'), 'foo bar baz']
+    ])
+  })
+
+  test('array of mixed values', () => {
+    assertClass([
+      [['x', { y: true, z: true }], 'foo x y z'],
+      [['x', { y: true, z: false }], 'foo x y'],
+      [['f', { z: true }], 'foo f z'],
+      [['l', 'f', { n: true, z: true }], 'foo l f n z'],
+      [['x', {}], 'foo x'],
+      [undefined, 'foo']
+    ])
+  })
+
+  test('class merge between parent and child', () => {
+    const root = document.createElement('div')
+
+    const childClass: ClassItem = { value: 'd' }
+    const child = {
+      props: {},
+      render: () => h('div', { class: ['c', childClass.value] })
+    }
+
+    const parentClass: ClassItem = { value: 'b' }
+    const parent = {
+      props: {},
+      render: () => h(child, { class: ['a', parentClass.value] })
+    }
+
+    render(h(parent), root)
+    expect(root.children[0].className).toBe('c d a b')
+
+    parentClass.value = 'e'
+    // the `foo` here is just for forcing parent to be updated
+    // (otherwise it's skipped since its props never change)
+    render(h(parent, { foo: 1 }), root)
+    expect(root.children[0].className).toBe('c d a e')
+
+    parentClass.value = 'f'
+    render(h(parent, { foo: 2 }), root)
+    expect(root.children[0].className).toBe('c d a f')
+
+    parentClass.value = { foo: true }
+    childClass.value = ['bar', 'baz']
+    render(h(parent, { foo: 3 }), root)
+    expect(root.children[0].className).toBe('c bar baz a foo')
+  })
+
+  test('class merge between multiple nested components sharing same element', () => {
+    const component1 = createComponent({
+      props: {},
+      render() {
+        return this.$slots.default()[0]
+      }
+    })
+
+    const component2 = createComponent({
+      props: {},
+      render() {
+        return this.$slots.default()[0]
+      }
+    })
+
+    const component3 = createComponent({
+      props: {},
+      render() {
+        return h(
+          'div',
+          {
+            class: 'staticClass'
+          },
+          [this.$slots.default()]
+        )
+      }
+    })
+
+    const root = document.createElement('div')
+    const componentClass1 = { value: 'componentClass1' }
+    const componentClass2 = { value: 'componentClass2' }
+    const componentClass3 = { value: 'componentClass3' }
+
+    const wrapper = () =>
+      h(component1, { class: componentClass1.value }, () => [
+        h(component2, { class: componentClass2.value }, () => [
+          h(component3, { class: componentClass3.value }, () => ['some text'])
+        ])
+      ])
+
+    render(wrapper(), root)
+    expect(root.children[0].className).toBe(
+      'staticClass componentClass3 componentClass2 componentClass1'
+    )
+
+    componentClass1.value = 'c1'
+    render(wrapper(), root)
+    expect(root.children[0].className).toBe(
+      'staticClass componentClass3 componentClass2 c1'
+    )
+
+    componentClass2.value = 'c2'
+    render(wrapper(), root)
+    expect(root.children[0].className).toBe('staticClass componentClass3 c2 c1')
+
+    componentClass3.value = 'c3'
+    render(wrapper(), root)
+    expect(root.children[0].className).toBe('staticClass c3 c2 c1')
+  })
+
+  test('deep update', () => {
+    const root = document.createElement('div')
+    const test = {
+      a: true,
+      b: false
+    }
+
+    const wrapper = () => h('div', { class: test })
+    render(wrapper(), root)
+    expect(root.children[0].className).toBe('a')
+
+    test.b = true
+    render(wrapper(), root)
+    expect(root.children[0].className).toBe('a b')
+  })
+
+  // a vdom patch edge case where the user has several un-keyed elements of the
+  // same tag next to each other, and toggling them.
+  test('properly remove staticClass for toggling un-keyed children', () => {
+    const root = document.createElement('div')
+    const ok = { value: true }
+    const wrapper = () =>
+      h('div', [ok.value ? h('div', { class: 'a' }) : h('div')])
+
+    render(wrapper(), root)
+    expect(root.children[0].children[0].className).toBe('a')
+
+    ok.value = false
+    render(wrapper(), root)
+    expect(root.children[0].children[0].className).toBe('')
+  })
+})
index b7ab9b19d39f22697e994c7a35b772fe6659f0f9..92f4f0887f4b44c38fa87bba4f2889bae23d8f3a 100644 (file)
@@ -2,7 +2,10 @@ import { ElementWithTransition } from '../components/Transition'
 
 // compiler should normalize class + :class bindings on the same element
 // into a single binding ['staticClass', dynamic]
-export function patchClass(el: Element, value: string, isSVG: boolean) {
+export function patchClass(el: Element, value: string | null, isSVG: boolean) {
+  if (value == null) {
+    value = ''
+  }
   // directly setting className should be faster than setAttribute in theory
   if (isSVG) {
     el.setAttribute('class', value)