diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/bundler/bundler_cjs2esm.test.ts | 20 | ||||
-rw-r--r-- | test/bundler/bundler_edgecase.test.ts | 56 | ||||
-rw-r--r-- | test/bundler/bundler_minify.test.ts | 54 | ||||
-rw-r--r-- | test/bundler/esbuild/css.test.ts | 6 | ||||
-rw-r--r-- | test/bundler/esbuild/dce.test.ts | 2 | ||||
-rw-r--r-- | test/bundler/esbuild/default.test.ts | 63 | ||||
-rw-r--r-- | test/bundler/esbuild/importstar.test.ts | 4 | ||||
-rw-r--r-- | test/bundler/esbuild/importstar_ts.test.ts | 4 | ||||
-rw-r--r-- | test/bundler/esbuild/loader.test.ts | 7 | ||||
-rw-r--r-- | test/bundler/esbuild/lower.test.ts | 12 | ||||
-rw-r--r-- | test/bundler/esbuild/packagejson.test.ts | 12 | ||||
-rw-r--r-- | test/bundler/esbuild/splitting.test.ts | 20 | ||||
-rw-r--r-- | test/bundler/esbuild/ts.test.ts | 658 | ||||
-rw-r--r-- | test/bundler/expectBundled.md | 20 | ||||
-rw-r--r-- | test/bundler/expectBundled.ts | 95 | ||||
-rw-r--r-- | test/bundler/report-bundler-test-progress.sh | 2 | ||||
-rwxr-xr-x | test/bundler/run-single-bundler-test.sh | 4 |
17 files changed, 825 insertions, 214 deletions
diff --git a/test/bundler/bundler_cjs2esm.test.ts b/test/bundler/bundler_cjs2esm.test.ts index 4c37e9591..ffe9a98f8 100644 --- a/test/bundler/bundler_cjs2esm.test.ts +++ b/test/bundler/bundler_cjs2esm.test.ts @@ -4,5 +4,23 @@ import { bundlerTest, expectBundled, itBundled, testForFile } from "./expectBund var { describe, test, expect } = testForFile(import.meta.path); describe("bundler", () => { - return; + // TODO: I must be misunderstanding how the cjs to esm transforms work. since this should pass + itBundled("cjs2esm/ModuleExportsFunction", { + files: { + "/entry.js": /* js */ ` + import { foo } from 'lib'; + console.log(foo()); + `, + "/node_modules/lib/index.js": /* js */ ` + module.exports.foo = function() { + return 'foo'; + } + `, + }, + minifySyntax: true, + platform: "bun", + onAfterBundle(api) { + assert(!api.readFile("/out.js").includes("__commonJS"), "should not include the commonJS helper"); + }, + }); }); diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index cb8a90b81..98c8c0a8e 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -37,18 +37,50 @@ describe("bundler", () => { }, run: true, }); - // itBundled("edgecase/PureCommentInLineComment", { - // files: { - // "/entry.js": /* js */ ` - // (function () { - // // Some text that contains a pure comment in it like /* @__PURE__ */, with other text around it. + itBundled("edgecase/BunPluginTreeShakeImport", { + // This only appears at runtime and not with bun build, even with --transform + files: { + "/entry.ts": /* js */ ` + import { A, B } from "./somewhere-else"; + import { plugin } from "bun"; - // // console.log; + plugin(B()); - // fn2("TODO: should this call be kept?"); - // })(); - // `, - // }, - // dce: true, - // }); + new A().chainedMethods(); + `, + "/somewhere-else.ts": /* js */ ` + export class A { + chainedMethods() { + console.log("hey"); + } + } + export function B() { + return { name: 'hey' } + } + `, + }, + external: ["external"], + mode: "transform", + minifySyntax: true, + platform: "bun", + run: { file: "/entry.ts" }, + }); + itBundled("edgecase/TemplateStringIssue622", { + files: { + "/entry.ts": /* js */ ` + capture(\`\\?\`); + capture(hello\`\\?\`); + `, + }, + capture: ["`\\\\?`", "hello`\\\\?`"], + platform: "bun", + }); + itBundled("edgecase/StringNullBytes", { + files: { + "/entry.ts": /* js */ ` + capture("Hello\0"); + `, + }, + capture: ['"Hello\0"'], + }); }); diff --git a/test/bundler/bundler_minify.test.ts b/test/bundler/bundler_minify.test.ts new file mode 100644 index 000000000..7eacfbde6 --- /dev/null +++ b/test/bundler/bundler_minify.test.ts @@ -0,0 +1,54 @@ +import { describe } from "bun:test"; +import { itBundled } from "./expectBundled"; + +describe("bundler", () => { + itBundled("minify/TemplateStringFolding", { + files: { + "/entry.js": /* js */ ` + capture(\`\${1}-\${2}-\${3}-\${null}-\${undefined}-\${true}-\${false}\`); + capture(\`\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C\`.length) + capture(\`\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C\`.length === 8) + capture(\`\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C\`.length == 8) + capture(\`\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C\`.length === 1) + capture(\`\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C\`.length == 1) + capture("\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C".length) + capture("\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C".length === 8) + capture("\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C".length == 8) + capture("\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C".length === 1) + capture("\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C".length == 1) + capture('\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C'.length) + capture('\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C'.length === 8) + capture('\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C'.length == 8) + capture('\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C'.length === 1) + capture('\\uD83D\\uDE0B \\uD83D\\uDCCB \\uD83D\\uDC4C'.length == 1) + capture(\`😋📋👌\`.length === 6) + capture(\`😋📋👌\`.length == 6) + capture(\`😋📋👌\`.length === 2) + capture(\`😋📋👌\`.length == 2) + `, + }, + capture: [ + '"1-2-3-null-undefined-true-false"', + "8", + "true", + "true", + "false", + "false", + "8", + "true", + "true", + "false", + "false", + "8", + "true", + "true", + "false", + "false", + "true", + "true", + "false", + "false", + ], + platform: "bun", + }); +}); diff --git a/test/bundler/esbuild/css.test.ts b/test/bundler/esbuild/css.test.ts index 2e0798082..83203cb6c 100644 --- a/test/bundler/esbuild/css.test.ts +++ b/test/bundler/esbuild/css.test.ts @@ -350,7 +350,7 @@ var { describe, test, expect } = testForFile(import.meta.path); // /* TODO FIX expectedScanLog: `entry.css: ERROR: Bundling with conditional "@import" rules is not currently supported // `, */ // }); -// itBundled("css/CSSAndJavaScriptCodeSplittingIssue1064", { +// itBundled("css/CSSAndJavaScriptCodeSplittingESBuildIssue1064", { // // GENERATED // files: { // "/a.js": /* js */ ` @@ -376,7 +376,7 @@ var { describe, test, expect } = testForFile(import.meta.path); // format: "esm", // splitting: true, // }); -// itBundled("css/CSSExternalQueryAndHashNoMatchIssue1822", { +// itBundled("css/CSSExternalQueryAndHashNoMatchESBuildIssue1822", { // // GENERATED // files: { // "/entry.css": /* css */ ` @@ -391,7 +391,7 @@ var { describe, test, expect } = testForFile(import.meta.path); // NOTE: You can mark the path "foo/bar.png#baz" as external to exclude it from the bundle, which will remove this error. // `, */ // }); -// itBundled("css/CSSExternalQueryAndHashMatchIssue1822", { +// itBundled("css/CSSExternalQueryAndHashMatchESBuildIssue1822", { // // GENERATED // files: { // "/entry.css": /* css */ ` diff --git a/test/bundler/esbuild/dce.test.ts b/test/bundler/esbuild/dce.test.ts index 8b3e2c289..4ba4f4b48 100644 --- a/test/bundler/esbuild/dce.test.ts +++ b/test/bundler/esbuild/dce.test.ts @@ -641,7 +641,7 @@ describe("bundler", () => { stdout: "1", }, }); - itBundled("dce/PackageJsonSideEffectsFalseNoWarningInNodeModulesIssue999", { + itBundled("dce/PackageJsonSideEffectsFalseNoWarningInNodeModulesESBuildIssue999", { files: { "/Users/user/project/src/entry.js": /* js */ ` import "demo-pkg" diff --git a/test/bundler/esbuild/default.test.ts b/test/bundler/esbuild/default.test.ts index 93bc21492..5ee1c0f1c 100644 --- a/test/bundler/esbuild/default.test.ts +++ b/test/bundler/esbuild/default.test.ts @@ -1,6 +1,13 @@ import assert from "assert"; import dedent from "dedent"; -import { ESBUILD_PATH, bundlerTest, expectBundled, itBundled, testForFile } from "../expectBundled"; +import { + ESBUILD_PATH, + RUN_UNCHECKED_TESTS, + bundlerTest, + expectBundled, + itBundled, + testForFile, +} from "../expectBundled"; var { describe, test, expect } = testForFile(import.meta.path); // Tests ported from: @@ -1012,27 +1019,39 @@ describe("bundler", () => { stdout: "./test.txt", }, }); - itBundled("default/RequireWithoutCall", { - // TODO: MANUAL CHECK: `require` on line one has to be renamed to `__require` + itBundled("default/RequireWithoutCallPlatformNeutral", { + // `require` on line one has to be renamed to `__require` files: { "/entry.js": /* js */ ` const req = require req('./entry') + capture(req) `, }, platform: "neutral", + onAfterBundle(api) { + const varName = api.captureFile("/out.js")[0]; + const assignmentValue = api.readFile("/out.js").match(new RegExp(`${varName} = (.*);`))![1]; + expect(assignmentValue).not.toBe("require"); + }, }); - itBundled("default/NestedRequireWithoutCall", { - // TODO: MANUAL CHECK: `require` on line one has to be renamed to `__require` + itBundled("default/NestedRequireWithoutCallPlatformNeutral", { + // `require` on line one has to be renamed to `__require` files: { "/entry.js": /* js */ ` (() => { const req = require req('./entry') + capture(req) })() `, }, platform: "neutral", + onAfterBundle(api) { + const varName = api.captureFile("/out.js")[0]; + const assignmentValue = api.readFile("/out.js").match(new RegExp(`${varName} = (.*);`))![1]; + expect(assignmentValue).not.toBe("require"); + }, }); itBundled("default/RequireWithCallInsideTry", { files: { @@ -1067,7 +1086,7 @@ describe("bundler", () => { run: [{ file: "/test1.js" }, { file: "/test2.js" }], }); itBundled("default/RequireWithoutCallInsideTry", { - // TODO: MANUAL CHECK: `require` on line one has to be renamed to `__require` + // `require` has to be renamed to `__require` files: { "/entry.js": /* js */ ` try { @@ -1075,10 +1094,16 @@ describe("bundler", () => { var aliasedRequire = require; aliasedRequire('./locale/' + name); getSetGlobalLocale(oldLocale); + capture(aliasedRequire) } catch (e) {} `, }, platform: "neutral", + onAfterBundle(api) { + const varName = api.captureFile("/out.js")[0]; + const assignmentValue = api.readFile("/out.js").match(new RegExp(`${varName} = (.*);`))![1]; + expect(assignmentValue).not.toBe("require"); + }, }); itBundled("default/RequirePropertyAccessCommonJS", { files: { @@ -1886,10 +1911,6 @@ describe("bundler", () => { outfile: "/out.js", minifyIdentifiers: true, mode: "transform", - run: { - // TODO: use bun here after https://github.com/oven-sh/bun/issues/1724 is fixed - runtime: "node", - }, }); itBundled("default/WithStatementTaintingNoBundle", { // TODO: MANUAL CHECK: make sure the snapshot we use works. @@ -1977,7 +1998,7 @@ describe("bundler", () => { assert(!text.includes("renameMe"), "Should have renamed all `renameMe` variabled"); }, }); - itBundled("default/ImportReExportES6Issue149", { + itBundled("default/ImportReExportES6ESBuildIssue149", { files: { "/app.jsx": /* jsx */ ` import { p as Part, h, render } from './import'; @@ -2405,7 +2426,7 @@ describe("bundler", () => { stdout: '[{},{},{"foo":123},{"bar":123}] true', }, }); - itBundled("default/EmptyExportClauseBundleAsCommonJSIssue910", { + itBundled("default/EmptyExportClauseBundleAsCommonJSESBuildIssue910", { files: { "/entry.js": `console.log(JSON.stringify(require('./types.mjs')))`, "/types.mjs": `export {}`, @@ -2432,7 +2453,7 @@ describe("bundler", () => { assert(api.readFile("/out.js").includes('"use strict";'), '"use strict"; was emitted'); }, }); - itBundled("default/UseStrictDirectiveBundleIssue1837", { + itBundled("default/UseStrictDirectiveBundleESBuildIssue1837", { files: { "/entry.js": /* js */ ` const p = require('./cjs').foo; @@ -2453,7 +2474,7 @@ describe("bundler", () => { stdout: "function", }, }); - itBundled("default/UseStrictDirectiveBundleIIFEIssue2264", { + itBundled("default/UseStrictDirectiveBundleIIFEESBuildIssue2264", { files: { "/entry.js": /* js */ ` 'use strict' @@ -2465,7 +2486,7 @@ describe("bundler", () => { assert(api.readFile("/out.js").includes('"use strict";'), '"use strict"; should be emitted'); }, }); - itBundled("default/UseStrictDirectiveBundleCJSIssue2264", { + itBundled("default/UseStrictDirectiveBundleCJSESBuildIssue2264", { files: { "/entry.js": /* js */ ` 'use strict' @@ -2477,7 +2498,7 @@ describe("bundler", () => { assert(api.readFile("/out.js").includes('"use strict";'), '"use strict"; should be emitted'); }, }); - itBundled("default/UseStrictDirectiveBundleESMIssue2264", { + itBundled("default/UseStrictDirectiveBundleESMESBuildIssue2264", { files: { "/entry.js": /* js */ ` 'use strict' @@ -4222,7 +4243,7 @@ describe("bundler", () => { stdout: `[[1,1,1],[1,1,1],[2,2,2,null,null]]`, }, }); - itBundled("default/DefineInfiniteLoopIssue2407", { + itBundled("default/DefineInfiniteLoopESBuildIssue2407", { files: { "/entry.js": /* js */ ` a.b() @@ -4568,7 +4589,7 @@ describe("bundler", () => { // mode: "transform", // external: ["a", "b", "c", "react/jsx-dev-runtime"], // }); - return; + if (!RUN_UNCHECKED_TESTS) return; // I cant get bun to use `this` as the JSX runtime. It's a pretty silly idea anyways. itBundled("default/JSXThisValueCommonJS", { files: { @@ -4999,7 +5020,7 @@ describe("bundler", () => { }, external: ["some-path"], }); - itBundled("default/StrictModeNestedFnDeclKeepNamesVariableInliningIssue1552", { + itBundled("default/StrictModeNestedFnDeclKeepNamesVariableInliningESBuildIssue1552", { // GENERATED files: { "/entry.js": /* js */ ` @@ -5784,7 +5805,7 @@ describe("bundler", () => { b.js: NOTE: Another definition of "x" comes from "b.js" here: `, */ }); - itBundled("default/NonDeterminismIssue2537", { + itBundled("default/NonDeterminismESBuildIssue2537", { // GENERATED files: { "/entry.ts": /* ts */ ` @@ -6327,7 +6348,7 @@ describe("bundler", () => { `, }, }); - itBundled("default/ErrorMessageCrashStdinIssue2913", { + itBundled("default/ErrorMessageCrashStdinESBuildIssue2913", { // GENERATED files: { "/project/node_modules/fflate/package.json": `{ "main": "main.js" }`, diff --git a/test/bundler/esbuild/importstar.test.ts b/test/bundler/esbuild/importstar.test.ts index 482655236..c9b78f7be 100644 --- a/test/bundler/esbuild/importstar.test.ts +++ b/test/bundler/esbuild/importstar.test.ts @@ -1028,7 +1028,7 @@ describe("bundler", () => { stdout: '{"foo":"foo"}', }, }); - itBundled("importstar/Issue176", { + itBundled("importstar/ESBuildIssue176", { files: { "/entry.js": /* js */ ` import * as things from './folders' @@ -1102,7 +1102,7 @@ describe("bundler", () => { stdout: '{"bar":"bar","foo":"foo"}', }, }); - itBundled("importstar/ImportDefaultNamespaceComboIssue446", { + itBundled("importstar/ImportDefaultNamespaceComboESBuildIssue446", { files: { "/external-default2.js": /* js */ ` import def, {default as default2} from 'external' diff --git a/test/bundler/esbuild/importstar_ts.test.ts b/test/bundler/esbuild/importstar_ts.test.ts index 5641753c0..76afe6d7e 100644 --- a/test/bundler/esbuild/importstar_ts.test.ts +++ b/test/bundler/esbuild/importstar_ts.test.ts @@ -1,5 +1,5 @@ import { test, describe } from "bun:test"; -import { expectBundled, itBundled } from "../expectBundled"; +import { RUN_UNCHECKED_TESTS, expectBundled, itBundled } from "../expectBundled"; // Tests ported from: // https://github.com/evanw/esbuild/blob/main/internal/bundler_tests/bundler_importstar_ts_test.go @@ -7,7 +7,7 @@ import { expectBundled, itBundled } from "../expectBundled"; // For debug, all files are written to $TEMP/bun-bundle-tests/ts describe("bundler", () => { - return; + if (!RUN_UNCHECKED_TESTS) return; itBundled("ts/TSImportStarUnused", { // GENERATED files: { diff --git a/test/bundler/esbuild/loader.test.ts b/test/bundler/esbuild/loader.test.ts index 648464d6e..1ca741749 100644 --- a/test/bundler/esbuild/loader.test.ts +++ b/test/bundler/esbuild/loader.test.ts @@ -1,4 +1,4 @@ -import { expectBundled, itBundled, testForFile } from "../expectBundled"; +import { RUN_UNCHECKED_TESTS, expectBundled, itBundled, testForFile } from "../expectBundled"; var { describe, test, expect } = testForFile(import.meta.path); // Tests ported from: @@ -31,7 +31,7 @@ describe("bundler", () => { }, }); - itBundled("loader/LoaderJSONSharedWithMultipleEntriesIssue413", { + itBundled("loader/LoaderJSONSharedWithMultipleEntriesESBuildIssue413", { // GENERATED files: { "/a.js": /* js */ ` @@ -62,8 +62,7 @@ describe("bundler", () => { }, ], }); - - return; + if (!RUN_UNCHECKED_TESTS) return; itBundled("loader/LoaderFile", { // GENERATED files: { diff --git a/test/bundler/esbuild/lower.test.ts b/test/bundler/esbuild/lower.test.ts index b22cdbd74..23b18397d 100644 --- a/test/bundler/esbuild/lower.test.ts +++ b/test/bundler/esbuild/lower.test.ts @@ -1,4 +1,4 @@ -import { expectBundled, itBundled, testForFile } from "../expectBundled"; +import { RUN_UNCHECKED_TESTS, expectBundled, itBundled, testForFile } from "../expectBundled"; var { describe, test, expect } = testForFile(import.meta.path); // Tests ported from: @@ -7,7 +7,7 @@ var { describe, test, expect } = testForFile(import.meta.path); // For debug, all files are written to $TEMP/bun-bundle-tests/lower describe("bundler", () => { - return; + if (!RUN_UNCHECKED_TESTS) return; itBundled("lower/LowerOptionalCatchNameCollisionNoBundle", { // GENERATED files: { @@ -1207,7 +1207,7 @@ describe("bundler", () => { }, unsupportedJSFeatures: "es2021", }); - itBundled("lower/LowerPrivateSuperStaticBundleIssue2158", { + itBundled("lower/LowerPrivateSuperStaticBundleESBuildIssue2158", { // GENERATED files: { "/entry.js": /* js */ ` @@ -1451,7 +1451,7 @@ describe("bundler", () => { unsupportedJSFeatures: "es2018", mode: "transform", }); - itBundled("lower/ClassSuperThisIssue242NoBundle", { + itBundled("lower/ClassSuperThisESBuildIssue242NoBundle", { // GENERATED files: { "/entry.ts": /* ts */ ` @@ -1678,7 +1678,7 @@ describe("bundler", () => { }, mode: "passthrough", }); - itBundled("lower/LowerPrivateClassFieldStaticIssue1424", { + itBundled("lower/LowerPrivateClassFieldStaticESBuildIssue1424", { // GENERATED files: { "/entry.js": /* js */ ` @@ -1692,7 +1692,7 @@ describe("bundler", () => { `, }, }); - itBundled("lower/LowerNullishCoalescingAssignmentIssue1493", { + itBundled("lower/LowerNullishCoalescingAssignmentESBuildIssue1493", { // GENERATED files: { "/entry.js": /* js */ ` diff --git a/test/bundler/esbuild/packagejson.test.ts b/test/bundler/esbuild/packagejson.test.ts index 0cdb46569..e3e5387b3 100644 --- a/test/bundler/esbuild/packagejson.test.ts +++ b/test/bundler/esbuild/packagejson.test.ts @@ -1,4 +1,4 @@ -import { expectBundled, itBundled, testForFile } from "../expectBundled"; +import { RUN_UNCHECKED_TESTS, expectBundled, itBundled, testForFile } from "../expectBundled"; var { describe, test, expect } = testForFile(import.meta.path); // Tests ported from: @@ -7,7 +7,7 @@ var { describe, test, expect } = testForFile(import.meta.path); // For debug, all files are written to $TEMP/bun-bundle-tests/packagejson describe("bundler", () => { - return; + if (!RUN_UNCHECKED_TESTS) return; itBundled("packagejson/PackageJsonMain", { // GENERATED files: { @@ -605,7 +605,7 @@ describe("bundler", () => { "/Users/user/project/src/demo-pkg/ext-browser/index.js": `export let browser = 'browser'`, }, }); - itBundled("packagejson/PackageJsonBrowserIssue2002A", { + itBundled("packagejson/PackageJsonBrowserESBuildIssue2002A", { // GENERATED files: { "/Users/user/project/src/entry.js": `require('pkg/sub')`, @@ -621,7 +621,7 @@ describe("bundler", () => { "/Users/user/project/src/node_modules/sub/bar.js": `works()`, }, }); - itBundled("packagejson/PackageJsonBrowserIssue2002B", { + itBundled("packagejson/PackageJsonBrowserESBuildIssue2002B", { // GENERATED files: { "/Users/user/project/src/entry.js": `require('pkg/sub')`, @@ -637,7 +637,7 @@ describe("bundler", () => { "/Users/user/project/src/node_modules/pkg/sub/bar.js": `works()`, }, }); - itBundled("packagejson/PackageJsonBrowserIssue2002C", { + itBundled("packagejson/PackageJsonBrowserESBuildIssue2002C", { // GENERATED files: { "/Users/user/project/src/entry.js": `require('pkg/sub')`, @@ -1857,7 +1857,7 @@ describe("bundler", () => { NOTE: Node's package format requires that CommonJS files in a "type": "module" package use the ".cjs" file extension. `, */ }); - itBundled("packagejson/PackageJsonNodePathsIssue2752", { + itBundled("packagejson/PackageJsonNodePathsESBuildIssue2752", { // GENERATED files: { "/src/entry.js": /* js */ ` diff --git a/test/bundler/esbuild/splitting.test.ts b/test/bundler/esbuild/splitting.test.ts index 72d0d70de..1dc2c3d11 100644 --- a/test/bundler/esbuild/splitting.test.ts +++ b/test/bundler/esbuild/splitting.test.ts @@ -1,4 +1,4 @@ -import { itBundled, testForFile } from "../expectBundled"; +import { RUN_UNCHECKED_TESTS, itBundled, testForFile } from "../expectBundled"; var { describe, test, expect } = testForFile(import.meta.path); // Tests ported from: @@ -31,7 +31,7 @@ describe("bundler", () => { "/out/b.js": "123", }, }); - return; + if (!RUN_UNCHECKED_TESTS) return; itBundled("splitting/SplittingSharedCommonJSIntoES6", { // GENERATED files: { @@ -152,7 +152,7 @@ describe("bundler", () => { splitting: true, format: "esm", }); - itBundled("splitting/SplittingCircularReferenceIssue251", { + itBundled("splitting/SplittingCircularReferenceESBuildIssue251", { // GENERATED files: { "/a.js": /* js */ ` @@ -195,7 +195,7 @@ describe("bundler", () => { /* TODO FIX expectedCompileLog: `common.js: WARNING: Import "missing" will always be undefined because the file "empty.js" has no exports `, */ }); - itBundled("splitting/SplittingReExportIssue273", { + itBundled("splitting/SplittingReExportESBuildIssue273", { // GENERATED files: { "/a.js": `export const a = 1`, @@ -205,7 +205,7 @@ describe("bundler", () => { splitting: true, format: "esm", }); - itBundled("splitting/SplittingDynamicImportIssue272", { + itBundled("splitting/SplittingDynamicImportESBuildIssue272", { // GENERATED files: { "/a.js": `import('./b')`, @@ -215,7 +215,7 @@ describe("bundler", () => { splitting: true, format: "esm", }); - itBundled("splitting/SplittingDynamicImportOutsideSourceTreeIssue264", { + itBundled("splitting/SplittingDynamicImportOutsideSourceTreeESBuildIssue264", { // GENERATED files: { "/Users/user/project/src/entry1.js": `import('package')`, @@ -310,7 +310,7 @@ describe("bundler", () => { minifyWhitespace: true, format: "esm", }); - itBundled("splitting/SplittingMinifyIdentifiersCrashIssue437", { + itBundled("splitting/SplittingMinifyIdentifiersCrashESBuildIssue437", { // GENERATED files: { "/a.js": /* js */ ` @@ -329,7 +329,7 @@ describe("bundler", () => { minifyIdentifiers: true, format: "esm", }); - itBundled("splitting/SplittingHybridESMAndCJSIssue617", { + itBundled("splitting/SplittingHybridESMAndCJSESBuildIssue617", { // GENERATED files: { "/a.js": `export let foo`, @@ -358,7 +358,7 @@ describe("bundler", () => { format: "esm", splitting: true, }); - itBundled("splitting/EdgeCaseIssue2793WithSplitting", { + itBundled("splitting/EdgeCaseESBuildIssue2793WithSplitting", { // GENERATED files: { "/src/a.js": `export const A = 42;`, @@ -373,7 +373,7 @@ describe("bundler", () => { format: "esm", splitting: true, }); - itBundled("splitting/EdgeCaseIssue2793WithoutSplitting", { + itBundled("splitting/EdgeCaseESBuildIssue2793WithoutSplitting", { // GENERATED files: { "/src/a.js": `export const A = 42;`, diff --git a/test/bundler/esbuild/ts.test.ts b/test/bundler/esbuild/ts.test.ts index 5678edc1e..a3ac313a4 100644 --- a/test/bundler/esbuild/ts.test.ts +++ b/test/bundler/esbuild/ts.test.ts @@ -1,5 +1,5 @@ import assert from "assert"; -import { itBundled, testForFile } from "../expectBundled"; +import { RUN_UNCHECKED_TESTS, itBundled, testForFile } from "../expectBundled"; var { describe, test, expect } = testForFile(import.meta.path); // Tests ported from: @@ -393,18 +393,12 @@ describe("bundler", () => { const b = api.readFile("/out/b.js"); // make sure the minification trick "enum[enum.K=V]=K" is used, but `enum` - assert(a.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.A=0]=["']A["']\b/), "should be using enum minification trick (1)"); - assert(a.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.B=1]=["']B["']\b/), "should be using enum minification trick (2)"); - assert( - a.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.C=[a-zA-Z$]]=["']C["']\b/), - "should be using enum minification trick (3)", - ); - assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.X=0]=["']X["']\b/), "should be using enum minification trick (4)"); - assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.Y=1]=["']Y["']\b/), "should be using enum minification trick (5)"); - assert( - b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.Z=[a-zA-Z$]]=["']Z["']\b/), - "should be using enum minification trick (6)", - ); + assert(a.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.A=0]=["']A["']/), "should be using enum minification trick (1)"); + assert(a.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.B=1]=["']B["']/), "should be using enum minification trick (2)"); + assert(a.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.C=[a-zA-Z$]]=["']C["']/), "should be using enum minification trick (3)"); + assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.X=0]=["']X["']/), "should be using enum minification trick (4)"); + assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.Y=1]=["']Y["']/), "should be using enum minification trick (5)"); + assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.Z=[a-zA-Z$]]=["']Z["']/), "should be using enum minification trick (6)"); }, runtimeFiles: { "/test.js": /* js */ ` @@ -426,7 +420,7 @@ describe("bundler", () => { `, }, }); - const TSMinifyNestedEnum = itBundled("ts/TSMinifyNestedEnum", { + itBundled("ts/TSMinifyNestedEnum", { files: { "/a.ts": `function foo(arg) { enum Foo { A, B, C = Foo, D = arg } return Foo }\ncapture(foo)`, "/b.ts": `export function foo(arg) { enum Foo { X, Y, Z = Foo, W = arg } return Foo }`, @@ -508,9 +502,7 @@ describe("bundler", () => { assert(!b.includes("=>"), "b should not use arrow"); }, }); - return; itBundled("ts/TSMinifyNamespace", { - // GENERATED files: { "/a.ts": /* ts */ ` namespace Foo { @@ -518,6 +510,7 @@ describe("bundler", () => { foo(Foo, Bar) } } + capture(Foo) `, "/b.ts": /* ts */ ` export namespace Foo { @@ -532,9 +525,22 @@ describe("bundler", () => { minifyWhitespace: true, minifyIdentifiers: true, mode: "transform", + onAfterBundle(api) { + api.writeFile("/out/a.edited.js", api.readFile("/out/a.js").replace(/capture\((.*?)\)/, `export const Foo = $1`)); + }, + runtimeFiles: { + "/test.js": /* js */ ` + let called = false; + globalThis.foo = (a, b) => called = true; + await import('./out/a.edited.js'); + assert(called, 'foo should be called from a.ts'); + called = false; + await import('./out/b.js'); + assert(called, 'foo should be called from b.ts'); + `, + }, }); itBundled("ts/TSMinifyNamespaceNoLogicalAssignment", { - // GENERATED files: { "/a.ts": /* ts */ ` namespace Foo { @@ -558,9 +564,16 @@ describe("bundler", () => { outdir: "/", mode: "transform", unsupportedJSFeatures: ["logical-assignment"], + onAfterBundle(api) { + const a = api.readFile("/a.js"); + assert(a.includes("Bar"), "a should not be empty"); + assert(!a.includes("||="), "a should not use logical assignment"); + const b = api.readFile("/b.js"); + assert(b.includes("Bar"), "b should not be empty"); + assert(!b.includes("||="), "b should not use logical assignment"); + }, }); itBundled("ts/TSMinifyNamespaceNoArrow", { - // GENERATED files: { "/a.ts": /* ts */ ` namespace Foo { @@ -583,9 +596,17 @@ describe("bundler", () => { minifyIdentifiers: true, outdir: "/", mode: "transform", + unsupportedJSFeatures: ["arrow"], + onAfterBundle(api) { + const a = api.readFile("/a.js"); + assert(a.includes("foo"), "a should not be empty"); + assert(!a.includes("=>"), "a should not use arrow"); + const b = api.readFile("/b.js"); + assert(b.includes("foo"), "b should not be empty"); + assert(!b.includes("=>"), "b should not use arrow"); + }, }); itBundled("ts/TSMinifyDerivedClass", { - // GENERATED files: { "/entry.ts": /* ts */ ` class Foo extends Bar { @@ -597,44 +618,71 @@ describe("bundler", () => { bar(); } } + + export {Foo} `, }, minifySyntax: true, - unsupportedJSFeatures: "es2015", - mode: "transform", + runtimeFiles: { + "/test.js": /* js */ ` + let calledFoo = false; + let calledBar = false; + globalThis.foo = () => calledFoo = true; + globalThis.bar = () => calledBar = true; + globalThis.Bar = class Bar { + constructor() { + console.log('super') + this.hello = 3; + } + }; + const {Foo} = await import('./entry.js'); + import assert from 'assert'; + const instance = new Foo(); + console.log(instance.foo, instance.bar, instance.hello); + assert(calledFoo, 'foo should be called'); + assert(calledBar, 'bar should be called'); + `, + }, + run: { + file: "/test.js", + stdout: "super\n1 2 3", + }, }); itBundled("ts/TSImportVsLocalCollisionAllTypes", { - // GENERATED files: { "/entry.ts": /* ts */ ` import {a, b, c, d, e} from './other.ts' let a const b = 0 var c - function d() {} - class e {} - console.log(a, b, c, d, e) + function d() { return 5; } + class e { constructor() { this.prop = 2; }} + console.log(JSON.stringify([a, b, c, d(), new e])) `, "/other.ts": ``, }, + run: { + stdout: '[null,0,null,5,{"prop":2}]', + }, }); itBundled("ts/TSImportVsLocalCollisionMixed", { - // GENERATED files: { "/entry.ts": /* ts */ ` import {a, b, c, d, e, real} from './other.ts' let a const b = 0 var c - function d() {} - class e {} - console.log(a, b, c, d, e, real) + function d() { return 5; } + class e { constructor() { this.prop = 2; }} + console.log(JSON.stringify([a, b, c, d(), new e, real])) `, "/other.ts": `export let real = 123`, }, + run: { + stdout: '[null,0,null,5,{"prop":2},123]', + }, }); itBundled("ts/TSImportEqualsEliminationTest", { - // GENERATED files: { "/entry.ts": /* ts */ ` import a = foo.a @@ -648,44 +696,62 @@ describe("bundler", () => { export let bar = c `, }, + runtimeFiles: { + "/test.js": /* js */ ` + globalThis.foo = { + a: { b: { c: 123 } }, + get x() { + throw new Error('should not be called') + } + }; + const {bar} = await import('./out.js'); + console.log(bar); + `, + }, + run: { + file: "/test.js", + stdout: "123", + }, }); itBundled("ts/TSImportEqualsTreeShakingFalse", { - // GENERATED files: { "/entry.ts": /* ts */ ` import { foo } from 'pkg' import used = foo.used - import unused = foo.unused + import unused_keep = foo.unused export { used } `, }, - mode: "passthrough", + treeShaking: false, + dce: true, + mode: "transform", }); itBundled("ts/TSImportEqualsTreeShakingTrue", { - // GENERATED files: { "/entry.ts": /* ts */ ` import { foo } from 'pkg' import used = foo.used - import unused = foo.unused + import unused_drop = foo.unused export { used } `, }, - mode: "passthrough", + dce: true, + treeShaking: true, + mode: "transform", }); itBundled("ts/TSImportEqualsBundle", { - // GENERATED files: { "/entry.ts": /* ts */ ` import { foo } from 'pkg' import used = foo.used - import unused = foo.unused + import unused_drop = foo.unused export { used } `, }, + dce: true, + external: ["pkg"], }); itBundled("ts/TSMinifiedBundleES6", { - // GENERATED files: { "/entry.ts": /* ts */ ` import {foo} from './a' @@ -700,13 +766,15 @@ describe("bundler", () => { minifySyntax: true, minifyWhitespace: true, minifyIdentifiers: true, + run: { + stdout: "123", + }, }); itBundled("ts/TSMinifiedBundleCommonJS", { - // GENERATED files: { "/entry.ts": /* ts */ ` const {foo} = require('./a') - console.log(foo(), require('./j.json')) + console.log(JSON.stringify([foo(), require('./j.json')])) `, "/a.ts": /* ts */ ` exports.foo = function() { @@ -718,9 +786,38 @@ describe("bundler", () => { minifySyntax: true, minifyWhitespace: true, minifyIdentifiers: true, + run: { + stdout: '[123,{"test":true}]', + }, + }); + // TODO: all situations with decorators are currently not runtime-checked. as of writing bun crashes when hitting them at all. + itBundled("ts/TypeScriptDecoratorsSimpleCase", { + files: { + "/entry.ts": /* ts */ ` + function decorator(...args) { + console.log('decorator called', JSON.stringify(args)) + } + + @decorator + class Foo { + @decorator + bar() { + console.log('bar called') + } + } + + new Foo().bar() + `, + }, + run: { + stdout: ` + decorator called [{},"bar",{"writable":true,"enumerable":false,"configurable":true}] + decorator called [null] + bar called + `, + }, }); itBundled("ts/TypeScriptDecorators", { - // GENERATED files: { "/entry.js": /* js */ ` import all from './all' @@ -859,19 +956,17 @@ describe("bundler", () => { }, }); itBundled("ts/TypeScriptDecoratorsKeepNames", { - // GENERATED files: { "/entry.ts": /* ts */ ` @decoratorMustComeAfterName class Foo {} `, }, + keepNames: true, }); - itBundled("ts/TypeScriptDecoratorScopeIssue2147", { - // GENERATED + itBundled("ts/TypeScriptDecoratorScopeESBuildIssue2147", { files: { "/entry.ts": /* ts */ ` - let foo = 1 class Foo { method1(@dec(foo) foo = 2) {} method2(@dec(() => foo) foo = 3) {} @@ -880,7 +975,6 @@ describe("bundler", () => { class Bar { static x = class { static y = () => { - let bar = 1 @dec(bar) @dec(() => bar) class Baz { @@ -895,10 +989,22 @@ describe("bundler", () => { } `, }, - mode: "passthrough", + mode: "transform", + onAfterBundle(api) { + const capturedCalls = api.captureFile("/out.js", "dec"); + expect(capturedCalls).toEqual([ + "foo", + "() => foo", + "bar", + "() => bar", + "() => bar", + "() => bar", + "bar", + "() => bar", + ]); + }, }); - itBundled("ts/TSExportDefaultTypeIssue316", { - // GENERATED + itBundled("ts/TSExportDefaultTypeESBuildIssue316", { files: { "/entry.ts": /* ts */ ` import dc_def, { bar as dc } from './keep/declare-class' @@ -1017,9 +1123,23 @@ describe("bundler", () => { export let bar = 123 `, }, + runtimeFiles: { + "/test.js": /* js */ ` + globalThis.foo = 123456; + const mod = (await import('./out.js')).default; + console.log(JSON.stringify(mod)) + console.log(JSON.stringify(mod.map(x => typeof x))) + `, + }, + run: { + file: "/test.js", + stdout: ` + [123456,123,123456,123,null,123,123456,123,123456,123,{"num":0},123,{"num":0},123,123,123,123,123,123,123] + ["number","number","number","number","function","number","number","number","number","number","object","number","object","number","number","number","number","number","number","number"] + `, + }, }); itBundled("ts/TSImplicitExtensions", { - // GENERATED files: { "/entry.ts": /* ts */ ` import './pick-js.js' @@ -1030,21 +1150,23 @@ describe("bundler", () => { import './order-jsx.jsx' `, "/pick-js.js": `console.log("correct")`, - "/pick-js.ts": `console.log("wrong")`, - "/pick-ts.jsx": `console.log("wrong")`, + "/pick-js.ts": `console.log("FAILED")`, + "/pick-ts.jsx": `console.log("FAILED")`, "/pick-ts.ts": `console.log("correct")`, "/pick-jsx.jsx": `console.log("correct")`, - "/pick-jsx.tsx": `console.log("wrong")`, - "/pick-tsx.js": `console.log("wrong")`, + "/pick-jsx.tsx": `console.log("FAILED")`, + "/pick-tsx.js": `console.log("FAILED")`, "/pick-tsx.tsx": `console.log("correct")`, "/order-js.ts": `console.log("correct")`, - "/order-js.tsx": `console.log("wrong")`, + "/order-js.tsx": `console.log("FAILED")`, "/order-jsx.ts": `console.log("correct")`, - "/order-jsx.tsx": `console.log("wrong")`, + "/order-jsx.tsx": `console.log("FAILED")`, + }, + run: { + stdout: "correct\n".repeat(6), }, }); itBundled("ts/TSImplicitExtensionsMissing", { - // GENERATED files: { "/entry.ts": /* ts */ ` import './mjs.mjs' @@ -1059,21 +1181,23 @@ describe("bundler", () => { "/js.ts.js": ``, "/jsx.tsx.jsx": ``, }, - /* TODO FIX expectedScanLog: `entry.ts: ERROR: Could not resolve "./mjs.mjs" - entry.ts: ERROR: Could not resolve "./cjs.cjs" - entry.ts: ERROR: Could not resolve "./js.js" - entry.ts: ERROR: Could not resolve "./jsx.jsx" - `, */ + bundleErrors: { + "/entry.ts": [ + `Could not resolve: "./mjs.mjs"`, + `Could not resolve: "./cjs.cjs"`, + `Could not resolve: "./js.js"`, + `Could not resolve: "./jsx.jsx"`, + ], + }, }); - itBundled("ts/ExportTypeIssue379", { - // GENERATED + itBundled("ts/ExportTypeESBuildIssue379", { files: { "/entry.ts": /* ts */ ` import * as A from './a' import * as B from './b' import * as C from './c' import * as D from './d' - console.log(A, B, C, D) + console.log(JSON.stringify([A, B, C, D])) `, "/a.ts": /* ts */ ` type Test = Element @@ -1098,135 +1222,284 @@ describe("bundler", () => { `, "/test.ts": `export type Test = Element`, }, + run: { + stdout: '[{"foo":123},{"foo":123},{"foo":123},{"foo":123}]', + }, + useDefineForClassFields: false, }); itBundled("ts/ThisInsideFunctionTS", { - // GENERATED files: { "/entry.ts": /* ts */ ` - function foo(x = this) { console.log(this) } + function foo(x = this) { return [x, this]; } const objFoo = { - foo(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } } class Foo { x = this - static y = this.z - foo(x = this) { console.log(this) } - static bar(x = this) { console.log(this) } + static z = 456; + static y = this.z; + foo(x = this) { return [x, this]; } + static bar(x = this) { return [x, this]; } } - new Foo(foo(objFoo)) + + assert.deepEqual(foo('bun'), ['bun', undefined]); + assert.deepEqual(foo.call('this'), ['this', 'this']); + assert.deepEqual(foo.call('this', 'bun'), ['bun', 'this']); + assert.deepEqual(objFoo.foo('bun'), ['bun', objFoo]); + assert.deepEqual(objFoo.foo(), [objFoo, objFoo]); + const fooInstance = new Foo(); + assert(fooInstance.x === fooInstance, 'Foo#x'); + assert(Foo.y === 456, 'Foo.y'); + assert.deepEqual(Foo.bar('bun'), ['bun', Foo]); + assert.deepEqual(Foo.bar(), [Foo, Foo]); + assert.deepEqual(fooInstance.foo(), [fooInstance, fooInstance]); + assert.deepEqual(fooInstance.foo('bun'), ['bun', fooInstance]); + if (nested) { - function bar(x = this) { console.log(this) } + function bar(x = this) { return [x, this]; } const objBar = { - foo(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } } class Bar { x = this + static z = 456; static y = this.z - foo(x = this) { console.log(this) } - static bar(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } + static bar(x = this) { return [x, this]; } } - new Bar(bar(objBar)) + + assert.deepEqual(bar('bun'), ['bun', undefined]); + assert.deepEqual(bar.call('this'), ['this', 'this']); + assert.deepEqual(bar.call('this', 'bun'), ['bun', 'this']); + assert.deepEqual(objBar.foo('bun'), ['bun', objBar]); + assert.deepEqual(objBar.foo(), [objBar, objBar]); + const barInstance = new Bar(); + assert(barInstance.x === barInstance, 'Bar#x'); + assert(Bar.y === 456, 'Bar.y'); + assert.deepEqual(Bar.bar('bun'), ['bun', Bar]); + assert.deepEqual(Bar.bar(), [Bar, Bar]); + assert.deepEqual(barInstance.foo(), [barInstance, barInstance]); + assert.deepEqual(barInstance.foo('bun'), ['bun', barInstance]); } `, }, + runtimeFiles: { + "/test.js": /* js */ ` + globalThis.nested = true; + globalThis.assert = (await import('assert')).default; + import('./out') + `, + }, + run: { + file: "/test.js", + }, }); itBundled("ts/ThisInsideFunctionTSUseDefineForClassFields", { - // GENERATED files: { "/entry.ts": /* ts */ ` - function foo(x = this) { console.log(this) } + function foo(x = this) { return [x, this]; } const objFoo = { - foo(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } } class Foo { x = this - static y = this.z - foo(x = this) { console.log(this) } - static bar(x = this) { console.log(this) } + static z = 456; + static y = this.z; + foo(x = this) { return [x, this]; } + static bar(x = this) { return [x, this]; } } - new Foo(foo(objFoo)) + + assert.deepEqual(foo('bun'), ['bun', undefined]); + assert.deepEqual(foo.call('this'), ['this', 'this']); + assert.deepEqual(foo.call('this', 'bun'), ['bun', 'this']); + assert.deepEqual(objFoo.foo('bun'), ['bun', objFoo]); + assert.deepEqual(objFoo.foo(), [objFoo, objFoo]); + const fooInstance = new Foo(); + assert(fooInstance.x === fooInstance, 'Foo#x'); + assert(Foo.y === 456, 'Foo.y'); + assert.deepEqual(Foo.bar('bun'), ['bun', Foo]); + assert.deepEqual(Foo.bar(), [Foo, Foo]); + assert.deepEqual(fooInstance.foo(), [fooInstance, fooInstance]); + assert.deepEqual(fooInstance.foo('bun'), ['bun', fooInstance]); + if (nested) { - function bar(x = this) { console.log(this) } + function bar(x = this) { return [x, this]; } const objBar = { - foo(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } } class Bar { x = this + static z = 456; static y = this.z - foo(x = this) { console.log(this) } - static bar(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } + static bar(x = this) { return [x, this]; } } - new Bar(bar(objBar)) + + assert.deepEqual(bar('bun'), ['bun', undefined]); + assert.deepEqual(bar.call('this'), ['this', 'this']); + assert.deepEqual(bar.call('this', 'bun'), ['bun', 'this']); + assert.deepEqual(objBar.foo('bun'), ['bun', objBar]); + assert.deepEqual(objBar.foo(), [objBar, objBar]); + const barInstance = new Bar(); + assert(barInstance.x === barInstance, 'Bar#x'); + assert(Bar.y === 456, 'Bar.y'); + assert.deepEqual(Bar.bar('bun'), ['bun', Bar]); + assert.deepEqual(Bar.bar(), [Bar, Bar]); + assert.deepEqual(barInstance.foo(), [barInstance, barInstance]); + assert.deepEqual(barInstance.foo('bun'), ['bun', barInstance]); } `, }, + runtimeFiles: { + "/test.js": /* js */ ` + globalThis.nested = true; + globalThis.assert = (await import('assert')).default; + import('./out') + `, + }, + run: { + file: "/test.js", + }, + useDefineForClassFields: true, }); itBundled("ts/ThisInsideFunctionTSNoBundle", { - // GENERATED files: { "/entry.ts": /* ts */ ` - function foo(x = this) { console.log(this) } + function foo(x = this) { return [x, this]; } const objFoo = { - foo(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } } class Foo { x = this - static y = this.z - foo(x = this) { console.log(this) } - static bar(x = this) { console.log(this) } + static z = 456; + static y = this.z; + foo(x = this) { return [x, this]; } + static bar(x = this) { return [x, this]; } } - new Foo(foo(objFoo)) + + assert.deepEqual(foo('bun'), ['bun', undefined]); + assert.deepEqual(foo.call('this'), ['this', 'this']); + assert.deepEqual(foo.call('this', 'bun'), ['bun', 'this']); + assert.deepEqual(objFoo.foo('bun'), ['bun', objFoo]); + assert.deepEqual(objFoo.foo(), [objFoo, objFoo]); + const fooInstance = new Foo(); + assert(fooInstance.x === fooInstance, 'Foo#x'); + assert(Foo.y === 456, 'Foo.y'); + assert.deepEqual(Foo.bar('bun'), ['bun', Foo]); + assert.deepEqual(Foo.bar(), [Foo, Foo]); + assert.deepEqual(fooInstance.foo(), [fooInstance, fooInstance]); + assert.deepEqual(fooInstance.foo('bun'), ['bun', fooInstance]); + if (nested) { - function bar(x = this) { console.log(this) } + function bar(x = this) { return [x, this]; } const objBar = { - foo(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } } class Bar { x = this + static z = 456; static y = this.z - foo(x = this) { console.log(this) } - static bar(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } + static bar(x = this) { return [x, this]; } } - new Bar(bar(objBar)) + + assert.deepEqual(bar('bun'), ['bun', undefined]); + assert.deepEqual(bar.call('this'), ['this', 'this']); + assert.deepEqual(bar.call('this', 'bun'), ['bun', 'this']); + assert.deepEqual(objBar.foo('bun'), ['bun', objBar]); + assert.deepEqual(objBar.foo(), [objBar, objBar]); + const barInstance = new Bar(); + assert(barInstance.x === barInstance, 'Bar#x'); + assert(Bar.y === 456, 'Bar.y'); + assert.deepEqual(Bar.bar('bun'), ['bun', Bar]); + assert.deepEqual(Bar.bar(), [Bar, Bar]); + assert.deepEqual(barInstance.foo(), [barInstance, barInstance]); + assert.deepEqual(barInstance.foo('bun'), ['bun', barInstance]); } `, }, - mode: "passthrough", + mode: "transform", + runtimeFiles: { + "/test.js": /* js */ ` + globalThis.nested = true; + globalThis.assert = (await import('assert')).default; + import('./out') + `, + }, + run: { + file: "/test.js", + }, }); itBundled("ts/ThisInsideFunctionTSNoBundleUseDefineForClassFields", { // GENERATED files: { "/entry.ts": /* ts */ ` - function foo(x = this) { console.log(this) } + function foo(x = this) { return [x, this]; } const objFoo = { - foo(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } } class Foo { x = this - static y = this.z - foo(x = this) { console.log(this) } - static bar(x = this) { console.log(this) } + static z = 456; + static y = this.z; + foo(x = this) { return [x, this]; } + static bar(x = this) { return [x, this]; } } - new Foo(foo(objFoo)) + + assert.deepEqual(foo('bun'), ['bun', undefined]); + assert.deepEqual(foo.call('this'), ['this', 'this']); + assert.deepEqual(foo.call('this', 'bun'), ['bun', 'this']); + assert.deepEqual(objFoo.foo('bun'), ['bun', objFoo]); + assert.deepEqual(objFoo.foo(), [objFoo, objFoo]); + const fooInstance = new Foo(); + assert(fooInstance.x === fooInstance, 'Foo#x'); + assert(Foo.y === 456, 'Foo.y'); + assert.deepEqual(Foo.bar('bun'), ['bun', Foo]); + assert.deepEqual(Foo.bar(), [Foo, Foo]); + assert.deepEqual(fooInstance.foo(), [fooInstance, fooInstance]); + assert.deepEqual(fooInstance.foo('bun'), ['bun', fooInstance]); + if (nested) { - function bar(x = this) { console.log(this) } + function bar(x = this) { return [x, this]; } const objBar = { - foo(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } } class Bar { x = this + static z = 456; static y = this.z - foo(x = this) { console.log(this) } - static bar(x = this) { console.log(this) } + foo(x = this) { return [x, this]; } + static bar(x = this) { return [x, this]; } } - new Bar(bar(objBar)) + + assert.deepEqual(bar('bun'), ['bun', undefined]); + assert.deepEqual(bar.call('this'), ['this', 'this']); + assert.deepEqual(bar.call('this', 'bun'), ['bun', 'this']); + assert.deepEqual(objBar.foo('bun'), ['bun', objBar]); + assert.deepEqual(objBar.foo(), [objBar, objBar]); + const barInstance = new Bar(); + assert(barInstance.x === barInstance, 'Bar#x'); + assert(Bar.y === 456, 'Bar.y'); + assert.deepEqual(Bar.bar('bun'), ['bun', Bar]); + assert.deepEqual(Bar.bar(), [Bar, Bar]); + assert.deepEqual(barInstance.foo(), [barInstance, barInstance]); + assert.deepEqual(barInstance.foo('bun'), ['bun', barInstance]); } `, }, - mode: "passthrough", + mode: "transform", + runtimeFiles: { + "/test.js": /* js */ ` + globalThis.nested = true; + globalThis.assert = (await import('assert')).default; + import('./out') + `, + }, + run: { + file: "/test.js", + }, }); itBundled("ts/TSComputedClassFieldUseDefineFalse", { - // GENERATED files: { "/entry.ts": /* ts */ ` class Foo { @@ -1237,13 +1510,43 @@ describe("bundler", () => { @dec [y] = z; } - new Foo() + export default Foo; `, }, - mode: "passthrough", + runtimeFiles: { + "/test.js": /* js */ ` + globalThis.q = 'q1'; + globalThis.r = 'r1'; + globalThis.s = 's1'; + globalThis.x = 'x1'; + globalThis.y = 'y1'; + globalThis.z = 'z1'; + globalThis.dec = function(...args) { + console.log(JSON.stringify([this, ...args])); + }; + const Foo = (await import('./out')).default; + globalThis.q = 'q2'; + globalThis.r = 'r2'; + globalThis.s = 's2'; + globalThis.x = 'x2'; + globalThis.y = 'y2'; + globalThis.z = 'z2'; + const y = new Foo(); + console.log(JSON.stringify(y)); + `, + }, + useDefineForClassFields: false, + mode: "transform", + run: { + stdout: ` + [null,{},"x1",null] + [null,{},"y1",null] + {"r1":"s2","y1":"z2"} + `, + file: "/test.js", + }, }); itBundled("ts/TSComputedClassFieldUseDefineTrue", { - // GENERATED files: { "/entry.ts": /* ts */ ` class Foo { @@ -1254,13 +1557,43 @@ describe("bundler", () => { @dec [y] = z; } - new Foo() + export default Foo; `, }, - mode: "passthrough", + runtimeFiles: { + "/test.js": /* js */ ` + globalThis.q = 'q1'; + globalThis.r = 'r1'; + globalThis.s = 's1'; + globalThis.x = 'x1'; + globalThis.y = 'y1'; + globalThis.z = 'z1'; + globalThis.dec = function(...args) { + console.log(JSON.stringify([this, ...args])); + }; + const Foo = (await import('./out')).default; + globalThis.q = 'q2'; + globalThis.r = 'r2'; + globalThis.s = 's2'; + globalThis.x = 'x2'; + globalThis.y = 'y2'; + globalThis.z = 'z2'; + const y = new Foo(); + console.log(JSON.stringify(y)); + `, + }, + useDefineForClassFields: true, + mode: "transform", + run: { + stdout: ` + [null,{},"x1",null] + [null,{},"y1",null] + {"r1":"s2","y1":"z2"} + `, + file: "/test.js", + }, }); itBundled("ts/TSComputedClassFieldUseDefineTrueLower", { - // GENERATED files: { "/entry.ts": /* ts */ ` class Foo { @@ -1271,14 +1604,44 @@ describe("bundler", () => { @dec [y] = z; } - new Foo() + export default Foo; + `, + }, + runtimeFiles: { + "/test.js": /* js */ ` + globalThis.q = 'q1'; + globalThis.r = 'r1'; + globalThis.s = 's1'; + globalThis.x = 'x1'; + globalThis.y = 'y1'; + globalThis.z = 'z1'; + globalThis.dec = function(...args) { + console.log(JSON.stringify([this, ...args])); + }; + const Foo = (await import('./out')).default; + globalThis.q = 'q2'; + globalThis.r = 'r2'; + globalThis.s = 's2'; + globalThis.x = 'x2'; + globalThis.y = 'y2'; + globalThis.z = 'z2'; + const y = new Foo(); + console.log(JSON.stringify(y)); `, }, useDefineForClassFields: true, - mode: "passthrough", + mode: "transform", + run: { + stdout: ` + [null,{},"x1",null] + [null,{},"y1",null] + {"r1":"s2","y1":"z2"} + `, + file: "/test.js", + }, + unsupportedJSFeatures: ["class-field"], }); itBundled("ts/TSAbstractClassFieldUseAssign", { - // GENERATED files: { "/entry.ts": /* ts */ ` const keepThis = Symbol('keepThis') @@ -1288,15 +1651,15 @@ describe("bundler", () => { [keepThis]: any abstract REMOVE_THIS_TOO: any abstract [AND_REMOVE_THIS]: any - abstract [(x => y => x + y)('nested')('scopes')]: any + abstract [(x => y => x + y)('nested')('scopes_REMOVE')]: any } (() => new Foo())() `, }, - mode: "passthrough", + dce: true, + useDefineForClassFields: false, }); itBundled("ts/TSAbstractClassFieldUseDefine", { - // GENERATED files: { "/entry.ts": /* ts */ ` const keepThisToo = Symbol('keepThisToo') @@ -1311,24 +1674,28 @@ describe("bundler", () => { (() => new Foo())() `, }, - mode: "passthrough", + mode: "transform", + useDefineForClassFields: true, }); itBundled("ts/TSImportMTS", { - // GENERATED files: { "/entry.ts": `import './imported.mjs'`, "/imported.mts": `console.log('works')`, }, + run: { + stdout: "works", + }, }); itBundled("ts/TSImportCTS", { - // GENERATED files: { "/entry.ts": `require('./required.cjs')`, "/required.cjs": `console.log('works')`, }, + run: { + stdout: "works", + }, }); itBundled("ts/TSSideEffectsFalseWarningTypeDeclarations", { - // GENERATED files: { "/entry.ts": /* ts */ ` import "some-js" @@ -1348,14 +1715,11 @@ describe("bundler", () => { "/node_modules/empty-dts/package.json": `{ "main": "./foo.d.ts", "sideEffects": false }`, "/node_modules/empty-dts/foo.d.ts": `export type Foo = number`, }, - /* TODO FIX expectedScanLog: `entry.ts: WARNING: Ignoring this import because "node_modules/some-js/foo.js" was marked as having no side effects - node_modules/some-js/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file: - entry.ts: WARNING: Ignoring this import because "node_modules/some-ts/foo.ts" was marked as having no side effects - node_modules/some-ts/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file: - `, */ + onAfterBundle(api) { + expect(api.readFile("/out.js").trim()).toBe(""); + }, }); itBundled("ts/TSSiblingNamespace", { - // GENERATED files: { "/let.ts": /* ts */ ` export namespace x { export let y = 123 } @@ -1379,8 +1743,24 @@ describe("bundler", () => { `, }, entryPoints: ["/let.ts", "/function.ts", "/class.ts", "/namespace.ts", "/enum.ts"], - mode: "passthrough", + mode: "transform", + runtimeFiles: { + "/test.js": /* js */ ` + import assert from 'assert' + const test_let = (await import('./let.js')).x + assert(test_let.x === test_let.x, "let.ts worked") + const test_function = (await import('./function.js')).x + assert(test_function.x === test_function.x, "function.ts worked") + const test_class = (await import('./class.js')).x + assert(test_class.x === test_class.x, "class.ts worked") + const test_namespace = (await import('./namespace.js')).x + assert(test_namespace.x === test_namespace.x, "namespace.ts worked") + const test_enum = (await import('./enum.js')).x + assert(test_enum.x === test_enum.x, "enum.ts worked") + `, + }, }); + if (!RUN_UNCHECKED_TESTS) return; itBundled("ts/TSSiblingEnum", { // GENERATED files: { diff --git a/test/bundler/expectBundled.md b/test/bundler/expectBundled.md index 4e944543c..be455910c 100644 --- a/test/bundler/expectBundled.md +++ b/test/bundler/expectBundled.md @@ -188,3 +188,23 @@ Places that are not required to be dce'd contain `POSSIBLE_REMOVAL` and do not t ## keepNames tricks In `esbuild/default.test.ts`, test `default/KeepNamesTreeShaking`, we call the esbuild cli to minify identifiers, and then check the code for expected class names to survive the minification (keep names forcibily sets functions `.name`). + +# capture + +This lets you capture the exact js that is emitted by wrapping it in a function call `capture`. Like a partial snapshot. + +```ts +itBundled("minify/TemplateStringFolding", { + files: { + "/entry.js": /* js */ ` + capture(\`😋📋👌\`.length) + capture(\`😋📋👌\`.length === 6) + capture(\`😋📋👌\`.length == 6) + capture(\`😋📋👌\`.length === 2) + capture(\`😋📋👌\`.length == 2) + `, + }, + minifySyntax: true, + capture: ["6", "true", "true", "false", "false"], +}); +``` diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts index 0b467dddf..3bffb4271 100644 --- a/test/bundler/expectBundled.ts +++ b/test/bundler/expectBundled.ts @@ -32,6 +32,16 @@ const DEBUG = process.env.BUN_BUNDLER_TEST_DEBUG; const FILTER = process.env.BUN_BUNDLER_TEST_FILTER; /** Path to the bun. TODO: Once bundler is merged, we should remove the `bun-debug` fallback. */ const BUN_EXE = (process.env.BUN_EXE && Bun.which(process.env.BUN_EXE)) ?? Bun.which("bun-debug") ?? bunExe(); +/** + * If set to true, run an alternate validation for tests which is much looser. + * We are only testing for: + * - bundler does not crash + * - output js has no syntax errors + * + * Defaults to true unless you are running a single test. + */ +const LOOSE = !process.env.BUN_BUNDLER_TEST_FILTER && process.env.BUN_BUNDLER_TEST_LOOSE !== "false"; +export const RUN_UNCHECKED_TESTS = LOOSE; const outBaseTemplate = path.join(tmpdir(), "bun-build-tests", `${ESBUILD ? "esbuild" : "bun"}-`); if (!existsSync(path.dirname(outBaseTemplate))) mkdirSync(path.dirname(outBaseTemplate), { recursive: true }); @@ -57,7 +67,7 @@ export interface BundlerTestInput { entryPointsRaw?: string[]; /** Defaults to bundle */ mode?: "bundle" | "transform"; - /** Used for `default/ErrorMessageCrashStdinIssue2913`. */ + /** Used for `default/ErrorMessageCrashStdinESBuildIssue2913`. */ stdin?: { contents: string; cwd: string }; /** Use when doing something weird with entryPoints and you need to check other output paths. */ outputPaths?: string[]; @@ -106,6 +116,7 @@ export interface BundlerTestInput { treeShaking?: boolean; unsupportedCSSFeatures?: string[]; unsupportedJSFeatures?: string[]; + /** if set to true or false, create or edit tsconfig.json to set compilerOptions.useDefineForClassFields */ useDefineForClassFields?: boolean; sourceMap?: boolean | "inline" | "external"; @@ -152,6 +163,9 @@ export interface BundlerTestInput { files: string[]; }; + /** Captures `capture()` function calls in the output. */ + capture?: string[]; + /** Run after bundle happens but before runtime. */ onAfterBundle?(api: BundlerTestBundleAPI): void; } @@ -167,6 +181,10 @@ export interface BundlerTestBundleAPI { appendFile(file: string, contents: string): void; expectFile(file: string): Expect; assertFileExists(file: string): void; + /** + * Finds all `capture(...)` calls and returns the strings within each function call. + */ + captureFile(file: string, fnName?: string): string[]; warnings: Record<string, { file: string; error: string; line?: string; col?: string }[]>; options: BundlerTestInput; @@ -220,6 +238,7 @@ export function expectBundled( banner, bundleErrors, bundleWarnings, + capture, dce, dceKeepMarkerCount, define, @@ -236,6 +255,7 @@ export function expectBundled( legalComments, loader, mainFields, + matchesReference, metafile, minifyIdentifiers, minifySyntax, @@ -255,7 +275,7 @@ export function expectBundled( treeShaking, unsupportedCSSFeatures, unsupportedJSFeatures, - matchesReference, + useDefineForClassFields, ...unknownProps } = opts; @@ -287,6 +307,12 @@ export function expectBundled( if (!ESBUILD && format !== "esm") { throw new Error("formats besides esm not implemented in bun build"); } + if (!ESBUILD && platform === "neutral") { + throw new Error("platform=neutral not implemented in bun build"); + } + if (!ESBUILD && mode === "transform") { + throw new Error("mode=transform not implemented in bun build"); + } if (!ESBUILD && metafile) { throw new Error("metafile not implemented in bun build"); } @@ -358,6 +384,26 @@ export function expectBundled( writeFileSync(filename, dedent(contents).replace(/\{\{root\}\}/g, root)); } + if (useDefineForClassFields !== undefined) { + if (existsSync(path.join(root, "tsconfig.json"))) { + try { + const tsconfig = JSON.parse(readFileSync(path.join(root, "tsconfig.json"), "utf8")); + tsconfig.compilerOptions = tsconfig.compilerOptions ?? {}; + tsconfig.compilerOptions.useDefineForClassFields = useDefineForClassFields; + writeFileSync(path.join(root, "tsconfig.json"), JSON.stringify(tsconfig, null, 2)); + } catch (error) { + console.log( + "DEBUG NOTE: specifying useDefineForClassFields causes tsconfig.json to be parsed as JSON and not JSONC.", + ); + } + } else { + writeFileSync( + path.join(root, "tsconfig.json"), + JSON.stringify({ compilerOptions: { useDefineForClassFields } }, null, 2), + ); + } + } + // Run bun build cli. In the future we can move to using `Bun.Bundler` const cmd = ( !ESBUILD @@ -391,6 +437,7 @@ export function expectBundled( // keepNames && `--keep-names`, // mainFields && `--main-fields=${mainFields}`, // loader && Object.entries(loader).map(([k, v]) => ["--loader", `${k}=${v}`]), + mode === "transform" && "--transform", ] : [ ESBUILD_PATH, @@ -478,8 +525,9 @@ export function expectBundled( if (!success) { if (!ESBUILD) { + const errorText = stderr.toString("utf-8"); const errorRegex = /^error: (.*?)\n(?:.*?\n\s*\^\s*\n(.*?)\n)?/gms; - const allErrors = [...stderr!.toString("utf-8").matchAll(errorRegex)] + const allErrors = [...errorText.matchAll(errorRegex)] .map(([_str1, error, source]) => { if (!source) { if (error === "FileNotFound") { @@ -494,10 +542,16 @@ export function expectBundled( .filter(Boolean) as any[]; if (allErrors.length === 0) { - console.log(stderr!.toString("utf-8")); + console.log(errorText); } - if (stderr!.toString("utf-8").includes("Crash report saved to:")) { + if ( + errorText.includes("Crash report saved to:") || + errorText.includes("panic: reached unreachable code") || + errorText.includes("Panic: reached unreachable code") || + errorText.includes("Segmentation fault") || + errorText.includes("bun has crashed") + ) { throw new Error("Bun crashed during build"); } @@ -639,6 +693,15 @@ export function expectBundled( }, warnings: warningReference, options: opts, + captureFile: (file, fnName = "capture") => { + const fileContents = readFile(file); + const regex = new RegExp(`\\b${fnName}\\s*\\(((?:\\(\\))?.*?)\\)`, "g"); + const matches = [...fileContents.matchAll(regex)]; + if (matches.length === 0) { + throw new Error(`No ${fnName} calls found in ${file}`); + } + return matches.map(match => match[1]); + }, } satisfies BundlerTestBundleAPI; // DCE keep scan @@ -750,6 +813,11 @@ export function expectBundled( } } + if (capture) { + const captures = api.captureFile(path.relative(root, outfile ?? outputPaths[0])); + expect(captures).toEqual(capture); + } + // Write runtime files to disk as well as run the post bundle hook. for (const [file, contents] of Object.entries(runtimeFiles ?? {})) { mkdirSync(path.dirname(path.join(root, file)), { recursive: true }); @@ -907,7 +975,22 @@ export function itBundled(id: string, opts: BundlerTestInput): BundlerTestRef { } } - it(id, () => expectBundled(id, opts)); + if (LOOSE) { + try { + expectBundled(id, opts); + it(id, () => {}); + } catch (error: any) { + if (error.message === "Bun crashed during build") { + it(id, () => { + throw error; + }); + } else { + it.skip(id, () => {}); + } + } + } else { + it(id, () => expectBundled(id, opts)); + } return ref; } itBundled.skip = (id: string, opts: BundlerTestInput) => { diff --git a/test/bundler/report-bundler-test-progress.sh b/test/bundler/report-bundler-test-progress.sh index 1ee0fec66..7c0266c59 100644 --- a/test/bundler/report-bundler-test-progress.sh +++ b/test/bundler/report-bundler-test-progress.sh @@ -12,7 +12,7 @@ total_skip=0 for test in $tests; do defined=$(grep "^import" $test -v | grep -v expectBundled.md | grep -Ec "expectBundled|itBundled") - output=$(bun test $test 2>&1 | tail -n 5) + output=$(BUN_BUNDLER_TEST_LOOSE=false bun test $test 2>&1 | tail -n 5) pass=$(echo "$output" | grep "pass" | cut -d " " -f 2) fail=$(echo "$output" | grep "fail" | cut -d " " -f 2) skip=$(echo "$output" | grep "skip" | cut -d " " -f 2) diff --git a/test/bundler/run-single-bundler-test.sh b/test/bundler/run-single-bundler-test.sh index 0b3cf96b2..a22aee77d 100755 --- a/test/bundler/run-single-bundler-test.sh +++ b/test/bundler/run-single-bundler-test.sh @@ -6,6 +6,10 @@ if [ -z "$1" ]; then exit 1 fi +if [ -z "$BUN_EXE"]; then + BUN_EXE=$(which bd 2>/dev/null || which bun 2>/dev/null) +fi + __dirname="$(dirname $(realpath "$0"))" cd "$__dirname" |