Skip to content

test: add test for runtime-dom/modules/class #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 193 additions & 0 deletions packages/runtime-dom/__tests__/modules/class.spec.ts
Original file line number Diff line number Diff line change
@@ -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')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference between the 2 cases is that child2 has an empty props declaration, therefore it successfully triggered class merging while the other one didn't.
I didn't see anything about this behavior in the inheritAttrs RFS.
Seems a bug in the current implementation?
@yyx990803


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('')
})
})
5 changes: 4 additions & 1 deletion packages/runtime-dom/src/modules/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down