From 3545b6dc08b33a730a10cc09506cccf46c651cf0 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Sat, 21 Mar 2026 10:20:16 +0100 Subject: [PATCH] Update tests --- dist/__tests__/e2e/e2e.test.d.ts | 1 - dist/__tests__/e2e/e2e.test.js | 52 ---------- .../cache/grab-cache-names.test.d.ts | 1 - .../functions/cache/grab-cache-names.test.js | 37 ------- .../server/bunext-req-handler.test.d.ts | 1 - .../server/bunext-req-handler.test.js | 73 -------------- .../server/grab-web-meta-html.test.d.ts | 1 - .../server/grab-web-meta-html.test.js | 97 ------------------- .../functions/server/handle-hmr.test.d.ts | 1 - .../functions/server/handle-hmr.test.js | 37 ------- .../functions/server/handle-routes.test.d.ts | 1 - .../functions/server/handle-routes.test.js | 65 ------------- .../functions/server/start-server.test.d.ts | 1 - .../functions/server/start-server.test.js | 36 ------- dist/__tests__/hydration/hydration.test.d.ts | 1 - dist/__tests__/hydration/hydration.test.js | 62 ------------ .../utils/deserialize-query.test.d.ts | 1 - .../__tests__/utils/deserialize-query.test.js | 33 ------- dist/__tests__/utils/ejson.test.d.ts | 1 - dist/__tests__/utils/ejson.test.js | 52 ---------- dist/__tests__/utils/grab-app-names.test.d.ts | 1 - dist/__tests__/utils/grab-app-names.test.js | 23 ----- dist/__tests__/utils/grab-app-port.test.d.ts | 1 - dist/__tests__/utils/grab-app-port.test.js | 38 -------- dist/__tests__/utils/grab-constants.test.d.ts | 1 - dist/__tests__/utils/grab-constants.test.js | 30 ------ dist/__tests__/utils/grab-dir-names.test.d.ts | 1 - dist/__tests__/utils/grab-dir-names.test.js | 55 ----------- dist/__tests__/utils/grab-origin.test.d.ts | 1 - dist/__tests__/utils/grab-origin.test.js | 32 ------ dist/__tests__/utils/grab-page-name.test.d.ts | 1 - dist/__tests__/utils/grab-page-name.test.js | 46 --------- dist/__tests__/utils/is-development.test.d.ts | 1 - dist/__tests__/utils/is-development.test.js | 32 ------ dist/__tests__/utils/numberfy.test.d.ts | 1 - dist/__tests__/utils/numberfy.test.js | 43 -------- tsconfig.json | 8 +- 37 files changed, 7 insertions(+), 862 deletions(-) delete mode 100644 dist/__tests__/e2e/e2e.test.d.ts delete mode 100644 dist/__tests__/e2e/e2e.test.js delete mode 100644 dist/__tests__/functions/cache/grab-cache-names.test.d.ts delete mode 100644 dist/__tests__/functions/cache/grab-cache-names.test.js delete mode 100644 dist/__tests__/functions/server/bunext-req-handler.test.d.ts delete mode 100644 dist/__tests__/functions/server/bunext-req-handler.test.js delete mode 100644 dist/__tests__/functions/server/grab-web-meta-html.test.d.ts delete mode 100644 dist/__tests__/functions/server/grab-web-meta-html.test.js delete mode 100644 dist/__tests__/functions/server/handle-hmr.test.d.ts delete mode 100644 dist/__tests__/functions/server/handle-hmr.test.js delete mode 100644 dist/__tests__/functions/server/handle-routes.test.d.ts delete mode 100644 dist/__tests__/functions/server/handle-routes.test.js delete mode 100644 dist/__tests__/functions/server/start-server.test.d.ts delete mode 100644 dist/__tests__/functions/server/start-server.test.js delete mode 100644 dist/__tests__/hydration/hydration.test.d.ts delete mode 100644 dist/__tests__/hydration/hydration.test.js delete mode 100644 dist/__tests__/utils/deserialize-query.test.d.ts delete mode 100644 dist/__tests__/utils/deserialize-query.test.js delete mode 100644 dist/__tests__/utils/ejson.test.d.ts delete mode 100644 dist/__tests__/utils/ejson.test.js delete mode 100644 dist/__tests__/utils/grab-app-names.test.d.ts delete mode 100644 dist/__tests__/utils/grab-app-names.test.js delete mode 100644 dist/__tests__/utils/grab-app-port.test.d.ts delete mode 100644 dist/__tests__/utils/grab-app-port.test.js delete mode 100644 dist/__tests__/utils/grab-constants.test.d.ts delete mode 100644 dist/__tests__/utils/grab-constants.test.js delete mode 100644 dist/__tests__/utils/grab-dir-names.test.d.ts delete mode 100644 dist/__tests__/utils/grab-dir-names.test.js delete mode 100644 dist/__tests__/utils/grab-origin.test.d.ts delete mode 100644 dist/__tests__/utils/grab-origin.test.js delete mode 100644 dist/__tests__/utils/grab-page-name.test.d.ts delete mode 100644 dist/__tests__/utils/grab-page-name.test.js delete mode 100644 dist/__tests__/utils/is-development.test.d.ts delete mode 100644 dist/__tests__/utils/is-development.test.js delete mode 100644 dist/__tests__/utils/numberfy.test.d.ts delete mode 100644 dist/__tests__/utils/numberfy.test.js diff --git a/dist/__tests__/e2e/e2e.test.d.ts b/dist/__tests__/e2e/e2e.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/e2e/e2e.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/e2e/e2e.test.js b/dist/__tests__/e2e/e2e.test.js deleted file mode 100644 index daeaec4..0000000 --- a/dist/__tests__/e2e/e2e.test.js +++ /dev/null @@ -1,52 +0,0 @@ -import { describe, expect, test, beforeAll, afterAll } from "bun:test"; -import startServer from "../../../src/functions/server/start-server"; -import bunextInit from "../../../src/functions/bunext-init"; -import path from "path"; -import fs from "fs"; -let originalCwd = process.cwd(); -describe("E2E Integration", () => { - let server; - beforeAll(async () => { - // Change to the fixture directory to simulate actual user repo - const fixtureDir = path.resolve(__dirname, "../__fixtures__/app"); - process.chdir(fixtureDir); - // Mock grabAppPort to assign dynamically to avoid port conflicts - global.CONFIG = { development: true }; - }); - afterAll(async () => { - if (server) { - server.stop(true); - } - process.chdir(originalCwd); - // Ensure to remove the dummy generated .bunext folder - const dotBunext = path.resolve(__dirname, "../__fixtures__/app/.bunext"); - if (fs.existsSync(dotBunext)) { - fs.rmSync(dotBunext, { recursive: true, force: true }); - } - const pubBunext = path.resolve(__dirname, "../__fixtures__/app/public/__bunext"); - if (fs.existsSync(pubBunext)) { - fs.rmSync(pubBunext, { recursive: true, force: true }); - } - }); - test("boots up the server and correctly routes to index.tsx page", async () => { - // Mock to randomize port - // Note: Bun test runs modules in isolation but startServer imports grab-app-port - // If we can't easily mock we can set PORT env - process.env.PORT = "0"; // Let Bun.serve pick port - await bunextInit(); - server = await startServer(); - expect(server).toBeDefined(); - // Fetch the index page - const response = await fetch(`http://localhost:${server.port}/`); - expect(response.status).toBe(200); - const html = await response.text(); - expect(html).toContain("Hello E2E"); - }); - test("returns 404 for unknown route", async () => { - const response = await fetch(`http://localhost:${server.port}/unknown-foo-bar123`); - expect(response.status).toBe(404); - const text = await response.text(); - // Assume default 404 preset component is rendered - expect(text).toContain("404"); - }); -}); diff --git a/dist/__tests__/functions/cache/grab-cache-names.test.d.ts b/dist/__tests__/functions/cache/grab-cache-names.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/functions/cache/grab-cache-names.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/functions/cache/grab-cache-names.test.js b/dist/__tests__/functions/cache/grab-cache-names.test.js deleted file mode 100644 index 5b3e3ba..0000000 --- a/dist/__tests__/functions/cache/grab-cache-names.test.js +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import grabCacheNames from "../../../functions/cache/grab-cache-names"; -describe("grabCacheNames", () => { - it("returns cache_name and cache_meta_name for a simple key", () => { - const { cache_name, cache_meta_name } = grabCacheNames({ key: "home" }); - expect(cache_name).toBe("home.res.html"); - expect(cache_meta_name).toBe("home.meta.json"); - }); - it("defaults paradigm to html", () => { - const { cache_name } = grabCacheNames({ key: "page" }); - expect(cache_name).toEndWith(".res.html"); - }); - it("uses json paradigm when specified", () => { - const { cache_name } = grabCacheNames({ key: "api-data", paradigm: "json" }); - expect(cache_name).toBe("api-data.res.json"); - }); - it("URL-encodes the key", () => { - const { cache_name, cache_meta_name } = grabCacheNames({ - key: "/blog/hello world", - }); - const encoded = encodeURIComponent("/blog/hello world"); - expect(cache_name).toBe(`${encoded}.res.html`); - expect(cache_meta_name).toBe(`${encoded}.meta.json`); - }); - it("handles keys with special characters", () => { - const key = "page?id=1&sort=asc"; - const { cache_name } = grabCacheNames({ key }); - expect(cache_name).toBe(`${encodeURIComponent(key)}.res.html`); - }); - it("cache_meta_name always uses .meta.json regardless of paradigm", () => { - const { cache_meta_name } = grabCacheNames({ - key: "test", - paradigm: "json", - }); - expect(cache_meta_name).toBe("test.meta.json"); - }); -}); diff --git a/dist/__tests__/functions/server/bunext-req-handler.test.d.ts b/dist/__tests__/functions/server/bunext-req-handler.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/functions/server/bunext-req-handler.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/functions/server/bunext-req-handler.test.js b/dist/__tests__/functions/server/bunext-req-handler.test.js deleted file mode 100644 index 6269047..0000000 --- a/dist/__tests__/functions/server/bunext-req-handler.test.js +++ /dev/null @@ -1,73 +0,0 @@ -import { describe, expect, test, mock, afterAll } from "bun:test"; -import bunextRequestHandler from "../../../../src/functions/server/bunext-req-handler"; -mock.module("../../../../src/utils/is-development", () => ({ - default: () => true -})); -mock.module("../../../../src/utils/grab-constants", () => ({ - default: () => ({ - config: { - middleware: async ({ url }) => { - if (url.pathname === "/blocked") { - return new Response("Blocked by middleware", { status: 403 }); - } - return undefined; - } - } - }) -})); -mock.module("../../../../src/functions/server/handle-routes", () => ({ - default: async () => new Response("api-routes") -})); -mock.module("../../../../src/functions/server/handle-public", () => ({ - default: async () => new Response("public") -})); -mock.module("../../../../src/functions/server/handle-files", () => ({ - default: async () => new Response("files") -})); -mock.module("../../../../src/functions/server/web-pages/handle-web-pages", () => ({ - default: async () => new Response("web-pages") -})); -/** - * Tests for the `bunext-req-handler` module. - * Ensures that requests are correctly routed to the proper subsystem. - */ -describe("bunext-req-handler", () => { - afterAll(() => { - mock.restore(); - }); - test("middleware is caught", async () => { - const req = new Request("http://localhost/blocked"); - const res = await bunextRequestHandler({ req }); - expect(res.status).toBe(403); - expect(await res.text()).toBe("Blocked by middleware"); - }); - test("routes /__hmr to handleHmr in dev", async () => { - global.ROUTER = { match: () => ({}) }; - global.HMR_CONTROLLERS = []; - const req = new Request("http://localhost/__hmr", { - headers: { referer: "http://localhost/" } - }); - const res = await bunextRequestHandler({ req }); - expect(res.headers.get("Content-Type")).toBe("text/event-stream"); - }); - test("routes /api/ to handleRoutes", async () => { - const req = new Request("http://localhost/api/users"); - const res = await bunextRequestHandler({ req }); - expect(await res.text()).toBe("api-routes"); - }); - test("routes /public/ to handlePublic", async () => { - const req = new Request("http://localhost/public/image.png"); - const res = await bunextRequestHandler({ req }); - expect(await res.text()).toBe("public"); - }); - test("routes files like .js to handleFiles", async () => { - const req = new Request("http://localhost/script.js"); - const res = await bunextRequestHandler({ req }); - expect(await res.text()).toBe("files"); - }); - test("routes anything else to handleWebPages", async () => { - const req = new Request("http://localhost/about"); - const res = await bunextRequestHandler({ req }); - expect(await res.text()).toBe("web-pages"); - }); -}); diff --git a/dist/__tests__/functions/server/grab-web-meta-html.test.d.ts b/dist/__tests__/functions/server/grab-web-meta-html.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/functions/server/grab-web-meta-html.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/functions/server/grab-web-meta-html.test.js b/dist/__tests__/functions/server/grab-web-meta-html.test.js deleted file mode 100644 index f5a8b67..0000000 --- a/dist/__tests__/functions/server/grab-web-meta-html.test.js +++ /dev/null @@ -1,97 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import grabWebMetaHTML from "../../../functions/server/web-pages/grab-web-meta-html"; -describe("grabWebMetaHTML", () => { - it("returns empty string for empty meta object", () => { - expect(grabWebMetaHTML({ meta: {} })).toBe(""); - }); - it("generates a title tag", () => { - const html = grabWebMetaHTML({ meta: { title: "My Page" } }); - expect(html).toContain("My Page"); - }); - it("generates a description meta tag", () => { - const html = grabWebMetaHTML({ meta: { description: "A description" } }); - expect(html).toContain(' { - const html = grabWebMetaHTML({ - meta: { keywords: ["react", "bun", "ssr"] }, - }); - expect(html).toContain('content="react, bun, ssr"'); - }); - it("uses string keywords directly", () => { - const html = grabWebMetaHTML({ meta: { keywords: "react, bun" } }); - expect(html).toContain('content="react, bun"'); - }); - it("generates author meta tag", () => { - const html = grabWebMetaHTML({ meta: { author: "Alice" } }); - expect(html).toContain(' { - const html = grabWebMetaHTML({ meta: { robots: "noindex" } }); - expect(html).toContain(' { - const html = grabWebMetaHTML({ - meta: { canonical: "https://example.com/page" }, - }); - expect(html).toContain(' { - const html = grabWebMetaHTML({ meta: { themeColor: "#ff0000" } }); - expect(html).toContain(' { - const html = grabWebMetaHTML({ - meta: { - og: { - title: "OG Title", - description: "OG Desc", - image: "https://example.com/img.png", - url: "https://example.com", - type: "website", - siteName: "Example", - locale: "en_US", - }, - }, - }); - expect(html).toContain(' { - const html = grabWebMetaHTML({ - meta: { - twitter: { - card: "summary_large_image", - title: "Tweet Title", - description: "Tweet Desc", - image: "https://example.com/tw.png", - site: "@example", - creator: "@alice", - }, - }, - }); - expect(html).toContain(' { - const html = grabWebMetaHTML({ meta: { og: { title: "Only Title" } } }); - expect(html).toContain("og:title"); - expect(html).not.toContain("og:description"); - expect(html).not.toContain("og:image"); - }); - it("does not emit tags for missing fields", () => { - const html = grabWebMetaHTML({ meta: { title: "Hello" } }); - expect(html).not.toContain("description"); - expect(html).not.toContain("og:"); - expect(html).not.toContain("twitter:"); - }); -}); diff --git a/dist/__tests__/functions/server/handle-hmr.test.d.ts b/dist/__tests__/functions/server/handle-hmr.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/functions/server/handle-hmr.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/functions/server/handle-hmr.test.js b/dist/__tests__/functions/server/handle-hmr.test.js deleted file mode 100644 index 5c6b60f..0000000 --- a/dist/__tests__/functions/server/handle-hmr.test.js +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, expect, test, mock, beforeEach, afterEach } from "bun:test"; -import handleHmr from "../../../../src/functions/server/handle-hmr"; -describe("handle-hmr", () => { - beforeEach(() => { - global.ROUTER = { - match: (path) => { - if (path === "/test") - return { filePath: "/test-file" }; - return null; - } - }; - global.HMR_CONTROLLERS = []; - global.BUNDLER_CTX_MAP = [ - { local_path: "/test-file" } - ]; - }); - afterEach(() => { - global.ROUTER = undefined; - global.HMR_CONTROLLERS = []; - global.BUNDLER_CTX_MAP = undefined; - }); - test("sets up SSE stream and pushes to HMR_CONTROLLERS", async () => { - const req = new Request("http://localhost/hmr", { - headers: { - "referer": "http://localhost/test" - } - }); - const res = await handleHmr({ req }); - expect(res.status).toBe(200); - 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(controller.page_url).toBe("http://localhost/test"); - expect(controller.target_map?.local_path).toBe("/test-file"); - }); -}); diff --git a/dist/__tests__/functions/server/handle-routes.test.d.ts b/dist/__tests__/functions/server/handle-routes.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/functions/server/handle-routes.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/functions/server/handle-routes.test.js b/dist/__tests__/functions/server/handle-routes.test.js deleted file mode 100644 index 2bd7610..0000000 --- a/dist/__tests__/functions/server/handle-routes.test.js +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, expect, test, mock, afterAll } from "bun:test"; -import handleRoutes from "../../../../src/functions/server/handle-routes"; -mock.module("../../../../src/utils/is-development", () => ({ - default: () => false -})); -mock.module("../../../../src/utils/grab-constants", () => ({ - default: () => ({ MBInBytes: 1048576, ServerDefaultRequestBodyLimitBytes: 5242880 }) -})); -mock.module("../../../../src/utils/grab-router", () => ({ - default: () => ({ - match: (path) => { - if (path === "/api/test") - return { filePath: "/test-path" }; - if (path === "/api/large") - return { filePath: "/large-path" }; - return null; - } - }) -})); -mock.module("../../../../src/utils/grab-route-params", () => ({ - default: async () => ({ params: {}, searchParams: {} }) -})); -mock.module("/test-path", () => ({ - default: async () => new Response("OK", { status: 200 }) -})); -mock.module("/large-path", () => ({ - default: async () => new Response("Large OK", { status: 200 }), - config: { maxRequestBodyMB: 1 } -})); -/** - * Tests for routing logic within `handle-routes`. - */ -describe("handle-routes", () => { - afterAll(() => { - mock.restore(); - }); - test("returns 401 for unknown route", async () => { - const req = new Request("http://localhost/api/unknown"); - const res = await handleRoutes({ req }); - expect(res.status).toBe(401); - const json = await res.json(); - expect(json.success).toBe(false); - expect(json.msg).toContain("not found"); - }); - test("calls matched module default export", async () => { - const req = new Request("http://localhost/api/test"); - const res = await handleRoutes({ req }); - expect(res.status).toBe(200); - expect(await res.text()).toBe("OK"); - }); - test("enforces request body size limits", async () => { - // limit is 1MB from mock config - const req = new Request("http://localhost/api/large", { - method: "POST", - headers: { - "content-length": "2000000" // ~2MB - }, - body: "x".repeat(10) // the actual body doesn't matter since handleRoutes only checks the header - }); - const res = await handleRoutes({ req }); - expect(res.status).toBe(413); - const json = await res.json(); - expect(json.success).toBe(false); - }); -}); diff --git a/dist/__tests__/functions/server/start-server.test.d.ts b/dist/__tests__/functions/server/start-server.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/functions/server/start-server.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/functions/server/start-server.test.js b/dist/__tests__/functions/server/start-server.test.js deleted file mode 100644 index fb359fb..0000000 --- a/dist/__tests__/functions/server/start-server.test.js +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, expect, test, mock, afterEach } from "bun:test"; -import startServer from "../../../../src/functions/server/start-server"; -import { log } from "../../../../src/utils/log"; -// Mock log so we don't spam terminal during tests -mock.module("../../../../src/utils/log", () => ({ - log: { - server: mock((msg) => { }), - info: mock((msg) => { }), - error: mock((msg) => { }), - } -})); -// Mock grabConfig so it doesn't try to look for bunext.config.ts and exit process -mock.module("../../../../src/functions/grab-config", () => ({ - default: async () => ({}) -})); -// Mock grabAppPort to return 0 so Bun.serve picks a random port -mock.module("../../../../src/utils/grab-app-port", () => ({ - default: () => 0 -})); -describe("startServer", () => { - afterEach(() => { - if (global.SERVER) { - global.SERVER.stop(true); - global.SERVER = undefined; - } - }); - test("starts the server and assigns to global.SERVER", async () => { - global.CONFIG = { development: true }; - const server = await startServer(); - expect(server).toBeDefined(); - expect(server.port).toBeGreaterThan(0); - expect(global.SERVER).toBe(server); - expect(log.server).toHaveBeenCalled(); - server.stop(true); - }); -}); diff --git a/dist/__tests__/hydration/hydration.test.d.ts b/dist/__tests__/hydration/hydration.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/hydration/hydration.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/hydration/hydration.test.js b/dist/__tests__/hydration/hydration.test.js deleted file mode 100644 index 60b9b38..0000000 --- a/dist/__tests__/hydration/hydration.test.js +++ /dev/null @@ -1,62 +0,0 @@ -import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -import { describe, expect, test, beforeEach, afterEach } from "bun:test"; -import React, { useState } from "react"; -import { renderToString } from "react-dom/server"; -import { hydrateRoot } from "react-dom/client"; -import { GlobalWindow } from "happy-dom"; -// A mock application component to test hydration -function App() { - const [count, setCount] = useState(0); - return (_jsxs("div", { id: "app-root", children: [_jsx("h1", { children: "Test Hydration" }), _jsxs("p", { "data-testid": "count", children: ["Count: ", count] }), _jsx("button", { "data-testid": "btn", onClick: () => setCount(c => c + 1), children: "Increment" })] })); -} -describe("React Hydration", () => { - let window; - let document; - beforeEach(() => { - window = new GlobalWindow(); - document = window.document; - global.window = window; - global.document = document; - global.navigator = { userAgent: "node.js" }; - }); - afterEach(() => { - // Clean up global mocks - delete global.window; - delete global.document; - delete global.navigator; - window.close(); - }); - test("hydrates a server-rendered component and binds events", async () => { - // 1. Server-side render - const html = renderToString(_jsx(App, {})); - // 2. Setup DOM as it would be delivered to the client - document.body.innerHTML = `
${html}
`; - const rootNode = document.getElementById("root"); - // 3. Hydrate - let hydrateError = null; - try { - await new Promise((resolve) => { - hydrateRoot(rootNode, _jsx(App, {}), { - onRecoverableError: (err) => { - hydrateError = err; - } - }); - setTimeout(resolve, 50); // let React finish hydration - }); - } - catch (e) { - hydrateError = e; - } - // Verify no hydration errors - expect(hydrateError).toBeNull(); - // 4. Verify client-side interactivity - const button = document.querySelector('[data-testid="btn"]'); - const countText = document.querySelector('[data-testid="count"]'); - expect(countText.textContent).toBe("Count: 0"); - // Simulate click - button.dispatchEvent(new window.Event("click", { bubbles: true })); - // Let async state updates process - await new Promise(r => setTimeout(r, 50)); - expect(countText.textContent).toBe("Count: 1"); - }); -}); diff --git a/dist/__tests__/utils/deserialize-query.test.d.ts b/dist/__tests__/utils/deserialize-query.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/utils/deserialize-query.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/utils/deserialize-query.test.js b/dist/__tests__/utils/deserialize-query.test.js deleted file mode 100644 index f1bc5bd..0000000 --- a/dist/__tests__/utils/deserialize-query.test.js +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import deserializeQuery from "../../utils/deserialize-query"; -describe("deserializeQuery", () => { - it("passes through a plain object unchanged", () => { - const input = { foo: "bar" }; - expect(deserializeQuery(input)).toEqual({ foo: "bar" }); - }); - it("parses a JSON string into an object", () => { - const input = JSON.stringify({ a: 1, b: "hello" }); - expect(deserializeQuery(input)).toEqual({ a: 1, b: "hello" }); - }); - it("deep-parses string values that look like JSON objects", () => { - const nested = { filter: JSON.stringify({ status: "active" }) }; - const result = deserializeQuery(nested); - expect(result.filter).toEqual({ status: "active" }); - }); - it("deep-parses string values that look like JSON arrays", () => { - const nested = { ids: JSON.stringify([1, 2, 3]) }; - const result = deserializeQuery(nested); - expect(result.ids).toEqual([1, 2, 3]); - }); - it("leaves plain string values alone", () => { - const input = { name: "alice", age: "30" }; - expect(deserializeQuery(input)).toEqual({ name: "alice", age: "30" }); - }); - it("returns an empty object for an empty JSON string", () => { - expect(deserializeQuery("{}")).toEqual({}); - }); - it("returns an empty object for an invalid JSON string", () => { - // EJSON.parse returns undefined → Object(undefined) → {} - expect(deserializeQuery("not-json")).toEqual({}); - }); -}); diff --git a/dist/__tests__/utils/ejson.test.d.ts b/dist/__tests__/utils/ejson.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/utils/ejson.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/utils/ejson.test.js b/dist/__tests__/utils/ejson.test.js deleted file mode 100644 index 120908c..0000000 --- a/dist/__tests__/utils/ejson.test.js +++ /dev/null @@ -1,52 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import EJSON from "../../utils/ejson"; -describe("EJSON.parse", () => { - it("parses a valid JSON string", () => { - expect(EJSON.parse('{"a":1}')).toEqual({ a: 1 }); - }); - it("parses a JSON array string", () => { - expect(EJSON.parse('[1,2,3]')).toEqual([1, 2, 3]); - }); - it("returns undefined for null input", () => { - expect(EJSON.parse(null)).toBeUndefined(); - }); - it("returns undefined for empty string", () => { - expect(EJSON.parse("")).toBeUndefined(); - }); - it("returns undefined for invalid JSON", () => { - expect(EJSON.parse("{bad json")).toBeUndefined(); - }); - it("returns the object directly when passed an object (typeof object)", () => { - const obj = { x: 1 }; - expect(EJSON.parse(obj)).toBe(obj); - }); - it("returns undefined for a number input", () => { - expect(EJSON.parse(42)).toBeUndefined(); - }); - it("applies a reviver function", () => { - const result = EJSON.parse('{"a":"2"}', (key, value) => key === "a" ? Number(value) : value); - expect(result).toEqual({ a: 2 }); - }); -}); -describe("EJSON.stringify", () => { - it("stringifies an object", () => { - expect(EJSON.stringify({ a: 1 })).toBe('{"a":1}'); - }); - it("stringifies an array", () => { - expect(EJSON.stringify([1, 2, 3])).toBe("[1,2,3]"); - }); - it("applies spacing", () => { - expect(EJSON.stringify({ a: 1 }, null, 2)).toBe('{\n "a": 1\n}'); - }); - it("returns undefined for circular references", () => { - const obj = {}; - obj.self = obj; - expect(EJSON.stringify(obj)).toBeUndefined(); - }); - it("stringifies null", () => { - expect(EJSON.stringify(null)).toBe("null"); - }); - it("stringifies a number", () => { - expect(EJSON.stringify(42)).toBe("42"); - }); -}); diff --git a/dist/__tests__/utils/grab-app-names.test.d.ts b/dist/__tests__/utils/grab-app-names.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/utils/grab-app-names.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/utils/grab-app-names.test.js b/dist/__tests__/utils/grab-app-names.test.js deleted file mode 100644 index 3b971c4..0000000 --- a/dist/__tests__/utils/grab-app-names.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import AppNames from "../../utils/grab-app-names"; -describe("AppNames", () => { - it("has a defaultPort of 7000", () => { - expect(AppNames.defaultPort).toBe(7000); - }); - it("has the correct defaultAssetPrefix", () => { - expect(AppNames.defaultAssetPrefix).toBe("_bunext/static"); - }); - it("has name Bunext", () => { - expect(AppNames.name).toBe("Bunext"); - }); - it("has a version string", () => { - expect(typeof AppNames.version).toBe("string"); - expect(AppNames.version.length).toBeGreaterThan(0); - }); - it("has defaultDistDir as .bunext", () => { - expect(AppNames.defaultDistDir).toBe(".bunext"); - }); - it("has RootPagesComponentName as __root", () => { - expect(AppNames.RootPagesComponentName).toBe("__root"); - }); -}); diff --git a/dist/__tests__/utils/grab-app-port.test.d.ts b/dist/__tests__/utils/grab-app-port.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/utils/grab-app-port.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/utils/grab-app-port.test.js b/dist/__tests__/utils/grab-app-port.test.js deleted file mode 100644 index 02c6b91..0000000 --- a/dist/__tests__/utils/grab-app-port.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "bun:test"; -import grabAppPort from "../../utils/grab-app-port"; -const originalEnv = process.env.PORT; -beforeEach(() => { - delete process.env.PORT; - global.CONFIG = {}; -}); -afterEach(() => { - if (originalEnv !== undefined) { - process.env.PORT = originalEnv; - } - else { - delete process.env.PORT; - } -}); -describe("grabAppPort", () => { - it("returns the default port (7000) when no config or env set", () => { - expect(grabAppPort()).toBe(7000); - }); - it("uses PORT env variable when set", () => { - process.env.PORT = "8080"; - expect(grabAppPort()).toBe(8080); - }); - it("uses config.port when PORT env is not set", () => { - global.CONFIG = { port: 3000 }; - expect(grabAppPort()).toBe(3000); - }); - it("PORT env takes precedence over config.port", () => { - process.env.PORT = "9000"; - global.CONFIG = { port: 3000 }; - expect(grabAppPort()).toBe(9000); - }); - it("handles non-numeric PORT env gracefully via numberfy", () => { - process.env.PORT = "abc"; - // numberfy strips non-numeric chars, "abc" → "" → 0 - expect(grabAppPort()).toBe(0); - }); -}); diff --git a/dist/__tests__/utils/grab-constants.test.d.ts b/dist/__tests__/utils/grab-constants.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/utils/grab-constants.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/utils/grab-constants.test.js b/dist/__tests__/utils/grab-constants.test.js deleted file mode 100644 index a8f0c6f..0000000 --- a/dist/__tests__/utils/grab-constants.test.js +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, it, expect, beforeEach } from "bun:test"; -import grabConstants from "../../utils/grab-constants"; -beforeEach(() => { - global.CONFIG = {}; -}); -describe("grabConstants", () => { - it("has the correct ClientRootElementIDName", () => { - expect(grabConstants().ClientRootElementIDName).toBe("__bunext"); - }); - it("has the correct ClientWindowPagePropsName", () => { - expect(grabConstants().ClientWindowPagePropsName).toBe("__PAGE_PROPS__"); - }); - it("has the correct ClientRootComponentWindowName", () => { - expect(grabConstants().ClientRootComponentWindowName).toBe("BUNEXT_ROOT"); - }); - it("calculates MBInBytes as 1024 * 1024", () => { - expect(grabConstants().MBInBytes).toBe(1024 * 1024); - }); - it("ServerDefaultRequestBodyLimitBytes is 10 MB", () => { - expect(grabConstants().ServerDefaultRequestBodyLimitBytes).toBe(10 * 1024 * 1024); - }); - it("MaxBundlerRebuilds is 5", () => { - expect(grabConstants().MaxBundlerRebuilds).toBe(5); - }); - it("returns the current global.CONFIG", () => { - const cfg = { port: 9000 }; - global.CONFIG = cfg; - expect(grabConstants().config).toBe(cfg); - }); -}); diff --git a/dist/__tests__/utils/grab-dir-names.test.d.ts b/dist/__tests__/utils/grab-dir-names.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/utils/grab-dir-names.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/utils/grab-dir-names.test.js b/dist/__tests__/utils/grab-dir-names.test.js deleted file mode 100644 index c76456d..0000000 --- a/dist/__tests__/utils/grab-dir-names.test.js +++ /dev/null @@ -1,55 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import path from "path"; -import grabDirNames from "../../utils/grab-dir-names"; -describe("grabDirNames", () => { - it("derives all paths from process.cwd()", () => { - const cwd = process.cwd(); - const dirs = grabDirNames(); - expect(dirs.ROOT_DIR).toBe(cwd); - expect(dirs.SRC_DIR).toBe(path.join(cwd, "src")); - expect(dirs.PAGES_DIR).toBe(path.join(cwd, "src", "pages")); - expect(dirs.API_DIR).toBe(path.join(cwd, "src", "pages", "api")); - expect(dirs.PUBLIC_DIR).toBe(path.join(cwd, "public")); - }); - it("nests HYDRATION_DST_DIR under public/__bunext/pages", () => { - const dirs = grabDirNames(); - expect(dirs.HYDRATION_DST_DIR).toBe(path.join(dirs.PUBLIC_DIR, "__bunext", "pages")); - }); - it("nests BUNEXT_CACHE_DIR under public/__bunext/cache", () => { - const dirs = grabDirNames(); - expect(dirs.BUNEXT_CACHE_DIR).toBe(path.join(dirs.PUBLIC_DIR, "__bunext", "cache")); - }); - it("places map JSON file inside HYDRATION_DST_DIR", () => { - const dirs = grabDirNames(); - expect(dirs.HYDRATION_DST_DIR_MAP_JSON_FILE).toBe(path.join(dirs.HYDRATION_DST_DIR, "map.json")); - }); - it("places CONFIG_FILE at root", () => { - const dirs = grabDirNames(); - expect(dirs.CONFIG_FILE).toBe(path.join(dirs.ROOT_DIR, "bunext.config.ts")); - }); - it("places BUNX_TMP_DIR inside .bunext", () => { - const dirs = grabDirNames(); - expect(dirs.BUNX_TMP_DIR).toContain(".bunext"); - expect(dirs.BUNX_TMP_DIR).toEndWith(".tmp"); - }); - it("places BUNX_HYDRATION_SRC_DIR under client/hydration-src", () => { - const dirs = grabDirNames(); - expect(dirs.BUNX_HYDRATION_SRC_DIR).toContain(path.join("client", "hydration-src")); - }); - it("sets 404 file name to not-found", () => { - const dirs = grabDirNames(); - expect(dirs.BUNX_ROOT_404_FILE_NAME).toBe("not-found"); - }); - it("sets 500 file name to server-error", () => { - const dirs = grabDirNames(); - expect(dirs.BUNX_ROOT_500_FILE_NAME).toBe("server-error"); - }); - it("preset 404 component path ends with not-found.tsx", () => { - const dirs = grabDirNames(); - expect(dirs.BUNX_ROOT_404_PRESET_COMPONENT).toEndWith("not-found.tsx"); - }); - it("preset 500 component path ends with server-error.tsx", () => { - const dirs = grabDirNames(); - expect(dirs.BUNX_ROOT_500_PRESET_COMPONENT).toEndWith("server-error.tsx"); - }); -}); diff --git a/dist/__tests__/utils/grab-origin.test.d.ts b/dist/__tests__/utils/grab-origin.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/utils/grab-origin.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/utils/grab-origin.test.js b/dist/__tests__/utils/grab-origin.test.js deleted file mode 100644 index 56f66b9..0000000 --- a/dist/__tests__/utils/grab-origin.test.js +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "bun:test"; -import grabOrigin from "../../utils/grab-origin"; -const originalPort = process.env.PORT; -beforeEach(() => { - delete process.env.PORT; - global.CONFIG = {}; -}); -afterEach(() => { - if (originalPort !== undefined) { - process.env.PORT = originalPort; - } - else { - delete process.env.PORT; - } -}); -describe("grabOrigin", () => { - it("returns config.origin when set", () => { - global.CONFIG = { origin: "https://example.com" }; - expect(grabOrigin()).toBe("https://example.com"); - }); - it("falls back to http://localhost: using default port", () => { - expect(grabOrigin()).toBe("http://localhost:7000"); - }); - it("falls back using PORT env variable", () => { - process.env.PORT = "8080"; - expect(grabOrigin()).toBe("http://localhost:8080"); - }); - it("falls back using config.port", () => { - global.CONFIG = { port: 3700 }; - expect(grabOrigin()).toBe("http://localhost:3700"); - }); -}); diff --git a/dist/__tests__/utils/grab-page-name.test.d.ts b/dist/__tests__/utils/grab-page-name.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/utils/grab-page-name.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/utils/grab-page-name.test.js b/dist/__tests__/utils/grab-page-name.test.js deleted file mode 100644 index 97d4c4d..0000000 --- a/dist/__tests__/utils/grab-page-name.test.js +++ /dev/null @@ -1,46 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import grabPageName from "../../utils/grab-page-name"; -describe("grabPageName", () => { - it("returns the page name for a simple page path", () => { - expect(grabPageName({ path: "/home/user/project/src/pages/about.tsx" })) - .toBe("about"); - }); - it("returns 'index' for a root index file (no -index stripping at root)", () => { - // -index suffix is only stripped when joined: e.g. "blog-index" → "blog" - // A standalone "index" filename has no leading dash so stays as-is - expect(grabPageName({ path: "/home/user/project/src/pages/index.tsx" })) - .toBe("index"); - }); - it("handles nested page paths", () => { - expect(grabPageName({ path: "/home/user/project/src/pages/blog/post.tsx" })).toBe("blog-post"); - }); - it("strips -index suffix from nested index files", () => { - expect(grabPageName({ path: "/home/user/project/src/pages/blog/index.tsx" })).toBe("blog"); - }); - it("converts dynamic segments [slug] by replacing brackets", () => { - const result = grabPageName({ - path: "/home/user/project/src/pages/blog/[slug].tsx", - }); - // [ → - and ] is dropped (not a-z or -), so [slug] → -slug - expect(result).toBe("blog--slug"); - }); - it("converts spread [...params] segments", () => { - const result = grabPageName({ - path: "/home/user/project/src/pages/[...params].tsx", - }); - // "[...params]" → remove ext → "[...params]" - // [ → "-" → "-...params]" - // "..." → "-" → "--params]" - // strip non [a-z-] → "--params" - expect(result).toBe("--params"); - }); - it("strips uppercase letters (only a-z and - are kept)", () => { - // [^a-z\-] strips uppercase — 'A' is removed, 'bout' remains - expect(grabPageName({ path: "/home/user/project/src/pages/About.tsx" })).toBe("bout"); - }); - it("handles deeply nested paths", () => { - expect(grabPageName({ - path: "/home/user/project/src/pages/admin/users/list.tsx", - })).toBe("admin-users-list"); - }); -}); diff --git a/dist/__tests__/utils/is-development.test.d.ts b/dist/__tests__/utils/is-development.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/utils/is-development.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/utils/is-development.test.js b/dist/__tests__/utils/is-development.test.js deleted file mode 100644 index 6474a2d..0000000 --- a/dist/__tests__/utils/is-development.test.js +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "bun:test"; -import isDevelopment from "../../utils/is-development"; -const originalEnv = process.env.NODE_ENV; -beforeEach(() => { - // Reset global config before each test - global.CONFIG = {}; -}); -afterEach(() => { - process.env.NODE_ENV = originalEnv; -}); -describe("isDevelopment", () => { - it("returns false when NODE_ENV is production", () => { - process.env.NODE_ENV = "production"; - global.CONFIG = { development: true }; - expect(isDevelopment()).toBe(false); - }); - it("returns true when config.development is true and NODE_ENV is not production", () => { - process.env.NODE_ENV = "development"; - global.CONFIG = { development: true }; - expect(isDevelopment()).toBe(true); - }); - it("returns false when config.development is false", () => { - process.env.NODE_ENV = "development"; - global.CONFIG = { development: false }; - expect(isDevelopment()).toBe(false); - }); - it("returns false when config.development is undefined", () => { - process.env.NODE_ENV = "development"; - global.CONFIG = {}; - expect(isDevelopment()).toBe(false); - }); -}); diff --git a/dist/__tests__/utils/numberfy.test.d.ts b/dist/__tests__/utils/numberfy.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/__tests__/utils/numberfy.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/__tests__/utils/numberfy.test.js b/dist/__tests__/utils/numberfy.test.js deleted file mode 100644 index e8469ae..0000000 --- a/dist/__tests__/utils/numberfy.test.js +++ /dev/null @@ -1,43 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import numberfy, { _n } from "../../utils/numberfy"; -describe("numberfy", () => { - it("converts a plain integer string", () => { - expect(numberfy("42")).toBe(42); - }); - it("converts a float string preserving decimals", () => { - expect(numberfy("3.14")).toBe(3.14); - }); - it("strips non-numeric characters", () => { - expect(numberfy("$1,234.56")).toBe(1234.56); - }); - it("returns 0 for an empty string", () => { - expect(numberfy("")).toBe(0); - }); - it("returns 0 for undefined", () => { - expect(numberfy(undefined)).toBe(0); - }); - it("returns 0 for null", () => { - expect(numberfy(null)).toBe(0); - }); - it("passes through a number directly", () => { - expect(numberfy(7)).toBe(7); - }); - it("rounds when no decimals specified and no decimal in input", () => { - expect(numberfy("5.0")).toBe(5); - }); - it("respects decimals=0 by rounding", () => { - expect(numberfy("3.7", 0)).toBe(4); - }); - it("respects explicit decimals parameter", () => { - expect(numberfy("3.14159", 2)).toBe(3.14); - }); - it("preserves existing decimal places when no decimals arg given", () => { - expect(numberfy("1.500")).toBe(1.5); - }); - it("strips a trailing dot", () => { - expect(numberfy("5.")).toBe(5); - }); - it("_n alias works identically", () => { - expect(_n("10")).toBe(10); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index ca2fe1c..c41dd23 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,11 @@ "declaration": true }, "include": ["src"], - "exclude": ["node_modules", "dist"] + "exclude": [ + "node_modules", + "dist", + "src/__tests__", + "**/__tests__", + "__tests__" + ] }