refactor: simplify runtime-dom class tests

This commit is contained in:
Evan You 2020-04-10 15:23:01 -04:00
parent 6eb3399311
commit ed235f16de
6 changed files with 34 additions and 187 deletions

View File

@ -1,7 +1,7 @@
import { patchProp } from '../../src/patchProp'
import { xlinkNS } from '../../src/modules/attrs'
describe('attrs', () => {
describe('runtime-dom: attrs patching', () => {
test('xlink attributes', () => {
const el = document.createElementNS('http://www.w3.org/2000/svg', 'use')
patchProp(el, 'xlink:href', null, 'a', true)

View File

@ -1,188 +1,31 @@
// https://github.com/vuejs/vue/blob/dev/test/unit/features/directives/class.spec.js
import { patchProp } from '../../src/patchProp'
import { ElementWithTransition } from '../../src/components/Transition'
import { svgNS } from '../../src/nodeOps'
import { h, render, defineComponent } 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']
])
describe('runtime-dom: class patching', () => {
test('basics', () => {
const el = document.createElement('div')
patchProp(el, 'class', null, 'foo')
expect(el.className).toBe('foo')
patchProp(el, 'class', null, null)
expect(el.className).toBe('')
})
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('transition class', () => {
const el = document.createElement('div') as ElementWithTransition
el._vtc = new Set(['bar', 'baz'])
patchProp(el, 'class', null, 'foo')
expect(el.className).toBe('foo bar baz')
patchProp(el, 'class', null, null)
expect(el.className).toBe('bar baz')
delete el._vtc
patchProp(el, 'class', null, 'foo')
expect(el.className).toBe('foo')
})
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 = {
render: () => h('div', { class: ['c', childClass.value] })
}
const parentClass: ClassItem = { value: 'b' }
const parent = {
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 = defineComponent({
render() {
return this.$slots.default!()[0]
}
})
const component2 = defineComponent({
render() {
return this.$slots.default!()[0]
}
})
const component3 = defineComponent({
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('')
test('svg', () => {
const el = document.createElementNS(svgNS, 'svg')
patchProp(el, 'class', null, 'foo', true)
expect(el.getAttribute('class')).toBe('foo')
})
})

View File

@ -2,7 +2,7 @@ import { patchProp } from '../../src/patchProp'
const timeout = () => new Promise(r => setTimeout(r))
describe(`events`, () => {
describe(`runtime-dom: events patching`, () => {
it('should assign event handler', async () => {
const el = document.createElement('div')
const event = new Event('click')

View File

@ -1,6 +1,6 @@
import { patchProp } from '../../src/patchProp'
describe(`module style`, () => {
describe(`runtime-dom: style patching`, () => {
it('string', () => {
const el = document.createElement('div')
patchProp(el, 'style', {}, 'color:red')

View File

@ -6,15 +6,18 @@ 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)
} else {
// directly setting className should be faster than setAttribute in theory
// if this is an element during a transition, take the temporary transition
// classes into account.
const transitionClasses = (el as ElementWithTransition)._vtc
if (transitionClasses) {
value = [value, ...transitionClasses].join(' ')
value = (value
? [value, ...transitionClasses]
: [...transitionClasses]
).join(' ')
}
el.className = value
}

View File

@ -1,7 +1,8 @@
import { RendererOptions } from '@vue/runtime-core'
export const svgNS = 'http://www.w3.org/2000/svg'
const doc = (typeof document !== 'undefined' ? document : null) as Document
const svgNS = 'http://www.w3.org/2000/svg'
let tempContainer: HTMLElement
let tempSVGContainer: SVGElement