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 {
|
||||
type DebuggerEvent,
|
||||
type InjectionKey,
|
||||
type Ref,
|
||||
TrackOpTypes,
|
||||
TriggerOpTypes,
|
||||
createComponent,
|
||||
createIf,
|
||||
createTextNode,
|
||||
getCurrentInstance,
|
||||
inject,
|
||||
nextTick,
|
||||
onBeforeMount,
|
||||
onBeforeUnmount,
|
||||
onBeforeUpdate,
|
||||
onMounted,
|
||||
onRenderTracked,
|
||||
onRenderTriggered,
|
||||
onUnmounted,
|
||||
onUpdated,
|
||||
provide,
|
||||
reactive,
|
||||
ref,
|
||||
renderEffect,
|
||||
setText,
|
||||
template,
|
||||
} from '../src'
|
||||
import { makeRender } from './_utils'
|
||||
import { ITERATE_KEY } from '@vue/reactivity'
|
||||
|
||||
const define = makeRender<any>()
|
||||
|
||||
describe('apiLifecycle', () => {
|
||||
// TODO: test
|
||||
describe('api: lifecycle hooks', () => {
|
||||
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
|
||||
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 handleUpdatedChild = vi.fn()
|
||||
|
||||
|
@ -67,7 +507,7 @@ describe('apiLifecycle', () => {
|
|||
})
|
||||
|
||||
// #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 handleUpdatedChild = vi.fn()
|
||||
|
||||
|
@ -114,47 +554,4 @@ describe('apiLifecycle', () => {
|
|||
expect(handleUpdated).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 {
|
||||
// core
|
||||
type Ref,
|
||||
type DebuggerEvent,
|
||||
TrackOpTypes,
|
||||
TriggerOpTypes,
|
||||
reactive,
|
||||
ref,
|
||||
readonly,
|
||||
|
|
Loading…
Reference in New Issue