feat: centralize application framework primitives
Quality / quality (ubuntu-latest) (push) Failing after 14m38s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-22 23:07:46 +05:30
parent 96e082b943
commit a3ddd39b7b
73 changed files with 1429 additions and 84 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/test",
"version": "0.8.9",
"version": "0.8.10",
"private": true,
"type": "module",
"main": "src/index.ts",
+47 -1
View File
@@ -3,6 +3,9 @@ import { createContext, type Context, type ProblemDetails } from "@wrnexus/core"
export interface TestRequestOptions extends Omit<RequestInit, "body"> {
body?: BodyInit | Record<string, unknown> | URLSearchParams | FormData | null;
baseUrl?: string;
params?: Record<string, string>;
user?: unknown;
locals?: Record<string, unknown>;
}
/** Build a web-standard Request with convenient JSON/FormData handling. */
@@ -31,7 +34,50 @@ export function testRequest(path = "/", options: TestRequestOptions = {}): Reque
/** Create a complete Context suitable for middleware and route unit tests. */
export function testContext(path = "/", options: TestRequestOptions = {}): Context {
const request = testRequest(path, options);
return createContext(request, new URL(request.url));
const ctx = createContext(request, new URL(request.url));
ctx.params = { ...options.params };
ctx.user = options.user;
ctx.locals = { ...options.locals };
return ctx;
}
/** Preferred descriptive alias for testContext(). */
export const createTestContext = testContext;
/** Fluent, Bun-compatible fetch mock (including Bun.fetch.preconnect). */
export type FetchMock = typeof fetch & {
readonly calls: ReadonlyArray<{ input: string | URL | Request; init?: RequestInit }>;
respondOnce(response: Response | (() => Response | Promise<Response>)): FetchMock;
reset(): void;
};
export function createFetchMock(): FetchMock {
const responses: Array<Response | (() => Response | Promise<Response>)> = [];
const calls: Array<{ input: string | URL | Request; init?: RequestInit }> = [];
const implementation = async (input: string | URL | Request, init?: RequestInit) => {
calls.push({ input, init });
const next = responses.shift();
if (!next) throw new Error("WRN-TEST-FETCH: no response was queued for this request");
return typeof next === "function" ? next() : next.clone();
};
const mock = implementation as FetchMock;
Object.defineProperties(mock, {
calls: { get: () => calls },
respondOnce: {
value(response: Response | (() => Response | Promise<Response>)) {
responses.push(response);
return mock;
},
},
reset: {
value() {
responses.length = 0;
calls.length = 0;
},
},
preconnect: { value: () => undefined },
});
return mock;
}
export interface JsonResponse<T> {
+12 -1
View File
@@ -181,15 +181,26 @@ export async function createHarness(
};
}
/** Preferred full-stack name; retained alongside createHarness for compatibility. */
export const createTestApp = createHarness;
export {
testRequest,
testContext,
createTestContext,
readJsonResponse,
expectProblem,
deferred,
waitFor,
MemoryCookieJar,
createFetchMock,
} from "./advanced.ts";
export type {
TestRequestOptions,
JsonResponse,
Deferred,
WaitForOptions,
FetchMock,
} from "./advanced.ts";
export type { TestRequestOptions, JsonResponse, Deferred, WaitForOptions } from "./advanced.ts";
export { withDatabaseRollback, createFactory, captureBrowserArtifacts } from "./platform.ts";
export type { TransactionalDatabase, BrowserArtifactPage } from "./platform.ts";
+28 -1
View File
@@ -1,4 +1,12 @@
import { test, expect, renderComponent, mountHtml, callRoute } from "../src/index.ts";
import {
test,
expect,
renderComponent,
mountHtml,
callRoute,
createFetchMock,
createTestContext,
} from "../src/index.ts";
const COUNTER = `component Counter {
props {
@@ -30,3 +38,22 @@ test("callRoute invokes an API handler with a Context", async () => {
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ ok: true, path: "/api/ping", method: "POST" });
});
test("createTestContext installs route params, locals and a user without casts", () => {
const user = { id: "u1" };
const ctx = createTestContext("/items/42", {
params: { id: "42" },
user,
locals: { trace: "test" },
});
expect(ctx.params.id).toBe("42");
expect(ctx.user).toBe(user);
expect(ctx.locals.trace).toBe("test");
});
test("createFetchMock is fluent and compatible with Bun fetch", async () => {
const fetchMock: typeof fetch = createFetchMock().respondOnce(Response.json({ ok: true }));
const response = await fetchMock("https://example.test/token");
expect(await response.json()).toEqual({ ok: true });
expect(typeof fetchMock.preconnect).toBe("function");
});