Update watcher function. Add tests
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
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({});
|
||||
});
|
||||
});
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
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:<port> 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");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user