]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-vapor): render slot fallback if slot content is not a valid block
authordaiwei <daiwei521@126.com>
Mon, 21 Jul 2025 00:29:31 +0000 (08:29 +0800)
committerdaiwei <daiwei521@126.com>
Mon, 21 Jul 2025 12:47:54 +0000 (20:47 +0800)
close #13668

packages/runtime-vapor/__tests__/componentSlots.spec.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/componentSlots.ts

index 58076fff9eea213c8180d87ef5ed089951560798..f54137724ccbc60b099823bba234e7c51e14f89b 100644 (file)
@@ -502,5 +502,64 @@ describe('component: slots', () => {
       await nextTick()
       expect(host.innerHTML).toBe('<div><h1></h1><!--slot--></div>')
     })
+
+    test('render fallback when slot content is not valid', async () => {
+      const Child = {
+        setup() {
+          return createSlot('default', null, () =>
+            document.createTextNode('fallback'),
+          )
+        },
+      }
+
+      const { html } = define({
+        setup() {
+          return createComponent(Child, null, {
+            default: () => {
+              return template('<!--comment-->')()
+            },
+          })
+        },
+      }).render()
+
+      expect(html()).toBe('fallback<!--slot-->')
+    })
+
+    test('render fallback when v-if condition is false', async () => {
+      const Child = {
+        setup() {
+          return createSlot('default', null, () =>
+            document.createTextNode('fallback'),
+          )
+        },
+      }
+
+      const toggle = ref(false)
+
+      const { html } = define({
+        setup() {
+          return createComponent(Child, null, {
+            default: () => {
+              return createIf(
+                () => toggle.value,
+                () => {
+                  return document.createTextNode('content')
+                },
+              )
+            },
+          })
+        },
+      }).render()
+
+      expect(html()).toBe('fallback<!--if--><!--slot-->')
+
+      toggle.value = true
+      await nextTick()
+      expect(html()).toBe('content<!--if--><!--slot-->')
+
+      toggle.value = false
+      await nextTick()
+      expect(html()).toBe('fallback<!--if--><!--slot-->')
+    })
   })
 })
index e021ce84b051432f5bda391d9459f04453ac9a13..50a453dc483a21e6dca96c0c7961997f770f995e 100644 (file)
@@ -67,9 +67,15 @@ export class DynamicFragment extends VaporFragment {
 
     if (this.fallback && !isValidBlock(this.nodes)) {
       parent && remove(this.nodes, parent)
-      this.nodes =
-        (this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
-        []
+      // if current nodes is a DynamicFragment, call its update with the fallback
+      // to handle nested dynamic fragment
+      if (this.nodes instanceof DynamicFragment) {
+        this.nodes.update(this.fallback)
+      } else {
+        this.nodes =
+          (this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
+          []
+      }
       parent && insert(this.nodes, parent, this.anchor)
     }
 
index 100c99cdb8af19f17437e323fbe19806c9769c97..4215548f2e6504f23d8dd11bb1a14c19770630ce 100644 (file)
@@ -126,6 +126,7 @@ export function createSlot(
     const renderSlot = () => {
       const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
       if (slot) {
+        fragment.fallback = fallback
         // create and cache bound version of the slot to make it stable
         // so that we avoid unnecessary updates if it resolves to the same slot
         fragment.update(