]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): prevent unmounted vnode from being inserted during transition...
authoredison <daiwei521@126.com>
Wed, 12 Feb 2025 07:30:08 +0000 (15:30 +0800)
committerGitHub <noreply@github.com>
Wed, 12 Feb 2025 07:30:08 +0000 (15:30 +0800)
close #12860

packages/runtime-core/src/components/KeepAlive.ts
packages/runtime-core/src/renderer.ts
packages/vue/__tests__/e2e/Transition.spec.ts

index 5976f3a4b339f725833e929dbe2e02cb62faf253..f2b7bdf97386973e9b0cb4cc9e43af0e32574952 100644 (file)
@@ -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) {
index 90cc22f547032c5a830be2fdda6bc1b96fceeeb3..05c4ac345eb81f9e030bc2078febc24c22f1dfe6 100644 (file)
@@ -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()
index 1315259f07578f63fa03a53c925ca68b06f7c3ad..14441bd823b84a1df298315d45228eee6dab579c 100644 (file)
@@ -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<HTMLDivElement>
+        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: `
+            <div id="container">
+              <transition>
+                <KeepAlive :include="includeRef">
+                  <TrueBranch v-if="toggle"></TrueBranch>
+                </KeepAlive>
+              </transition>
+            </div>
+            <button id="toggleBtn" @click="click">button</button>
+          `,
+            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('<div>0</div>')
+
+        await click('#toggleBtn')
+        await transitionFinish()
+        expect(await html('#container')).toBe('<!--v-if-->')
+        expect(unmountSpy).toBeCalledTimes(1)
+        expect(await storageContainer!.evaluate(x => x.innerHTML)).toBe(``)
+      },
+      E2E_TIMEOUT,
+    )
   })
 
   describe('transition with Suspense', () => {