refactor: simplify runtime-dom class tests
This commit is contained in:
		
							parent
							
								
									6eb3399311
								
							
						
					
					
						commit
						ed235f16de
					
				@ -1,7 +1,7 @@
 | 
				
			|||||||
import { patchProp } from '../../src/patchProp'
 | 
					import { patchProp } from '../../src/patchProp'
 | 
				
			||||||
import { xlinkNS } from '../../src/modules/attrs'
 | 
					import { xlinkNS } from '../../src/modules/attrs'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('attrs', () => {
 | 
					describe('runtime-dom: attrs patching', () => {
 | 
				
			||||||
  test('xlink attributes', () => {
 | 
					  test('xlink attributes', () => {
 | 
				
			||||||
    const el = document.createElementNS('http://www.w3.org/2000/svg', 'use')
 | 
					    const el = document.createElementNS('http://www.w3.org/2000/svg', 'use')
 | 
				
			||||||
    patchProp(el, 'xlink:href', null, 'a', true)
 | 
					    patchProp(el, 'xlink:href', null, 'a', true)
 | 
				
			||||||
 | 
				
			|||||||
@ -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'
 | 
					describe('runtime-dom: class patching', () => {
 | 
				
			||||||
 | 
					  test('basics', () => {
 | 
				
			||||||
type ClassItem = {
 | 
					    const el = document.createElement('div')
 | 
				
			||||||
  value: string | object | string[]
 | 
					    patchProp(el, 'class', null, 'foo')
 | 
				
			||||||
}
 | 
					    expect(el.className).toBe('foo')
 | 
				
			||||||
 | 
					    patchProp(el, 'class', null, null)
 | 
				
			||||||
function assertClass(assertions: Array<Array<any>>) {
 | 
					    expect(el.className).toBe('')
 | 
				
			||||||
  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', () => {
 | 
					  test('transition class', () => {
 | 
				
			||||||
    assertClass([
 | 
					    const el = document.createElement('div') as ElementWithTransition
 | 
				
			||||||
      [{ bar: true, baz: false }, 'foo bar'],
 | 
					    el._vtc = new Set(['bar', 'baz'])
 | 
				
			||||||
      [{ baz: true }, 'foo baz'],
 | 
					    patchProp(el, 'class', null, 'foo')
 | 
				
			||||||
      [null, 'foo'],
 | 
					    expect(el.className).toBe('foo bar baz')
 | 
				
			||||||
      [{ 'bar baz': true, qux: false }, 'foo bar baz'],
 | 
					    patchProp(el, 'class', null, null)
 | 
				
			||||||
      [{ qux: true }, 'foo qux']
 | 
					    expect(el.className).toBe('bar baz')
 | 
				
			||||||
    ])
 | 
					    delete el._vtc
 | 
				
			||||||
 | 
					    patchProp(el, 'class', null, 'foo')
 | 
				
			||||||
 | 
					    expect(el.className).toBe('foo')
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test('array value', () => {
 | 
					  test('svg', () => {
 | 
				
			||||||
    assertClass([
 | 
					    const el = document.createElementNS(svgNS, 'svg')
 | 
				
			||||||
      [['bar', 'baz'], 'foo bar baz'],
 | 
					    patchProp(el, 'class', null, 'foo', true)
 | 
				
			||||||
      [['qux', 'baz'], 'foo qux baz'],
 | 
					    expect(el.getAttribute('class')).toBe('foo')
 | 
				
			||||||
      [['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('')
 | 
					 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import { patchProp } from '../../src/patchProp'
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const timeout = () => new Promise(r => setTimeout(r))
 | 
					const timeout = () => new Promise(r => setTimeout(r))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe(`events`, () => {
 | 
					describe(`runtime-dom: events patching`, () => {
 | 
				
			||||||
  it('should assign event handler', async () => {
 | 
					  it('should assign event handler', async () => {
 | 
				
			||||||
    const el = document.createElement('div')
 | 
					    const el = document.createElement('div')
 | 
				
			||||||
    const event = new Event('click')
 | 
					    const event = new Event('click')
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { patchProp } from '../../src/patchProp'
 | 
					import { patchProp } from '../../src/patchProp'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe(`module style`, () => {
 | 
					describe(`runtime-dom: style patching`, () => {
 | 
				
			||||||
  it('string', () => {
 | 
					  it('string', () => {
 | 
				
			||||||
    const el = document.createElement('div')
 | 
					    const el = document.createElement('div')
 | 
				
			||||||
    patchProp(el, 'style', {}, 'color:red')
 | 
					    patchProp(el, 'style', {}, 'color:red')
 | 
				
			||||||
 | 
				
			|||||||
@ -6,15 +6,18 @@ export function patchClass(el: Element, value: string | null, isSVG: boolean) {
 | 
				
			|||||||
  if (value == null) {
 | 
					  if (value == null) {
 | 
				
			||||||
    value = ''
 | 
					    value = ''
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // directly setting className should be faster than setAttribute in theory
 | 
					 | 
				
			||||||
  if (isSVG) {
 | 
					  if (isSVG) {
 | 
				
			||||||
    el.setAttribute('class', value)
 | 
					    el.setAttribute('class', value)
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
 | 
					    // directly setting className should be faster than setAttribute in theory
 | 
				
			||||||
    // if this is an element during a transition, take the temporary transition
 | 
					    // if this is an element during a transition, take the temporary transition
 | 
				
			||||||
    // classes into account.
 | 
					    // classes into account.
 | 
				
			||||||
    const transitionClasses = (el as ElementWithTransition)._vtc
 | 
					    const transitionClasses = (el as ElementWithTransition)._vtc
 | 
				
			||||||
    if (transitionClasses) {
 | 
					    if (transitionClasses) {
 | 
				
			||||||
      value = [value, ...transitionClasses].join(' ')
 | 
					      value = (value
 | 
				
			||||||
 | 
					        ? [value, ...transitionClasses]
 | 
				
			||||||
 | 
					        : [...transitionClasses]
 | 
				
			||||||
 | 
					      ).join(' ')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    el.className = value
 | 
					    el.className = value
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,8 @@
 | 
				
			|||||||
import { RendererOptions } from '@vue/runtime-core'
 | 
					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 doc = (typeof document !== 'undefined' ? document : null) as Document
 | 
				
			||||||
const svgNS = 'http://www.w3.org/2000/svg'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
let tempContainer: HTMLElement
 | 
					let tempContainer: HTMLElement
 | 
				
			||||||
let tempSVGContainer: SVGElement
 | 
					let tempSVGContainer: SVGElement
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user