test: add test for runtime-dom/modules/class (#75)
This commit is contained in:
parent
2383b45e32
commit
d1527fbee4
193
packages/runtime-dom/__tests__/modules/class.spec.ts
Normal file
193
packages/runtime-dom/__tests__/modules/class.spec.ts
Normal file
@ -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('')
|
||||||
|
})
|
||||||
|
})
|
@ -2,7 +2,10 @@ import { ElementWithTransition } from '../components/Transition'
|
|||||||
|
|
||||||
// compiler should normalize class + :class bindings on the same element
|
// compiler should normalize class + :class bindings on the same element
|
||||||
// into a single binding ['staticClass', dynamic]
|
// 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
|
// directly setting className should be faster than setAttribute in theory
|
||||||
if (isSVG) {
|
if (isSVG) {
|
||||||
el.setAttribute('class', value)
|
el.setAttribute('class', value)
|
||||||
|
Loading…
Reference in New Issue
Block a user