mirror of
				https://github.com/vuejs/babel-plugin-jsx.git
				synced 2025-11-04 11:22:19 +08:00 
			
		
		
		
	feat: should support passing object slots via JSX children (#204)
* feat: should support passing object slots via JSX children * feat: add unit test * feat: remove `cloneDeep` of `isSlot` * chore(deps): add `@babel/template`
This commit is contained in:
		@@ -11,11 +11,10 @@
 | 
			
		||||
    "url": "git+https://github.com/vuejs/jsx-next.git"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "npm run build && webpack-dev-server",
 | 
			
		||||
    "build": "tsc",
 | 
			
		||||
    "lint": "eslint 'src/*.ts'",
 | 
			
		||||
    "test": "npm run build && jest --coverage",
 | 
			
		||||
    "prepublishOnly": "npm run build"
 | 
			
		||||
    "test": "yarn build && jest --coverage",
 | 
			
		||||
    "prepublishOnly": "yarn build"
 | 
			
		||||
  },
 | 
			
		||||
  "bugs": {
 | 
			
		||||
    "url": "https://github.com/vuejs/jsx-next/issues"
 | 
			
		||||
@@ -26,6 +25,7 @@
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@babel/helper-module-imports": "^7.0.0",
 | 
			
		||||
    "@babel/plugin-syntax-jsx": "^7.0.0",
 | 
			
		||||
    "@babel/template": "^7.0.0",
 | 
			
		||||
    "@babel/traverse": "^7.0.0",
 | 
			
		||||
    "@babel/types": "^7.0.0",
 | 
			
		||||
    "@vue/babel-helper-vue-transform-on": "^1.0.0-rc.2",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import * as t from '@babel/types';
 | 
			
		||||
import * as BabelCore from '@babel/core';
 | 
			
		||||
import template from '@babel/template';
 | 
			
		||||
import syntaxJsx from '@babel/plugin-syntax-jsx';
 | 
			
		||||
import { addNamed, isModule, addNamespace } from '@babel/helper-module-imports';
 | 
			
		||||
import { NodePath } from '@babel/traverse';
 | 
			
		||||
@@ -23,7 +24,6 @@ export type ExcludesBoolean = <T>(x: T | false | true) => x is T;
 | 
			
		||||
 | 
			
		||||
const hasJSX = (parentPath: NodePath) => {
 | 
			
		||||
  let fileHasJSX = false;
 | 
			
		||||
 | 
			
		||||
  parentPath.traverse({
 | 
			
		||||
    JSXElement(path) { // skip ts error
 | 
			
		||||
      fileHasJSX = true;
 | 
			
		||||
@@ -62,6 +62,7 @@ export default ({ types }: typeof BabelCore) => ({
 | 
			
		||||
            'resolveDirective',
 | 
			
		||||
            'mergeProps',
 | 
			
		||||
            'createTextVNode',
 | 
			
		||||
            'isVNode',
 | 
			
		||||
          ];
 | 
			
		||||
          if (isModule(path)) {
 | 
			
		||||
            // import { createVNode } from "vue";
 | 
			
		||||
@@ -83,6 +84,24 @@ export default ({ types }: typeof BabelCore) => ({
 | 
			
		||||
                return identifier;
 | 
			
		||||
              });
 | 
			
		||||
            });
 | 
			
		||||
            state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
 | 
			
		||||
              if (importMap.runtimeIsSlot) {
 | 
			
		||||
                return importMap.runtimeIsSlot;
 | 
			
		||||
              }
 | 
			
		||||
              const { name: isVNodeName } = state.get('isVNode')();
 | 
			
		||||
              const isSlot = path.scope.generateUidIdentifier('isSlot');
 | 
			
		||||
              const ast = template.ast`
 | 
			
		||||
                function ${isSlot.name}(s) {
 | 
			
		||||
                  return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
 | 
			
		||||
                }
 | 
			
		||||
              `;
 | 
			
		||||
              const lastImport = (path.get('body') as NodePath[]).filter((p) => p.isImportDeclaration()).pop();
 | 
			
		||||
              if (lastImport) {
 | 
			
		||||
                lastImport.insertAfter(ast);
 | 
			
		||||
              }
 | 
			
		||||
              importMap.runtimeIsSlot = isSlot;
 | 
			
		||||
              return isSlot;
 | 
			
		||||
            });
 | 
			
		||||
          } else {
 | 
			
		||||
            // var _vue = require('vue');
 | 
			
		||||
            let sourceName = '';
 | 
			
		||||
 
 | 
			
		||||
@@ -83,12 +83,10 @@ const transformJSXElement = (
 | 
			
		||||
 | 
			
		||||
  const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE;
 | 
			
		||||
 | 
			
		||||
  const createVNode = t.callExpression(createIdentifier(state, 'createVNode'), [
 | 
			
		||||
    tag,
 | 
			
		||||
    props,
 | 
			
		||||
    (children.length || slots) ? (
 | 
			
		||||
      isComponent
 | 
			
		||||
        ? t.objectExpression([
 | 
			
		||||
  let VNodeChild;
 | 
			
		||||
 | 
			
		||||
  if (children.length > 1 || slots) {
 | 
			
		||||
    VNodeChild = isComponent ? t.objectExpression([
 | 
			
		||||
      !!children.length && t.objectProperty(
 | 
			
		||||
        t.identifier('default'),
 | 
			
		||||
        t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, children))),
 | 
			
		||||
@@ -102,9 +100,82 @@ const transformJSXElement = (
 | 
			
		||||
        t.identifier('_'),
 | 
			
		||||
        t.numericLiteral(slotFlag),
 | 
			
		||||
      ),
 | 
			
		||||
        ].filter(Boolean as any))
 | 
			
		||||
        : t.arrayExpression(children)
 | 
			
		||||
    ) : t.nullLiteral(),
 | 
			
		||||
    ].filter(Boolean as any)) : t.arrayExpression(children);
 | 
			
		||||
  } else if (children.length === 1) {
 | 
			
		||||
    const child = children[0];
 | 
			
		||||
    if (t.isIdentifier(child)) {
 | 
			
		||||
      VNodeChild = t.conditionalExpression(
 | 
			
		||||
        t.callExpression(state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(), [child]),
 | 
			
		||||
        child,
 | 
			
		||||
        t.objectExpression([
 | 
			
		||||
          t.objectProperty(
 | 
			
		||||
            t.identifier('default'),
 | 
			
		||||
            t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, [child]))),
 | 
			
		||||
          ),
 | 
			
		||||
          optimize && t.objectProperty(
 | 
			
		||||
            t.identifier('_'),
 | 
			
		||||
            t.numericLiteral(slotFlag),
 | 
			
		||||
          ) as any,
 | 
			
		||||
        ].filter(Boolean)),
 | 
			
		||||
      );
 | 
			
		||||
    } else if (
 | 
			
		||||
      t.isCallExpression(child) && child.loc && isComponent
 | 
			
		||||
    ) { // the element was generated and doesn't have location information
 | 
			
		||||
      const slotId = path.scope.generateUidIdentifier('slot');
 | 
			
		||||
      const scope = path.hub.getScope();
 | 
			
		||||
      if (scope) {
 | 
			
		||||
        scope.push({
 | 
			
		||||
          id: slotId,
 | 
			
		||||
          kind: 'let',
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      VNodeChild = t.conditionalExpression(
 | 
			
		||||
        t.callExpression(
 | 
			
		||||
          state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(),
 | 
			
		||||
          [t.assignmentExpression('=', slotId, child)],
 | 
			
		||||
        ),
 | 
			
		||||
        slotId,
 | 
			
		||||
        t.objectExpression([
 | 
			
		||||
          t.objectProperty(
 | 
			
		||||
            t.identifier('default'),
 | 
			
		||||
            t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, [slotId]))),
 | 
			
		||||
          ),
 | 
			
		||||
          optimize && t.objectProperty(
 | 
			
		||||
            t.identifier('_'),
 | 
			
		||||
            t.numericLiteral(slotFlag),
 | 
			
		||||
          ) as any,
 | 
			
		||||
        ].filter(Boolean)),
 | 
			
		||||
      );
 | 
			
		||||
    } else if (t.isFunctionExpression(child) || t.isArrowFunctionExpression(child)) {
 | 
			
		||||
      VNodeChild = t.objectExpression([
 | 
			
		||||
        t.objectProperty(
 | 
			
		||||
          t.identifier('default'),
 | 
			
		||||
          child,
 | 
			
		||||
        ),
 | 
			
		||||
      ]);
 | 
			
		||||
    } else if (t.isObjectExpression(child)) {
 | 
			
		||||
      VNodeChild = t.objectExpression([
 | 
			
		||||
        ...child.properties,
 | 
			
		||||
        optimize && t.objectProperty(
 | 
			
		||||
          t.identifier('_'),
 | 
			
		||||
          t.numericLiteral(slotFlag),
 | 
			
		||||
        ),
 | 
			
		||||
      ].filter(Boolean as any));
 | 
			
		||||
    } else {
 | 
			
		||||
      VNodeChild = isComponent ? t.objectExpression([
 | 
			
		||||
        t.objectProperty(
 | 
			
		||||
          t.identifier('default'),
 | 
			
		||||
          t.arrowFunctionExpression([], t.arrayExpression([child])),
 | 
			
		||||
        ),
 | 
			
		||||
      ]) : t.arrayExpression([child]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const createVNode = t.callExpression(createIdentifier(state, 'createVNode'), [
 | 
			
		||||
    tag,
 | 
			
		||||
    props,
 | 
			
		||||
    VNodeChild || t.nullLiteral(),
 | 
			
		||||
    !!patchFlag && optimize && t.numericLiteral(patchFlag),
 | 
			
		||||
    !!dynamicPropNames.size && optimize
 | 
			
		||||
    && t.arrayExpression(
 | 
			
		||||
 
 | 
			
		||||
@@ -127,9 +127,53 @@ exports[`override props single: single 1`] = `
 | 
			
		||||
_createVNode(\\"div\\", a, null);"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`reassign variable as component: reassign variable as component 1`] = `
 | 
			
		||||
exports[`passing object slots via JSX children multiple expressions: multiple expressions 1`] = `
 | 
			
		||||
"import { createVNode as _createVNode } from \\"vue\\";
 | 
			
		||||
import { resolveComponent as _resolveComponent } from \\"vue\\";
 | 
			
		||||
 | 
			
		||||
_createVNode(_resolveComponent(\\"A\\"), null, {
 | 
			
		||||
  default: () => [foo, bar],
 | 
			
		||||
  _: 1
 | 
			
		||||
});"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`passing object slots via JSX children single expression, function expression: single expression, function expression 1`] = `
 | 
			
		||||
"import { createVNode as _createVNode } from \\"vue\\";
 | 
			
		||||
import { resolveComponent as _resolveComponent } from \\"vue\\";
 | 
			
		||||
 | 
			
		||||
_createVNode(_resolveComponent(\\"A\\"), null, {
 | 
			
		||||
  default: () => \\"foo\\"
 | 
			
		||||
});"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`passing object slots via JSX children single expression, non-literal value: runtime check: single expression, non-literal value: runtime check 1`] = `
 | 
			
		||||
"import { createVNode as _createVNode } from \\"vue\\";
 | 
			
		||||
import { isVNode as _isVNode } from \\"vue\\";
 | 
			
		||||
import { resolveComponent as _resolveComponent } from \\"vue\\";
 | 
			
		||||
 | 
			
		||||
let _slot;
 | 
			
		||||
 | 
			
		||||
function _isSlot(s) {
 | 
			
		||||
  return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !_isVNode(s);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const foo = () => 1;
 | 
			
		||||
 | 
			
		||||
_createVNode(_resolveComponent(\\"A\\"), null, _isSlot(_slot = foo()) ? _slot : {
 | 
			
		||||
  default: () => [_slot],
 | 
			
		||||
  _: 1
 | 
			
		||||
});"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`reassign variable as component: reassign variable as component 1`] = `
 | 
			
		||||
"import { isVNode as _isVNode } from \\"vue\\";
 | 
			
		||||
import { createVNode as _createVNode } from \\"vue\\";
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
 | 
			
		||||
function _isSlot(s) {
 | 
			
		||||
  return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !_isVNode(s);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let a = 1;
 | 
			
		||||
const A = defineComponent({
 | 
			
		||||
  setup(_, {
 | 
			
		||||
@@ -146,7 +190,7 @@ const _a = function () {
 | 
			
		||||
  return a;
 | 
			
		||||
}();
 | 
			
		||||
 | 
			
		||||
a = _createVNode(A, null, {
 | 
			
		||||
a = _createVNode(A, null, _isSlot(a) ? a : {
 | 
			
		||||
  default: () => [_a],
 | 
			
		||||
  _: 2
 | 
			
		||||
});"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,9 @@
 | 
			
		||||
import {
 | 
			
		||||
  reactive, ref, defineComponent, CSSProperties, ComponentPublicInstance,
 | 
			
		||||
  reactive,
 | 
			
		||||
  ref,
 | 
			
		||||
  defineComponent,
 | 
			
		||||
  CSSProperties,
 | 
			
		||||
  ComponentPublicInstance,
 | 
			
		||||
} from 'vue';
 | 
			
		||||
import { shallowMount, mount, VueWrapper } from '@vue/test-utils';
 | 
			
		||||
 | 
			
		||||
@@ -66,9 +70,7 @@ describe('Transform JSX', () => {
 | 
			
		||||
 | 
			
		||||
    const wrapper = mount({
 | 
			
		||||
      render() {
 | 
			
		||||
        return (
 | 
			
		||||
          <Child class="parent" foo={1} />
 | 
			
		||||
        );
 | 
			
		||||
        return <Child class="parent" foo={1} />;
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    expect(wrapper.classes()).toStrictEqual([]);
 | 
			
		||||
@@ -148,7 +150,9 @@ describe('Transform JSX', () => {
 | 
			
		||||
        return () => <div {...propsA} {...propsB} />;
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    expect(wrapper.html()).toBe('<div style="color: blue; width: 300px; height: 300px;"></div>');
 | 
			
		||||
    expect(wrapper.html()).toBe(
 | 
			
		||||
      '<div style="color: blue; width: 300px; height: 300px;"></div>',
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('JSXSpreadChild', () => {
 | 
			
		||||
@@ -259,7 +263,7 @@ describe('directive', () => {
 | 
			
		||||
        calls.push(1);
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
    const wrapper = shallowMount(({
 | 
			
		||||
    const wrapper = shallowMount({
 | 
			
		||||
      directives: { custom: customDirective },
 | 
			
		||||
      setup() {
 | 
			
		||||
        return () => (
 | 
			
		||||
@@ -272,28 +276,28 @@ describe('directive', () => {
 | 
			
		||||
          />
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    }));
 | 
			
		||||
    });
 | 
			
		||||
    const node = wrapper.vm.$.subTree;
 | 
			
		||||
    expect(calls).toEqual(expect.arrayContaining([1]));
 | 
			
		||||
    expect(node.dirs).toHaveLength(1);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('vHtml', () => {
 | 
			
		||||
    const wrapper = shallowMount(({
 | 
			
		||||
    const wrapper = shallowMount({
 | 
			
		||||
      setup() {
 | 
			
		||||
        return () => <h1 v-html="<div>foo</div>"></h1>;
 | 
			
		||||
      },
 | 
			
		||||
    }));
 | 
			
		||||
    });
 | 
			
		||||
    expect(wrapper.html()).toBe('<h1><div>foo</div></h1>');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('vText', () => {
 | 
			
		||||
    const text = 'foo';
 | 
			
		||||
    const wrapper = shallowMount(({
 | 
			
		||||
    const wrapper = shallowMount({
 | 
			
		||||
      setup() {
 | 
			
		||||
        return () => <div v-text={text}></div>;
 | 
			
		||||
      },
 | 
			
		||||
    }));
 | 
			
		||||
    });
 | 
			
		||||
    expect(wrapper.html()).toBe('<div>foo</div>');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -315,7 +319,11 @@ describe('slots', () => {
 | 
			
		||||
 | 
			
		||||
    const wrapper = mount({
 | 
			
		||||
      setup() {
 | 
			
		||||
        return () => <A v-slots={{ foo: (val: string) => val }}><span>default</span></A>;
 | 
			
		||||
        return () => (
 | 
			
		||||
          <A v-slots={{ foo: (val: string) => val }}>
 | 
			
		||||
            <span>default</span>
 | 
			
		||||
          </A>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -325,11 +333,7 @@ describe('slots', () => {
 | 
			
		||||
  test('without default', () => {
 | 
			
		||||
    const A = defineComponent({
 | 
			
		||||
      setup(_, { slots }) {
 | 
			
		||||
        return () => (
 | 
			
		||||
          <div>
 | 
			
		||||
            {slots.foo?.('foo')}
 | 
			
		||||
          </div>
 | 
			
		||||
        );
 | 
			
		||||
        return () => <div>{slots.foo?.('foo')}</div>;
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -362,7 +366,11 @@ describe('PatchFlags', () => {
 | 
			
		||||
        const onClick = () => {
 | 
			
		||||
          visible.value = false;
 | 
			
		||||
        };
 | 
			
		||||
        return () => <div v-show={visible.value} onClick={onClick}>NEED_PATCH</div>;
 | 
			
		||||
        return () => (
 | 
			
		||||
          <div v-show={visible.value} onClick={onClick}>
 | 
			
		||||
            NEED_PATCH
 | 
			
		||||
          </div>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -380,7 +388,9 @@ describe('PatchFlags', () => {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return () => (
 | 
			
		||||
          <div {...bindProps} class="static" onClick={onClick}>full props</div>
 | 
			
		||||
          <div {...bindProps} class="static" onClick={onClick}>
 | 
			
		||||
            full props
 | 
			
		||||
          </div>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
@@ -455,9 +465,7 @@ describe('variables outside slots', () => {
 | 
			
		||||
        const textarea = <textarea id="textarea" {...attrs} />;
 | 
			
		||||
        return (
 | 
			
		||||
          <A inc={this.inc}>
 | 
			
		||||
            <div>
 | 
			
		||||
              {textarea}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>{textarea}</div>
 | 
			
		||||
            <button id="button" onClick={this.inc}>+1</button>
 | 
			
		||||
          </A>
 | 
			
		||||
        );
 | 
			
		||||
@@ -480,7 +488,6 @@ test('reassign variable as component should work', () => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /* eslint-disable */
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  const _a2 = 2;
 | 
			
		||||
  a = _a2;
 | 
			
		||||
  /* eslint-enable */
 | 
			
		||||
@@ -495,3 +502,72 @@ test('reassign variable as component should work', () => {
 | 
			
		||||
 | 
			
		||||
  expect(wrapper.html()).toBe('<span>2</span>');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('should support passing object slots via JSX children', () => {
 | 
			
		||||
  const A = defineComponent({
 | 
			
		||||
    setup(_, { slots }) {
 | 
			
		||||
      return () => (
 | 
			
		||||
        <span>
 | 
			
		||||
          {slots.default?.()}
 | 
			
		||||
          {slots.foo?.()}
 | 
			
		||||
        </span>
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('single expression, variable', () => {
 | 
			
		||||
    const slots = { default: () => 1, foo: () => 2 };
 | 
			
		||||
 | 
			
		||||
    const wrapper = mount({
 | 
			
		||||
      render() {
 | 
			
		||||
        return <A>{slots}</A>;
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    expect(wrapper.html()).toBe('<span>12</span>');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('single expression, object literal', () => {
 | 
			
		||||
    const wrapper = mount({
 | 
			
		||||
      render() {
 | 
			
		||||
        return <A>{{ default: () => 1, foo: () => 2 }}</A>;
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    expect(wrapper.html()).toBe('<span>12</span>');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('single expression, object literal', () => {
 | 
			
		||||
    const wrapper = mount({
 | 
			
		||||
      render() {
 | 
			
		||||
        return <A>{{ default: () => 1, foo: () => 2 }}</A>;
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    expect(wrapper.html()).toBe('<span>12</span>');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('single expression, non-literal value', () => {
 | 
			
		||||
    const foo = () => 1;
 | 
			
		||||
 | 
			
		||||
    const wrapper = mount({
 | 
			
		||||
      render() {
 | 
			
		||||
        return <A>{foo()}</A>;
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    expect(wrapper.html()).toBe('<span>1<!----></span>');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('single expression, function expression', () => {
 | 
			
		||||
    const wrapper = mount({
 | 
			
		||||
      render() {
 | 
			
		||||
        return (
 | 
			
		||||
          <A>{() => 'foo'}</A>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    expect(wrapper.html()).toBe('<span>foo<!----></span>');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,11 @@
 | 
			
		||||
import { transform } from '@babel/core';
 | 
			
		||||
import JSX, { Opts } from '../src';
 | 
			
		||||
 | 
			
		||||
interface Test {
 | 
			
		||||
  name: string;
 | 
			
		||||
  from: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const transpile = (
 | 
			
		||||
  source: string, options: Opts = {},
 | 
			
		||||
) => new Promise((resolve, reject) => transform(
 | 
			
		||||
@@ -18,7 +23,7 @@ const transpile = (
 | 
			
		||||
  },
 | 
			
		||||
));
 | 
			
		||||
 | 
			
		||||
const tests = [
 | 
			
		||||
const tests: Test[] = [
 | 
			
		||||
  {
 | 
			
		||||
    name: 'input[type="checkbox"]',
 | 
			
		||||
    from: '<input type="checkbox" v-model={test} />',
 | 
			
		||||
@@ -155,7 +160,7 @@ tests.forEach((
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const overridePropsTests = [{
 | 
			
		||||
const overridePropsTests: Test[] = [{
 | 
			
		||||
  name: 'single',
 | 
			
		||||
  from: '<div {...a} />',
 | 
			
		||||
}, {
 | 
			
		||||
@@ -173,3 +178,34 @@ overridePropsTests.forEach((
 | 
			
		||||
    },
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const slotsTests: Test[] = [
 | 
			
		||||
  {
 | 
			
		||||
    name: 'multiple expressions',
 | 
			
		||||
    from: '<A>{foo}{bar}</A>',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'single expression, function expression',
 | 
			
		||||
    from: `
 | 
			
		||||
      <A>{() => "foo"}</A>
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'single expression, non-literal value: runtime check',
 | 
			
		||||
    from: `
 | 
			
		||||
      const foo = () => 1;
 | 
			
		||||
      <A>{foo()}</A>;
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
slotsTests.forEach(({
 | 
			
		||||
  name, from,
 | 
			
		||||
}) => {
 | 
			
		||||
  test(
 | 
			
		||||
    `passing object slots via JSX children ${name}`,
 | 
			
		||||
    async () => {
 | 
			
		||||
      expect(await transpile(from, { optimize: true })).toMatchSnapshot(name);
 | 
			
		||||
    },
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user