From c6e3fab3debf9613ba8eb0c30e707972b04160be Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 10 Apr 2025 11:40:54 +0800 Subject: [PATCH] test: port tests --- .../__tests__/components/KeepAlive.spec.ts | 217 ++++++++++++++++++ .../runtime-vapor/src/components/KeepAlive.ts | 20 +- 2 files changed, 224 insertions(+), 13 deletions(-) diff --git a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts index ae0bf090e..8f45cfc33 100644 --- a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts @@ -686,5 +686,222 @@ describe('VaporKeepAlive', () => { test('include + exclude', async () => { await assertNameMatch({ include: () => 'one,two', exclude: () => 'two' }) }) + + test('max', async () => { + const spyAC = vi.fn() + const spyBC = vi.fn() + const spyCC = vi.fn() + const spyAA = vi.fn() + const spyBA = vi.fn() + const spyCA = vi.fn() + const spyADA = vi.fn() + const spyBDA = vi.fn() + const spyCDA = vi.fn() + const spyAUM = vi.fn() + const spyBUM = vi.fn() + const spyCUM = vi.fn() + + function assertCount(calls: number[]) { + expect([ + spyAC.mock.calls.length, + spyAA.mock.calls.length, + spyADA.mock.calls.length, + spyAUM.mock.calls.length, + spyBC.mock.calls.length, + spyBA.mock.calls.length, + spyBDA.mock.calls.length, + spyBUM.mock.calls.length, + spyCC.mock.calls.length, + spyCA.mock.calls.length, + spyCDA.mock.calls.length, + spyCUM.mock.calls.length, + ]).toEqual(calls) + } + const viewRef = ref('a') + const views: Record = { + a: defineVaporComponent({ + name: 'a', + setup() { + onBeforeMount(() => spyAC()) + onActivated(() => spyAA()) + onDeactivated(() => spyADA()) + onUnmounted(() => spyAUM()) + return template(`one`)() + }, + }), + b: defineVaporComponent({ + name: 'b', + setup() { + onBeforeMount(() => spyBC()) + onActivated(() => spyBA()) + onDeactivated(() => spyBDA()) + onUnmounted(() => spyBUM()) + return template(`two`)() + }, + }), + c: defineVaporComponent({ + name: 'c', + setup() { + onBeforeMount(() => spyCC()) + onActivated(() => spyCA()) + onDeactivated(() => spyCDA()) + onUnmounted(() => spyCUM()) + return template(`three`)() + }, + }), + } + + define({ + setup() { + return createComponent( + VaporKeepAlive, + { max: () => 2 }, + { + default: () => createDynamicComponent(() => views[viewRef.value]), + }, + ) + }, + }).render() + assertCount([1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + + viewRef.value = 'b' + await nextTick() + assertCount([1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0]) + + viewRef.value = 'c' + await nextTick() + // should prune A because max cache reached + assertCount([1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0]) + + viewRef.value = 'b' + await nextTick() + // B should be reused, and made latest + assertCount([1, 1, 1, 1, 1, 2, 1, 0, 1, 1, 1, 0]) + + viewRef.value = 'a' + await nextTick() + // C should be pruned because B was used last so C is the oldest cached + assertCount([2, 2, 1, 1, 1, 2, 2, 0, 1, 1, 1, 1]) + }) + }) + + describe('cache invalidation', () => { + function setup() { + const viewRef = ref('one') + const includeRef = ref('one,two') + define({ + setup() { + return createComponent( + VaporKeepAlive, + { include: () => includeRef.value }, + { + default: () => createDynamicComponent(() => views[viewRef.value]), + }, + ) + }, + }).render() + return { viewRef, includeRef } + } + + function setupExclude() { + const viewRef = ref('one') + const excludeRef = ref('') + define({ + setup() { + return createComponent( + VaporKeepAlive, + { exclude: () => excludeRef.value }, + { + default: () => createDynamicComponent(() => views[viewRef.value]), + }, + ) + }, + }).render() + return { viewRef, excludeRef } + } + + test('on include change', async () => { + const { viewRef, includeRef } = setup() + + viewRef.value = 'two' + await nextTick() + assertHookCalls(oneHooks, [1, 1, 1, 1, 0]) + assertHookCalls(twoHooks, [1, 1, 1, 0, 0]) + + includeRef.value = 'two' + await nextTick() + assertHookCalls(oneHooks, [1, 1, 1, 1, 1]) + assertHookCalls(twoHooks, [1, 1, 1, 0, 0]) + + viewRef.value = 'one' + await nextTick() + assertHookCalls(oneHooks, [2, 2, 1, 1, 1]) + assertHookCalls(twoHooks, [1, 1, 1, 1, 0]) + }) + + test('on exclude change', async () => { + const { viewRef, excludeRef } = setupExclude() + + viewRef.value = 'two' + await nextTick() + assertHookCalls(oneHooks, [1, 1, 1, 1, 0]) + assertHookCalls(twoHooks, [1, 1, 1, 0, 0]) + + excludeRef.value = 'one' + await nextTick() + assertHookCalls(oneHooks, [1, 1, 1, 1, 1]) + assertHookCalls(twoHooks, [1, 1, 1, 0, 0]) + + viewRef.value = 'one' + await nextTick() + assertHookCalls(oneHooks, [2, 2, 1, 1, 1]) + assertHookCalls(twoHooks, [1, 1, 1, 1, 0]) + }) + + test('on include change + view switch', async () => { + const { viewRef, includeRef } = setup() + + viewRef.value = 'two' + await nextTick() + assertHookCalls(oneHooks, [1, 1, 1, 1, 0]) + assertHookCalls(twoHooks, [1, 1, 1, 0, 0]) + + includeRef.value = 'one' + viewRef.value = 'one' + await nextTick() + assertHookCalls(oneHooks, [1, 1, 2, 1, 0]) + // two should be pruned + assertHookCalls(twoHooks, [1, 1, 1, 1, 1]) + }) + + test('on exclude change + view switch', async () => { + const { viewRef, excludeRef } = setupExclude() + + viewRef.value = 'two' + await nextTick() + assertHookCalls(oneHooks, [1, 1, 1, 1, 0]) + assertHookCalls(twoHooks, [1, 1, 1, 0, 0]) + + excludeRef.value = 'two' + viewRef.value = 'one' + await nextTick() + assertHookCalls(oneHooks, [1, 1, 2, 1, 0]) + // two should be pruned + assertHookCalls(twoHooks, [1, 1, 1, 1, 1]) + }) + + test('should not prune current active instance', async () => { + const { viewRef, includeRef } = setup() + + includeRef.value = 'two' + await nextTick() + assertHookCalls(oneHooks, [1, 1, 1, 0, 0]) + assertHookCalls(twoHooks, [0, 0, 0, 0, 0]) + + viewRef.value = 'two' + await nextTick() + assertHookCalls(oneHooks, [1, 1, 1, 0, 1]) + assertHookCalls(twoHooks, [1, 1, 1, 0, 0]) + }) }) }) diff --git a/packages/runtime-vapor/src/components/KeepAlive.ts b/packages/runtime-vapor/src/components/KeepAlive.ts index 45844180a..ec7cce0be 100644 --- a/packages/runtime-vapor/src/components/KeepAlive.ts +++ b/packages/runtime-vapor/src/components/KeepAlive.ts @@ -62,9 +62,8 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ ;(keepAliveInstance as any).__v_cache = cache } - const { include, exclude, max } = props - function shouldCache(instance: VaporComponentInstance) { + const { include, exclude } = props const name = getComponentName(instance.type) return !( (include && (!name || !matches(include, name))) || @@ -73,6 +72,7 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ } function cacheBlock() { + const { max } = props // TODO suspense const currentBlock = keepAliveInstance.block! if (!isValidBlock(currentBlock)) return @@ -119,15 +119,6 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ instance.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE } - // const name = getComponentName(instance.type) - // if ( - // !( - // (include && (!name || !matches(include, name))) || - // (exclude && name && matches(exclude, name)) - // ) - // ) { - // instance.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE - // } if (shouldCache(instance)) { instance.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE } @@ -138,7 +129,7 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ parentNode: ParentNode, anchor: Node, ) => { - const cachedBlock = cache.get(instance.type)! + const cachedBlock = (current = cache.get(instance.type)!) insert((instance.block = cachedBlock.block), parentNode, anchor) queuePostFlushCb(() => { instance.isDeactivated = false @@ -182,7 +173,10 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ function pruneCacheEntry(key: CacheKey) { const cached = cache.get(key) if (cached) { - unmountComponent(cached) + resetShapeFlag(cached) + if (cached !== current) { + unmountComponent(cached) + } } cache.delete(key) keys.delete(key)