diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSkip.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSkip.spec.ts.snap new file mode 100644 index 00000000000..79ddaa94d2c --- /dev/null +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSkip.spec.ts.snap @@ -0,0 +1,407 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`compiler: v-skip > transform > basic 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { createCommentVNode: _createCommentVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + return (_ctx.ok) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("div", { key: 1 })) + } +}" +`; + +exports[`compiler: v-skip > transform > nested v-skip 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { createCommentVNode: _createCommentVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, Fragment: _Fragment } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [ + (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("span", { key: 1 })) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : (_openBlock(), _createElementBlock("div", { key: 1 }, [ + (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("span", { key: 1 })) + ])) + } +}" +`; + +exports[`compiler: v-skip > transform > on Teleport 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, createCommentVNode: _createCommentVNode, Teleport: _Teleport, openBlock: _openBlock, createBlock: _createBlock } = _Vue + + return (_ctx.ok) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createBlock(_Teleport, { + key: 1, + to: "target" + })) + } +}" +`; + +exports[`compiler: v-skip > transform > on component with default slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, resolveComponent: _resolveComponent, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, ["foo"], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : (_openBlock(), _createBlock(_component_Comp, { key: 1 }, { + default: _withCtx(() => ["foo"]), + _: 1 /* STABLE */ + })) + } +}" +`; + +exports[`compiler: v-skip > transform > on component with dynamic slot + default slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, resolveComponent: _resolveComponent, resolveSkipComponent: _resolveSkipComponent, openBlock: _openBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_openBlock(), _createBlock(_resolveSkipComponent(_ctx.ok, _component_Comp), null, { + [_ctx.foo]: _withCtx(() => ["foo"]), + default: _withCtx(() => ["default"]), + _: 2 /* DYNAMIC */ + }, 1024 /* DYNAMIC_SLOTS */)) + } +}" +`; + +exports[`compiler: v-skip > transform > on component with dynamic slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, resolveSkipComponent: _resolveSkipComponent, openBlock: _openBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_openBlock(), _createBlock(_resolveSkipComponent(_ctx.ok, _component_Comp), null, { + [_ctx.foo]: _withCtx(() => [_toDisplayString(_ctx.foo)]), + _: 2 /* DYNAMIC */ + }, 1024 /* DYNAMIC_SLOTS */)) + } +}" +`; + +exports[`compiler: v-skip > transform > on component with implicit default slot + v-if 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, resolveComponent: _resolveComponent, Fragment: _Fragment, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [ + (_ctx.yes) + ? (_openBlock(), _createElementBlock("span", { key: 0 }, _toDisplayString(_ctx.default), 1 /* TEXT */)) + : _createCommentVNode("v-if", true) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : (_openBlock(), _createBlock(_component_Comp, { key: 1 }, { + default: _withCtx(() => [ + (_ctx.yes) + ? (_openBlock(), _createElementBlock("span", { key: 0 }, _toDisplayString(_ctx.default), 1 /* TEXT */)) + : _createCommentVNode("v-if", true) + ]), + _: 1 /* STABLE */ + })) + } +}" +`; + +exports[`compiler: v-skip > transform > on component with multiple implicit default slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, createElementVNode: _createElementVNode, toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [ + _createElementVNode("span"), + _createElementVNode("div") + ], 64 /* STABLE_FRAGMENT */)) + : (_openBlock(), _createBlock(_component_Comp, { key: 1 }, { + foo: _withCtx(() => [_toDisplayString(_ctx.foo)]), + default: _withCtx(() => [ + _createElementVNode("span"), + _createElementVNode("div") + ]), + _: 1 /* STABLE */ + })) + } +}" +`; + +exports[`compiler: v-skip > transform > on component with multiple named slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, resolveComponent: _resolveComponent, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, ["default"], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : (_openBlock(), _createBlock(_component_Comp, { key: 1 }, { + default: _withCtx(() => ["default"]), + foo: _withCtx(() => ["foo"]), + _: 1 /* STABLE */ + })) + } +}" +`; + +exports[`compiler: v-skip > transform > on component with name default slot + v-if 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, createSlots: _createSlots, toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, resolveSkipComponent: _resolveSkipComponent, openBlock: _openBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_openBlock(), _createBlock(_resolveSkipComponent(_ctx.ok, _component_Comp), null, _createSlots({ _: 2 /* DYNAMIC */ }, [ + (_ctx.yes) + ? { + name: "default", + fn: _withCtx(() => [_toDisplayString(_ctx.default)]), + key: "0" + } + : undefined + ]), 1024 /* DYNAMIC_SLOTS */)) + } +}" +`; + +exports[`compiler: v-skip > transform > on component without slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, createCommentVNode: _createCommentVNode, resolveComponent: _resolveComponent, openBlock: _openBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createBlock(_component_Comp, { key: 1 })) + } +}" +`; + +exports[`compiler: v-skip > transform > on dynamic component with default slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, resolveDynamicComponent: _resolveDynamicComponent, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createElementBlock: _createElementBlock } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, ["foo"], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : (_openBlock(), _createBlock(_resolveDynamicComponent(_ctx.Comp), { key: 1 }, { + default: _withCtx(() => ["foo"]), + _: 1 /* STABLE */ + })) + } +}" +`; + +exports[`compiler: v-skip > transform > on dynamic component with dynamic slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, resolveDynamicComponent: _resolveDynamicComponent, openBlock: _openBlock, createBlock: _createBlock, resolveSkipComponent: _resolveSkipComponent } = _Vue + + return (_openBlock(), _createBlock(_resolveSkipComponent(_ctx.ok, _resolveDynamicComponent(_ctx.Comp)), null, { + [_ctx.foo]: _withCtx(() => ["foo"]), + _: 2 /* DYNAMIC */ + }, 1024 /* DYNAMIC_SLOTS */)) + } +}" +`; + +exports[`compiler: v-skip > transform > v-else + v-skip 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock("div", { key: 0 })) + : (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [ + (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("div", { key: 1 })) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + } +}" +`; + +exports[`compiler: v-skip > transform > v-else-if + v-skip 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock("div", { key: 0 })) + : (_ctx.yes) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [ + (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("div", { key: 1 })) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : _createCommentVNode("v-if", true) + } +}" +`; + +exports[`compiler: v-skip > transform > v-if + v-skip 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { createCommentVNode: _createCommentVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, Fragment: _Fragment } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [ + (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("div", { key: 1 })) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : _createCommentVNode("v-if", true) + } +}" +`; + +exports[`compiler: v-skip > transform > v-skip with key 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { createCommentVNode: _createCommentVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + return (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("div", { key: "foo" })) + } +}" +`; + +exports[`compiler: v-skip > transform > with component children 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { resolveComponent: _resolveComponent, openBlock: _openBlock, createBlock: _createBlock, createElementBlock: _createElementBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createBlock(_component_Comp, { key: 0 })) + : (_openBlock(), _createElementBlock("div", { key: 1 }, [ + (_openBlock(), _createBlock(_component_Comp, { key: 0 })) + ])) + } +}" +`; + +exports[`compiler: v-skip > transform > with element children 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock("span", { key: 0 })) + : (_openBlock(), _createElementBlock("div", { key: 1 }, [ + (_openBlock(), _createElementBlock("span", { key: 0 })) + ])) + } +}" +`; + +exports[`compiler: v-skip > transform > with multiple children 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [ + _createElementVNode("span"), + _createVNode(_component_Comp) + ], 64 /* STABLE_FRAGMENT */)) + : (_openBlock(), _createElementBlock("div", { key: 1 }, [ + _createElementVNode("span"), + _createVNode(_component_Comp) + ])) + } +}" +`; + +exports[`compiler: v-skip > transform > with text children 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { toDisplayString: _toDisplayString, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [_toDisplayString(_ctx.foo)], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : (_openBlock(), _createElementBlock("div", { key: 1 }, _toDisplayString(_ctx.foo), 1 /* TEXT */)) + } +}" +`; + +exports[`compiler: v-skip > transform > with v-memo 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { openBlock: _openBlock, createElementBlock: _createElementBlock, withMemo: _withMemo } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock("span", { key: 0 })) + : _withMemo([1], () => (_openBlock(), _createElementBlock("div", { key: 1 }, [ + (_openBlock(), _createElementBlock("span", { key: 0 })) + ])), _cache, 0) + } +}" +`; diff --git a/packages/compiler-core/__tests__/transforms/vSkip.spec.ts b/packages/compiler-core/__tests__/transforms/vSkip.spec.ts new file mode 100644 index 00000000000..b352ecf3f46 --- /dev/null +++ b/packages/compiler-core/__tests__/transforms/vSkip.spec.ts @@ -0,0 +1,552 @@ +import { + type CallExpression, + type CompilerOptions, + type ComponentNode, + type ElementNode, + ElementTypes, + ErrorCodes, + type IfBranchNode, + type IfNode, + NodeTypes, + RESOLVE_SKIP_COMPONENT, + type RootNode, + type SimpleExpressionNode, + type SkipNode, + type VNodeCall, + WITH_MEMO, + generate, + baseParse as parse, + transform, + transformElement, + transformExpression, +} from '@vue/compiler-core' +import { transformIf } from '../../src/transforms/vIf' +import { transformFor } from '../../src/transforms/vFor' +import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet' +import { transformSkip } from '../../src/transforms/vSkip' +import { transformMemo } from '../../src/transforms/vMemo' + +export function parseWithSkipTransform( + template: string, + options: CompilerOptions = { prefixIdentifiers: true }, +): { + root: RootNode + node: SkipNode | ComponentNode +} { + const ast = parse(template, options) + transform(ast, { + nodeTransforms: [ + transformIf, + transformSkip, + transformMemo, + transformFor, + transformExpression, + transformSlotOutlet, + transformElement, + ], + ...options, + }) + return { + root: ast, + node: ast.children[0] as SkipNode | ComponentNode, + } +} + +describe('compiler: v-skip', () => { + describe('transform', () => { + test('basic', () => { + const { root, node } = parseWithSkipTransform(`
`) as { + root: RootNode + node: SkipNode + } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect(node.consequent.type === NodeTypes.JS_CALL_EXPRESSION).toBe(true) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('with text children', () => { + const { root, node } = parseWithSkipTransform( + `
{{foo}}
`, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.INTERPOLATION, + ) + expect( + ( + ((node.consequent as IfBranchNode).children[0] as any) + .content as SimpleExpressionNode + ).content, + ).toBe(`_ctx.foo`) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('with element children', () => { + const { root, node } = parseWithSkipTransform( + `
`, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as ElementNode).tag, + ).toBe(`span`) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('with component children', () => { + const { root, node } = parseWithSkipTransform( + `
`, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as ElementNode).tag, + ).toBe(`Comp`) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('with multiple children', () => { + const { root, node } = parseWithSkipTransform( + `
`, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(2) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as ElementNode).tag, + ).toBe(`span`) + expect((node.consequent as IfBranchNode).children[1].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[1] as ElementNode).tag, + ).toBe(`Comp`) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('nested v-skip', () => { + const { root, node } = parseWithSkipTransform( + `
`, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.SKIP, + ) + expect( + ( + ((node.consequent as IfBranchNode).children[0] as SkipNode) + .test as SimpleExpressionNode + ).content, + ).toBe(`_ctx.nested`) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + const nestedNode = (node.consequent as IfBranchNode) + .children[0] as SkipNode + expect(nestedNode.type).toBe(NodeTypes.SKIP) + expect((nestedNode.test as SimpleExpressionNode).content).toBe( + `_ctx.nested`, + ) + expect(nestedNode.consequent.type === NodeTypes.JS_CALL_EXPRESSION).toBe( + true, + ) + expect(nestedNode.alternate.children.length).toBe(1) + expect(nestedNode.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((nestedNode.alternate.children[0] as ElementNode).tag).toBe(`span`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('v-if + v-skip', () => { + const { root, node } = parseWithSkipTransform( + `
`, + ) + expect(node.type).toBe(NodeTypes.IF) + const ifNode = node as unknown as IfNode + const branch = ifNode.branches[0] + expect((branch.condition as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect(branch.children.length).toBe(1) + // skipNode + expect(branch.children[0].type).toBe(NodeTypes.SKIP) + expect( + ((branch.children[0] as SkipNode).test as SimpleExpressionNode).content, + ).toBe(`_ctx.nested`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('v-skip with key', () => { + const { root, node } = parseWithSkipTransform( + `
`, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.nested`) + expect(node.consequent.type === NodeTypes.JS_CALL_EXPRESSION).toBe(true) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect( + (node.alternate.children[0] as ElementNode).props[0], + ).toMatchObject({ + name: 'key', + type: NodeTypes.ATTRIBUTE, + value: { + content: 'foo', + }, + }) + expect(generate(root).code).toMatchSnapshot() + }) + + test('v-else + v-skip', () => { + const { root, node } = parseWithSkipTransform( + `
`, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.IF) + const elseNode = node as unknown as IfNode + const branch = elseNode.branches[1] + expect(branch.children.length).toBe(1) + // skipNode + expect(branch.children[0].type).toBe(NodeTypes.SKIP) + expect( + ((branch.children[0] as SkipNode).test as SimpleExpressionNode).content, + ).toBe(`_ctx.nested`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('v-else-if + v-skip', () => { + const { root, node } = parseWithSkipTransform( + `
`, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.IF) + const elseIfNode = node as unknown as IfNode + const branch = elseIfNode.branches[1] + expect((branch.condition as SimpleExpressionNode).content).toBe( + `_ctx.yes`, + ) + expect(branch.children.length).toBe(1) + // skipNode + expect(branch.children[0].type).toBe(NodeTypes.SKIP) + expect( + ((branch.children[0] as SkipNode).test as SimpleExpressionNode).content, + ).toBe(`_ctx.nested`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('with v-memo', () => { + const { root, node } = parseWithSkipTransform( + `
`, + ) as { + root: RootNode + node: SkipNode + } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + const codegenNode = (node.alternate.children[0] as ElementNode) + .codegenNode! + expect(codegenNode.type).toBe(NodeTypes.JS_CALL_EXPRESSION) + expect((codegenNode as any).callee).toBe(WITH_MEMO) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on component without slot', () => { + // equivalent to + const { root, node } = parseWithSkipTransform(``) as { + root: RootNode + node: SkipNode + } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect(node.consequent.type === NodeTypes.JS_CALL_EXPRESSION).toBe(true) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`Comp`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on component with default slot', () => { + const { root, node } = parseWithSkipTransform( + `foo`, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.TEXT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as any).content, + ).toBe(`foo`) + expect(node.alternate.children.length).toBe(1) + expect((node.alternate.children[0] as ElementNode).tagType).toBe( + ElementTypes.COMPONENT, + ) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`Comp`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on component with multiple named slot', () => { + const { root, node } = parseWithSkipTransform( + ` + + + `, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.TEXT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as any).content, + ).toBe(`default`) + expect(node.alternate.children.length).toBe(1) + expect((node.alternate.children[0] as ElementNode).tagType).toBe( + ElementTypes.COMPONENT, + ) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`Comp`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on component with multiple implicit default slot', () => { + const { root, node } = parseWithSkipTransform( + ` + + +
+ `, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(2) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as ElementNode).tag, + ).toBe(`span`) + expect((node.consequent as IfBranchNode).children[1].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[1] as ElementNode).tag, + ).toBe(`div`) + expect(node.alternate.children.length).toBe(1) + expect((node.alternate.children[0] as ElementNode).tagType).toBe( + ElementTypes.COMPONENT, + ) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`Comp`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on component with name default slot + v-if', () => { + const { root, node } = parseWithSkipTransform( + ` + + `, + ) as { root: RootNode; node: ComponentNode } + expect(node.type).toBe(NodeTypes.ELEMENT) + expect(node.tagType).toBe(ElementTypes.COMPONENT) + const codegenNode = node.codegenNode! as VNodeCall + expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL) + const vnodeTag = codegenNode.tag as CallExpression + expect(vnodeTag.type).toBe(NodeTypes.JS_CALL_EXPRESSION) + expect(vnodeTag.callee).toBe(RESOLVE_SKIP_COMPONENT) + expect((vnodeTag.arguments[0] as SimpleExpressionNode).content).toBe( + `_ctx.ok`, + ) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on component with implicit default slot + v-if', () => { + const { root, node } = parseWithSkipTransform( + ` + {{default}} + `, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on component with dynamic slot', () => { + const { root, node } = parseWithSkipTransform( + ` + + `, + ) as { root: RootNode; node: ComponentNode } + expect(node.type).toBe(NodeTypes.ELEMENT) + expect(node.tagType).toBe(ElementTypes.COMPONENT) + const codegenNode = node.codegenNode! as VNodeCall + expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL) + const vnodeTag = codegenNode.tag as CallExpression + expect(vnodeTag.type).toBe(NodeTypes.JS_CALL_EXPRESSION) + expect(vnodeTag.callee).toBe(RESOLVE_SKIP_COMPONENT) + expect((vnodeTag.arguments[0] as SimpleExpressionNode).content).toBe( + `_ctx.ok`, + ) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on component with dynamic slot + default slot', () => { + const { root, node } = parseWithSkipTransform( + ` + + + `, + ) as { root: RootNode; node: ComponentNode } + expect(node.type).toBe(NodeTypes.ELEMENT) + expect(node.tagType).toBe(ElementTypes.COMPONENT) + const codegenNode = node.codegenNode! as VNodeCall + expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL) + const vnodeTag = codegenNode.tag as CallExpression + expect(vnodeTag.type).toBe(NodeTypes.JS_CALL_EXPRESSION) + expect(vnodeTag.callee).toBe(RESOLVE_SKIP_COMPONENT) + expect((vnodeTag.arguments[0] as SimpleExpressionNode).content).toBe( + `_ctx.ok`, + ) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on dynamic component with default slot', () => { + const { root, node } = parseWithSkipTransform( + `foo`, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.TEXT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as any).content, + ).toBe(`foo`) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`component`) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on dynamic component with dynamic slot', () => { + const { root, node } = parseWithSkipTransform( + ` + + `, + ) as { root: RootNode; node: ComponentNode } + expect(node.type).toBe(NodeTypes.ELEMENT) + expect(node.tagType).toBe(ElementTypes.COMPONENT) + const codegenNode = node.codegenNode! as VNodeCall + expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL) + const vnodeTag = codegenNode.tag as CallExpression + expect(vnodeTag.type).toBe(NodeTypes.JS_CALL_EXPRESSION) + expect(vnodeTag.callee).toBe(RESOLVE_SKIP_COMPONENT) + expect((vnodeTag.arguments[0] as SimpleExpressionNode).content).toBe( + `_ctx.ok`, + ) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on Teleport', () => { + const { root, node } = parseWithSkipTransform( + ``, + ) as { root: RootNode; node: SkipNode } + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect(node.consequent.type === NodeTypes.JS_CALL_EXPRESSION).toBe(true) + expect(node.alternate.children.length).toBe(1) + expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`teleport`) + expect(generate(root).code).toMatchSnapshot() + }) + }) + + describe('errors', () => { + test('no expression', () => { + const onError = vi.fn() + const { node } = parseWithSkipTransform(`
`, { onError }) + expect(onError.mock.calls[0]).toMatchObject([ + { + code: ErrorCodes.X_V_SKIP_NO_EXPRESSION, + loc: node.loc, + }, + ]) + }) + + test('on