]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(compiler): tests for vIf codegen w/ blocks optimization
authorEvan You <yyx990803@gmail.com>
Tue, 1 Oct 2019 19:04:58 +0000 (15:04 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 1 Oct 2019 19:05:08 +0000 (15:05 -0400)
packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
packages/compiler-core/__tests__/testUtils.ts [new file with mode: 0644]
packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap [new file with mode: 0644]
packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/__tests__/transforms/vIf.spec.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/transforms/vIf.ts

index 2bd766517055f6d9bb445b59d69477726b9373c7..359a881ddca59c6e6d9675f960c9e60140951122 100644 (file)
@@ -5,7 +5,7 @@ exports[`compiler: integration tests function mode 1`] = `
 
 return function render() {
   with (this) {
-    const { createVNode: _createVNode, toString: _toString, openBlock: _openBlock, applyDirectives: _applyDirectives, createBlock: _createBlock, Empty: _Empty, Fragment: _Fragment, renderList: _renderList } = _Vue
+    const { createVNode: _createVNode, toString: _toString, openBlock: _openBlock, createBlock: _createBlock, Empty: _Empty, Fragment: _Fragment, renderList: _renderList } = _Vue
     
     return _createVNode(\\"div\\", {
       id: \\"foo\\",
@@ -13,16 +13,8 @@ return function render() {
     }, [
       _toString(world.burn()),
       (_openBlock(), ok
-        ? _createBlock(
-            \\"div\\",
-            { key: 0 },
-            \\"yes\\"
-          )
-        : _createBlock(
-            _Fragment,
-            { key: 1 },
-            \\"no\\"
-          )),
+        ? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
+        : _createBlock(_Fragment, { key: 1 }, \\"no\\")),
       _renderList(list, (value, index) => {
         return _createVNode(\\"div\\", null, [
           _createVNode(\\"span\\", null, _toString(value + index))
@@ -34,7 +26,7 @@ return function render() {
 `;
 
 exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
-"const { createVNode, toString, openBlock, applyDirectives, createBlock, Empty, Fragment, renderList } = Vue
+"const { createVNode, toString, openBlock, createBlock, Empty, Fragment, renderList } = Vue
 
 return function render() {
   const _ctx = this
@@ -44,16 +36,8 @@ return function render() {
   }, [
     toString(_ctx.world.burn()),
     (openBlock(), (_ctx.ok)
-      ? createBlock(
-          \\"div\\",
-          { key: 0 },
-          \\"yes\\"
-        )
-      : createBlock(
-          Fragment,
-          { key: 1 },
-          \\"no\\"
-        )),
+      ? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
+      : createBlock(Fragment, { key: 1 }, \\"no\\")),
     renderList(_ctx.list, (value, index) => {
       return createVNode(\\"div\\", null, [
         createVNode(\\"span\\", null, toString(value + index))
@@ -64,7 +48,7 @@ return function render() {
 `;
 
 exports[`compiler: integration tests module mode 1`] = `
-"import { createVNode, toString, openBlock, applyDirectives, createBlock, Empty, Fragment, renderList } from \\"vue\\"
+"import { createVNode, toString, openBlock, createBlock, Empty, Fragment, renderList } from \\"vue\\"
 
 export default function render() {
   const _ctx = this
@@ -74,16 +58,8 @@ export default function render() {
   }, [
     _toString(_ctx.world.burn()),
     (openBlock(), (_ctx.ok)
-      ? createBlock(
-          \\"div\\",
-          { key: 0 },
-          \\"yes\\"
-        )
-      : createBlock(
-          Fragment,
-          { key: 1 },
-          \\"no\\"
-        )),
+      ? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
+      : createBlock(Fragment, { key: 1 }, \\"no\\")),
     _renderList(_ctx.list, (value, index) => {
       return createVNode(\\"div\\", null, [
         createVNode(\\"span\\", null, _toString(value + index))
diff --git a/packages/compiler-core/__tests__/testUtils.ts b/packages/compiler-core/__tests__/testUtils.ts
new file mode 100644 (file)
index 0000000..8fd6222
--- /dev/null
@@ -0,0 +1,28 @@
+import { NodeTypes } from '../src'
+
+const leadingBracketRE = /^\[/
+const bracketsRE = /^\[|\]$/g
+
+// Create a matcher for an object
+// where non-static expressions should be wrapped in []
+// e.g.
+// - createObjectMatcher({ 'foo': '[bar]' }) matches { foo: bar }
+// - createObjectMatcher({ '[foo]': 'bar' }) matches { [foo]: "bar" }
+export function createObjectMatcher(obj: any) {
+  return {
+    type: NodeTypes.JS_OBJECT_EXPRESSION,
+    properties: Object.keys(obj).map(key => ({
+      type: NodeTypes.JS_PROPERTY,
+      key: {
+        type: NodeTypes.SIMPLE_EXPRESSION,
+        content: key.replace(bracketsRE, ''),
+        isStatic: !leadingBracketRE.test(key)
+      },
+      value: {
+        type: NodeTypes.SIMPLE_EXPRESSION,
+        content: obj[key].replace(bracketsRE, ''),
+        isStatic: !leadingBracketRE.test(obj[key])
+      }
+    }))
+  }
+}
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
new file mode 100644 (file)
index 0000000..a6b34c8
--- /dev/null
@@ -0,0 +1,79 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`compiler: transform v-if codegen basic v-if 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Empty: _Empty } = _Vue
+    
+    return (_openBlock(), ok
+      ? _createBlock(\\"div\\", { key: 0 })
+      : _createBlock(_Empty))
+  }
+}"
+`;
+
+exports[`compiler: transform v-if codegen template v-if 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, Empty: _Empty } = _Vue
+    
+    return (_openBlock(), ok
+      ? _createBlock(_Fragment, { key: 0 }, [
+          _createVNode(\\"div\\"),
+          \\"hello\\",
+          _createVNode(\\"p\\")
+        ])
+      : _createBlock(_Empty))
+  }
+}"
+`;
+
+exports[`compiler: transform v-if codegen v-if + v-else 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Empty: _Empty } = _Vue
+    
+    return (_openBlock(), ok
+      ? _createBlock(\\"div\\", { key: 0 })
+      : _createBlock(\\"p\\", { key: 1 }))
+  }
+}"
+`;
+
+exports[`compiler: transform v-if codegen v-if + v-else-if + v-else 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Empty: _Empty, Fragment: _Fragment } = _Vue
+    
+    return (_openBlock(), ok
+      ? _createBlock(\\"div\\", { key: 0 })
+      : orNot
+        ? _createBlock(\\"p\\", { key: 1 })
+        : _createBlock(_Fragment, { key: 2 }, \\"fine\\"))
+  }
+}"
+`;
+
+exports[`compiler: transform v-if codegen v-if + v-else-if 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Empty: _Empty } = _Vue
+    
+    return (_openBlock(), ok
+      ? _createBlock(\\"div\\", { key: 0 })
+      : orNot
+        ? _createBlock(\\"p\\", { key: 1 })
+        : _createBlock(_Empty))
+  }
+}"
+`;
index 8f2cce9eef500b07333c19e9abe9e3c85ec174db..2ca90b95208aa95907796af672050fb2facbcfa9 100644 (file)
@@ -25,6 +25,7 @@ import { transformOn } from '../../src/transforms/vOn'
 import { transformStyle } from '../../src/transforms/transformStyle'
 import { transformBind } from '../../src/transforms/vBind'
 import { PatchFlags } from '@vue/shared'
+import { createObjectMatcher } from '../testUtils'
 
 function parseWithElementTransform(
   template: string,
@@ -47,25 +48,6 @@ function parseWithElementTransform(
   }
 }
 
-function createStaticObjectMatcher(obj: any) {
-  return {
-    type: NodeTypes.JS_OBJECT_EXPRESSION,
-    properties: Object.keys(obj).map(key => ({
-      type: NodeTypes.JS_PROPERTY,
-      key: {
-        type: NodeTypes.SIMPLE_EXPRESSION,
-        content: key,
-        isStatic: true
-      },
-      value: {
-        type: NodeTypes.SIMPLE_EXPRESSION,
-        content: obj[key],
-        isStatic: true
-      }
-    }))
-  }
-}
-
 describe('compiler: element transform', () => {
   test('import + resovle component', () => {
     const { root } = parseWithElementTransform(`<Foo/>`)
@@ -78,7 +60,7 @@ describe('compiler: element transform', () => {
     expect(node.callee).toBe(`_${CREATE_VNODE}`)
     expect(node.arguments).toMatchObject([
       `"div"`,
-      createStaticObjectMatcher({
+      createObjectMatcher({
         id: 'foo',
         class: 'bar'
       })
@@ -90,7 +72,7 @@ describe('compiler: element transform', () => {
     expect(node.callee).toBe(`_${CREATE_VNODE}`)
     expect(node.arguments).toMatchObject([
       `"div"`,
-      createStaticObjectMatcher({
+      createObjectMatcher({
         id: 'foo'
       }),
       [
@@ -147,7 +129,7 @@ describe('compiler: element transform', () => {
       type: NodeTypes.JS_CALL_EXPRESSION,
       callee: `_${MERGE_PROPS}`,
       arguments: [
-        createStaticObjectMatcher({
+        createObjectMatcher({
           id: 'foo'
         }),
         {
@@ -172,7 +154,7 @@ describe('compiler: element transform', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `obj`
         },
-        createStaticObjectMatcher({
+        createObjectMatcher({
           id: 'foo'
         })
       ]
@@ -189,14 +171,14 @@ describe('compiler: element transform', () => {
       type: NodeTypes.JS_CALL_EXPRESSION,
       callee: `_${MERGE_PROPS}`,
       arguments: [
-        createStaticObjectMatcher({
+        createObjectMatcher({
           id: 'foo'
         }),
         {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `obj`
         },
-        createStaticObjectMatcher({
+        createObjectMatcher({
           class: 'bar'
         })
       ]
@@ -213,7 +195,7 @@ describe('compiler: element transform', () => {
       type: NodeTypes.JS_CALL_EXPRESSION,
       callee: `_${MERGE_PROPS}`,
       arguments: [
-        createStaticObjectMatcher({
+        createObjectMatcher({
           id: 'foo'
         }),
         {
@@ -226,7 +208,7 @@ describe('compiler: element transform', () => {
             }
           ]
         },
-        createStaticObjectMatcher({
+        createObjectMatcher({
           class: 'bar'
         })
       ]
@@ -243,7 +225,7 @@ describe('compiler: element transform', () => {
       type: NodeTypes.JS_CALL_EXPRESSION,
       callee: `_${MERGE_PROPS}`,
       arguments: [
-        createStaticObjectMatcher({
+        createObjectMatcher({
           id: 'foo'
         }),
         {
index 5871476fdebbafd18b5d60874083b312d3cfef13..283a07cd2dd6775e29ef73246ee10440904ce1e3 100644 (file)
@@ -8,31 +8,46 @@ import {
   ElementNode,
   TextNode,
   CommentNode,
-  SimpleExpressionNode
+  SimpleExpressionNode,
+  SequenceExpression,
+  ConditionalExpression,
+  CallExpression
 } from '../../src/ast'
 import { ErrorCodes } from '../../src/errors'
-import { CompilerOptions } from '../../src'
+import { CompilerOptions, generate } from '../../src'
+import {
+  OPEN_BLOCK,
+  CREATE_BLOCK,
+  EMPTY,
+  FRAGMENT,
+  MERGE_PROPS,
+  APPLY_DIRECTIVES
+} from '../../src/runtimeConstants'
+import { createObjectMatcher } from '../testUtils'
 
 function parseWithIfTransform(
   template: string,
   options: CompilerOptions = {},
   returnIndex: number = 0
-): IfNode {
-  const node = parse(template, options)
-  transform(node, {
+) {
+  const ast = parse(template, options)
+  transform(ast, {
     nodeTransforms: [transformIf, transformElement],
     ...options
   })
   if (!options.onError) {
-    expect(node.children.length).toBe(1)
-    expect(node.children[0].type).toBe(NodeTypes.IF)
+    expect(ast.children.length).toBe(1)
+    expect(ast.children[0].type).toBe(NodeTypes.IF)
+  }
+  return {
+    root: ast,
+    node: ast.children[returnIndex] as IfNode
   }
-  return node.children[returnIndex] as IfNode
 }
 
 describe('compiler: transform v-if', () => {
   test('basic v-if', () => {
-    const node = parseWithIfTransform(`<div v-if="ok"/>`)
+    const { node } = parseWithIfTransform(`<div v-if="ok"/>`)
     expect(node.type).toBe(NodeTypes.IF)
     expect(node.branches.length).toBe(1)
     expect((node.branches[0].condition as SimpleExpressionNode).content).toBe(
@@ -44,7 +59,7 @@ describe('compiler: transform v-if', () => {
   })
 
   test('template v-if', () => {
-    const node = parseWithIfTransform(
+    const { node } = parseWithIfTransform(
       `<template v-if="ok"><div/>hello<p/></template>`
     )
     expect(node.type).toBe(NodeTypes.IF)
@@ -62,7 +77,7 @@ describe('compiler: transform v-if', () => {
   })
 
   test('v-if + v-else', () => {
-    const node = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
+    const { node } = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
     expect(node.type).toBe(NodeTypes.IF)
     expect(node.branches.length).toBe(2)
 
@@ -80,7 +95,9 @@ describe('compiler: transform v-if', () => {
   })
 
   test('v-if + v-else-if', () => {
-    const node = parseWithIfTransform(`<div v-if="ok"/><p v-else-if="orNot"/>`)
+    const { node } = parseWithIfTransform(
+      `<div v-if="ok"/><p v-else-if="orNot"/>`
+    )
     expect(node.type).toBe(NodeTypes.IF)
     expect(node.branches.length).toBe(2)
 
@@ -98,7 +115,7 @@ describe('compiler: transform v-if', () => {
   })
 
   test('v-if + v-else-if + v-else', () => {
-    const node = parseWithIfTransform(
+    const { node } = parseWithIfTransform(
       `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
     )
     expect(node.type).toBe(NodeTypes.IF)
@@ -124,7 +141,7 @@ describe('compiler: transform v-if', () => {
   })
 
   test('comment between branches', () => {
-    const node = parseWithIfTransform(`
+    const { node } = parseWithIfTransform(`
       <div v-if="ok"/>
       <!--foo-->
       <p v-else-if="orNot"/>
@@ -158,24 +175,20 @@ describe('compiler: transform v-if', () => {
   })
 
   test('should prefix v-if condition', () => {
-    const node = parseWithIfTransform(`<div v-if="ok"/>`, {
+    const { node } = parseWithIfTransform(`<div v-if="ok"/>`, {
       prefixIdentifiers: true
-    }) as IfNode
+    })
     expect(node.branches[0].condition).toMatchObject({
       type: NodeTypes.SIMPLE_EXPRESSION,
       content: `_ctx.ok`
     })
   })
 
-  describe('codegen', () => {
-    // TODO
-  })
-
   describe('errors', () => {
     test('error on v-else missing adjacent v-if', () => {
       const onError = jest.fn()
 
-      const node1 = parseWithIfTransform(`<div v-else/>`, { onError })
+      const { node: node1 } = parseWithIfTransform(`<div v-else/>`, { onError })
       expect(onError.mock.calls[0]).toMatchObject([
         {
           code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
@@ -183,7 +196,11 @@ describe('compiler: transform v-if', () => {
         }
       ])
 
-      const node2 = parseWithIfTransform(`<div/><div v-else/>`, { onError }, 1)
+      const { node: node2 } = parseWithIfTransform(
+        `<div/><div v-else/>`,
+        { onError },
+        1
+      )
       expect(onError.mock.calls[1]).toMatchObject([
         {
           code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
@@ -191,7 +208,7 @@ describe('compiler: transform v-if', () => {
         }
       ])
 
-      const node3 = parseWithIfTransform(
+      const { node: node3 } = parseWithIfTransform(
         `<div/>foo<div v-else/>`,
         { onError },
         2
@@ -207,7 +224,9 @@ describe('compiler: transform v-if', () => {
     test('error on v-else-if missing adjacent v-if', () => {
       const onError = jest.fn()
 
-      const node1 = parseWithIfTransform(`<div v-else-if="foo"/>`, { onError })
+      const { node: node1 } = parseWithIfTransform(`<div v-else-if="foo"/>`, {
+        onError
+      })
       expect(onError.mock.calls[0]).toMatchObject([
         {
           code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
@@ -215,7 +234,7 @@ describe('compiler: transform v-if', () => {
         }
       ])
 
-      const node2 = parseWithIfTransform(
+      const { node: node2 } = parseWithIfTransform(
         `<div/><div v-else-if="foo"/>`,
         { onError },
         1
@@ -227,7 +246,7 @@ describe('compiler: transform v-if', () => {
         }
       ])
 
-      const node3 = parseWithIfTransform(
+      const { node: node3 } = parseWithIfTransform(
         `<div/>foo<div v-else-if="foo"/>`,
         { onError },
         2
@@ -240,4 +259,214 @@ describe('compiler: transform v-if', () => {
       ])
     })
   })
+
+  describe('codegen', () => {
+    function assertSharedCodegen(node: SequenceExpression, depth: number = 0) {
+      expect(node).toMatchObject({
+        type: NodeTypes.JS_SEQUENCE_EXPRESSION,
+        expressions: [
+          {
+            type: NodeTypes.JS_CALL_EXPRESSION,
+            callee: `_${OPEN_BLOCK}`,
+            arguments: []
+          },
+          {
+            type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
+            test: {
+              content: `ok`
+            },
+            consequent: {
+              type: NodeTypes.JS_CALL_EXPRESSION,
+              callee: `_${CREATE_BLOCK}`
+            },
+            alternate:
+              depth < 1
+                ? {
+                    type: NodeTypes.JS_CALL_EXPRESSION,
+                    callee: `_${CREATE_BLOCK}`
+                  }
+                : {
+                    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
+                    test: {
+                      content: `orNot`
+                    },
+                    consequent: {
+                      type: NodeTypes.JS_CALL_EXPRESSION,
+                      callee: `_${CREATE_BLOCK}`
+                    },
+                    alternate: {
+                      type: NodeTypes.JS_CALL_EXPRESSION,
+                      callee: `_${CREATE_BLOCK}`
+                    }
+                  }
+          }
+        ]
+      })
+    }
+
+    test('basic v-if', () => {
+      const {
+        root,
+        node: { codegenNode }
+      } = parseWithIfTransform(`<div v-if="ok"/>`)
+      assertSharedCodegen(codegenNode)
+      const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
+        .consequent as CallExpression
+      expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`])
+      const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
+        .alternate as CallExpression
+      expect(branch2.arguments).toMatchObject([`_${EMPTY}`])
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('template v-if', () => {
+      const {
+        root,
+        node: { codegenNode }
+      } = parseWithIfTransform(`<template v-if="ok"><div/>hello<p/></template>`)
+      assertSharedCodegen(codegenNode)
+      const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
+        .consequent as CallExpression
+      expect(branch1.arguments).toMatchObject([
+        `_${FRAGMENT}`,
+        `{ key: 0 }`,
+        [
+          { type: NodeTypes.ELEMENT, tag: 'div' },
+          { type: NodeTypes.TEXT, content: `hello` },
+          { type: NodeTypes.ELEMENT, tag: 'p' }
+        ]
+      ])
+      const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
+        .alternate as CallExpression
+      expect(branch2.arguments).toMatchObject([`_${EMPTY}`])
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('v-if + v-else', () => {
+      const {
+        root,
+        node: { codegenNode }
+      } = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
+      assertSharedCodegen(codegenNode)
+      const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
+        .consequent as CallExpression
+      expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`])
+      const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
+        .alternate as CallExpression
+      expect(branch2.arguments).toMatchObject([`"p"`, `{ key: 1 }`])
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('v-if + v-else-if', () => {
+      const {
+        root,
+        node: { codegenNode }
+      } = parseWithIfTransform(`<div v-if="ok"/><p v-else-if="orNot" />`)
+      assertSharedCodegen(codegenNode, 1)
+      const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
+        .consequent as CallExpression
+      expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`])
+      const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
+        .alternate as ConditionalExpression
+      expect((branch2.consequent as CallExpression).arguments).toMatchObject([
+        `"p"`,
+        `{ key: 1 }`
+      ])
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('v-if + v-else-if + v-else', () => {
+      const {
+        root,
+        node: { codegenNode }
+      } = parseWithIfTransform(
+        `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
+      )
+      assertSharedCodegen(codegenNode, 1)
+      const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
+        .consequent as CallExpression
+      expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`])
+      const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
+        .alternate as ConditionalExpression
+      expect((branch2.consequent as CallExpression).arguments).toMatchObject([
+        `"p"`,
+        `{ key: 1 }`
+      ])
+      expect((branch2.alternate as CallExpression).arguments).toMatchObject([
+        `_${FRAGMENT}`,
+        `{ key: 2 }`,
+        [
+          {
+            type: NodeTypes.TEXT,
+            content: `fine`
+          }
+        ]
+      ])
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('key injection (only v-bind)', () => {
+      const {
+        node: { codegenNode }
+      } = parseWithIfTransform(`<div v-if="ok" v-bind="obj"/>`)
+      const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
+        .consequent as CallExpression
+      expect(branch1.arguments[1]).toMatchObject({
+        type: NodeTypes.JS_CALL_EXPRESSION,
+        callee: `_${MERGE_PROPS}`,
+        arguments: [`{ key: 0 }`, { content: `obj` }]
+      })
+    })
+
+    test('key injection (before v-bind)', () => {
+      const {
+        node: { codegenNode }
+      } = parseWithIfTransform(`<div v-if="ok" id="foo" v-bind="obj"/>`)
+      const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
+        .consequent as CallExpression
+      expect(branch1.arguments[1]).toMatchObject({
+        type: NodeTypes.JS_CALL_EXPRESSION,
+        callee: `_${MERGE_PROPS}`,
+        arguments: [
+          createObjectMatcher({
+            key: '[0]',
+            id: 'foo'
+          }),
+          { content: `obj` }
+        ]
+      })
+    })
+
+    test('key injection (after v-bind)', () => {
+      const {
+        node: { codegenNode }
+      } = parseWithIfTransform(`<div v-if="ok" v-bind="obj" id="foo"/>`)
+      const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
+        .consequent as CallExpression
+      expect(branch1.arguments[1]).toMatchObject({
+        type: NodeTypes.JS_CALL_EXPRESSION,
+        callee: `_${MERGE_PROPS}`,
+        arguments: [
+          `{ key: 0 }`,
+          { content: `obj` },
+          createObjectMatcher({
+            id: 'foo'
+          })
+        ]
+      })
+    })
+
+    test('key injection (w/ custom directive)', () => {
+      const {
+        node: { codegenNode }
+      } = parseWithIfTransform(`<div v-if="ok" v-foo />`)
+      const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
+        .consequent as CallExpression
+      expect(branch1.callee).toBe(`_${APPLY_DIRECTIVES}`)
+      const realBranch = branch1.arguments[0] as CallExpression
+      expect(realBranch.arguments[1]).toBe(`{ key: 0 }`)
+    })
+
+    test('with comments', () => {})
+  })
 })
index 5f80c074a647671b68f402900bbbf17bbef19010..0875573e04e03fd718c675a7e965577bdd25e37a 100644 (file)
@@ -300,7 +300,7 @@ function genNodeListAsArray(
   context: CodegenContext
 ) {
   const multilines =
-    nodes.length > 1 ||
+    nodes.length > 3 ||
     ((!__BROWSER__ || __DEV__) &&
       nodes.some(
         n =>
@@ -513,7 +513,7 @@ function genFor(node: ForNode, context: CodegenContext) {
 function genCallExpression(
   node: CallExpression,
   context: CodegenContext,
-  multilines = node.arguments.length > 2
+  multilines = false
 ) {
   context.push(node.callee + `(`, node, true)
   multilines && context.indent()
index 9c415fd6c5891ac41b105617412a141691ae2273..456cda0dadb17ee7350ed16c2de0e02523164f41 100644 (file)
@@ -19,7 +19,8 @@ import {
   JSChildNode,
   ObjectExpression,
   createObjectProperty,
-  Property
+  Property,
+  ExpressionNode
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { processExpression } from './transformExpression'
@@ -28,7 +29,8 @@ import {
   CREATE_BLOCK,
   EMPTY,
   FRAGMENT,
-  APPLY_DIRECTIVES
+  APPLY_DIRECTIVES,
+  MERGE_PROPS
 } from '../runtimeConstants'
 import { isString } from '@vue/shared'
 
@@ -51,14 +53,14 @@ export const transformIf = createStructuralDirectiveTransform(
     }
 
     if (dir.name === 'if') {
+      const branch = createIfBranch(node, dir)
       const codegenNode = createSequenceExpression([
         createCallExpression(context.helper(OPEN_BLOCK))
       ])
-
       context.replaceNode({
         type: NodeTypes.IF,
         loc: node.loc,
-        branches: [createIfBranch(node, dir)],
+        branches: [branch],
         codegenNode
       })
 
@@ -66,7 +68,7 @@ export const transformIf = createStructuralDirectiveTransform(
       // transformed.
       return () => {
         codegenNode.expressions.push(
-          createCodegenNodeForBranch(node, dir, 0, context)
+          createCodegenNodeForBranch(node, branch, 0, context)
         )
       }
     } else {
@@ -104,7 +106,7 @@ export const transformIf = createStructuralDirectiveTransform(
             } else {
               parentCondition.alternate = createCodegenNodeForBranch(
                 node,
-                dir,
+                branch,
                 sibling.branches.length - 1,
                 context
               )
@@ -138,25 +140,26 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
 
 function createCodegenNodeForBranch(
   node: ElementNode,
-  dir: DirectiveNode,
+  branch: IfBranchNode,
   index: number,
   context: TransformContext
 ): ConditionalExpression | CallExpression {
-  if (dir.exp) {
+  if (branch.condition) {
     return createConditionalExpression(
-      dir.exp,
-      createChildrenCodegenNode(node, index, context),
+      branch.condition,
+      createChildrenCodegenNode(node, branch, index, context),
       createCallExpression(context.helper(CREATE_BLOCK), [
         context.helper(EMPTY)
       ])
     )
   } else {
-    return createChildrenCodegenNode(node, index, context)
+    return createChildrenCodegenNode(node, branch, index, context)
   }
 }
 
 function createChildrenCodegenNode(
   node: ElementNode,
+  branch: IfBranchNode,
   index: number,
   { helper }: TransformContext
 ): CallExpression {
@@ -166,11 +169,11 @@ function createChildrenCodegenNode(
     return createCallExpression(helper(CREATE_BLOCK), [
       helper(FRAGMENT),
       keyExp,
-      node.children
+      branch.children
     ])
   } else {
     let childCodegen = node.codegenNode!
-    if (childCodegen.callee === helper(APPLY_DIRECTIVES)) {
+    if (childCodegen.callee.includes(APPLY_DIRECTIVES)) {
       childCodegen = childCodegen.arguments[0] as CallExpression
     }
     // change child to a block
@@ -181,7 +184,10 @@ function createChildrenCodegenNode(
       childCodegen.arguments[1] = keyExp
     } else {
       // inject branch key if not already have a key
-      const props = existingProps as CallExpression | ObjectExpression
+      const props = existingProps as
+        | CallExpression
+        | ObjectExpression
+        | ExpressionNode
       if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
         // merged props... add ours
         // only inject key to object literal if it's the first argument so that
@@ -192,11 +198,17 @@ function createChildrenCodegenNode(
         } else {
           props.arguments.unshift(keyExp)
         }
-      } else {
+      } else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
         props.properties.unshift(createKeyProperty(index))
+      } else {
+        // single v-bind with expression
+        childCodegen.arguments[1] = createCallExpression(helper(MERGE_PROPS), [
+          keyExp,
+          props
+        ])
       }
     }
-    return childCodegen
+    return node.codegenNode!
   }
 }