mirror of https://github.com/vuejs/core.git
test(runtime-vapor): api lifecycle hooks (#215)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
1008199647
commit
969f53f2e7
|
@ -1,28 +1,468 @@
|
||||||
import {
|
import {
|
||||||
|
type DebuggerEvent,
|
||||||
type InjectionKey,
|
type InjectionKey,
|
||||||
type Ref,
|
type Ref,
|
||||||
|
TrackOpTypes,
|
||||||
|
TriggerOpTypes,
|
||||||
createComponent,
|
createComponent,
|
||||||
|
createIf,
|
||||||
createTextNode,
|
createTextNode,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
inject,
|
inject,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
onBeforeMount,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onBeforeUpdate,
|
||||||
|
onMounted,
|
||||||
onRenderTracked,
|
onRenderTracked,
|
||||||
onRenderTriggered,
|
onRenderTriggered,
|
||||||
|
onUnmounted,
|
||||||
onUpdated,
|
onUpdated,
|
||||||
provide,
|
provide,
|
||||||
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
renderEffect,
|
renderEffect,
|
||||||
setText,
|
setText,
|
||||||
|
template,
|
||||||
} from '../src'
|
} from '../src'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
import { ITERATE_KEY } from '@vue/reactivity'
|
||||||
|
|
||||||
const define = makeRender<any>()
|
const define = makeRender<any>()
|
||||||
|
|
||||||
describe('apiLifecycle', () => {
|
describe('api: lifecycle hooks', () => {
|
||||||
// TODO: test
|
it('onBeforeMount', () => {
|
||||||
|
const fn = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe(``)
|
||||||
|
})
|
||||||
|
const { render, host } = define({
|
||||||
|
setup() {
|
||||||
|
onBeforeMount(fn)
|
||||||
|
return () => template('<div></div>')()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
render()
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('onMounted', () => {
|
||||||
|
const fn = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe(``)
|
||||||
|
})
|
||||||
|
const { render, host } = define({
|
||||||
|
setup() {
|
||||||
|
onMounted(fn)
|
||||||
|
return () => template('<div></div>')()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
render()
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('onBeforeUpdate', async () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const fn = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe('0')
|
||||||
|
})
|
||||||
|
const { render, host } = define({
|
||||||
|
setup() {
|
||||||
|
onBeforeUpdate(fn)
|
||||||
|
return (() => {
|
||||||
|
const n0 = createTextNode()
|
||||||
|
renderEffect(() => {
|
||||||
|
setText(n0, count.value)
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
render()
|
||||||
|
count.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
|
expect(host.innerHTML).toBe('1')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('state mutation in onBeforeUpdate', async () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const fn = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe('0')
|
||||||
|
count.value++
|
||||||
|
})
|
||||||
|
const renderSpy = vi.fn()
|
||||||
|
|
||||||
|
const { render, host } = define({
|
||||||
|
setup() {
|
||||||
|
onBeforeUpdate(fn)
|
||||||
|
return (() => {
|
||||||
|
const n0 = createTextNode()
|
||||||
|
renderEffect(() => {
|
||||||
|
renderSpy()
|
||||||
|
setText(n0, count.value)
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
render()
|
||||||
|
expect(renderSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('onUpdated', async () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const fn = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe('1')
|
||||||
|
})
|
||||||
|
|
||||||
|
const { render, host } = define({
|
||||||
|
setup() {
|
||||||
|
onUpdated(fn)
|
||||||
|
return (() => {
|
||||||
|
const n0 = createTextNode()
|
||||||
|
renderEffect(() => {
|
||||||
|
setText(n0, count.value)
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
render()
|
||||||
|
|
||||||
|
count.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('onBeforeUnmount', async () => {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const fn = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe('<div></div>')
|
||||||
|
})
|
||||||
|
const { render, host } = define({
|
||||||
|
setup() {
|
||||||
|
return (() => {
|
||||||
|
const n0 = createIf(
|
||||||
|
() => toggle.value,
|
||||||
|
() => createComponent(Child),
|
||||||
|
)
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onBeforeUnmount(fn)
|
||||||
|
return (() => {
|
||||||
|
const t0 = template('<div></div>')
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
// expect(fn).toHaveBeenCalledTimes(1) // FIXME: not called
|
||||||
|
expect(host.innerHTML).toBe('<!--if-->')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('onUnmounted', async () => {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const fn = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe('<div></div>')
|
||||||
|
})
|
||||||
|
const { render, host } = define({
|
||||||
|
setup() {
|
||||||
|
return (() => {
|
||||||
|
const n0 = createIf(
|
||||||
|
() => toggle.value,
|
||||||
|
() => createComponent(Child),
|
||||||
|
)
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onUnmounted(fn)
|
||||||
|
return (() => {
|
||||||
|
const t0 = template('<div></div>')
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
// expect(fn).toHaveBeenCalledTimes(1) // FIXME: not called
|
||||||
|
expect(host.innerHTML).toBe('<!--if-->')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('onBeforeUnmount in onMounted', async () => {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const fn = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe('<div></div>')
|
||||||
|
})
|
||||||
|
const { render, host } = define({
|
||||||
|
setup() {
|
||||||
|
return (() => {
|
||||||
|
const n0 = createIf(
|
||||||
|
() => toggle.value,
|
||||||
|
() => createComponent(Child),
|
||||||
|
)
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
onBeforeUnmount(fn)
|
||||||
|
})
|
||||||
|
return (() => {
|
||||||
|
const t0 = template('<div></div>')
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
// expect(fn).toHaveBeenCalledTimes(1) // FIXME: not called
|
||||||
|
expect(host.innerHTML).toBe('<!--if-->')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('lifecycle call order', async () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const toggle = ref(true)
|
||||||
|
const calls: string[] = []
|
||||||
|
|
||||||
|
const { render } = define({
|
||||||
|
setup() {
|
||||||
|
onBeforeMount(() => calls.push('onBeforeMount'))
|
||||||
|
onMounted(() => calls.push('onMounted'))
|
||||||
|
onBeforeUpdate(() => calls.push('onBeforeUpdate'))
|
||||||
|
onUpdated(() => calls.push('onUpdated'))
|
||||||
|
onBeforeUnmount(() => calls.push('onBeforeUnmount'))
|
||||||
|
onUnmounted(() => calls.push('onUnmounted'))
|
||||||
|
return (() => {
|
||||||
|
const n0 = createIf(
|
||||||
|
() => toggle.value,
|
||||||
|
() => createComponent(Mid, { count: () => count.value }),
|
||||||
|
)
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Mid = {
|
||||||
|
props: ['count'],
|
||||||
|
setup(props: any) {
|
||||||
|
onBeforeMount(() => calls.push('mid onBeforeMount'))
|
||||||
|
onMounted(() => calls.push('mid onMounted'))
|
||||||
|
onBeforeUpdate(() => calls.push('mid onBeforeUpdate'))
|
||||||
|
onUpdated(() => calls.push('mid onUpdated'))
|
||||||
|
onBeforeUnmount(() => calls.push('mid onBeforeUnmount'))
|
||||||
|
onUnmounted(() => calls.push('mid onUnmounted'))
|
||||||
|
return (() => {
|
||||||
|
const n0 = createComponent(Child, { count: () => props.count })
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
props: ['count'],
|
||||||
|
setup(props: any) {
|
||||||
|
onBeforeMount(() => calls.push('child onBeforeMount'))
|
||||||
|
onMounted(() => calls.push('child onMounted'))
|
||||||
|
onBeforeUpdate(() => calls.push('child onBeforeUpdate'))
|
||||||
|
onUpdated(() => calls.push('child onUpdated'))
|
||||||
|
onBeforeUnmount(() => calls.push('child onBeforeUnmount'))
|
||||||
|
onUnmounted(() => calls.push('child onUnmounted'))
|
||||||
|
return (() => {
|
||||||
|
const t0 = template('<div></div>')
|
||||||
|
const n0 = t0()
|
||||||
|
renderEffect(() => setText(n0, props.count))
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// mount
|
||||||
|
render()
|
||||||
|
expect(calls).toEqual([
|
||||||
|
'onBeforeMount',
|
||||||
|
'mid onBeforeMount',
|
||||||
|
'child onBeforeMount',
|
||||||
|
'child onMounted',
|
||||||
|
'mid onMounted',
|
||||||
|
'onMounted',
|
||||||
|
])
|
||||||
|
|
||||||
|
calls.length = 0
|
||||||
|
|
||||||
|
// update
|
||||||
|
count.value++
|
||||||
|
await nextTick()
|
||||||
|
// FIXME: not called
|
||||||
|
// expect(calls).toEqual([
|
||||||
|
// 'root onBeforeUpdate',
|
||||||
|
// 'mid onBeforeUpdate',
|
||||||
|
// 'child onBeforeUpdate',
|
||||||
|
// 'child onUpdated',
|
||||||
|
// 'mid onUpdated',
|
||||||
|
// 'root onUpdated',
|
||||||
|
// ])
|
||||||
|
|
||||||
|
calls.length = 0
|
||||||
|
|
||||||
|
// unmount
|
||||||
|
toggle.value = false
|
||||||
|
// FIXME: not called
|
||||||
|
// expect(calls).toEqual([
|
||||||
|
// 'root onBeforeUnmount',
|
||||||
|
// 'mid onBeforeUnmount',
|
||||||
|
// 'child onBeforeUnmount',
|
||||||
|
// 'child onUnmounted',
|
||||||
|
// 'mid onUnmounted',
|
||||||
|
// 'root onUnmounted',
|
||||||
|
// ])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('onRenderTracked', async () => {
|
||||||
|
const events: DebuggerEvent[] = []
|
||||||
|
const onTrack = vi.fn((e: DebuggerEvent) => {
|
||||||
|
events.push(e)
|
||||||
|
})
|
||||||
|
const obj = reactive({ foo: 1, bar: 2 })
|
||||||
|
|
||||||
|
const { render } = define({
|
||||||
|
setup() {
|
||||||
|
onRenderTracked(onTrack)
|
||||||
|
return (() => {
|
||||||
|
const n0 = createTextNode()
|
||||||
|
renderEffect(() => {
|
||||||
|
setText(n0, [obj.foo, 'bar' in obj, Object.keys(obj).join('')])
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render()
|
||||||
|
expect(onTrack).toHaveBeenCalledTimes(3)
|
||||||
|
expect(events).toMatchObject([
|
||||||
|
{
|
||||||
|
target: obj,
|
||||||
|
type: TrackOpTypes.GET,
|
||||||
|
key: 'foo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: obj,
|
||||||
|
type: TrackOpTypes.HAS,
|
||||||
|
key: 'bar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: obj,
|
||||||
|
type: TrackOpTypes.ITERATE,
|
||||||
|
key: ITERATE_KEY,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('onRenderTrigger', async () => {
|
||||||
|
const events: DebuggerEvent[] = []
|
||||||
|
const onTrigger = vi.fn((e: DebuggerEvent) => {
|
||||||
|
events.push(e)
|
||||||
|
})
|
||||||
|
const obj = reactive<{
|
||||||
|
foo: number
|
||||||
|
bar?: number
|
||||||
|
}>({ foo: 1, bar: 2 })
|
||||||
|
|
||||||
|
const { render } = define({
|
||||||
|
setup() {
|
||||||
|
onRenderTriggered(onTrigger)
|
||||||
|
return (() => {
|
||||||
|
const n0 = createTextNode()
|
||||||
|
renderEffect(() => {
|
||||||
|
setText(n0, [obj.foo, 'bar' in obj, Object.keys(obj).join('')])
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render()
|
||||||
|
|
||||||
|
obj.foo++
|
||||||
|
await nextTick()
|
||||||
|
expect(onTrigger).toHaveBeenCalledTimes(1)
|
||||||
|
expect(events[0]).toMatchObject({
|
||||||
|
type: TriggerOpTypes.SET,
|
||||||
|
key: 'foo',
|
||||||
|
oldValue: 1,
|
||||||
|
newValue: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
delete obj.bar
|
||||||
|
await nextTick()
|
||||||
|
expect(onTrigger).toHaveBeenCalledTimes(2)
|
||||||
|
expect(events[1]).toMatchObject({
|
||||||
|
type: TriggerOpTypes.DELETE,
|
||||||
|
key: 'bar',
|
||||||
|
oldValue: 2,
|
||||||
|
})
|
||||||
|
;(obj as any).baz = 3
|
||||||
|
await nextTick()
|
||||||
|
expect(onTrigger).toHaveBeenCalledTimes(3)
|
||||||
|
expect(events[2]).toMatchObject({
|
||||||
|
type: TriggerOpTypes.ADD,
|
||||||
|
key: 'baz',
|
||||||
|
newValue: 3,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('runs shared hook fn for each instance', async () => {
|
||||||
|
const fn = vi.fn()
|
||||||
|
const toggle = ref(true)
|
||||||
|
const { render } = define({
|
||||||
|
setup() {
|
||||||
|
return createIf(
|
||||||
|
() => toggle.value,
|
||||||
|
() => [createComponent(Child), createComponent(Child)],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onBeforeMount(fn)
|
||||||
|
onBeforeUnmount(fn)
|
||||||
|
return template('<div></div>')()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
expect(fn).toHaveBeenCalledTimes(2)
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
// expect(fn).toHaveBeenCalledTimes(4) // FIXME: not called unmounted hook
|
||||||
|
})
|
||||||
|
|
||||||
// #136
|
// #136
|
||||||
test('should trigger updated hooks across components. (parent -> child)', async () => {
|
it('should trigger updated hooks across components. (parent -> child)', async () => {
|
||||||
const handleUpdated = vi.fn()
|
const handleUpdated = vi.fn()
|
||||||
const handleUpdatedChild = vi.fn()
|
const handleUpdatedChild = vi.fn()
|
||||||
|
|
||||||
|
@ -67,7 +507,7 @@ describe('apiLifecycle', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// #136
|
// #136
|
||||||
test('should trigger updated hooks across components. (child -> parent)', async () => {
|
it('should trigger updated hooks across components. (child -> parent)', async () => {
|
||||||
const handleUpdated = vi.fn()
|
const handleUpdated = vi.fn()
|
||||||
const handleUpdatedChild = vi.fn()
|
const handleUpdatedChild = vi.fn()
|
||||||
|
|
||||||
|
@ -114,47 +554,4 @@ describe('apiLifecycle', () => {
|
||||||
expect(handleUpdated).toHaveBeenCalledTimes(1)
|
expect(handleUpdated).toHaveBeenCalledTimes(1)
|
||||||
expect(handleUpdatedChild).toHaveBeenCalledTimes(1)
|
expect(handleUpdatedChild).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('onRenderTracked', async () => {
|
|
||||||
const onTrackedFn = vi.fn()
|
|
||||||
const count = ref(0)
|
|
||||||
const { host, render } = define({
|
|
||||||
setup() {
|
|
||||||
onRenderTracked(onTrackedFn)
|
|
||||||
return (() => {
|
|
||||||
const n0 = createTextNode()
|
|
||||||
renderEffect(() => {
|
|
||||||
setText(n0, count.value)
|
|
||||||
})
|
|
||||||
return n0
|
|
||||||
})()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
render()
|
|
||||||
await nextTick()
|
|
||||||
expect(onTrackedFn).toBeCalled()
|
|
||||||
expect(host.innerHTML).toBe('0')
|
|
||||||
})
|
|
||||||
test('onRenderTrigger', async () => {
|
|
||||||
const onRenderTriggerFn = vi.fn()
|
|
||||||
const count = ref(0)
|
|
||||||
const { host, render } = define({
|
|
||||||
setup() {
|
|
||||||
onRenderTriggered(onRenderTriggerFn)
|
|
||||||
return (() => {
|
|
||||||
const n0 = createTextNode()
|
|
||||||
renderEffect(() => {
|
|
||||||
setText(n0, count.value)
|
|
||||||
})
|
|
||||||
return n0
|
|
||||||
})()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
render()
|
|
||||||
count.value++
|
|
||||||
await nextTick()
|
|
||||||
expect(onRenderTriggerFn).toBeCalled()
|
|
||||||
expect(onRenderTriggerFn).toHaveBeenCalledOnce()
|
|
||||||
expect(host.innerHTML).toBe('1')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,6 +4,9 @@ export const version = __VERSION__
|
||||||
export {
|
export {
|
||||||
// core
|
// core
|
||||||
type Ref,
|
type Ref,
|
||||||
|
type DebuggerEvent,
|
||||||
|
TrackOpTypes,
|
||||||
|
TriggerOpTypes,
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
readonly,
|
readonly,
|
||||||
|
|
Loading…
Reference in New Issue