mirror of
https://github.com/vuejs/babel-plugin-jsx.git
synced 2024-11-10 09:39:14 +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:
parent
b443f847ca
commit
5b6323f22a
@ -11,11 +11,10 @@
|
|||||||
"url": "git+https://github.com/vuejs/jsx-next.git"
|
"url": "git+https://github.com/vuejs/jsx-next.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run build && webpack-dev-server",
|
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"lint": "eslint 'src/*.ts'",
|
"lint": "eslint 'src/*.ts'",
|
||||||
"test": "npm run build && jest --coverage",
|
"test": "yarn build && jest --coverage",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "yarn build"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vuejs/jsx-next/issues"
|
"url": "https://github.com/vuejs/jsx-next/issues"
|
||||||
@ -26,6 +25,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-module-imports": "^7.0.0",
|
"@babel/helper-module-imports": "^7.0.0",
|
||||||
"@babel/plugin-syntax-jsx": "^7.0.0",
|
"@babel/plugin-syntax-jsx": "^7.0.0",
|
||||||
|
"@babel/template": "^7.0.0",
|
||||||
"@babel/traverse": "^7.0.0",
|
"@babel/traverse": "^7.0.0",
|
||||||
"@babel/types": "^7.0.0",
|
"@babel/types": "^7.0.0",
|
||||||
"@vue/babel-helper-vue-transform-on": "^1.0.0-rc.2",
|
"@vue/babel-helper-vue-transform-on": "^1.0.0-rc.2",
|
||||||
@ -48,4 +48,4 @@
|
|||||||
"typescript": "^4.0.2",
|
"typescript": "^4.0.2",
|
||||||
"vue": "3.0.0"
|
"vue": "3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import * as t from '@babel/types';
|
import * as t from '@babel/types';
|
||||||
import * as BabelCore from '@babel/core';
|
import * as BabelCore from '@babel/core';
|
||||||
|
import template from '@babel/template';
|
||||||
import syntaxJsx from '@babel/plugin-syntax-jsx';
|
import syntaxJsx from '@babel/plugin-syntax-jsx';
|
||||||
import { addNamed, isModule, addNamespace } from '@babel/helper-module-imports';
|
import { addNamed, isModule, addNamespace } from '@babel/helper-module-imports';
|
||||||
import { NodePath } from '@babel/traverse';
|
import { NodePath } from '@babel/traverse';
|
||||||
@ -23,7 +24,6 @@ export type ExcludesBoolean = <T>(x: T | false | true) => x is T;
|
|||||||
|
|
||||||
const hasJSX = (parentPath: NodePath) => {
|
const hasJSX = (parentPath: NodePath) => {
|
||||||
let fileHasJSX = false;
|
let fileHasJSX = false;
|
||||||
|
|
||||||
parentPath.traverse({
|
parentPath.traverse({
|
||||||
JSXElement(path) { // skip ts error
|
JSXElement(path) { // skip ts error
|
||||||
fileHasJSX = true;
|
fileHasJSX = true;
|
||||||
@ -62,6 +62,7 @@ export default ({ types }: typeof BabelCore) => ({
|
|||||||
'resolveDirective',
|
'resolveDirective',
|
||||||
'mergeProps',
|
'mergeProps',
|
||||||
'createTextVNode',
|
'createTextVNode',
|
||||||
|
'isVNode',
|
||||||
];
|
];
|
||||||
if (isModule(path)) {
|
if (isModule(path)) {
|
||||||
// import { createVNode } from "vue";
|
// import { createVNode } from "vue";
|
||||||
@ -83,6 +84,24 @@ export default ({ types }: typeof BabelCore) => ({
|
|||||||
return identifier;
|
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 {
|
} else {
|
||||||
// var _vue = require('vue');
|
// var _vue = require('vue');
|
||||||
let sourceName = '';
|
let sourceName = '';
|
||||||
|
@ -83,28 +83,99 @@ const transformJSXElement = (
|
|||||||
|
|
||||||
const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE;
|
const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE;
|
||||||
|
|
||||||
const createVNode = t.callExpression(createIdentifier(state, 'createVNode'), [
|
let VNodeChild;
|
||||||
tag,
|
|
||||||
props,
|
if (children.length > 1 || slots) {
|
||||||
(children.length || slots) ? (
|
VNodeChild = isComponent ? t.objectExpression([
|
||||||
isComponent
|
!!children.length && t.objectProperty(
|
||||||
? t.objectExpression([
|
t.identifier('default'),
|
||||||
!!children.length && t.objectProperty(
|
t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, children))),
|
||||||
|
),
|
||||||
|
...(slots ? (
|
||||||
|
t.isObjectExpression(slots)
|
||||||
|
? (slots! as t.ObjectExpression).properties
|
||||||
|
: [t.spreadElement(slots!)]
|
||||||
|
) : []),
|
||||||
|
optimize && t.objectProperty(
|
||||||
|
t.identifier('_'),
|
||||||
|
t.numericLiteral(slotFlag),
|
||||||
|
),
|
||||||
|
].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.identifier('default'),
|
||||||
t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, children))),
|
t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, [child]))),
|
||||||
),
|
),
|
||||||
...(slots ? (
|
|
||||||
t.isObjectExpression(slots)
|
|
||||||
? (slots! as t.ObjectExpression).properties
|
|
||||||
: [t.spreadElement(slots!)]
|
|
||||||
) : []),
|
|
||||||
optimize && t.objectProperty(
|
optimize && t.objectProperty(
|
||||||
t.identifier('_'),
|
t.identifier('_'),
|
||||||
t.numericLiteral(slotFlag),
|
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]))),
|
||||||
),
|
),
|
||||||
].filter(Boolean as any))
|
optimize && t.objectProperty(
|
||||||
: t.arrayExpression(children)
|
t.identifier('_'),
|
||||||
) : t.nullLiteral(),
|
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),
|
!!patchFlag && optimize && t.numericLiteral(patchFlag),
|
||||||
!!dynamicPropNames.size && optimize
|
!!dynamicPropNames.size && optimize
|
||||||
&& t.arrayExpression(
|
&& t.arrayExpression(
|
||||||
|
@ -127,9 +127,53 @@ exports[`override props single: single 1`] = `
|
|||||||
_createVNode(\\"div\\", a, null);"
|
_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 { 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';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
function _isSlot(s) {
|
||||||
|
return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !_isVNode(s);
|
||||||
|
}
|
||||||
|
|
||||||
let a = 1;
|
let a = 1;
|
||||||
const A = defineComponent({
|
const A = defineComponent({
|
||||||
setup(_, {
|
setup(_, {
|
||||||
@ -146,7 +190,7 @@ const _a = function () {
|
|||||||
return a;
|
return a;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
a = _createVNode(A, null, {
|
a = _createVNode(A, null, _isSlot(a) ? a : {
|
||||||
default: () => [_a],
|
default: () => [_a],
|
||||||
_: 2
|
_: 2
|
||||||
});"
|
});"
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
reactive, ref, defineComponent, CSSProperties, ComponentPublicInstance,
|
reactive,
|
||||||
|
ref,
|
||||||
|
defineComponent,
|
||||||
|
CSSProperties,
|
||||||
|
ComponentPublicInstance,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { shallowMount, mount, VueWrapper } from '@vue/test-utils';
|
import { shallowMount, mount, VueWrapper } from '@vue/test-utils';
|
||||||
|
|
||||||
@ -66,9 +70,7 @@ describe('Transform JSX', () => {
|
|||||||
|
|
||||||
const wrapper = mount({
|
const wrapper = mount({
|
||||||
render() {
|
render() {
|
||||||
return (
|
return <Child class="parent" foo={1} />;
|
||||||
<Child class="parent" foo={1} />
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.classes()).toStrictEqual([]);
|
expect(wrapper.classes()).toStrictEqual([]);
|
||||||
@ -123,7 +125,7 @@ describe('Transform JSX', () => {
|
|||||||
const wrapper = shallowMount({
|
const wrapper = shallowMount({
|
||||||
setup() {
|
setup() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return () => <div class="a" {...{ class: 'b' } } />;
|
return () => <div class="a" {...{ class: 'b' }} />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.classes().sort()).toEqual(['a', 'b'].sort());
|
expect(wrapper.classes().sort()).toEqual(['a', 'b'].sort());
|
||||||
@ -145,10 +147,12 @@ describe('Transform JSX', () => {
|
|||||||
const wrapper = shallowMount({
|
const wrapper = shallowMount({
|
||||||
setup() {
|
setup() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return () => <div { ...propsA } { ...propsB } />;
|
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', () => {
|
test('JSXSpreadChild', () => {
|
||||||
@ -259,7 +263,7 @@ describe('directive', () => {
|
|||||||
calls.push(1);
|
calls.push(1);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const wrapper = shallowMount(({
|
const wrapper = shallowMount({
|
||||||
directives: { custom: customDirective },
|
directives: { custom: customDirective },
|
||||||
setup() {
|
setup() {
|
||||||
return () => (
|
return () => (
|
||||||
@ -272,28 +276,28 @@ describe('directive', () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
const node = wrapper.vm.$.subTree;
|
const node = wrapper.vm.$.subTree;
|
||||||
expect(calls).toEqual(expect.arrayContaining([1]));
|
expect(calls).toEqual(expect.arrayContaining([1]));
|
||||||
expect(node.dirs).toHaveLength(1);
|
expect(node.dirs).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('vHtml', () => {
|
test('vHtml', () => {
|
||||||
const wrapper = shallowMount(({
|
const wrapper = shallowMount({
|
||||||
setup() {
|
setup() {
|
||||||
return () => <h1 v-html="<div>foo</div>"></h1>;
|
return () => <h1 v-html="<div>foo</div>"></h1>;
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
expect(wrapper.html()).toBe('<h1><div>foo</div></h1>');
|
expect(wrapper.html()).toBe('<h1><div>foo</div></h1>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('vText', () => {
|
test('vText', () => {
|
||||||
const text = 'foo';
|
const text = 'foo';
|
||||||
const wrapper = shallowMount(({
|
const wrapper = shallowMount({
|
||||||
setup() {
|
setup() {
|
||||||
return () => <div v-text={text}></div>;
|
return () => <div v-text={text}></div>;
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
expect(wrapper.html()).toBe('<div>foo</div>');
|
expect(wrapper.html()).toBe('<div>foo</div>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -315,7 +319,11 @@ describe('slots', () => {
|
|||||||
|
|
||||||
const wrapper = mount({
|
const wrapper = mount({
|
||||||
setup() {
|
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', () => {
|
test('without default', () => {
|
||||||
const A = defineComponent({
|
const A = defineComponent({
|
||||||
setup(_, { slots }) {
|
setup(_, { slots }) {
|
||||||
return () => (
|
return () => <div>{slots.foo?.('foo')}</div>;
|
||||||
<div>
|
|
||||||
{slots.foo?.('foo')}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -362,7 +366,11 @@ describe('PatchFlags', () => {
|
|||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
visible.value = false;
|
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 () => (
|
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} />;
|
const textarea = <textarea id="textarea" {...attrs} />;
|
||||||
return (
|
return (
|
||||||
<A inc={this.inc}>
|
<A inc={this.inc}>
|
||||||
<div>
|
<div>{textarea}</div>
|
||||||
{textarea}
|
|
||||||
</div>
|
|
||||||
<button id="button" onClick={this.inc}>+1</button>
|
<button id="button" onClick={this.inc}>+1</button>
|
||||||
</A>
|
</A>
|
||||||
);
|
);
|
||||||
@ -480,7 +488,6 @@ test('reassign variable as component should work', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// @ts-ignore
|
|
||||||
const _a2 = 2;
|
const _a2 = 2;
|
||||||
a = _a2;
|
a = _a2;
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
@ -495,3 +502,72 @@ test('reassign variable as component should work', () => {
|
|||||||
|
|
||||||
expect(wrapper.html()).toBe('<span>2</span>');
|
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 { transform } from '@babel/core';
|
||||||
import JSX, { Opts } from '../src';
|
import JSX, { Opts } from '../src';
|
||||||
|
|
||||||
|
interface Test {
|
||||||
|
name: string;
|
||||||
|
from: string;
|
||||||
|
}
|
||||||
|
|
||||||
const transpile = (
|
const transpile = (
|
||||||
source: string, options: Opts = {},
|
source: string, options: Opts = {},
|
||||||
) => new Promise((resolve, reject) => transform(
|
) => new Promise((resolve, reject) => transform(
|
||||||
@ -18,7 +23,7 @@ const transpile = (
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
const tests = [
|
const tests: Test[] = [
|
||||||
{
|
{
|
||||||
name: 'input[type="checkbox"]',
|
name: 'input[type="checkbox"]',
|
||||||
from: '<input type="checkbox" v-model={test} />',
|
from: '<input type="checkbox" v-model={test} />',
|
||||||
@ -155,7 +160,7 @@ tests.forEach((
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const overridePropsTests = [{
|
const overridePropsTests: Test[] = [{
|
||||||
name: 'single',
|
name: 'single',
|
||||||
from: '<div {...a} />',
|
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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user