diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index 65e801de277..0944c40d6b2 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -8,13 +8,16 @@ import { KeepAlive, Suspense, type SuspenseProps, + createBlock, createCommentVNode, + createElementBlock, h, nextTick, nodeOps, onErrorCaptured, onMounted, onUnmounted, + openBlock, ref, render, resolveDynamicComponent, @@ -26,6 +29,7 @@ import { import { computed, createApp, defineComponent, inject, provide } from 'vue' import type { RawSlots } from 'packages/runtime-core/src/componentSlots' import { resetSuspenseId } from '../../src/components/Suspense' +import { PatchFlags } from '@vue/shared' describe('Suspense', () => { const deps: Promise[] = [] @@ -2161,6 +2165,63 @@ describe('Suspense', () => { await Promise.all(deps) }) + // #12920 + test('unmount Suspense after children self-update', async () => { + const Comp = defineAsyncComponent({ + setup() { + const show = ref(true) + onMounted(() => { + // trigger self-update + show.value = !show.value + }) + return () => + show.value + ? (openBlock(), createElementBlock('div', { key: 0 }, 'show')) + : (openBlock(), createElementBlock('div', { key: 1 }, 'hidden')) + }, + }) + + const toggle = ref(true) + const root = nodeOps.createElement('div') + const App = { + render() { + return ( + openBlock(), + createElementBlock( + Fragment, + null, + [ + h('h1', null, toggle.value), + toggle.value + ? (openBlock(), + createBlock( + Suspense, + { key: 0 }, + { + default: h(Comp), + }, + )) + : createCommentVNode('v-if', true), + ], + PatchFlags.STABLE_FRAGMENT, + ) + ) + }, + } + render(h(App), root) + expect(serializeInner(root)).toBe(`

true

`) + + await Promise.all(deps) + await nextTick() + expect(serializeInner(root)).toBe(`

true

hidden
`) + + // unmount suspense + toggle.value = false + await Promise.all(deps) + await nextTick() + expect(serializeInner(root)).toBe(`

true

`) + }) + describe('warnings', () => { // base function to check if a combination of slots warns or not function baseCheckWarn( diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index 5e5e159aae0..c9cc5ded7c2 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -457,14 +457,17 @@ export function updateHOCHostEl( while (parent) { const root = parent.subTree if (root.suspense && root.suspense.activeBranch === vnode) { - root.el = vnode.el + root.suspense.vnode.el = root.el = vnode.el } if (root === vnode) { ;(vnode = parent.vnode).el = el - if (suspense && suspense.activeBranch === vnode) suspense.vnode.el = el parent = parent.parent } else { break } } + // also update suspense vnode el + if (suspense && suspense.activeBranch === vnode) { + suspense.vnode.el = el + } }