diff --git a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts index 4cd8727f0..ae0bf090e 100644 --- a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts @@ -7,7 +7,7 @@ import { onUnmounted, ref, } from 'vue' -import type { VaporComponent } from '../../src/component' +import type { LooseRawProps, VaporComponent } from '../../src/component' import { makeRender } from '../_utils' import { VaporKeepAliveImpl as VaporKeepAlive } from '../../src/components/KeepAlive' import { @@ -70,6 +70,13 @@ describe('VaporKeepAlive', () => { return n0 }, }) + oneTestHooks = { + beforeMount: vi.fn(), + mounted: vi.fn(), + activated: vi.fn(), + deactivated: vi.fn(), + unmounted: vi.fn(), + } oneTest = defineVaporComponent({ name: 'oneTest', setup() { @@ -469,4 +476,215 @@ describe('VaporKeepAlive', () => { assertHookCalls(oneHooks, [1, 1, 4, 3, 0]) assertHookCalls(twoHooks, [1, 1, 4, 4, 0]) // should remain inactive }) + + async function assertNameMatch(props: LooseRawProps) { + const outerRef = ref(true) + const viewRef = ref('one') + const { html } = define({ + setup() { + return createIf( + () => outerRef.value, + () => + createComponent(VaporKeepAlive, props, { + default: () => createDynamicComponent(() => views[viewRef.value]), + }), + ) + }, + }).render() + + expect(html()).toBe(`
one
`) + assertHookCalls(oneHooks, [1, 1, 1, 0, 0]) + assertHookCalls(twoHooks, [0, 0, 0, 0, 0]) + + viewRef.value = 'two' + await nextTick() + expect(html()).toBe(`
two
`) + assertHookCalls(oneHooks, [1, 1, 1, 1, 0]) + assertHookCalls(twoHooks, [1, 1, 0, 0, 0]) + + viewRef.value = 'one' + await nextTick() + expect(html()).toBe(`
one
`) + assertHookCalls(oneHooks, [1, 1, 2, 1, 0]) + assertHookCalls(twoHooks, [1, 1, 0, 0, 1]) + + viewRef.value = 'two' + await nextTick() + expect(html()).toBe(`
two
`) + assertHookCalls(oneHooks, [1, 1, 2, 2, 0]) + assertHookCalls(twoHooks, [2, 2, 0, 0, 1]) + + // teardown + outerRef.value = false + await nextTick() + expect(html()).toBe(``) + assertHookCalls(oneHooks, [1, 1, 2, 2, 1]) + assertHookCalls(twoHooks, [2, 2, 0, 0, 2]) + } + + async function assertNameMatchWithFlag(props: LooseRawProps) { + const outerRef = ref(true) + const viewRef = ref('one') + const { html } = define({ + setup() { + return createIf( + () => outerRef.value, + () => + createComponent(VaporKeepAlive, props, { + default: () => createDynamicComponent(() => views[viewRef.value]), + }), + ) + }, + }).render() + + expect(html()).toBe(`
one
`) + assertHookCalls(oneHooks, [1, 1, 1, 0, 0]) + assertHookCalls(oneTestHooks, [0, 0, 0, 0, 0]) + assertHookCalls(twoHooks, [0, 0, 0, 0, 0]) + + viewRef.value = 'oneTest' + await nextTick() + expect(html()).toBe(`
oneTest
`) + assertHookCalls(oneHooks, [1, 1, 1, 1, 0]) + assertHookCalls(oneTestHooks, [1, 1, 1, 0, 0]) + assertHookCalls(twoHooks, [0, 0, 0, 0, 0]) + + viewRef.value = 'two' + await nextTick() + expect(html()).toBe(`
two
`) + assertHookCalls(oneHooks, [1, 1, 1, 1, 0]) + assertHookCalls(oneTestHooks, [1, 1, 1, 1, 0]) + assertHookCalls(twoHooks, [1, 1, 0, 0, 0]) + + viewRef.value = 'one' + await nextTick() + expect(html()).toBe(`
one
`) + assertHookCalls(oneHooks, [1, 1, 2, 1, 0]) + assertHookCalls(oneTestHooks, [1, 1, 1, 1, 0]) + assertHookCalls(twoHooks, [1, 1, 0, 0, 1]) + + viewRef.value = 'oneTest' + await nextTick() + expect(html()).toBe(`
oneTest
`) + assertHookCalls(oneHooks, [1, 1, 2, 2, 0]) + assertHookCalls(oneTestHooks, [1, 1, 2, 1, 0]) + assertHookCalls(twoHooks, [1, 1, 0, 0, 1]) + + viewRef.value = 'two' + await nextTick() + expect(html()).toBe(`
two
`) + assertHookCalls(oneHooks, [1, 1, 2, 2, 0]) + assertHookCalls(oneTestHooks, [1, 1, 2, 2, 0]) + assertHookCalls(twoHooks, [2, 2, 0, 0, 1]) + + // teardown + outerRef.value = false + await nextTick() + expect(html()).toBe(``) + assertHookCalls(oneHooks, [1, 1, 2, 2, 1]) + assertHookCalls(oneTestHooks, [1, 1, 2, 2, 1]) + assertHookCalls(twoHooks, [2, 2, 0, 0, 2]) + } + + async function assertNameMatchWithFlagExclude(props: LooseRawProps) { + const outerRef = ref(true) + const viewRef = ref('one') + const { html } = define({ + setup() { + return createIf( + () => outerRef.value, + () => + createComponent(VaporKeepAlive, props, { + default: () => createDynamicComponent(() => views[viewRef.value]), + }), + ) + }, + }).render() + + expect(html()).toBe(`
one
`) + assertHookCalls(oneHooks, [1, 1, 0, 0, 0]) + assertHookCalls(oneTestHooks, [0, 0, 0, 0, 0]) + assertHookCalls(twoHooks, [0, 0, 0, 0, 0]) + + viewRef.value = 'oneTest' + await nextTick() + expect(html()).toBe(`
oneTest
`) + assertHookCalls(oneHooks, [1, 1, 0, 0, 1]) + assertHookCalls(oneTestHooks, [1, 1, 0, 0, 0]) + assertHookCalls(twoHooks, [0, 0, 0, 0, 0]) + + viewRef.value = 'two' + await nextTick() + expect(html()).toBe(`
two
`) + assertHookCalls(oneHooks, [1, 1, 0, 0, 1]) + assertHookCalls(oneTestHooks, [1, 1, 0, 0, 1]) + assertHookCalls(twoHooks, [1, 1, 1, 0, 0]) + + viewRef.value = 'one' + await nextTick() + expect(html()).toBe(`
one
`) + assertHookCalls(oneHooks, [2, 2, 0, 0, 1]) + assertHookCalls(oneTestHooks, [1, 1, 0, 0, 1]) + assertHookCalls(twoHooks, [1, 1, 1, 1, 0]) + + viewRef.value = 'oneTest' + await nextTick() + expect(html()).toBe(`
oneTest
`) + assertHookCalls(oneHooks, [2, 2, 0, 0, 2]) + assertHookCalls(oneTestHooks, [2, 2, 0, 0, 1]) + assertHookCalls(twoHooks, [1, 1, 1, 1, 0]) + + viewRef.value = 'two' + await nextTick() + expect(html()).toBe(`
two
`) + assertHookCalls(oneHooks, [2, 2, 0, 0, 2]) + assertHookCalls(oneTestHooks, [2, 2, 0, 0, 2]) + assertHookCalls(twoHooks, [1, 1, 2, 1, 0]) + + // teardown + outerRef.value = false + await nextTick() + expect(html()).toBe(``) + assertHookCalls(oneHooks, [2, 2, 0, 0, 2]) + assertHookCalls(oneTestHooks, [2, 2, 0, 0, 2]) + assertHookCalls(twoHooks, [1, 1, 2, 2, 1]) + } + + describe('props', () => { + test('include (string)', async () => { + await assertNameMatch({ include: () => 'one' }) + }) + + test('include (regex)', async () => { + await assertNameMatch({ include: () => /^one$/ }) + }) + + test('include (regex with g flag)', async () => { + await assertNameMatchWithFlag({ include: () => /one/g }) + }) + + test('include (array)', async () => { + await assertNameMatch({ include: () => ['one'] }) + }) + + test('exclude (string)', async () => { + await assertNameMatch({ exclude: () => 'two' }) + }) + + test('exclude (regex)', async () => { + await assertNameMatch({ exclude: () => /^two$/ }) + }) + + test('exclude (regex with a flag)', async () => { + await assertNameMatchWithFlagExclude({ exclude: () => /one/g }) + }) + + test('exclude (array)', async () => { + await assertNameMatch({ exclude: () => ['two'] }) + }) + + test('include + exclude', async () => { + await assertNameMatch({ include: () => 'one,two', exclude: () => 'two' }) + }) + }) }) diff --git a/packages/runtime-vapor/src/components/KeepAlive.ts b/packages/runtime-vapor/src/components/KeepAlive.ts index f40a508a5..45844180a 100644 --- a/packages/runtime-vapor/src/components/KeepAlive.ts +++ b/packages/runtime-vapor/src/components/KeepAlive.ts @@ -64,13 +64,21 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({ const { include, exclude, max } = props + function shouldCache(instance: VaporComponentInstance) { + const name = getComponentName(instance.type) + return !( + (include && (!name || !matches(include, name))) || + (exclude && name && matches(exclude, name)) + ) + } + function cacheBlock() { // TODO suspense const currentBlock = keepAliveInstance.block! if (!isValidBlock(currentBlock)) return const block = getInnerBlock(currentBlock)! - if (!block) return + if (!block || !shouldCache(block)) return const key = block.type if (cache.has(key)) { @@ -111,13 +119,16 @@ 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)) - ) - ) { + // 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 } }