diff --git a/packages/runtime-core/__tests__/rendererSuspense.spec.ts b/packages/runtime-core/__tests__/rendererSuspense.spec.ts index b81d3844..5470d56a 100644 --- a/packages/runtime-core/__tests__/rendererSuspense.spec.ts +++ b/packages/runtime-core/__tests__/rendererSuspense.spec.ts @@ -35,51 +35,6 @@ describe('renderer: suspense', () => { } } - it('basic usage (nested + multiple deps)', async () => { - const msg = ref('hello') - - const AsyncChild = createAsyncComponent({ - setup(props: { msg: string }) { - return () => h('div', props.msg) - } - }) - - const AsyncChild2 = createAsyncComponent( - { - setup(props: { msg: string }) { - return () => h('div', props.msg) - } - }, - 10 - ) - - const Mid = { - setup() { - return () => - h(AsyncChild, { - msg: msg.value - }) - } - } - - const Comp = { - setup() { - return () => - h(Suspense, [msg.value, h(Mid), h(AsyncChild2, { msg: 'child 2' })]) - } - } - - const root = nodeOps.createElement('div') - render(h(Comp), root) - expect(serializeInner(root)).toBe(``) - - await Promise.all(deps) - await nextTick() - expect(serializeInner(root)).toBe( - `hello
hello
child 2
` - ) - }) - test('fallback content', async () => { const Async = createAsyncComponent({ render() { @@ -578,6 +533,104 @@ describe('renderer: suspense', () => { expect(serializeInner(root)).toBe(`
oops
`) }) + it('combined usage (nested async + nested suspense + multiple deps)', async () => { + const msg = ref('nested msg') + + const AsyncChildWithSuspense = createAsyncComponent({ + setup(props: { msg: string }) { + return () => + h(Suspense, null, { + default: h(AsyncInsideNestedSuspense, { msg: props.msg }), + fallback: h('div', 'nested fallback') + }) + } + }) + + const AsyncInsideNestedSuspense = createAsyncComponent( + { + setup(props: { msg: string }) { + return () => h('div', props.msg) + } + }, + 20 + ) + + const AsyncChildParent = createAsyncComponent({ + setup(props: { msg: string }) { + return () => h(NestedAsyncChild, { msg: props.msg }) + } + }) + + const NestedAsyncChild = createAsyncComponent( + { + setup(props: { msg: string }) { + return () => h('div', props.msg) + } + }, + 10 + ) + + const MiddleComponent = { + setup() { + return () => + h(AsyncChildWithSuspense, { + msg: msg.value + }) + } + } + + const Comp = { + setup() { + return () => + h(Suspense, null, { + default: [ + h(MiddleComponent), + h(AsyncChildParent, { + msg: 'root async' + }) + ], + fallback: h('div', 'root fallback') + }) + } + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + expect(serializeInner(root)).toBe(`
root fallback
`) + + /** + * + * + * + * (0) + * + * (2) + * (1) + * (3) + */ + + // both top level async deps resolved, but there is another nested dep + // so should still be in fallback state + await Promise.all([deps[0], deps[1]]) + await nextTick() + expect(serializeInner(root)).toBe(`
root fallback
`) + + // root suspense all deps resolved. should show root content now + // with nested suspense showing fallback content + await deps[3] + await nextTick() + expect(serializeInner(root)).toBe( + `
nested fallback
root async
` + ) + + // all deps resolved, nested suspense should resolve now + await Promise.all(deps) + await nextTick() + expect(serializeInner(root)).toBe( + `
nested msg
root async
` + ) + }) + test.todo('new async dep after resolve should cause suspense to restart') test.todo('portal inside suspense') diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 43040e7a..73488df3 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -846,11 +846,16 @@ export function createRenderer< effects, vnode, parentComponent, - container, - anchor + container } = suspense + + // this is initial anchor on mount + let { anchor } = suspense // unmount fallback tree if (fallbackTree.el) { + // if the fallback tree was mounted, it may have been moved + // as part of a parent suspense. get the latest anchor for insertion + anchor = getNextHostNode(fallbackTree) unmount(fallbackTree as HostVNode, parentComponent, suspense, true) } // move content from off-dom container to actual container