Skip to content

Commit

Permalink
fix(runtime-core): prevent unmounted vnode from being inserted during…
Browse files Browse the repository at this point in the history
… transition leave (#12862)

close #12860
  • Loading branch information
edison1105 authored Feb 12, 2025
1 parent 263f63f commit d6a6ec1
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 1 deletion.
5 changes: 5 additions & 0 deletions packages/runtime-core/src/components/KeepAlive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
8 changes: 7 additions & 1 deletion packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
69 changes: 69 additions & 0 deletions packages/vue/__tests__/e2e/Transition.spec.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit d6a6ec1

Please sign in to comment.