]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(suspense): handle edge case in patching list nodes within Suspense (#13306)
authoredison <daiwei521@126.com>
Fri, 16 May 2025 00:32:55 +0000 (08:32 +0800)
committerGitHub <noreply@github.com>
Fri, 16 May 2025 00:32:55 +0000 (08:32 +0800)
close #13305

packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
packages/runtime-core/src/renderer.ts

index 958c1274806c6b93563ffee36355282b98e62fa2..1d8bb84205bf835217806ca8a7b853baba9cbaeb 100644 (file)
@@ -861,6 +861,114 @@ describe('renderer: optimized mode', () => {
     expect(inner(root)).toBe('<div><div>true</div></div>')
   })
 
+  // #13305
+  test('patch Suspense nested in list nodes in optimized mode', async () => {
+    const deps: Promise<any>[] = []
+
+    const Item = {
+      props: {
+        someId: { type: Number, required: true },
+      },
+      async setup(props: any) {
+        const p = new Promise(resolve => setTimeout(resolve, 1))
+        deps.push(p)
+
+        await p
+        return () => (
+          openBlock(),
+          createElementBlock('li', null, [
+            createElementVNode(
+              'p',
+              null,
+              String(props.someId),
+              PatchFlags.TEXT,
+            ),
+          ])
+        )
+      },
+    }
+
+    const list = ref([1, 2, 3])
+    const App = {
+      setup() {
+        return () => (
+          openBlock(),
+          createElementBlock(
+            Fragment,
+            null,
+            [
+              createElementVNode(
+                'p',
+                null,
+                JSON.stringify(list.value),
+                PatchFlags.TEXT,
+              ),
+              createElementVNode('ol', null, [
+                (openBlock(),
+                createBlock(SuspenseImpl, null, {
+                  fallback: withCtx(() => [
+                    createElementVNode('li', null, 'Loading…'),
+                  ]),
+                  default: withCtx(() => [
+                    (openBlock(true),
+                    createElementBlock(
+                      Fragment,
+                      null,
+                      renderList(list.value, id => {
+                        return (
+                          openBlock(),
+                          createBlock(
+                            Item,
+                            {
+                              key: id,
+                              'some-id': id,
+                            },
+                            null,
+                            PatchFlags.PROPS,
+                            ['some-id'],
+                          )
+                        )
+                      }),
+                      PatchFlags.KEYED_FRAGMENT,
+                    )),
+                  ]),
+                  _: 1 /* STABLE */,
+                })),
+              ]),
+            ],
+            PatchFlags.STABLE_FRAGMENT,
+          )
+        )
+      },
+    }
+
+    const app = createApp(App)
+    app.mount(root)
+    expect(inner(root)).toBe(`<p>[1,2,3]</p>` + `<ol><li>Loading…</li></ol>`)
+
+    await Promise.all(deps)
+    await nextTick()
+    expect(inner(root)).toBe(
+      `<p>[1,2,3]</p>` +
+        `<ol>` +
+        `<li><p>1</p></li>` +
+        `<li><p>2</p></li>` +
+        `<li><p>3</p></li>` +
+        `</ol>`,
+    )
+
+    list.value = [3, 1, 2]
+    await nextTick()
+    expect(inner(root)).toBe(
+      `<p>[3,1,2]</p>` +
+        `<ol>` +
+        `<li><p>3</p></li>` +
+        `<li><p>1</p></li>` +
+        `<li><p>2</p></li>` +
+        `</ol>`,
+    )
+  })
+
   // #4183
   test('should not take unmount children fast path /w Suspense', async () => {
     const show = ref(true)
index 334858b7b3b3395c809a0ac9159233b4d47e847d..7b39aa917a25f7bb26ea98c349d61c4c4265aaac 100644 (file)
@@ -961,7 +961,8 @@ function baseCreateRenderer(
           // which also requires the correct parent container
           !isSameVNodeType(oldVNode, newVNode) ||
           // - In the case of a component, it could contain anything.
-          oldVNode.shapeFlag & (ShapeFlags.COMPONENT | ShapeFlags.TELEPORT))
+          oldVNode.shapeFlag &
+            (ShapeFlags.COMPONENT | ShapeFlags.TELEPORT | ShapeFlags.SUSPENSE))
           ? 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.