From df8672c48db16bb2272186ba6abfc22d192fcc66 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Sun, 19 May 2024 22:23:52 +0900 Subject: [PATCH 1/5] test(runtime-vapor): api lifecycle hooks --- .../__tests__/apiLifecycle.spec.ts | 434 ++++++++++++++++-- 1 file changed, 389 insertions(+), 45 deletions(-) diff --git a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts index c4de0cc67..65726bb17 100644 --- a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts +++ b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts @@ -2,24 +2,411 @@ import { type InjectionKey, type Ref, createComponent, + createIf, createTextNode, getCurrentInstance, inject, nextTick, + onBeforeMount, + onBeforeUnmount, + onBeforeUpdate, + onMounted, onRenderTracked, onRenderTriggered, + onUnmounted, onUpdated, provide, ref, renderEffect, setText, + template, } from '../src' import { makeRender } from './_utils' const define = makeRender() -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('
')() + }, + }) + render() + expect(fn).toHaveBeenCalledTimes(1) + }) + + it('onMounted', () => { + const fn = vi.fn(() => { + expect(host.innerHTML).toBe(``) + }) + const { render, host } = define({ + setup() { + onMounted(fn) + return () => template('
')() + }, + }) + 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('
') + }) + const { render, host } = define({ + setup() { + return (() => { + const n0 = createIf( + () => toggle.value, + () => createComponent(Child), + ) + return n0 + })() + }, + }) + + const Child = { + setup() { + onBeforeUnmount(fn) + return (() => { + const t0 = template('
') + const n0 = t0() + return n0 + })() + }, + } + + render() + + toggle.value = false + await nextTick() + // expect(fn).toHaveBeenCalledTimes(1) // FIXME: not called + expect(host.innerHTML).toBe('') + }) + + it('onUnmounted', async () => { + const toggle = ref(true) + const fn = vi.fn(() => { + expect(host.innerHTML).toBe('
') + }) + const { render, host } = define({ + setup() { + return (() => { + const n0 = createIf( + () => toggle.value, + () => createComponent(Child), + ) + return n0 + })() + }, + }) + + const Child = { + setup() { + onUnmounted(fn) + return (() => { + const t0 = template('
') + const n0 = t0() + return n0 + })() + }, + } + + render() + + toggle.value = false + await nextTick() + // expect(fn).toHaveBeenCalledTimes(1) // FIXME: not called + expect(host.innerHTML).toBe('') + }) + + it('onBeforeUnmount in onMounted', async () => { + const toggle = ref(true) + const fn = vi.fn(() => { + expect(host.innerHTML).toBe('
') + }) + 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('
') + const n0 = t0() + return n0 + })() + }, + } + + render() + + toggle.value = false + await nextTick() + // expect(fn).toHaveBeenCalledTimes(1) // FIXME: not called + expect(host.innerHTML).toBe('') + }) + + 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('
') + 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', + // ]) + }) + + 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') + // TODO: compatibility with packages/runtime-core/__tests__/apiLifecycle.spec.ts + }) + + 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') + // TODO: compatibility with packages/runtime-core/__tests__/apiLifecycle.spec.ts + }) + + 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('
')() + }, + } + + 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 () => { @@ -114,47 +501,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') - }) }) From aca79795b92cccbb3f3ded349c7c37fef27b531d Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Sun, 19 May 2024 22:30:12 +0900 Subject: [PATCH 2/5] test(runtime-vapor): api lifecycle hooks (onRenderTracked/onRenderTrigger) --- .../__tests__/apiLifecycle.spec.ts | 93 +++++++++++++++---- 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts index 65726bb17..870cb3a5a 100644 --- a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts +++ b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts @@ -1,3 +1,10 @@ +import { + type DebuggerEvent, + ITERATE_KEY, + TrackOpTypes, + TriggerOpTypes, + reactive, +} from '@vue/reactivity' import { type InjectionKey, type Ref, @@ -337,49 +344,97 @@ describe('api: lifecycle hooks', () => { }) test('onRenderTracked', async () => { - const onTrackedFn = vi.fn() - const count = ref(0) - const { host, render } = define({ + const events: DebuggerEvent[] = [] + const onTrack = vi.fn((e: DebuggerEvent) => { + events.push(e) + }) + const obj = reactive({ foo: 1, bar: 2 }) + + const { render } = define({ setup() { - onRenderTracked(onTrackedFn) + onRenderTracked(onTrack) return (() => { const n0 = createTextNode() renderEffect(() => { - setText(n0, count.value) + setText(n0, [obj.foo, 'bar' in obj, Object.keys(obj).join('')]) }) return n0 })() }, }) + render() - await nextTick() - expect(onTrackedFn).toBeCalled() - expect(host.innerHTML).toBe('0') - // TODO: compatibility with packages/runtime-core/__tests__/apiLifecycle.spec.ts + 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, + }, + ]) }) test('onRenderTrigger', async () => { - const onRenderTriggerFn = vi.fn() - const count = ref(0) - const { host, render } = define({ + 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(onRenderTriggerFn) + onRenderTriggered(onTrigger) return (() => { const n0 = createTextNode() renderEffect(() => { - setText(n0, count.value) + setText(n0, [obj.foo, 'bar' in obj, Object.keys(obj).join('')]) }) return n0 })() }, }) + render() - count.value++ + + obj.foo++ await nextTick() - expect(onRenderTriggerFn).toBeCalled() - expect(onRenderTriggerFn).toHaveBeenCalledOnce() - expect(host.innerHTML).toBe('1') - // TODO: compatibility with packages/runtime-core/__tests__/apiLifecycle.spec.ts + 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 () => { From 42c2d1c620a60759fc57758427be057804bd2543 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Sun, 19 May 2024 22:39:49 +0900 Subject: [PATCH 3/5] fix: exports from reactivity --- packages/runtime-vapor/__tests__/apiLifecycle.spec.ts | 8 +++----- packages/runtime-vapor/src/index.ts | 4 ++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts index 870cb3a5a..b145bd7b2 100644 --- a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts +++ b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts @@ -1,13 +1,10 @@ import { type DebuggerEvent, ITERATE_KEY, - TrackOpTypes, - TriggerOpTypes, - reactive, -} from '@vue/reactivity' -import { type InjectionKey, type Ref, + TrackOpTypes, + TriggerOpTypes, createComponent, createIf, createTextNode, @@ -23,6 +20,7 @@ import { onUnmounted, onUpdated, provide, + reactive, ref, renderEffect, setText, diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 959db2b69..c7fa2e5a6 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -4,6 +4,10 @@ export const version = __VERSION__ export { // core type Ref, + type DebuggerEvent, + ITERATE_KEY, + TrackOpTypes, + TriggerOpTypes, reactive, ref, readonly, From a265416ae633b8d26a8bdd0f20dba8a1147b38d0 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Tue, 21 May 2024 14:30:23 +0900 Subject: [PATCH 4/5] fix: use "it" fn --- packages/runtime-vapor/__tests__/apiLifecycle.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts index b145bd7b2..a83078d8c 100644 --- a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts +++ b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts @@ -341,7 +341,7 @@ describe('api: lifecycle hooks', () => { // ]) }) - test('onRenderTracked', async () => { + it('onRenderTracked', async () => { const events: DebuggerEvent[] = [] const onTrack = vi.fn((e: DebuggerEvent) => { events.push(e) @@ -382,7 +382,7 @@ describe('api: lifecycle hooks', () => { ]) }) - test('onRenderTrigger', async () => { + it('onRenderTrigger', async () => { const events: DebuggerEvent[] = [] const onTrigger = vi.fn((e: DebuggerEvent) => { events.push(e) @@ -462,7 +462,7 @@ describe('api: lifecycle hooks', () => { }) // #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() @@ -507,7 +507,7 @@ describe('api: lifecycle hooks', () => { }) // #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() From a0558b9a3140da4c4200c05d3ead4e014b652ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Mon, 27 May 2024 02:36:41 +0800 Subject: [PATCH 5/5] fix exports --- packages/runtime-vapor/__tests__/apiLifecycle.spec.ts | 2 +- packages/runtime-vapor/src/index.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts index a83078d8c..dc31059f2 100644 --- a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts +++ b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts @@ -1,6 +1,5 @@ import { type DebuggerEvent, - ITERATE_KEY, type InjectionKey, type Ref, TrackOpTypes, @@ -27,6 +26,7 @@ import { template, } from '../src' import { makeRender } from './_utils' +import { ITERATE_KEY } from '@vue/reactivity' const define = makeRender() diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index c7fa2e5a6..a654b44fa 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -5,7 +5,6 @@ export { // core type Ref, type DebuggerEvent, - ITERATE_KEY, TrackOpTypes, TriggerOpTypes, reactive,