diff --git a/packages/uni-app-uts/__tests__/android/transforms/transformElement.spec.ts b/packages/uni-app-uts/__tests__/android/transforms/transformElement.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f41b94c601a231abb2cde92e340315834a4d87b --- /dev/null +++ b/packages/uni-app-uts/__tests__/android/transforms/transformElement.spec.ts @@ -0,0 +1,1291 @@ +import { PatchFlags } from '@vue/shared' +import { + RESOLVE_COMPONENT, + CREATE_VNODE, + MERGE_PROPS, + RESOLVE_DIRECTIVE, + TO_HANDLERS, + helperNameMap, + TELEPORT, + RESOLVE_DYNAMIC_COMPONENT, + SUSPENSE, + KEEP_ALIVE, + BASE_TRANSITION, + NORMALIZE_CLASS, + NORMALIZE_STYLE, + NORMALIZE_PROPS, + GUARD_REACTIVE_PROPS, + baseParse as parse, + BindingTypes, +} from '@vue/compiler-core' +import { + NodeTypes, + createObjectProperty, + DirectiveNode, + RootNode, + VNodeCall, +} from '@vue/compiler-core' +import { transformElement as baseTransformElement } from '@vue/compiler-core' +import { compile as baseCompile } from '../../../src/plugins/android/uvue/compiler' +import { transformStyle } from '../../../src/plugins/android/uvue/compiler/transforms/transformStyle' +import { transformOn } from '../../../src/plugins/android/uvue/compiler/transforms/vOn' +import { transformBind } from '../../../src/plugins/android/uvue/compiler/transforms/vBind' + +import { createObjectMatcher, genFlagText } from '../testUtils' +import { transformText } from '../../../src/plugins/android/uvue/compiler/transforms/transformText' +import { CompilerOptions } from '../../../src/plugins/android/uvue/compiler/options' +import { + NodeTransform, + transform, +} from '../../../src/plugins/android/uvue/compiler/transform' +import { transformExpression } from '../../../src/plugins/android/uvue/compiler/transforms/transformExpression' + +const transformElement = baseTransformElement as unknown as NodeTransform + +function parseWithElementTransform( + template: string, + options: CompilerOptions = {} +): { + root: RootNode + node: VNodeCall +} { + // wrap raw template in an extra view so that it doesn't get turned into a + // block as root node + const ast = parse(`${template}`, options) + transform(ast, { + nodeTransforms: [transformElement, transformText], + ...options, + }) + const codegenNode = (ast as any).children[0].children[0] + .codegenNode as VNodeCall + expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL) + return { + root: ast, + node: codegenNode, + } +} + +function parseWithBind(template: string, options?: CompilerOptions) { + return parseWithElementTransform(template, { + ...options, + directiveTransforms: { + ...options?.directiveTransforms, + bind: transformBind, + }, + }) +} + +describe('compiler: element transform', () => { + test('import + resolve component', () => { + const { root } = parseWithElementTransform(``) + expect(root.helpers).toContain(RESOLVE_COMPONENT) + expect(root.components).toContain(`Foo`) + }) + + test('resolve implicitly self-referencing component', () => { + const { root } = parseWithElementTransform(``, { + filename: `/foo/bar/Example.vue?vue&type=template`, + }) + expect(root.helpers).toContain(RESOLVE_COMPONENT) + expect(root.components).toContain(`Example__self`) + }) + + test('resolve component from setup bindings', () => { + const { root, node } = parseWithElementTransform(``, { + bindingMetadata: { + Example: BindingTypes.SETUP_MAYBE_REF, + }, + }) + expect(root.helpers).not.toContain(RESOLVE_COMPONENT) + expect(node.tag).toBe(`$setup["Example"]`) + }) + + // test('resolve component from setup bindings (inline)', () => { + // const { root, node } = parseWithElementTransform(``, { + // inline: true, + // bindingMetadata: { + // Example: BindingTypes.SETUP_MAYBE_REF, + // }, + // }) + // expect(root.helpers).not.toContain(RESOLVE_COMPONENT) + // expect(node.tag).toBe(`_unref(Example)`) + // }) + + // test('resolve component from setup bindings (inline const)', () => { + // const { root, node } = parseWithElementTransform(``, { + // inline: true, + // bindingMetadata: { + // Example: BindingTypes.SETUP_CONST, + // }, + // }) + // expect(root.helpers).not.toContain(RESOLVE_COMPONENT) + // expect(node.tag).toBe(`Example`) + // }) + + test('resolve namespaced component from setup bindings', () => { + const { root, node } = parseWithElementTransform(``, { + bindingMetadata: { + Foo: BindingTypes.SETUP_MAYBE_REF, + }, + }) + expect(root.helpers).not.toContain(RESOLVE_COMPONENT) + expect(node.tag).toBe(`$setup["Foo"].Example`) + }) + + // test('resolve namespaced component from setup bindings (inline)', () => { + // const { root, node } = parseWithElementTransform(``, { + // inline: true, + // bindingMetadata: { + // Foo: BindingTypes.SETUP_MAYBE_REF, + // }, + // }) + // expect(root.helpers).not.toContain(RESOLVE_COMPONENT) + // expect(node.tag).toBe(`_unref(Foo).Example`) + // }) + + // test('resolve namespaced component from setup bindings (inline const)', () => { + // const { root, node } = parseWithElementTransform(``, { + // inline: true, + // bindingMetadata: { + // Foo: BindingTypes.SETUP_CONST, + // }, + // }) + // expect(root.helpers).not.toContain(RESOLVE_COMPONENT) + // expect(node.tag).toBe(`Foo.Example`) + // }) + + test('do not resolve component from non-script-setup bindings', () => { + const bindingMetadata = { + Example: BindingTypes.SETUP_MAYBE_REF, + } + Object.defineProperty(bindingMetadata, '__isScriptSetup', { value: false }) + const { root } = parseWithElementTransform(``, { + bindingMetadata, + }) + expect(root.helpers).toContain(RESOLVE_COMPONENT) + expect(root.components).toContain(`Example`) + }) + + test('static props', () => { + const { node } = parseWithElementTransform(``) + expect(node).toMatchObject({ + tag: `"view"`, + props: createObjectMatcher({ + id: 'foo', + class: 'bar', + }), + children: undefined, + }) + }) + + test('props + children', () => { + const { node } = parseWithElementTransform(``) + + expect(node).toMatchObject({ + tag: `"view"`, + props: createObjectMatcher({ + id: 'foo', + }), + children: [ + { + type: NodeTypes.ELEMENT, + tag: 'text', + codegenNode: { + type: NodeTypes.VNODE_CALL, + tag: `"text"`, + }, + }, + ], + }) + }) + + test('0 placeholder for children with no props', () => { + const { node } = parseWithElementTransform(``) + + expect(node).toMatchObject({ + tag: `"view"`, + props: undefined, + children: [ + { + type: NodeTypes.ELEMENT, + tag: 'text', + codegenNode: { + type: NodeTypes.VNODE_CALL, + tag: `"text"`, + }, + }, + ], + }) + }) + + test('v-bind="obj"', () => { + const { root, node } = parseWithElementTransform(``) + // single v-bind doesn't need mergeProps + expect(root.helpers).not.toContain(MERGE_PROPS) + expect(root.helpers).toContain(NORMALIZE_PROPS) + expect(root.helpers).toContain(GUARD_REACTIVE_PROPS) + + // should directly use `obj` in props position + expect(node.props).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: NORMALIZE_PROPS, + arguments: [ + { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: GUARD_REACTIVE_PROPS, + arguments: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `obj`, + }, + ], + }, + ], + }) + }) + + test('v-bind="obj" after static prop', () => { + const { root, node } = parseWithElementTransform( + `` + ) + expect(root.helpers).toContain(MERGE_PROPS) + + expect(node.props).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: MERGE_PROPS, + arguments: [ + createObjectMatcher({ + id: 'foo', + }), + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `obj`, + }, + ], + }) + }) + + test('v-bind="obj" before static prop', () => { + const { root, node } = parseWithElementTransform( + `` + ) + expect(root.helpers).toContain(MERGE_PROPS) + + expect(node.props).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: MERGE_PROPS, + arguments: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `obj`, + }, + createObjectMatcher({ + id: 'foo', + }), + ], + }) + }) + + test('v-bind="obj" between static props', () => { + const { root, node } = parseWithElementTransform( + `` + ) + expect(root.helpers).toContain(MERGE_PROPS) + + expect(node.props).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: MERGE_PROPS, + arguments: [ + createObjectMatcher({ + id: 'foo', + }), + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `obj`, + }, + createObjectMatcher({ + class: 'bar', + }), + ], + }) + }) + + test('v-on="obj"', () => { + const { root, node } = parseWithElementTransform( + `` + ) + expect(root.helpers).toContain(MERGE_PROPS) + + expect(node.props).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: MERGE_PROPS, + arguments: [ + createObjectMatcher({ + id: 'foo', + }), + { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: TO_HANDLERS, + arguments: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `obj`, + }, + `true`, + ], + }, + createObjectMatcher({ + class: 'bar', + }), + ], + }) + }) + + test('v-on="obj" on component', () => { + const { root, node } = parseWithElementTransform( + `` + ) + expect(root.helpers).toContain(MERGE_PROPS) + + expect(node.props).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: MERGE_PROPS, + arguments: [ + createObjectMatcher({ + id: 'foo', + }), + { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: TO_HANDLERS, + arguments: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `obj`, + }, + ], + }, + createObjectMatcher({ + class: 'bar', + }), + ], + }) + }) + + test('v-on="obj" + v-bind="obj"', () => { + const { root, node } = parseWithElementTransform( + `` + ) + expect(root.helpers).toContain(MERGE_PROPS) + + expect(node.props).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: MERGE_PROPS, + arguments: [ + createObjectMatcher({ + id: 'foo', + }), + { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: TO_HANDLERS, + arguments: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `handlers`, + }, + `true`, + ], + }, + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `obj`, + }, + ], + }) + }) + + test('should handle plain