Files
bun.sh/test/bundler/bundler_string.test.ts

222 lines
8.3 KiB
TypeScript

import { describe } from "bun:test";
import { dedent, itBundled } from "./expectBundled";
interface TemplateStringTest {
expr: string;
print?: string | boolean; // expect stdout
capture?: string | boolean; // expect literal transpilation
captureRaw?: string; // expect raw transpilation
}
const templateStringTests: Record<string, TemplateStringTest> = {
// note for writing tests: .print is .trim()'ed due to how run.stdout works
Empty: { expr: '""', captureRaw: '""' },
NullByte: { expr: '"hello\0"', captureRaw: '"hello\\x00"' },
EmptyTemplate: { expr: "``", captureRaw: '""' },
ConstantTemplate: { expr: "`asdf`", captureRaw: '"asdf"' },
AddConstant: { expr: "`${7 + 6}`", capture: true },
AddConstant2: { expr: "`${7 + 6 + 96}`", capture: true },
AddConstant3: { expr: "`${0.1 + 0.2}`", print: true },
SubtractConstant: { expr: "`${7 - 6}`", capture: true },
SubtractConstant2: { expr: "`${7 - 6 - 10}`", capture: true },
MultiplyConstant: { expr: "`${7 * 6}`", capture: true },
MultiplyConstant2: { expr: "`${7 * 6 * 2}`", capture: true },
MultiplyConstant3: { expr: "`${7.5 * 6.02}`", print: true },
DivideConstant: { expr: "`${7 / 6}`", print: true },
DivideConstant2: { expr: "`${7 / 6 / 2}`", print: true },
DivideConstant3: { expr: "`${7.5 / 6.02}`", print: true },
Exponent1: { expr: "`${1e0}`", capture: true },
Exponent2: { expr: "`${1e1}`", capture: true },
Exponent3: { expr: "`${0e1337}`", capture: true },
Exponent4: { expr: "`${-1e0}`", capture: true },
BigExponent1: { expr: "`${1e20}`", print: "100000000000000000000" },
BigExponent2: { expr: "`${1e21}`", print: "1e+21" },
BigNumber1: { expr: "`${999999999999999934463.9999999}`", print: "999999999999999900000" },
BigNumber2: { expr: "`${999999999999999934464.0000000}`", print: "1e+21" },
True: { expr: "`${true}`", capture: true },
False: { expr: "`${false}`", capture: true },
BigInt: { expr: "`${1n}`", print: "1" },
LongBigInt: {
expr: "`${-" + "1234".repeat(1000) + "n}`",
print: "-" + "1234".repeat(1000),
},
BigIntAdd: { expr: "`${1n + 2n}`", print: "3" },
BigIntSubtract: { expr: "`${1n - 2n}`", print: "-1" },
BigIntMultiply: { expr: "`${2n * 3n}`", print: "6" },
BigIntDivide: { expr: "`${6n / 2n}`", print: "3" },
BigIntModulo: { expr: "`${6n % 4n}`", print: "2" },
BigIntExponent: { expr: "`${2n ** 3n}`", print: "8" },
ArrowFunction: { expr: "`${() => 123}`", captureRaw: "`${()=>123}`" },
Function: { expr: "`${function() { return 123; }}`", captureRaw: "`${function(){return 123}}`" },
Identifier: { expr: "`${ident}`", captureRaw: "`${ident}`" },
IdentifierAdd: { expr: "`${ident + ident}`", captureRaw: "`${ident+ident}`" },
IdentifierConstAdd: { expr: "`${2 + ident}`", captureRaw: "`${2+ident}`" },
EscapeIssue1: {
expr: `\`\\abc\${ident}\``,
captureRaw: `\`abc\${ident}\``,
},
EscapeIssue2: {
expr: `\`\\abc\${ident}\``,
captureRaw: `\`abc\${ident}\``,
},
TernaryWithEscapeVariable: {
expr: '`${"1"}\\${${VARIABLE ? "SOMETHING" : ""}`',
captureRaw: '`1\\${${VARIABLE?"SOMETHING":""}`',
},
TernaryWithEscapeTrue: {
expr: '`${"1"}\\${${true ? "SOMETHING" : ""}`',
captureRaw: '"1${SOMETHING"',
},
TernaryWithEscapeFalse: {
expr: '`${"1"}\\${${false ? "SOMETHING" : ""}`',
captureRaw: '"1${"',
},
Fold: { expr: "`a${'b'}c${'d'}e`", capture: true },
FoldNested1: { expr: "`a${`b`}c${`${'d'}`}e`", capture: true },
FoldNested2: { expr: "`a${`b`}c${`1${'d'}`}e`", capture: true },
FoldNested3: { expr: "`a${`b`}c${`${'1'}${'d'}`}e`", capture: true },
FoldNested4: { expr: "`a${`b`}c${`${`${`${'d'}`}`}`}e`", capture: true },
FoldNested5: { expr: "`\\$${`d`}`", print: true }, // could be captured
FoldNested6: { expr: "`a\0${5}c\\${{$${`d`}e`", print: true },
EscapedDollar: { expr: "`\\${'a'}`", captureRaw: "\"${'a'}\"" },
EscapedDollar2: { expr: "`\\${'a'}\\${'b'}`", captureRaw: "\"${'a'}${'b'}\"" },
StringAddition: { expr: "`${1}\u2796` + 'rest'", print: true },
StringAddition2: { expr: "`\u2796${1}` + `a${Number(1)}b`", print: true },
StringAddition3: { expr: '`0${"\u2796"}` + `a${Number(1)}b`', print: true },
StringAddition4: { expr: "`${1}z` + `\u2796${Number(1)}rest`", print: true },
StringAddition5: { expr: "`\u2796${1}z` + `\u2796${Number(1)}rest`", print: true },
StringAddition6: { expr: "`${1}` + '\u2796rest'", print: true },
};
describe("bundler", () => {
for (const key in templateStringTests) {
const test = templateStringTests[key];
if ([test.capture, test.captureRaw, test.print].filter(x => x !== undefined).length !== 1) {
throw new Error(`Exactly one of capture or print must be defined for 'template/${key}'`);
}
let captureRaw = test.captureRaw;
if (test.capture === true) captureRaw = JSON.stringify(eval(test.expr)) as string;
else if (test.capture !== undefined) captureRaw = JSON.stringify(test.capture);
if (test.print === true) test.print = eval(test.expr) as string;
itBundled(
`string/${key}`,
captureRaw !== undefined
? {
files: {
"index.ts": dedent`
capture(${test.expr});
`,
},
capture: [captureRaw],
minifySyntax: true,
minifyWhitespace: true,
}
: {
files: {
"index.ts": dedent`
const capture = x => x;
console.log(capture(${test.expr}));
`,
},
run: {
stdout: test.print as string,
},
minifySyntax: true,
minifyWhitespace: true,
onAfterBundle(api) {
const capture = api.captureFile("out.js");
if (capture[0] === JSON.stringify(test.print)) {
// this is to tell the dev to change the test to use .capture
// as that is a more strict test (checking literal output)
// and if the test passes with this, we should be testing for that.
throw new Error(
`Test 'string/${key}': Passes capture test when the test only defines print. Rename .print to .capture on the test to fix this.`,
);
}
},
},
);
}
itBundled("string/TemplateFolding", {
files: {
"entry.js": /* js */ `
const s1 = "hello";
\`\${s1} world\`;
console.log(s1);
const s2 = \`hello\`;
console.log(s2);
const s3 = \`\${s2} world \${s1}\`;
console.log(s3);
const s4 = \`\${s1}\${s2}\${s3}\`;
console.log(s4);
const s5 = \`👋🌎\`;
console.log(s5);
const s6 = \`\${s5} 🌍 \${s1}\`;
console.log(s6);
const s7 = \`\${s1}\${s2}\${s3}\${s4}\${s5}\${s6}\`;
console.log(s7);
const hexCharacters = "a-f\\d";
console.log(hexCharacters);
const match3or4Hex = \`#?[\${hexCharacters}]{3}[\${hexCharacters}]?\`;
console.log(match3or4Hex);
const match6or8Hex = \`#?[\${hexCharacters}]{6}([\${hexCharacters}]{2})?\`;
console.log(match6or8Hex);
const nonHexChars = new RegExp(\`[^#\${hexCharacters}]\`, "gi");
console.log(nonHexChars);
const validHexSize = new RegExp(\`^\${match3or4Hex}\$|^\${match6or8Hex}$\`, "i");
console.log(validHexSize);
const INTERNAL = "OOPS";
function foo() {
return "NAME";
}
console.log(\`k\${foo()}=\${INTERNAL}j\`);
console.log("d" + INTERNAL);
console.log(INTERNAL + "l");
console.log("d" + INTERNAL + "l");
const CONST_VALUE = "CONST_VALUE";
function blaBla(a) {
const { propertyName } = a;
const condition = \`\${propertyName}.\${CONST_VALUE}AA.WHAT\`;
return condition;
}
console.log(CONST_VALUE);
console.log(CONST_VALUE === "CONST_VALUE");
`,
},
bundling: false,
run: {
stdout: `hello
hello
hello world hello
hellohellohello world hello
👋🌎
👋🌎 🌍 hello
hellohellohello world hellohellohellohello world hello👋🌎👋🌎 🌍 hello
a-f\d
#?[a-f\d]{3}[a-f\d]?
#?[a-f\d]{6}([a-f\d]{2})?
/[^#a-f\d]/gi
/^#?[a-f\d]{3}[a-f\d]?$|^#?[a-f\d]{6}([a-f\d]{2})?$/i
kNAME=OOPSj
dOOPS
OOPSl
dOOPSl
CONST_VALUE
true`,
},
});
});