From 44996d1a0a2de1bc6b3abfac6b2b8b3c969d4e01 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 31 May 2021 17:18:58 -0400 Subject: [PATCH] fix(suspense): fix suspense regression for errored template component fix #3857 --- .../__tests__/components/Suspense.spec.ts | 43 +++++++++++++++++++ packages/runtime-core/src/renderer.ts | 19 ++++---- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index bf209007d9e..4228bc8401c 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -632,6 +632,49 @@ describe('Suspense', () => { expect(serializeInner(root)).toBe(`
oops
`) }) + // #3857 + test('error handling w/ template optimization', async () => { + const Async = { + async setup() { + throw new Error('oops') + } + } + + const Comp = { + template: ` +
{{ errorMessage }}
+ +
+ +
+ +
+ `, + components: { Async }, + setup() { + const errorMessage = ref(null) + onErrorCaptured(err => { + errorMessage.value = + err instanceof Error + ? err.message + : `A non-Error value thrown: ${err}` + return false + }) + return { errorMessage } + } + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + expect(serializeInner(root)).toBe(`
fallback
`) + + await Promise.all(deps) + await nextTick() + expect(serializeInner(root)).toBe(`
oops
`) + }) + it('combined usage (nested async + nested suspense + multiple deps)', async () => { const msg = ref('nested msg') const calls: number[] = [] diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 36291a24f47..e190669951c 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1073,16 +1073,19 @@ function baseCreateRenderer( const newVNode = newChildren[i] // Determine the container (parent element) for the patch. const container = + // oldVNode may be an errored async setup() component inside Suspense + // which will not have a mounted element + oldVNode.el && // - In the case of a Fragment, we need to provide the actual parent // of the Fragment itself so it can move its children. - oldVNode.type === Fragment || - // - In the case of different nodes, there is going to be a replacement - // which also requires the correct parent container - !isSameVNodeType(oldVNode, newVNode) || - // - In the case of a component, it could contain anything. - oldVNode.shapeFlag & ShapeFlags.COMPONENT || - oldVNode.shapeFlag & ShapeFlags.TELEPORT - ? hostParentNode(oldVNode.el!)! + (oldVNode.type === Fragment || + // - In the case of different nodes, there is going to be a replacement + // which also requires the correct parent container + !isSameVNodeType(oldVNode, newVNode) || + // - In the case of a component, it could contain anything. + oldVNode.shapeFlag & ShapeFlags.COMPONENT || + oldVNode.shapeFlag & ShapeFlags.TELEPORT) + ? hostParentNode(oldVNode.el)! : // In other cases, the parent container is not actually used so we // just pass the block element here to avoid a DOM parentNode call. fallbackContainer