Jelajahi Sumber

fix(runtime-vapor): preserve fallback carrier order with fragment anchors

daiwei 1 Minggu lalu
induk
melakukan
4a6e016c37

+ 35 - 1
packages/runtime-vapor/__tests__/componentSlots.spec.ts

@@ -36,7 +36,7 @@ import {
 import { makeRender } from './_utils'
 import type { DynamicSlot } from '../src/componentSlots'
 import { setElementText, setText } from '../src/dom/prop'
-import { isValidBlock } from '../src/block'
+import { type Block, isValidBlock } from '../src/block'
 import { hydrateNode, setCurrentHydrationNode } from '../src/dom/hydration'
 import {
   DynamicFragment,
@@ -533,6 +533,40 @@ describe('component: slots', () => {
       expect(container.innerHTML).toBe('abxy!<!--slot-->')
     })
 
+    test('slot fallback controller re-syncs carrier order when fallback ends with a fragment anchor', async () => {
+      const container = document.createElement('div')
+      const carrierA = document.createTextNode('x')
+      const carrierB = document.createTextNode('y')
+      const marker = document.createTextNode('!')
+      const slotAnchor = document.createComment('slot')
+      const trailingFragment = new DynamicFragment('if', false, false)
+      trailingFragment.update(() => document.createTextNode('b'))
+      const fallback = new VaporFragment<Block>([
+        document.createTextNode('a'),
+        trailingFragment,
+      ])
+      const controller = new SlotFallbackController({
+        getParentBoundary: () => null,
+        getLocalFallback: () => () => fallback,
+        getContent: () => [carrierA, carrierB],
+        getParentNode: () => container,
+        getAnchor: () => slotAnchor,
+        runWithRenderCtx: fn => fn(),
+        isContentValid: () => false,
+        onValidityChange: vi.fn(),
+      })
+
+      container.append(carrierA, marker, carrierB, slotAnchor)
+      controller.recheck()
+
+      expect(container.innerHTML).toBe('ab<!--if-->x!y<!--slot-->')
+
+      controller.syncActiveFallback()
+      await nextTick()
+
+      expect(container.innerHTML).toBe('ab<!--if-->xy!<!--slot-->')
+    })
+
     test('slot fallback controller defaults to idle when isBusy is omitted', () => {
       const fallback = document.createTextNode('fallback')
       const controller = new SlotFallbackController({

+ 3 - 6
packages/runtime-vapor/src/fragment.ts

@@ -982,12 +982,9 @@ export class SlotFallbackController {
     }
 
     const carrierNodes = collectBlockNodes(this.host.getContent(), [], true)
-    const lastNode = block.nodes[block.nodes.length - 1]
-    if (
-      !carrierNodes.length ||
-      !(lastNode instanceof Node) ||
-      lastNode instanceof Comment
-    ) {
+    const fallbackNodes = collectBlockNodes(block, [], true)
+    const lastNode = fallbackNodes[fallbackNodes.length - 1]
+    if (!carrierNodes.length || !lastNode) {
       return
     }