diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index 65e801de277..999e18f4b93 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, @@ -23,9 +26,17 @@ import { watch, watchEffect, } from '@vue/runtime-test' -import { computed, createApp, defineComponent, inject, provide } from 'vue' +import { + computed, + createApp, + defineAsyncComponent as defineAsyncComp, + 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 +2172,134 @@ describe('Suspense', () => { await Promise.all(deps) }) + // #12920 + test('unmount Suspense after async child (with defineAsyncComponent) self-triggered update', async () => { + const Comp = defineComponent({ + setup() { + const show = ref(true) + onMounted(() => { + // trigger update + show.value = !show.value + }) + return () => + show.value + ? (openBlock(), createElementBlock('div', { key: 0 }, 'show')) + : (openBlock(), createElementBlock('div', { key: 1 }, 'hidden')) + }, + }) + + const AsyncComp = defineAsyncComp(() => { + const p = new Promise(resolve => { + resolve(Comp) + }) + deps.push(p.then(() => Promise.resolve())) + return p as any + }) + + 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(AsyncComp), + }, + )) + : createCommentVNode('v-if', true), + ], + PatchFlags.STABLE_FRAGMENT, + ) + ) + }, + } + render(h(App), root) + expect(serializeInner(root)).toBe(`

true

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

true

show
`) + + await nextTick() + expect(serializeInner(root)).toBe(`

true

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

true

`) + }) + + test('unmount Suspense after async child (with async setup) self-triggered update', async () => { + const AsyncComp = defineComponent({ + async setup() { + const show = ref(true) + onMounted(() => { + // trigger update + show.value = !show.value + }) + const p = new Promise(r => setTimeout(r, 1)) + // extra tick needed for Node 12+ + deps.push(p.then(() => Promise.resolve())) + 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(AsyncComp), + }, + )) + : 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 a1afae6201a..c9cc5ded7c2 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -451,13 +451,13 @@ function hasPropsChanged( } export function updateHOCHostEl( - { vnode, parent }: ComponentInternalInstance, + { vnode, parent, suspense }: ComponentInternalInstance, el: typeof vnode.el, // HostNode ): void { 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 @@ -466,4 +466,8 @@ export function updateHOCHostEl( break } } + // also update suspense vnode el + if (suspense && suspense.activeBranch === vnode) { + suspense.vnode.el = el + } }