Przeglądaj źródła

fix(runtime-vapor): isolate hydrating slot fallback state

daiwei 1 tydzień temu
rodzic
commit
fbba1abe9e

+ 42 - 0
packages/runtime-vapor/__tests__/componentSlots.spec.ts

@@ -37,6 +37,7 @@ import { makeRender } from './_utils'
 import type { DynamicSlot } from '../src/componentSlots'
 import { setElementText, setText } from '../src/dom/prop'
 import { isValidBlock } from '../src/block'
+import { hydrateNode, setCurrentHydrationNode } from '../src/dom/hydration'
 import {
   DynamicFragment,
   ForFragment,
@@ -45,7 +46,11 @@ import {
   SlotFragment,
   VaporFragment,
   getCurrentSlotBoundary,
+  getCurrentSlotEndAnchor,
+  isHydratingSlotFallbackActive,
   trackSlotBoundaryDirtying,
+  withHydratingSlotBoundary,
+  withHydratingSlotFallbackActive,
   withOwnedSlotBoundary,
   withSlotFallbackBoundary,
 } from '../src/fragment'
@@ -394,6 +399,43 @@ describe('component: slots', () => {
       expect(controller.takePendingRecheck()).toBe(false)
     })
 
+    test('withHydratingSlotBoundary isolates fallback-active state between boundaries without local markers', () => {
+      const start = document.createComment('[')
+      const end = document.createComment(']')
+      const host = document.createElement('div')
+      host.append(start, end)
+
+      hydrateNode(start, () => {
+        withHydratingSlotBoundary(() => {
+          const outerEnd = getCurrentSlotEndAnchor()
+          expect(outerEnd).toBe(end)
+
+          setCurrentHydrationNode(end)
+
+          withHydratingSlotBoundary(() => {
+            expect(getCurrentSlotEndAnchor()).toBe(end)
+            expect(isHydratingSlotFallbackActive()).toBe(false)
+
+            withHydratingSlotFallbackActive(() => {
+              expect(isHydratingSlotFallbackActive()).toBe(true)
+
+              setCurrentHydrationNode(end)
+              withHydratingSlotBoundary(() => {
+                expect(getCurrentSlotEndAnchor()).toBe(end)
+                expect(isHydratingSlotFallbackActive()).toBe(false)
+              })
+            })
+
+            expect(getCurrentSlotEndAnchor()).toBe(end)
+            expect(isHydratingSlotFallbackActive()).toBe(false)
+          })
+
+          expect(getCurrentSlotEndAnchor()).toBe(end)
+          expect(isHydratingSlotFallbackActive()).toBe(false)
+        })
+      })
+    })
+
     test('slot fallback controller stops fallback scope when fallback body throws', async () => {
       const source = ref(0)
       const effectRuns = vi.fn()

+ 7 - 11
packages/runtime-vapor/src/fragment.ts

@@ -1087,28 +1087,24 @@ export function getCurrentSlotEndAnchor(): Node | null {
 }
 
 export function withHydratingSlotBoundary<R>(fn: () => R): R {
-  let prevState: HydratingSlotBoundaryState | null = null
-  let pushedBoundaryState = false
+  let endAnchor = getCurrentSlotEndAnchor()
   let exitHydrationBoundary: (() => void) | undefined
 
   locateHydrationNode()
   if (isComment(currentHydrationNode!, '[')) {
-    const endAnchor = locateEndAnchor(currentHydrationNode)
+    endAnchor = locateEndAnchor(currentHydrationNode)
     setCurrentHydrationNode(currentHydrationNode.nextSibling)
-    prevState = setCurrentHydratingSlotBoundaryState({
-      endAnchor,
-      fallbackActive: false,
-    })
-    pushedBoundaryState = true
     exitHydrationBoundary = enterHydrationBoundary(endAnchor)
   }
+  const prevState = setCurrentHydratingSlotBoundaryState({
+    endAnchor,
+    fallbackActive: false,
+  })
 
   try {
     return fn()
   } finally {
-    if (pushedBoundaryState) {
-      setCurrentHydratingSlotBoundaryState(prevState)
-    }
+    setCurrentHydratingSlotBoundaryState(prevState)
     exitHydrationBoundary && exitHydrationBoundary()
   }
 }