vue3-yuanma/packages/runtime-core/__tests__/rendererComponent.spec.ts

325 lines
7.3 KiB
TypeScript

import {
ref,
h,
render,
nodeOps,
serializeInner,
nextTick,
VNode,
provide,
inject,
Ref,
watch,
SetupContext
} from '@vue/runtime-test'
describe('renderer: component', () => {
test('should update parent(hoc) component host el when child component self update', async () => {
const value = ref(true)
let parentVnode: VNode
let childVnode1: VNode
let childVnode2: VNode
const Parent = {
render: () => {
// let Parent first rerender
return (parentVnode = h(Child))
}
}
const Child = {
render: () => {
return value.value
? (childVnode1 = h('div'))
: (childVnode2 = h('span'))
}
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(serializeInner(root)).toBe(`<div></div>`)
expect(parentVnode!.el).toBe(childVnode1!.el)
value.value = false
await nextTick()
expect(serializeInner(root)).toBe(`<span></span>`)
expect(parentVnode!.el).toBe(childVnode2!.el)
})
it('should create an Component with props', () => {
const Comp = {
render: () => {
return h('div')
}
}
const root = nodeOps.createElement('div')
render(h(Comp, { id: 'foo', class: 'bar' }), root)
expect(serializeInner(root)).toBe(`<div id="foo" class="bar"></div>`)
})
it('should create an Component with direct text children', () => {
const Comp = {
render: () => {
return h('div', 'test')
}
}
const root = nodeOps.createElement('div')
render(h(Comp, { id: 'foo', class: 'bar' }), root)
expect(serializeInner(root)).toBe(`<div id="foo" class="bar">test</div>`)
})
it('should update an Component tag which is already mounted', () => {
const Comp1 = {
render: () => {
return h('div', 'foo')
}
}
const root = nodeOps.createElement('div')
render(h(Comp1), root)
expect(serializeInner(root)).toBe('<div>foo</div>')
const Comp2 = {
render: () => {
return h('span', 'foo')
}
}
render(h(Comp2), root)
expect(serializeInner(root)).toBe('<span>foo</span>')
})
// #2072
it('should not update Component if only changed props are declared emit listeners', () => {
const Comp1 = {
emits: ['foo'],
updated: jest.fn(),
render: () => null
}
const root = nodeOps.createElement('div')
render(
h(Comp1, {
onFoo: () => {}
}),
root
)
render(
h(Comp1, {
onFoo: () => {}
}),
root
)
expect(Comp1.updated).not.toHaveBeenCalled()
})
// #2043
test('component child synchronously updating parent state should trigger parent re-render', async () => {
const App = {
setup() {
const n = ref(0)
provide('foo', n)
return () => {
return [h('div', n.value), h(Child)]
}
}
}
const Child = {
setup() {
const n = inject<Ref<number>>('foo')!
n.value++
return () => {
return h('div', n.value)
}
}
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(`<div>0</div><div>1</div>`)
await nextTick()
expect(serializeInner(root)).toBe(`<div>1</div><div>1</div>`)
})
// #2170
test('instance.$el should be exposed to watch options', async () => {
function returnThis(this: any, _arg: any) {
return this
}
const propWatchSpy = jest.fn(returnThis)
const dataWatchSpy = jest.fn(returnThis)
let instance: any
const Comp = {
props: {
testProp: String
},
data() {
return {
testData: undefined
}
},
watch: {
testProp() {
// @ts-ignore
propWatchSpy(this.$el)
},
testData() {
// @ts-ignore
dataWatchSpy(this.$el)
}
},
created() {
instance = this
},
render() {
return h('div')
}
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
await nextTick()
expect(propWatchSpy).not.toHaveBeenCalled()
expect(dataWatchSpy).not.toHaveBeenCalled()
render(h(Comp, { testProp: 'prop ' }), root)
await nextTick()
expect(propWatchSpy).toHaveBeenCalledWith(instance.$el)
instance.testData = 1
await nextTick()
expect(dataWatchSpy).toHaveBeenCalledWith(instance.$el)
})
// #2200
test('component child updating parent state in pre-flush should trigger parent re-render', async () => {
const outer = ref(0)
const App = {
setup() {
const inner = ref(0)
return () => {
return [
h('div', inner.value),
h(Child, {
value: outer.value,
onUpdate: (val: number) => (inner.value = val)
})
]
}
}
}
const Child = {
props: ['value'],
setup(props: any, { emit }: SetupContext) {
watch(() => props.value, (val: number) => emit('update', val))
return () => {
return h('div', props.value)
}
}
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(`<div>0</div><div>0</div>`)
outer.value++
await nextTick()
expect(serializeInner(root)).toBe(`<div>1</div><div>1</div>`)
})
// #2521
test('should pause tracking deps when initializing legacy options', async () => {
let childInstance = null as any
const Child = {
props: ['foo'],
data() {
return {
count: 0
}
},
watch: {
foo: {
immediate: true,
handler() {
;(this as any).count
}
}
},
created() {
childInstance = this as any
childInstance.count
},
render() {
return h('h1', (this as any).count)
}
}
const App = {
setup() {
return () => h(Child)
},
updated: jest.fn()
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(App.updated).toHaveBeenCalledTimes(0)
childInstance.count++
await nextTick()
expect(App.updated).toHaveBeenCalledTimes(0)
})
describe('render with access caches', () => {
// #3297
test('should not set the access cache in the data() function (production mode)', () => {
const Comp = {
data() {
;(this as any).foo
return { foo: 1 }
},
render() {
return h('h1', (this as any).foo)
}
}
const root = nodeOps.createElement('div')
__DEV__ = false
render(h(Comp), root)
__DEV__ = true
expect(serializeInner(root)).toBe(`<h1>1</h1>`)
})
})
test('the component VNode should be cloned when reusing it', () => {
const App = {
render() {
const c = [h(Comp)]
return [c, c, c]
}
}
const ids: number[] = []
const Comp = {
render: () => h('h1'),
beforeUnmount() {
ids.push((this as any).$.uid)
}
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(`<h1></h1><h1></h1><h1></h1>`)
render(null, root)
expect(serializeInner(root)).toBe(``)
expect(ids).toEqual([ids[0], ids[0] + 1, ids[0] + 2])
})
})