// Bundle tests are tests concerning bundling bugs that only occur in DevServer. import { expect } from "bun:test"; import { devTest, emptyHtmlFile, minimalFramework } from "../bake-harness"; devTest("import identifier doesnt get renamed", { framework: minimalFramework, files: { "db.ts": `export const abc = "123";`, "routes/index.ts": ` import { abc } from '../db'; export default function (req, meta) { let v1 = ""; const v2 = v1 ? abc.toFixed(2) : abc.toString(); return new Response('Hello, ' + v2 + '!'); } `, }, async test(dev) { await dev.fetch("/").equals("Hello, 123!"); await dev.write("db.ts", `export const abc = "456";`); await dev.fetch("/").equals("Hello, 456!"); await dev.patch("routes/index.ts", { find: "Hello", replace: "Bun", }); await dev.fetch("/").equals("Bun, 456!"); }, }); devTest("symbol collision with import identifier", { framework: minimalFramework, files: { "db.ts": `export const abc = "123";`, "routes/index.ts": ` let import_db = 987; import { abc } from '../db'; export default function (req, meta) { let v1 = ""; const v2 = v1 ? abc.toFixed(2) : abc.toString(); return new Response('Hello, ' + v2 + ', ' + import_db + '!'); } `, }, async test(dev) { await dev.fetch("/").equals("Hello, 123, 987!"); await dev.write("db.ts", `export const abc = "456";`); await dev.fetch("/").equals("Hello, 456, 987!"); }, }); devTest('uses "development" condition', { framework: minimalFramework, files: { "node_modules/example/package.json": JSON.stringify({ name: "example", version: "1.0.0", exports: { ".": { development: "./development.js", default: "./production.js", }, }, }), "node_modules/example/development.js": `export default "development";`, "node_modules/example/production.js": `export default "production";`, "routes/index.ts": ` import environment from 'example'; export default function (req, meta) { return new Response('Environment: ' + environment); } `, }, async test(dev) { await dev.fetch("/").equals("Environment: development"); }, }); devTest("importing a file before it is created", { files: { "index.html": emptyHtmlFile({ styles: [], scripts: ["index.ts"], }), "index.ts": ` import { abc } from './second'; console.log('value: ' + abc); `, }, async test(dev) { await using c = await dev.client("/", { errors: [`index.ts:1:21: error: Could not resolve: "./second"`], }); await c.expectReload(async () => { await dev.write("second.ts", `export const abc = "456";`); }); await c.expectMessage("value: 456"); }, }); devTest("default export same-scope handling", { files: { "index.html": emptyHtmlFile({ styles: [], scripts: ["index.ts"], }), "index.ts": ` import.meta.hot.accept(); await import("./fixture1.ts"); console.log((new ((await import("./fixture2.ts")).default)).a); await import("./fixture3.ts"); console.log((new ((await import("./fixture4.ts")).default)).result); console.log((await import("./fixture5.ts")).default); console.log((await import("./fixture6.ts")).default); console.log((await import("./fixture7.ts")).default()); console.log((await import("./fixture8.ts")).default()); console.log((await import("./fixture9.ts")).default(false)); `, "fixture1.ts": ` const sideEffect = () => "a"; export default class A { [sideEffect()] = "ONE"; } console.log(new A().a); `, "fixture2.ts": ` const sideEffect = () => "a"; export default class A { [sideEffect()] = "TWO"; } `, "fixture3.ts": ` export default class A { result = "THREE" } console.log(new A().result); `, "fixture4.ts": ` import.meta.hot.accept(); export default class MOVE { result = "FOUR" } `, "fixture5.ts": ` const default_export = "FIVE"; export default default_export; `, "fixture6.ts": ` const default_export = "S"; function sideEffect() { return default_export + "EVEN"; } export default sideEffect(); console.log(default_export + "IX"); `, "fixture7.ts": ` export default function() { return "EIGHT" }; `, "fixture8.ts": ` import.meta.hot.accept(); export default function MOVE() { return "NINE" }; `, "fixture9.ts": ` export default function named(flag = true) { return flag ? "TEN" : "ELEVEN" }; console.log(named()); `, }, async test(dev) { await using c = await dev.client("/", { storeHotChunks: true }); c.expectMessage( // "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "TEN", "ELEVEN", ); const filesExpectingMove = Object.entries(dev.options.files) .filter(([, content]) => content.includes("MOVE")) .map(([path]) => path); for (const file of filesExpectingMove) { await dev.writeNoChanges(file); const chunk = await c.getMostRecentHmrChunk(); expect(chunk).toMatch(/default:\s*(function|class)\s*MOVE/); } await dev.writeNoChanges("fixture7.ts"); const chunk = await c.getMostRecentHmrChunk(); expect(chunk).toMatch(/default:\s*function/); // Since fixture7.ts is not marked as accepting, it will bubble the update // to `index.ts`, re-evaluate it and some of the dependencies. c.expectMessage("TWO", "FOUR", "FIVE", "SEVEN", "EIGHT", "NINE", "ELEVEN"); }, }); devTest("directory cache bust case #17576", { files: { "web/index.html": emptyHtmlFile({ styles: [], scripts: ["index.ts"], }), "web/index.ts": ` console.log(123); import.meta.hot.accept(); `, }, mainDir: "server", async test(dev) { await using c = await dev.client("/"); await c.expectMessage(123); await c.expectNoWebSocketActivity(async () => { await dev.write( "web/Test.ts", ` export const abc = 456; `, ); }); await dev.write( "web/index.ts", ` import { abc } from "./Test.ts"; console.log(abc); `, ); await c.expectMessage(456); }, }); devTest("deleting imported file shows error then recovers", { skip: [ "win32", // unlinkSync is having weird behavior ], files: { "index.html": emptyHtmlFile({ styles: [], scripts: ["index.ts"], }), "index.ts": ` import { value } from "./other"; console.log(value); `, "other.ts": ` export const value = 123; `, "unrelated.ts": ` export const value = 123; `, }, async test(dev) { await using c = await dev.client("/"); await c.expectMessage(123); await dev.delete("other.ts", { errors: ['index.ts:1:23: error: Could not resolve: "./other"'], }); await c.expectReload(async () => { await dev.write( "other.ts", ` export const value = 456; `, ); }); await c.expectMessage(456); await c.expectNoWebSocketActivity(async () => { await dev.delete("unrelated.ts"); }); }, }); devTest("importing html file", { files: { "index.html": emptyHtmlFile({ styles: [], scripts: ["index.ts"], }), "index.ts": ` import html from "./index.html"; console.log(html); `, }, async test(dev) { await using c = await dev.client("/", { errors: ["index.ts:1:18: error: Browser builds cannot import HTML files."], }); }, }); devTest("importing html file with text loader (#18154)", { files: { "index.html": emptyHtmlFile({ styles: [], scripts: ["index.ts"], }), "index.ts": ` import html from "./app.html" with { type: "text" }; console.log(html); `, "app.html": "
hello world
", }, htmlFiles: ["index.html"], async test(dev) { await using c = await dev.client("/", {}); await c.expectMessage("
hello world
"); }, }); devTest("importing bun on the client", { files: { "index.html": emptyHtmlFile({ styles: [], scripts: ["index.ts"], }), "index.ts": ` import bun from "bun"; console.log(bun); `, }, async test(dev) { await using c = await dev.client("/", { errors: ['index.ts:1:17: error: Browser build cannot import Bun builtin: "bun"'], }); }, }); devTest("import.meta.main", { files: { "index.html": emptyHtmlFile({ styles: [], scripts: ["index.ts"], }), "index.ts": ` console.log(import.meta.main); import.meta.hot.accept(); `, }, async test(dev) { await using c = await dev.client("/"); await c.expectMessage(false); // import.meta.main is always false because there is no single entry point await dev.write( "index.ts", ` require; console.log(import.meta.main); `, ); await c.expectMessage(false); }, }); devTest("commonjs forms", { timeoutMultiplier: 2, files: { "index.html": emptyHtmlFile({ styles: [], scripts: ["index.ts"], }), "index.ts": ` import cjs from "./cjs.js"; console.log(cjs); `, "cjs.js": ` module.exports.field = {}; `, }, async test(dev) { console.log("Initial"); await using c = await dev.client("/"); console.log(" expecting message"); await c.expectMessage({ field: {} }); console.log(" expecting reload"); await c.expectReload(async () => { console.log(" writing"); await dev.write("cjs.js", `exports.field = "1";`); console.log(" now reloading"); }); console.log(" expecting message"); await c.expectMessage({ field: "1" }); console.log("Second"); console.log(" expecting reload"); await c.expectReload(async () => { console.log(" writing"); await dev.write("cjs.js", `let theExports = exports; theExports.field = "2";`); }); console.log(" expecting message"); await c.expectMessage({ field: "2" }); console.log("Third"); console.log(" expecting reload"); await c.expectReload(async () => { console.log(" writing"); await dev.write("cjs.js", `let theModule = module; theModule.exports.field = "3";`); }); console.log(" expecting message"); await c.expectMessage({ field: "3" }); console.log("Fourth"); await c.expectReload(async () => { await dev.write("cjs.js", `let { exports } = module; exports.field = "4";`); }); await c.expectMessage({ field: "4" }); console.log("Fifth"); await c.expectReload(async () => { await dev.write("cjs.js", `var { exports } = module; exports.field = "4.5";`); }); await c.expectMessage({ field: "4.5" }); console.log("Sixth"); await c.expectReload(async () => { await dev.write("cjs.js", `let theExports = module.exports; theExports.field = "5";`); }); await c.expectMessage({ field: "5" }); console.log("Seventh"); await c.expectReload(async () => { await dev.write("cjs.js", `require; eval("module.exports.field = '6'");`); }); await c.expectMessage({ field: "6" }); }, });