diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts
index fd1913b2c9c..a448972e139 100644
--- a/packages/runtime-core/__tests__/components/Suspense.spec.ts
+++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts
@@ -54,6 +54,18 @@ describe('Suspense', () => {
}
}
+ const RouterView = {
+ setup(_: any, { slots }: any) {
+ const route = inject('route') as any
+ const depth = inject('depth', 0)
+ provide('depth', depth + 1)
+ return () => {
+ const current = route.value[depth]
+ return slots.default({ Component: current })[0]
+ }
+ },
+ }
+
test('fallback content', async () => {
const Async = defineAsyncComponent({
render() {
@@ -1041,18 +1053,6 @@ describe('Suspense', () => {
// #10098
test('switching branches w/ nested suspense', async () => {
- const RouterView = {
- setup(_: any, { slots }: any) {
- const route = inject('route') as any
- const depth = inject('depth', 0)
- provide('depth', depth + 1)
- return () => {
- const current = route.value[depth]
- return slots.default({ Component: current })[0]
- }
- },
- }
-
const OuterB = defineAsyncComponent({
setup: () => {
return () =>
@@ -1132,6 +1132,121 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(`
innerA
`)
})
+ // #10415
+ test('nested suspense (w/ suspensible) switch several times before parent suspense resolve', async () => {
+ const OuterA = defineAsyncComponent({
+ setup: () => {
+ return () =>
+ h(RouterView, null, {
+ default: ({ Component }: any) => [
+ h(Suspense, null, {
+ default: () => h(Component),
+ }),
+ ],
+ })
+ },
+ })
+
+ const InnerA = defineAsyncComponent({
+ setup: () => {
+ return () => h('div', 'innerA')
+ },
+ })
+
+ const route = shallowRef([OuterA, InnerA])
+ const InnerB = defineAsyncComponent(
+ {
+ setup: () => {
+ return () => h('div', 'innerB')
+ },
+ },
+ 5,
+ )
+
+ const InnerB1 = defineAsyncComponent(
+ {
+ setup: () => {
+ return () => h('div', 'innerB1')
+ },
+ },
+ 5,
+ )
+
+ const InnerB2 = defineAsyncComponent(
+ {
+ setup: () => {
+ return () => h('div', 'innerB2')
+ },
+ },
+ 5,
+ )
+
+ const OuterB = defineAsyncComponent(
+ {
+ setup() {
+ nextTick(async () => {
+ await new Promise(resolve => setTimeout(resolve, 1))
+ route.value = [OuterB, InnerB1]
+ })
+
+ nextTick(async () => {
+ await new Promise(resolve => setTimeout(resolve, 1))
+ route.value = [OuterB, InnerB2]
+ })
+
+ return () =>
+ h(RouterView, null, {
+ default: ({ Component }: any) => [
+ h(
+ Suspense,
+ { suspensible: true },
+ {
+ default: () => h(Component),
+ },
+ ),
+ ],
+ })
+ },
+ },
+ 5,
+ )
+
+ const Comp = {
+ setup() {
+ provide('route', route)
+ return () =>
+ h(RouterView, null, {
+ default: ({ Component }: any) => [
+ h(Suspense, null, {
+ default: () => h(Component),
+ }),
+ ],
+ })
+ },
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+ await Promise.all(deps)
+ await nextTick()
+ expect(serializeInner(root)).toBe(``)
+
+ await Promise.all(deps)
+ await nextTick()
+ expect(serializeInner(root)).toBe(`innerA
`)
+
+ deps.length = 0
+
+ route.value = [OuterB, InnerB]
+ await nextTick()
+
+ await Promise.all(deps)
+ await Promise.all(deps)
+ await Promise.all(deps)
+ await nextTick()
+ expect(serializeInner(root)).toBe(`innerB2
`)
+ })
+
test('branch switch to 3rd branch before resolve', async () => {
const calls: string[] = []
diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts
index 65b05c3dd7c..9461d7fec97 100644
--- a/packages/runtime-core/src/components/Suspense.ts
+++ b/packages/runtime-core/src/components/Suspense.ts
@@ -99,7 +99,11 @@ export const SuspenseImpl = {
// 2. mounting along with the pendingBranch of parentSuspense
// it is necessary to skip the current patch to avoid multiple mounts
// of inner components.
- if (parentSuspense && parentSuspense.deps > 0) {
+ if (
+ parentSuspense &&
+ parentSuspense.deps > 0 &&
+ !n1.suspense!.isInFallback
+ ) {
n2.suspense = n1.suspense!
n2.suspense.vnode = n2
n2.el = n1.el