]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: add tests edison/fix/slotInsertionState 13558/head
authordaiwei <daiwei521@126.com>
Thu, 3 Jul 2025 03:22:08 +0000 (11:22 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 3 Jul 2025 05:39:37 +0000 (13:39 +0800)
packages/runtime-vapor/__tests__/componentSlots.spec.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/dom/prop.ts
packages/runtime-vapor/src/insertionState.ts

index 58076fff9eea213c8180d87ef5ed089951560798..ff7aa56d05529acd72b10b05d3c7878b1e5299a9 100644 (file)
@@ -10,6 +10,7 @@ import {
   insert,
   prepend,
   renderEffect,
+  setInsertionState,
   template,
 } from '../src'
 import { currentInstance, nextTick, ref } from '@vue/runtime-dom'
@@ -502,5 +503,35 @@ describe('component: slots', () => {
       await nextTick()
       expect(host.innerHTML).toBe('<div><h1></h1><!--slot--></div>')
     })
+
+    test('consecutive slots with insertion state', async () => {
+      const { component: Child } = define({
+        setup() {
+          const n2 = template('<div><div>baz</div></div>', true)() as any
+          setInsertionState(n2, 0)
+          createSlot('default', null)
+          setInsertionState(n2, 0)
+          createSlot('foo', null)
+          return n2
+        },
+      })
+
+      const { html } = define({
+        setup() {
+          return createComponent(Child, null, {
+            default: () => template('default')(),
+            foo: () => template('foo')(),
+          })
+        },
+      }).render()
+
+      expect(html()).toBe(
+        `<div>` +
+          `default<!--slot-->` +
+          `foo<!--slot-->` +
+          `<div>baz</div>` +
+          `</div>`,
+      )
+    })
   })
 })
index b782afd38d35b66c9fed33675d4f4705efbceb3f..943bda67ca50bb8eca62f11cf8fac172fd47d6df 100644 (file)
@@ -105,10 +105,10 @@ export function isValidBlock(block: Block): boolean {
 
 export function insert(
   block: Block,
-  parent: ParentNode,
+  parent: ParentNode & { $anchor?: Node | null },
   anchor: Node | null | 0 = null, // 0 means prepend
 ): void {
-  anchor = anchor === 0 ? parent.firstChild : anchor
+  anchor = anchor === 0 ? parent.$anchor || parent.firstChild : anchor
   if (block instanceof Node) {
     if (!isHydrating) {
       parent.insertBefore(block, anchor)
index 8c42ad766a51d46d942831a5d81d112db21295e6..3b663da77153ed360b80fd087171944fa8a9cb69 100644 (file)
@@ -269,7 +269,7 @@ export function optimizePropertyLookup(): void {
   if (isOptimized) return
   isOptimized = true
   const proto = Element.prototype as any
-  proto.$evtclick = undefined
+  proto.$anchor = proto.$evtclick = undefined
   proto.$root = false
   proto.$html =
     proto.$txt =
index c8c7ffbcd1de3b1000cbc9fa62f50ee9c8c3a0b3..8c66843bd93cdd067fbe0cda6b1d98d23ba75562 100644 (file)
@@ -6,7 +6,18 @@ export let insertionAnchor: Node | 0 | undefined
  * (component, slot outlet, if, for) is created. The state is used for actual
  * insertion on client-side render, and used for node adoption during hydration.
  */
-export function setInsertionState(parent: ParentNode, anchor?: Node | 0): void {
+export function setInsertionState(
+  parent: ParentNode & { $anchor?: Node | null },
+  anchor?: Node | 0,
+): void {
+  // When setInsertionState(n3, 0) is called consecutively, the first prepend operation
+  // uses parent.firstChild as the anchor. However, after insertion, parent.firstChild
+  // changes and cannot serve as the anchor for subsequent prepends. Therefore, we cache
+  // the original parent.firstChild on the first call for subsequent prepend operations.
+  if (anchor === 0 && !parent.$anchor) {
+    parent.$anchor = parent.firstChild
+  }
+
   insertionParent = parent
   insertionAnchor = anchor
 }