]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix: vapor transition multiple chilren check
authordaiwei <daiwei521@126.com>
Sat, 8 Mar 2025 01:49:38 +0000 (09:49 +0800)
committerdaiwei <daiwei521@126.com>
Sat, 8 Mar 2025 01:49:38 +0000 (09:49 +0800)
packages/compiler-dom/src/transforms/Transition.ts
packages/compiler-vapor/__tests__/transforms/TransformTransition.spec.ts
packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap
packages/compiler-vapor/src/transforms/transformTransition.ts

index 85f83adc751805e2b6d1d16845edd36c49f18ebf..30ea083d8fcad649bb3db63947ba85299669eb67 100644 (file)
@@ -24,6 +24,9 @@ export const transformTransition: NodeTransform = (node, context) => {
 export function postTransformTransition(
   node: ComponentNode,
   onError: (error: CompilerError) => void,
+  hasMultipleChildren: (
+    node: ComponentNode,
+  ) => boolean = defaultHasMultipleChildren,
 ): () => void {
   return () => {
     if (!node.children.length) {
@@ -59,7 +62,9 @@ export function postTransformTransition(
   }
 }
 
-function hasMultipleChildren(node: ComponentNode | IfBranchNode): boolean {
+function defaultHasMultipleChildren(
+  node: ComponentNode | IfBranchNode,
+): boolean {
   // #1352 filter out potential comment nodes.
   const children = (node.children = node.children.filter(
     c =>
@@ -70,6 +75,7 @@ function hasMultipleChildren(node: ComponentNode | IfBranchNode): boolean {
   return (
     children.length !== 1 ||
     child.type === NodeTypes.FOR ||
-    (child.type === NodeTypes.IF && child.branches.some(hasMultipleChildren))
+    (child.type === NodeTypes.IF &&
+      child.branches.some(defaultHasMultipleChildren))
   )
 }
index 846179f879d56e408a34662f5adbb9a99e82f936..7c86b9f37c39e3595c910293bb87ab1b156dc02f 100644 (file)
@@ -57,21 +57,132 @@ describe('compiler: transition', () => {
     expect(code).contains('n0.$key = _ctx.key')
   })
 
-  test('warns if multiple children', () => {
+  function checkWarning(template: string, shouldWarn = true) {
     const onError = vi.fn()
-    compileWithElementTransform(
+    compileWithElementTransform(template, { onError })
+    if (shouldWarn) {
+      expect(onError).toHaveBeenCalled()
+      expect(onError.mock.calls).toMatchObject([
+        [{ code: DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN }],
+      ])
+    } else {
+      expect(onError).not.toHaveBeenCalled()
+    }
+  }
+
+  test('warns if multiple children', () => {
+    checkWarning(
       `<Transition>
         <h1>foo</h1>
         <h2>bar</h2>
       </Transition>`,
-      {
-        onError,
-      },
+      true,
+    )
+  })
+
+  test('warns with v-for', () => {
+    checkWarning(
+      `
+      <transition>
+        <div v-for="i in items">hey</div>
+      </transition>
+      `,
+      true,
+    )
+  })
+
+  test('warns with multiple v-if + v-for', () => {
+    checkWarning(
+      `
+      <transition>
+        <div v-if="a" v-for="i in items">hey</div>
+        <div v-else v-for="i in items">hey</div>
+      </transition>
+      `,
+      true,
+    )
+  })
+
+  test('warns with template v-if', () => {
+    checkWarning(
+      `
+      <transition>
+        <template v-if="ok"></template>
+      </transition>
+      `,
+      true,
+    )
+  })
+
+  test('warns with multiple templates', () => {
+    checkWarning(
+      `
+      <transition>
+        <template v-if="a"></template>
+        <template v-else></template>
+      </transition>
+      `,
+      true,
+    )
+  })
+
+  test('warns if multiple children with v-if', () => {
+    checkWarning(
+      `
+      <transition>
+        <div v-if="one">hey</div>
+        <div v-if="other">hey</div>
+      </transition>
+      `,
+      true,
+    )
+  })
+
+  test('does not warn with regular element', () => {
+    checkWarning(
+      `
+      <transition>
+        <div>hey</div>
+      </transition>
+      `,
+      false,
+    )
+  })
+
+  test('does not warn with one single v-if', () => {
+    checkWarning(
+      `
+      <transition>
+        <div v-if="a">hey</div>
+      </transition>
+      `,
+      false,
+    )
+  })
+
+  test('does not warn with v-if v-else-if v-else', () => {
+    checkWarning(
+      `
+      <transition>
+        <div v-if="a">hey</div>
+        <div v-else-if="b">hey</div>
+        <div v-else>hey</div>
+      </transition>
+      `,
+      false,
+    )
+  })
+
+  test('does not warn with v-if v-else', () => {
+    checkWarning(
+      `
+      <transition>
+        <div v-if="a">hey</div>
+        <div v-else>hey</div>
+      </transition>
+      `,
+      false,
     )
-    expect(onError).toHaveBeenCalled()
-    expect(onError.mock.calls).toMatchObject([
-      [{ code: DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN }],
-    ])
   })
 
   test('inject persisted when child has v-show', () => {
@@ -84,5 +195,21 @@ describe('compiler: transition', () => {
     ).toMatchSnapshot()
   })
 
-  // TODO more tests
+  test('the v-if/else-if/else branches in Transition should ignore comments', () => {
+    expect(
+      compileWithElementTransform(`
+    <transition>
+      <div v-if="a">hey</div>
+      <!-- this should be ignored -->
+      <div v-else-if="b">hey</div>
+      <!-- this should be ignored -->
+      <div v-else>
+        <p v-if="c"/>
+        <!-- this should not be ignored -->
+        <p v-else/>
+      </div>
+    </transition>
+    `).code,
+    ).toMatchSnapshot()
+  })
 })
index 4264677c7c407e00c20f7b83053f1ddb75623d15..a1de229f5c2d634e7f4769cb1e2b72f89376a9ab 100644 (file)
@@ -35,6 +35,43 @@ export function render(_ctx) {
 }"
 `;
 
+exports[`compiler: transition > the v-if/else-if/else branches in Transition should ignore comments 1`] = `
+"import { VaporTransition as _VaporTransition, createIf as _createIf, prepend as _prepend, createComponent as _createComponent, template as _template } from 'vue';
+const t0 = _template("<div>hey</div>")
+const t1 = _template("<p></p>")
+const t2 = _template("<div></div>")
+
+export function render(_ctx) {
+  const n16 = _createComponent(_VaporTransition, null, {
+    "default": () => {
+      const n0 = _createIf(() => (_ctx.a), () => {
+        const n2 = t0()
+        n2.$key = 2
+        return n2
+      }, () => _createIf(() => (_ctx.b), () => {
+        const n5 = t0()
+        n5.$key = 5
+        return n5
+      }, () => {
+        const n14 = t2()
+        const n9 = _createIf(() => (_ctx.c), () => {
+          const n11 = t1()
+          return n11
+        }, () => {
+          const n13 = t1()
+          return n13
+        })
+        _prepend(n14, n9)
+        n14.$key = 14
+        return n14
+      }))
+      return [n0, n3, n7]
+    }
+  }, true)
+  return n16
+}"
+`;
+
 exports[`compiler: transition > work with dynamic keyed children 1`] = `
 "import { VaporTransition as _VaporTransition, createKeyedFragment as _createKeyedFragment, createComponent as _createComponent, template as _template } from 'vue';
 const t0 = _template("<h1>foo</h1>")
index 918666decfeeed9dd09264ed524b00f78a4c1cab..857e3bcdf2cc47c9ed5cff35548fa7d84b694be5 100644 (file)
@@ -1,7 +1,12 @@
 import type { NodeTransform } from '@vue/compiler-vapor'
-import { ElementTypes, NodeTypes } from '@vue/compiler-core'
-import { isTransitionTag } from '../utils'
-import { postTransformTransition } from '@vue/compiler-dom'
+import { findDir, isTransitionTag } from '../utils'
+import {
+  type ElementNode,
+  ElementTypes,
+  NodeTypes,
+  isTemplateNode,
+  postTransformTransition,
+} from '@vue/compiler-dom'
 
 export const transformTransition: NodeTransform = (node, context) => {
   if (
@@ -9,7 +14,56 @@ export const transformTransition: NodeTransform = (node, context) => {
     node.tagType === ElementTypes.COMPONENT
   ) {
     if (isTransitionTag(node.tag)) {
-      return postTransformTransition(node, context.options.onError)
+      return postTransformTransition(
+        node,
+        context.options.onError,
+        hasMultipleChildren,
+      )
     }
   }
 }
+
+function hasMultipleChildren(node: ElementNode): boolean {
+  const children = (node.children = node.children.filter(
+    c =>
+      c.type !== NodeTypes.COMMENT &&
+      !(c.type === NodeTypes.TEXT && !c.content.trim()),
+  ))
+
+  const first = children[0]
+
+  // template
+  if (first && isTemplateNode(first)) {
+    return true
+  }
+
+  // has v-for
+  if (
+    children.length === 1 &&
+    first.type === NodeTypes.ELEMENT &&
+    findDir(first, 'for')
+  ) {
+    return true
+  }
+
+  const hasElse = (node: ElementNode) =>
+    findDir(node, 'else-if') || findDir(node, 'else', true)
+
+  // has v-if/v-else-if/v-else
+  if (
+    children.length > 1 &&
+    children.every(
+      (c, index) =>
+        c.type === NodeTypes.ELEMENT &&
+        // not has v-for
+        !findDir(c, 'for') &&
+        // if the first child has v-if, the rest should also have v-else-if/v-else
+        (index === 0 ? findDir(c, 'if') : hasElse(c)) &&
+        !hasMultipleChildren(c),
+    )
+  ) {
+    return false
+  }
+
+  return children.length > 1
+}