Переглянути джерело

fix(runtime-vapor): clean up thrown slot fallback scopes

daiwei 6 днів тому
батько
коміт
ff195edc42

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

@@ -394,6 +394,39 @@ describe('component: slots', () => {
       expect(controller.takePendingRecheck()).toBe(false)
     })
 
+    test('slot fallback controller stops fallback scope when fallback body throws', async () => {
+      const source = ref(0)
+      const effectRuns = vi.fn()
+      const cleanup = vi.fn()
+      const err = new Error('fallback boom')
+
+      const controller = new SlotFallbackController({
+        getParentBoundary: () => null,
+        getLocalFallback: () => () => {
+          onScopeDispose(cleanup)
+          renderEffect(() => {
+            effectRuns(source.value)
+          })
+          throw err
+        },
+        getContent: () => [],
+        getParentNode: () => null,
+        getAnchor: () => null,
+        runWithRenderCtx: fn => fn(),
+        onValidityChange: vi.fn(),
+      })
+
+      expect(() => controller.recheck()).toThrow(err)
+      expect(controller.getActiveFallback()).toBe(null)
+      expect(cleanup).toHaveBeenCalledTimes(1)
+      expect(effectRuns).toHaveBeenCalledTimes(1)
+
+      source.value++
+      await nextTick()
+
+      expect(effectRuns).toHaveBeenCalledTimes(1)
+    })
+
     test('slot fallback controller does not accumulate order-sync hooks', async () => {
       const fallback = new VaporFragment([
         document.createTextNode('a'),

+ 28 - 0
packages/runtime-vapor/__tests__/errorHandling.spec.ts

@@ -9,6 +9,7 @@ import {
 import {
   createComponent,
   createInvoker,
+  createSlot,
   createTemplateRefSetter,
   defineVaporComponent,
   delegateEvents,
@@ -216,6 +217,33 @@ describe('error handling', () => {
     expect(fn).toHaveBeenCalledWith(err, 'render function')
   })
 
+  test('in slot fallback body', () => {
+    const err = new Error('foo')
+    const fn = vi.fn()
+
+    const Comp: VaporComponent = {
+      setup() {
+        onErrorCaptured((err, instance, info) => {
+          fn(err, info)
+          return false
+        })
+        return createComponent(Child)
+      },
+    }
+
+    const Child = defineVaporComponent({
+      setup() {
+        return createSlot('default', null, () => {
+          throw err
+        })
+      },
+    })
+
+    define(Comp).render()
+    expect(fn).toHaveBeenCalledWith(err, 'setup function')
+    expect(`returned non-block value`).toHaveBeenWarned()
+  })
+
   test('in function ref', () => {
     const err = new Error('foo')
     const ref = () => {

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

@@ -976,6 +976,9 @@ export class SlotFallbackController {
         this.host.runWithRenderCtx(
           () => scope.run(() => renderSlotFallback(this.boundary)) || undefined,
         ) || undefined
+    } catch (err) {
+      scope.stop()
+      throw err
     } finally {
       this.isRenderingFallback = false
     }