Fix failing tests

This commit is contained in:
2026-10-03 03:18:03 +01:00
parent 3a4ece5255
commit 6e6cfef5e5
12 changed files with 182 additions and 138 deletions
+80 -49
View File
@@ -1,8 +1,6 @@
import { describe, expect, test, beforeAll, afterAll } from "bun:test";
import startServer from "../../../src/functions/server/start-server";
import path from "path";
import fs, { FSWatcher } from "fs";
import { execSync } from "child_process";
import {
BundlerCTXMap,
BunextConfig,
@@ -19,26 +17,35 @@ const fixtureDir = path.resolve(__dirname, "../../../test/e2e-fixture");
const fixturePagesDir = path.join(fixtureDir, "src", "pages");
const fixtureIndexPage = path.join(fixturePagesDir, "index.tsx");
// Bundler artifact map keys are relative to the consumer project root
// (the fixture once we chdir into it before loading the framework).
const relativeIndexPage = "src/pages/index.tsx";
let originalCwd = process.cwd();
let originalPort: string | undefined;
let startServer: (() => Promise<Bun.Server<any>>) | undefined;
declare global {
var CONFIG: BunextConfig;
var SERVER: Bun.Server<any> | undefined;
var RECOMPILING: boolean;
var WATCHER_TIMEOUT: any;
var ROUTER: FileSystemRouter;
var HMR_CONTROLLERS: GlobalHMRControllerObject[];
var LAST_BUILD_TIME: number;
var BUNDLER_CTX_MAP: { [k: string]: BundlerCTXMap } | undefined;
var BUNDLER_REBUILDS: 0;
var PAGES_SRC_WATCHER: FSWatcher | undefined;
var CURRENT_VERSION: string | undefined;
var PAGE_FILES: PageFiles[];
var ROOT_FILE_UPDATED: boolean;
var SKIPPED_BROWSER_MODULES: Set<string>;
var BUNDLER_CTX: BuildContext | undefined;
var DIR_NAMES: ReturnType<typeof grabDirNames>;
var BUNEXT_CONFIG: BunextConfig;
var BUNEXT_SERVER: Bun.Server<any> | undefined;
var BUNEXT_RECOMPILING: boolean;
var BUNEXT_WATCHER_TIMEOUT: any;
var BUNEXT_ROUTER: FileSystemRouter;
var BUNEXT_HMR_CONTROLLERS: GlobalHMRControllerObject[];
var BUNEXT_LAST_BUILD_TIME: number;
var BUNEXT_BUNDLER_CTX_MAP: { [k: string]: BundlerCTXMap } | undefined;
var BUNEXT_SSR_BUNDLER_CTX_MAP: { [k: string]: BundlerCTXMap };
var BUNEXT_BUNDLER_REBUILDS: 0;
var BUNEXT_PAGES_SRC_WATCHER: FSWatcher | undefined;
var BUNEXT_CURRENT_VERSION: string | undefined;
var BUNEXT_PAGE_FILES: PageFiles[];
var BUNEXT_ROOT_FILE_UPDATED: boolean;
var BUNEXT_SKIPPED_BROWSER_MODULES: Set<string>;
var BUNEXT_BUNDLER_CTX: BuildContext | undefined;
var BUNEXT_DIR_NAMES: ReturnType<typeof grabDirNames>;
var BUNEXT_CONSTANTS: ReturnType<typeof grabConstants>;
var BUNEXT_REACT_DOM_MODULE_CACHE: Map<string, any>;
var BUNEXT_REBUILD_RETRIES: number;
}
describe("E2E Integration", () => {
@@ -49,24 +56,50 @@ describe("E2E Integration", () => {
// Use port 0 so Bun.serve picks a random available port
process.env.PORT = "0";
// /test is gitignored, so bootstrap the fixture from scratch instead
// of trusting stale files.
fs.mkdirSync(fixturePagesDir, { recursive: true });
await Bun.write(
path.join(fixtureDir, "package.json"),
JSON.stringify({ name: "e2e-fixture", private: true }, null, 4),
);
await Bun.write(
fixtureIndexPage,
`export default function Page(){return <div>Hello E2E</div>}`,
);
const serverFilePath = path.join(fixturePagesDir, "index.server.ts");
if (fs.existsSync(serverFilePath)) {
fs.unlinkSync(serverFilePath);
}
const moduleDir = path.join(fixtureDir, "node_modules");
fs.mkdirSync(moduleDir, { recursive: true });
for (const mod of ["react", "react-dom"]) {
const target = path.join(moduleDir, mod);
if (!fs.existsSync(target)) {
fs.symlinkSync(
path.join(originalCwd, "node_modules", mod),
target,
);
}
}
process.chdir(fixtureDir);
execSync(`bun init -y && bun install react react-dom`, {
cwd: fixtureDir,
});
global.CONFIG = { development: true };
global.HMR_CONTROLLERS = [];
global.BUNDLER_REBUILDS = 0;
global.PAGE_FILES = [];
global.CONSTANTS = grabConstants();
global.SSR_BUNDLER_CTX_MAP = {};
global.BUNDLER_CTX_MAP = {};
global.REACT_DOM_MODULE_CACHE = new Map<string, any>();
global.REBUILD_RETRIES = 0;
// Framework directory constants must resolve against the fixture
// root; compute them before any framework module is loaded.
global.BUNEXT_DIR_NAMES = grabDirNames();
global.BUNEXT_CONFIG = { development: true };
global.BUNEXT_CONSTANTS = grabConstants();
global.BUNEXT_HMR_CONTROLLERS = [];
global.BUNEXT_BUNDLER_REBUILDS = 0;
global.BUNEXT_PAGE_FILES = [];
global.BUNEXT_SSR_BUNDLER_CTX_MAP = {};
global.BUNEXT_BUNDLER_CTX_MAP = {};
global.BUNEXT_REACT_DOM_MODULE_CACHE = new Map<string, any>();
global.BUNEXT_REBUILD_RETRIES = 0;
// Set up router pointing at the fixture's pages directory
global.ROUTER = new Bun.FileSystemRouter({
global.BUNEXT_ROUTER = new Bun.FileSystemRouter({
style: "nextjs",
dir: fixturePagesDir,
});
@@ -74,23 +107,32 @@ describe("E2E Integration", () => {
// Pre-populate the bundler context map so grab-page-component can
// look up the compiled path. The `path` value only needs to be
// present for the guard check; SSR does not require the file to exist.
global.BUNDLER_CTX_MAP = {
[fixtureIndexPage]: {
global.BUNEXT_BUNDLER_CTX_MAP = {
[relativeIndexPage]: {
path: ".bunext/public/pages/index.js",
hash: "index",
type: "text/javascript",
entrypoint: fixtureIndexPage,
local_path: fixtureIndexPage,
local_path: relativeIndexPage,
url_path: "/",
file_name: "index",
},
};
// Import the framework after bootstrap so its captured paths are
// relative to the fixture.
const { default: startServerFn } = await import(
"../../../src/functions/server/start-server"
);
startServer = startServerFn;
});
afterAll(async () => {
if (server) {
server.stop(true);
}
global.BUNEXT_SERVER = undefined;
process.chdir(originalCwd);
// Restore PORT env variable
@@ -100,7 +142,7 @@ describe("E2E Integration", () => {
delete process.env.PORT;
}
// Remove any generated .bunext artifacts from the fixture
// Remove generated artifacts from the fixture
const dotBunext = path.join(fixtureDir, ".bunext");
if (fs.existsSync(dotBunext)) {
fs.rmSync(dotBunext, { recursive: true, force: true });
@@ -108,7 +150,7 @@ describe("E2E Integration", () => {
});
test("boots up the server and correctly routes to index.tsx page", async () => {
server = await startServer();
server = await startServer!();
expect(server).toBeDefined();
expect(server.port).toBeGreaterThan(0);
@@ -147,17 +189,6 @@ export default server;
`,
);
// Add the fixture page to the BUNDLER_CTX_MAP
// global.BUNDLER_CTX_MAP[pageFilePath] = {
// path: ".bunext/public/pages/index.js",
// hash: "index",
// type: "text/javascript",
// entrypoint: pageFilePath,
// local_path: pageFilePath,
// url_path: "/",
// file_name: "index",
// };
const response = await fetch(`http://localhost:${server.port}/`);
expect(response.status).toBe(200);
@@ -168,4 +199,4 @@ export default server;
// Clean up
fs.unlinkSync(serverFilePath);
});
});
});
@@ -36,7 +36,7 @@ mock.module("../../../../src/functions/server/web-pages/handle-web-pages", () =>
describe("bunext-req-handler", () => {
beforeEach(() => {
global.CONSTANTS = {
global.BUNEXT_CONSTANTS = {
config: {
middleware: async ({ url }: any) => {
if (url.pathname === "/blocked") {
@@ -61,8 +61,8 @@ describe("bunext-req-handler", () => {
});
test("routes /__hmr to handleHmr in dev", async () => {
global.ROUTER = { match: () => ({}) } as any;
global.HMR_CONTROLLERS = [];
global.BUNEXT_ROUTER = { match: () => ({}) } as any;
global.BUNEXT_HMR_CONTROLLERS = [];
const req = new Request("http://localhost/__hmr", {
headers: { referer: "http://localhost/" }
});
@@ -24,15 +24,15 @@ import { log } from "../../../../src/utils/log";
import grabPageComponent from "../../../../src/functions/server/web-pages/grab-page-component";
describe("grabPageComponent", () => {
const originalRouter = global.ROUTER;
const originalRouter = global.BUNEXT_ROUTER;
afterEach(() => {
global.ROUTER = originalRouter;
global.BUNEXT_ROUTER = originalRouter;
mock.restore();
});
test("does not log an error for expected 404 page lookups", async () => {
global.ROUTER = new Bun.FileSystemRouter({
global.BUNEXT_ROUTER = new Bun.FileSystemRouter({
style: "nextjs",
dir: path.resolve(__dirname, "../../../../test/e2e-fixture/src/pages"),
});
@@ -3,25 +3,25 @@ import handleHmr from "../../../../src/functions/server/handle-hmr";
describe("handle-hmr", () => {
beforeEach(() => {
global.ROUTER = {
global.BUNEXT_ROUTER = {
match: (path: string) => {
if (path === "/test") return { filePath: "/test-file" };
return null;
}
} as any;
global.HMR_CONTROLLERS = [];
global.BUNDLER_CTX_MAP = {
"/test-file": { local_path: "/test-file" } as any,
global.BUNEXT_HMR_CONTROLLERS = [];
global.BUNEXT_BUNDLER_CTX_MAP = {
"test-file": { local_path: "/test-file" } as any,
};
});
afterEach(() => {
global.ROUTER = undefined as any;
global.HMR_CONTROLLERS = [];
global.BUNDLER_CTX_MAP = undefined as any;
global.BUNEXT_ROUTER = undefined as any;
global.BUNEXT_HMR_CONTROLLERS = [];
global.BUNEXT_BUNDLER_CTX_MAP = undefined as any;
});
test("sets up SSE stream and pushes to HMR_CONTROLLERS", async () => {
test("sets up SSE stream and pushes to BUNEXT_HMR_CONTROLLERS", async () => {
const req = new Request("http://localhost/hmr", {
headers: {
"referer": "http://localhost/test"
@@ -34,8 +34,8 @@ describe("handle-hmr", () => {
expect(res.headers.get("Content-Type")).toBe("text/event-stream");
expect(res.headers.get("Connection")).toBe("keep-alive");
expect(global.HMR_CONTROLLERS.length).toBe(1);
const controller = global.HMR_CONTROLLERS[0];
expect(global.BUNEXT_HMR_CONTROLLERS.length).toBe(1);
const controller = global.BUNEXT_HMR_CONTROLLERS[0];
expect(controller.page_url).toBe("http://localhost/test");
expect(controller.target_map?.local_path).toBe("/test-file");
});
@@ -32,16 +32,7 @@ function makeController(page_url: string, target_map?: { local_path: string }) {
describe("server-post-build-fn", () => {
beforeEach(() => {
grabPageComponent.mockClear();
global.ROUTER = {
match: (path: string) => {
if (path === "/about") return { filePath: "/pages/about.tsx" };
if (path === "/404") return { filePath: "/pages/404.tsx" };
if (path === "/new-page")
return { filePath: "/pages/new-page.tsx" };
return null;
},
} as any;
global.BUNDLER_CTX_MAP = {
global.BUNEXT_BUNDLER_CTX_MAP = {
"/pages/about.tsx": {
local_path: "/pages/about.tsx",
path: "pages/about.js",
@@ -50,26 +41,22 @@ describe("server-post-build-fn", () => {
local_path: "/pages/404.tsx",
path: "pages/404.js",
} as any,
"/pages/new-page.tsx": {
local_path: "/pages/new-page.tsx",
path: "pages/new-page.js",
} as any,
};
global.HMR_CONTROLLERS = [];
global.ROOT_FILE_UPDATED = false;
global.BUNEXT_HMR_CONTROLLERS = [];
global.BUNEXT_ROOT_FILE_UPDATED = false;
});
afterEach(() => {
global.ROUTER = undefined as any;
global.BUNDLER_CTX_MAP = undefined as any;
global.HMR_CONTROLLERS = [];
global.BUNEXT_BUNDLER_CTX_MAP = undefined as any;
global.BUNEXT_HMR_CONTROLLERS = [];
global.BUNEXT_ROOT_FILE_UPDATED = false;
});
test("soft-updates a normal page controller", async () => {
const c = makeController("http://localhost/about", {
local_path: "/pages/about.tsx",
});
global.HMR_CONTROLLERS = [c as any];
global.BUNEXT_HMR_CONTROLLERS = [c as any];
await serverPostBuildFn();
@@ -77,13 +64,14 @@ describe("server-post-build-fn", () => {
expect(c.enqueued[0]).toContain("event: update");
expect(c.enqueued[0]).not.toContain('"reload":true');
expect(c.enqueued[0]).toContain("/pages/about.tsx");
expect(grabPageComponent).toHaveBeenCalled();
});
test("soft-updates unmatched URL via custom 404 artifact", async () => {
const c = makeController("http://localhost/missing", {
local_path: "/pages/404.tsx",
});
global.HMR_CONTROLLERS = [c as any];
global.BUNEXT_HMR_CONTROLLERS = [c as any];
await serverPostBuildFn();
@@ -93,39 +81,73 @@ describe("server-post-build-fn", () => {
expect(grabPageComponent).toHaveBeenCalled();
});
test("full-reloads when a previously unmatched route now exists", async () => {
const c = makeController("http://localhost/new-page", {
test("full-reloads when the controller's target artifact is missing from the map", async () => {
const c = makeController("http://localhost/gone", {
local_path: "/pages/removed.tsx",
});
global.BUNEXT_HMR_CONTROLLERS = [c as any];
await serverPostBuildFn();
expect(c.enqueued.length).toBe(1);
expect(c.enqueued[0]).toContain('"reload":true');
expect(grabPageComponent).not.toHaveBeenCalled();
});
test("full-reloads every controller when reload_all_controllers is set", async () => {
const c1 = makeController("http://localhost/about", {
local_path: "/pages/about.tsx",
});
const c2 = makeController("http://localhost/missing", {
local_path: "/pages/404.tsx",
});
global.HMR_CONTROLLERS = [c as any];
global.BUNEXT_HMR_CONTROLLERS = [c1 as any, c2 as any];
await serverPostBuildFn({ reload_all_controllers: true });
expect(c1.enqueued.length).toBe(1);
expect(c1.enqueued[0]).toContain('"reload":true');
expect(c2.enqueued.length).toBe(1);
expect(c2.enqueued[0]).toContain('"reload":true');
expect(grabPageComponent).not.toHaveBeenCalled();
});
test("enqueues reload when the root file changed and resets the flag", async () => {
const c = makeController("http://localhost/about", {
local_path: "/pages/about.tsx",
});
global.BUNEXT_HMR_CONTROLLERS = [c as any];
global.BUNEXT_ROOT_FILE_UPDATED = true;
await serverPostBuildFn();
expect(c.enqueued.length).toBe(1);
expect(c.enqueued[0]).toContain('"reload":true');
expect(c.target_map?.local_path).toBe("/pages/new-page.tsx");
expect(global.BUNEXT_ROOT_FILE_UPDATED).toBe(false);
});
test("full-reloads unmatched tab with no prior map when route appears", async () => {
test("skips controllers without a target map", async () => {
const c = makeController("http://localhost/new-page");
global.HMR_CONTROLLERS = [c as any];
global.BUNEXT_HMR_CONTROLLERS = [c as any];
await serverPostBuildFn();
expect(c.enqueued.length).toBe(1);
expect(c.enqueued[0]).toContain('"reload":true');
expect(c.enqueued.length).toBe(0);
expect(grabPageComponent).not.toHaveBeenCalled();
});
test("full-reloads preset 404 tabs with no custom 404 page", async () => {
global.ROUTER = {
match: () => null,
} as any;
const c = makeController("http://localhost/missing");
global.HMR_CONTROLLERS = [c as any];
test("only touches controllers whose page is in changed_pages", async () => {
const changed = makeController("http://localhost/about", {
local_path: "/pages/about.tsx",
});
const untouched = makeController("http://localhost/missing", {
local_path: "/pages/404.tsx",
});
global.BUNEXT_HMR_CONTROLLERS = [changed as any, untouched as any];
await serverPostBuildFn();
await serverPostBuildFn({ changed_pages: ["/pages/about.tsx"] });
expect(c.enqueued.length).toBe(1);
expect(c.enqueued[0]).toContain('"reload":true');
expect(changed.enqueued.length).toBe(1);
expect(untouched.enqueued.length).toBe(0);
});
});
});
@@ -23,20 +23,20 @@ mock.module("../../../../src/utils/grab-app-port", () => ({
describe("startServer", () => {
afterEach(() => {
if (global.SERVER) {
global.SERVER.stop(true);
global.SERVER = undefined;
if (global.BUNEXT_SERVER) {
global.BUNEXT_SERVER.stop(true);
global.BUNEXT_SERVER = undefined;
}
});
test("starts the server and assigns to global.SERVER", async () => {
global.CONFIG = { development: true };
test("starts the server and assigns to global.BUNEXT_SERVER", async () => {
global.BUNEXT_CONFIG = { development: true };
const server = await startServer();
expect(server).toBeDefined();
expect(server.port).toBeGreaterThan(0);
expect(global.SERVER).toBe(server);
expect(global.BUNEXT_SERVER).toBe(server);
expect(log.server).toHaveBeenCalled();
server.stop(true);
+3 -3
View File
@@ -5,7 +5,7 @@ const originalEnv = process.env.PORT;
beforeEach(() => {
delete process.env.PORT;
(global as any).CONFIG = {};
(global as any).BUNEXT_CONFIG = {};
});
afterEach(() => {
@@ -27,13 +27,13 @@ describe("grabAppPort", () => {
});
it("uses config.port when PORT env is not set", () => {
(global as any).CONFIG = { port: 3000 };
(global as any).BUNEXT_CONFIG = { port: 3000 };
expect(grabAppPort()).toBe(3000);
});
it("PORT env takes precedence over config.port", () => {
process.env.PORT = "9000";
(global as any).CONFIG = { port: 3000 };
(global as any).BUNEXT_CONFIG = { port: 3000 };
expect(grabAppPort()).toBe(9000);
});
+3 -3
View File
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach } from "bun:test";
import grabConstants from "../../utils/grab-constants";
beforeEach(() => {
(global as any).CONFIG = {};
(global as any).BUNEXT_CONFIG = {};
});
describe("grabConstants", () => {
@@ -32,9 +32,9 @@ describe("grabConstants", () => {
expect(grabConstants().MaxBundlerRebuilds).toBe(5);
});
it("returns the current global.CONFIG", () => {
it("returns the current global.BUNEXT_CONFIG", () => {
const cfg = { port: 9000 };
(global as any).CONFIG = cfg;
(global as any).BUNEXT_CONFIG = cfg;
expect(grabConstants().config).toBe(cfg);
});
});
+3 -3
View File
@@ -5,7 +5,7 @@ const originalPort = process.env.PORT;
beforeEach(() => {
delete process.env.PORT;
(global as any).CONFIG = {};
(global as any).BUNEXT_CONFIG = {};
});
afterEach(() => {
@@ -18,7 +18,7 @@ afterEach(() => {
describe("grabOrigin", () => {
it("returns config.origin when set", () => {
(global as any).CONFIG = { origin: "https://example.com" };
(global as any).BUNEXT_CONFIG = { origin: "https://example.com" };
expect(grabOrigin()).toBe("https://example.com");
});
@@ -32,7 +32,7 @@ describe("grabOrigin", () => {
});
it("falls back using config.port", () => {
(global as any).CONFIG = { port: 3700 };
(global as any).BUNEXT_CONFIG = { port: 3700 };
expect(grabOrigin()).toBe("http://localhost:3700");
});
});
+9 -18
View File
@@ -1,13 +1,8 @@
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
import { describe, it, expect, afterEach } from "bun:test";
import isDevelopment from "../../utils/is-development";
const originalEnv = process.env.NODE_ENV;
beforeEach(() => {
// Reset global config before each test
(global as any).CONFIG = {};
});
afterEach(() => {
process.env.NODE_ENV = originalEnv;
});
@@ -15,25 +10,21 @@ afterEach(() => {
describe("isDevelopment", () => {
it("returns false when NODE_ENV is production", () => {
process.env.NODE_ENV = "production";
(global as any).CONFIG = { development: true };
expect(isDevelopment()).toBe(false);
});
it("returns true when config.development is true and NODE_ENV is not production", () => {
it("returns true when NODE_ENV is development", () => {
process.env.NODE_ENV = "development";
(global as any).CONFIG = { development: true };
expect(isDevelopment()).toBe(true);
});
it("returns false when config.development is false", () => {
process.env.NODE_ENV = "development";
(global as any).CONFIG = { development: false };
expect(isDevelopment()).toBe(false);
it("returns true when NODE_ENV is unset", () => {
delete process.env.NODE_ENV;
expect(isDevelopment()).toBe(true);
});
it("returns false when config.development is undefined", () => {
process.env.NODE_ENV = "development";
(global as any).CONFIG = {};
expect(isDevelopment()).toBe(false);
it("returns true for any other NODE_ENV value", () => {
process.env.NODE_ENV = "test";
expect(isDevelopment()).toBe(true);
});
});
});