From 2440b2f9d44049952966a78985e4ed3431e3024f Mon Sep 17 00:00:00 2001 From: daiwei Date: Wed, 12 Feb 2025 09:43:42 +0800 Subject: [PATCH 1/2] fix(runtime-core): prevent unmounted vnode from being inserted --- packages/runtime-core/src/renderer.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 90cc22f5470..05c4ac345eb 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -2049,7 +2049,13 @@ function baseCreateRenderer( queuePostRenderEffect(() => transition!.enter(el!), parentSuspense) } else { const { leave, delayLeave, afterLeave } = transition! - const remove = () => hostInsert(el!, container, anchor) + const remove = () => { + if (vnode.ctx!.isUnmounted) { + hostRemove(el!) + } else { + hostInsert(el!, container, anchor) + } + } const performLeave = () => { leave(el!, () => { remove() From 2ea4051a90af7cdb83423f2a1c93557fa8bd185d Mon Sep 17 00:00:00 2001 From: daiwei Date: Wed, 12 Feb 2025 11:35:42 +0800 Subject: [PATCH 2/2] test: add test --- .../runtime-core/src/components/KeepAlive.ts | 5 ++ packages/vue/__tests__/e2e/Transition.spec.ts | 69 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 5976f3a4b33..f2b7bdf9738 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -187,6 +187,11 @@ const KeepAliveImpl: ComponentOptions = { // Update components tree devtoolsComponentAdded(instance) } + + // for e2e test + if (__DEV__ && __BROWSER__) { + ;(instance as any).__keepAliveStorageContainer = storageContainer + } } function unmount(vnode: VNode) { diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts index 1315259f075..14441bd823b 100644 --- a/packages/vue/__tests__/e2e/Transition.spec.ts +++ b/packages/vue/__tests__/e2e/Transition.spec.ts @@ -1,3 +1,4 @@ +import type { ElementHandle } from 'puppeteer' import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils' import path from 'node:path' import { Transition, createApp, h, nextTick, ref } from 'vue' @@ -1653,6 +1654,74 @@ describe('e2e: Transition', () => { }, E2E_TIMEOUT, ) + + // #12860 + test( + 'unmount children', + async () => { + const unmountSpy = vi.fn() + let storageContainer: ElementHandle + const setStorageContainer = (container: any) => + (storageContainer = container) + await page().exposeFunction('unmountSpy', unmountSpy) + await page().exposeFunction('setStorageContainer', setStorageContainer) + await page().evaluate(() => { + const { unmountSpy, setStorageContainer } = window as any + const { createApp, ref, h, onUnmounted, getCurrentInstance } = ( + window as any + ).Vue + createApp({ + template: ` +
+ + + + + +
+ + `, + components: { + TrueBranch: { + name: 'TrueBranch', + setup() { + const instance = getCurrentInstance() + onUnmounted(() => { + unmountSpy() + setStorageContainer(instance.__keepAliveStorageContainer) + }) + const count = ref(0) + return () => h('div', count.value) + }, + }, + }, + setup: () => { + const includeRef = ref(['TrueBranch']) + const toggle = ref(true) + const click = () => { + toggle.value = !toggle.value + if (toggle.value) { + includeRef.value = ['TrueBranch'] + } else { + includeRef.value = [] + } + } + return { toggle, click, unmountSpy, includeRef } + }, + }).mount('#app') + }) + + await transitionFinish() + expect(await html('#container')).toBe('
0
') + + await click('#toggleBtn') + await transitionFinish() + expect(await html('#container')).toBe('') + expect(unmountSpy).toBeCalledTimes(1) + expect(await storageContainer!.evaluate(x => x.innerHTML)).toBe(``) + }, + E2E_TIMEOUT, + ) }) describe('transition with Suspense', () => {