UNPKG

@beqa/babel-plugin-transform-react-slots

Version:

The JSX to slot function transpilation plugin for babel

424 lines (381 loc) 13.1 kB
import babel from "@babel/core"; import { ANY_PROPERTY, getJSXElement, goThroughMemberExpression, getValuesFromVarDeclarator, } from "../src/utils"; describe("goThroughMemberExpression", () => { test("returns the first member expression when directly accessing the value; `pathToValue` becomes empty", () => { const ast = babel.parse(`let x = a.b.c`)!; babel.traverse(ast, { VariableDeclarator(path) { let pathToA: any; path.traverse({ Identifier(path) { if (path.node.name === "a") { pathToA = path; } }, }); const pathToValue = ["c", "b"]; const parent = goThroughMemberExpression(pathToA, pathToValue) as any; expect(parent.node.property.name).toBe("c"); expect(pathToValue).toEqual([]); }, }); }); test("`pathToValue` holds remaining property names when the expression is accessing an ancestor of the value", () => { const ast = babel.parse(`let x = a.b.c`)!; babel.traverse(ast, { VariableDeclarator(path) { let pathToA: any; path.traverse({ Identifier(path) { if (path.node.name === "a") { pathToA = path; } }, }); const pathToValue = ["d", "c", "b"]; goThroughMemberExpression(pathToA, pathToValue); expect(pathToValue).toEqual(["d"]); }, }); }); test("returns null when the expression goes deeper than specified; `pathToValue` becomes empty", () => { const ast = babel.parse(`let x = a.b.c.d`)!; babel.traverse(ast, { VariableDeclarator(path) { let pathToA: any; path.traverse({ Identifier(path) { if (path.node.name === "a") { pathToA = path; } }, }); const pathToValue = ["c", "b"]; const parent = goThroughMemberExpression(pathToA, pathToValue); expect(parent).toBeNull(); expect(pathToValue).toEqual([]); }, }); }); test("returns null when path is different than specified. The array holds remaining property names from the moment the paths diverged", () => { const ast = babel.parse(`let x = a.b.x.y`)!; babel.traverse(ast, { VariableDeclarator(path) { let pathToA: any; path.traverse({ Identifier(path) { if (path.node.name === "a") { pathToA = path; } }, }); const pathToValue = ["d", "c", "b"]; const parent = goThroughMemberExpression(pathToA, pathToValue); expect(parent).toBeNull(); expect(pathToValue).toEqual(["d", "c"]); }, }); }); test("accepts call expression nodes as arguments", () => { const ast = babel.parse(`let x = a().b.c`)!; babel.traverse(ast, { CallExpression(path) { const pathToValue = ["c", "b"]; const parent = goThroughMemberExpression(path, pathToValue) as any; expect(babel.types.isVariableDeclarator(parent?.parent)).toBe(true); expect(pathToValue).toEqual([]); }, }); }); test("accepts string literal property paths", () => { const ast = babel.parse(`let x = a["b"].c`)!; babel.traverse(ast, { VariableDeclarator(path) { let pathToA: any; path.traverse({ Identifier(path) { if (path.node.name === "a") { pathToA = path; } }, }); const pathToValue = ["c", "b"]; const parent = goThroughMemberExpression(pathToA, pathToValue); expect(babel.types.isVariableDeclarator(parent?.parent)).toBe(true); expect(pathToValue).toEqual([]); }, }); }); test("can match ANY_PROPERTY wildcard", () => { const ast = babel.parse(`let x = a.b.c.d`)!; babel.traverse(ast, { VariableDeclarator(path) { let pathToA: any; path.traverse({ Identifier(path) { if (path.node.name === "a") { pathToA = path; } }, }); const pathToValue = ["e", ANY_PROPERTY, "c", ANY_PROPERTY]; const parent = goThroughMemberExpression(pathToA, pathToValue); expect(babel.types.isVariableDeclarator(parent?.parent)).toBe(true); expect(pathToValue).toEqual(["e"]); }, }); }); }); describe("getValueFromVarDeclarator", () => { test("returns the identifier from the object pattern that holds the value", () => { const ast = babel.parse(`let { a: {b}, c } = literally.anything`)!; babel.traverse(ast, { VariableDeclarator(path) { const pathToValue = ["b", "a"]; for (const [identifier, newPathToValue] of getValuesFromVarDeclarator( path, pathToValue, )) { expect(identifier.node.name).toBe("b"); expect(newPathToValue).toEqual([]); } }, }); }); test("returns the identifier from the object pattern that's the ancestor of the value when pathToValue is longer", () => { const ast = babel.parse(`let { a, ...c } = [literally.anything]`)!; babel.traverse(ast, { VariableDeclarator(path) { const pathToValue = ["b", "a"]; for (const [identifier, newPathToValue] of getValuesFromVarDeclarator( path, pathToValue, )) { expect(identifier.node.name).toBe("a"); expect(newPathToValue).toEqual(["b"]); } }, }); }); test("returns the rest operator identifier when destructured properties don't match the path", () => { const ast = babel.parse( `let { a, b: {c, ...rest2}, ...rest1 } = [literallyAnything]`, )!; babel.traverse(ast, { VariableDeclarator(path) { const pathToValue = ["e", "d", "b"]; for (const [identifier, newPathToValue] of getValuesFromVarDeclarator( path, pathToValue, )) { expect(identifier.node.name).toBe("rest2"); expect(newPathToValue).toEqual(["e", "d"]); } }, }); }); test("returns the identifier with the new name", () => { const ast = babel.parse(`let { a, b: c } = ['literally.anything']`)!; babel.traverse(ast, { VariableDeclarator(path) { const pathToValue = ["d", "b"]; for (const [identifier, newPathToValue] of getValuesFromVarDeclarator( path, pathToValue, )) { expect(identifier.node.name).toBe("c"); expect(newPathToValue).toEqual(["d"]); } }, }); }); test("returns empty map when the paths have diverged", () => { const ast = babel.parse( `let { a: { b: { d } } } = ['literally.anything']`, )!; babel.traverse(ast, { VariableDeclarator(path) { const pathToValue = ["c", "b", "a"]; const identifiers = getValuesFromVarDeclarator(path, pathToValue); expect(identifiers).toEqual(new Map()); }, }); }); test("returns multiple identifiers when ANY_PROPERTY is used and can match multiple values", () => { const ast = babel.parse( `let { a: {b, c, d: e, ...rest} } = ['literally.anything']`, )!; babel.traverse(ast, { VariableDeclarator(path) { const pathToValue = [ANY_PROPERTY, "a"]; const identifiers = getValuesFromVarDeclarator(path, pathToValue); let iterator = identifiers.entries(); let currentEntry = iterator.next().value; expect(currentEntry[0].node.name).toBe("b"); expect(currentEntry[1]).toEqual([]); currentEntry = iterator.next().value; expect(currentEntry[0].node.name).toBe("c"); expect(currentEntry[1]).toEqual([]); currentEntry = iterator.next().value; expect(currentEntry[0].node.name).toBe("e"); expect(currentEntry[1]).toEqual([]); currentEntry = iterator.next().value; expect(currentEntry[0].node.name).toBe("rest"); expect(currentEntry[1]).toEqual([ANY_PROPERTY]); expect(iterator.next().done).toBe(true); }, }); }); test("returns multiple identifiers when object pattern declares the same value with different names", () => { const ast = babel.parse(`let { a, a:b, a: {c} } = ['literally.anything']`)!; babel.traverse(ast, { VariableDeclarator(path) { const pathToValue = ["c", "a"]; const identifiers = getValuesFromVarDeclarator(path, pathToValue); let iterator = identifiers.entries(); let currentEntry = iterator.next().value; expect(currentEntry[0].node.name).toBe("a"); expect(currentEntry[1]).toEqual(["c"]); currentEntry = iterator.next().value; expect(currentEntry[0].node.name).toBe("b"); expect(currentEntry[1]).toEqual(["c"]); currentEntry = iterator.next().value; expect(currentEntry[0].node.name).toBe("c"); expect(currentEntry[1]).toEqual([]); expect(iterator.next().done).toBe(true); }, }); }); }); describe("getJSXElement", () => { test("returns JSXElement when pathToSlottable exactly matches jsx element name", () => { const ast = babel.parse( ` <a.b.c>children</a.b.c>; <d.e />; <f></f>; `, { plugins: ["@babel/syntax-jsx"] }, )!; babel.traverse(ast, { JSXOpeningElement(path) { path.traverse({ JSXIdentifier(path) { switch (path.node.name) { case "a": { let pathToSlottable = ["c", "b"]; let el = getJSXElement(path, pathToSlottable); expect(babel.types.isJSXElement(el!.node)).toBe(true); expect(pathToSlottable).toEqual([]); break; } case "d": { let pathToSlottable = [ANY_PROPERTY]; let el = getJSXElement(path, pathToSlottable); expect(babel.types.isJSXElement(el!.node)).toBe(true); expect(pathToSlottable).toEqual([]); break; } case "f": { let pathToSlottable: any = []; let el = getJSXElement(path, pathToSlottable); expect(babel.types.isJSXElement(el!.node)).toBe(true); expect(pathToSlottable).toEqual([]); break; } } }, }); }, }); }); test("returns null when performing a match on jSXClosingElement", () => { const ast = babel.parse(`<a.b.c>children</a.b.c>;`, { plugins: ["@babel/syntax-jsx"], })!; babel.traverse(ast, { JSXClosingElement(path) { path.traverse({ JSXIdentifier(path) { if (path.node.name === "a") { let pathToSlottable = ["c", "b"]; let el = getJSXElement(path, pathToSlottable); expect(el).toBeNull(); expect(pathToSlottable).toEqual([]); } }, }); }, }); }); test("returns null when JSXMemberExpression goes deeper than specified in pathToSlottable", () => { const ast = babel.parse(`<a.b.c.d>children</a.b.c.d>;`, { plugins: ["@babel/syntax-jsx"], })!; babel.traverse(ast, { JSXOpeningElement(path) { path.traverse({ JSXIdentifier(path) { if (path.node.name === "a") { let pathToSlottable = ["c", "b"]; let el = getJSXElement(path, pathToSlottable); expect(el).toBeNull(); expect(pathToSlottable).toEqual([]); } }, }); }, }); }); test("returns null when JSXMemberExpression doesn't match pathToSlottable", () => { const ast = babel.parse(`<a.b.c>children</a.b.c>;`, { plugins: ["@babel/syntax-jsx"], })!; babel.traverse(ast, { JSXOpeningElement(path) { path.traverse({ JSXIdentifier(path) { if (path.node.name === "a") { let pathToSlottable = ["d", "e", "f"]; let el = getJSXElement(path, pathToSlottable); expect(el).toBeNull(); expect(pathToSlottable).toEqual(["d", "e", "f"]); } }, }); }, }); }); test("returns null when JSXMemberExpression ends earlier than specified in pathToSlottable", () => { const ast = babel.parse(`<a.b>children</a.b>;`, { plugins: ["@babel/syntax-jsx"], })!; babel.traverse(ast, { JSXOpeningElement(path) { path.traverse({ JSXIdentifier(path) { if (path.node.name === "a") { let pathToSlottable = ["c", "b"]; let el = getJSXElement(path, pathToSlottable); expect(el).toBeNull(); expect(pathToSlottable).toEqual(["c"]); } }, }); }, }); }); }); // test.only("test", () => { // const code = babel.transformSync( // `import * as A from "@beqa/react-slots"; x(A.useSlot);`, // { // plugins: [plugin], // } // ); // });