]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(suspense): handle suspense switching with nested suspense (#10184)
authoredison <daiwei521@126.com>
Sun, 25 Feb 2024 12:22:12 +0000 (20:22 +0800)
committerGitHub <noreply@github.com>
Sun, 25 Feb 2024 12:22:12 +0000 (20:22 +0800)
close #10098

packages/runtime-core/__tests__/components/Suspense.spec.ts
packages/runtime-core/src/components/Suspense.ts

index 928da872faf4d56f01a94598364f20391af43357..fd1913b2c9c307c6746defacf9caf1b8541cf441 100644 (file)
@@ -22,7 +22,7 @@ import {
   watch,
   watchEffect,
 } from '@vue/runtime-test'
-import { createApp, defineComponent } from 'vue'
+import { computed, createApp, defineComponent, inject, provide } from 'vue'
 import type { RawSlots } from 'packages/runtime-core/src/componentSlots'
 import { resetSuspenseId } from '../../src/components/Suspense'
 
@@ -1039,6 +1039,99 @@ describe('Suspense', () => {
     expect(serializeInner(root)).toBe(`<div>foo<div>foo nested</div></div>`)
   })
 
+  // #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 () =>
+          h(RouterView, null, {
+            default: ({ Component }: any) => [
+              h(Suspense, null, {
+                default: () => h(Component),
+              }),
+            ],
+          })
+      },
+    })
+
+    const InnerB = defineAsyncComponent({
+      setup: () => {
+        return () => h('div', 'innerB')
+      },
+    })
+
+    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 toggle = ref(true)
+    const route = computed(() => {
+      return toggle.value ? [OuterA, InnerA] : [OuterB, InnerB]
+    })
+
+    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(`<div>innerA</div>`)
+
+    deps.length = 0
+
+    toggle.value = false
+    await nextTick()
+    // toggle again
+    toggle.value = true
+
+    await Promise.all(deps)
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div>innerA</div>`)
+  })
+
   test('branch switch to 3rd branch before resolve', async () => {
     const calls: string[] = []
 
index 9b3d6765d9c0b305b32cd32303786634172aa919..65b05c3dd7c1a3f9a0ad7dc3b67d1ac51be84415 100644 (file)
@@ -100,7 +100,9 @@ export const SuspenseImpl = {
       // it is necessary to skip the current patch to avoid multiple mounts
       // of inner components.
       if (parentSuspense && parentSuspense.deps > 0) {
-        n2.suspense = n1.suspense
+        n2.suspense = n1.suspense!
+        n2.suspense.vnode = n2
+        n2.el = n1.el
         return
       }
       patchSuspense(