@wrnexus/test
WRNexusJS-aware component, route, and browser testing utilities.
Install the package
After WorkRoot approves private registry access, install the release-aligned package:
bun add @wrnexus/test@0.8.7Request preview access. Never put registry tokens in source control.
Testing utilities for WRNexusJS apps — component rendering, reactive-DOM mounting, route handler calls, and a full in-process app harness, plus a one-import re-export of bun:test.
Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.
Overview
@wrnexus/test is the server-side test toolkit you reach for when writing tests for a WRNexusJS app. It runs under bun test (invoked via wrnexus test) and gives you a single import surface: the bun:test primitives (test, expect, mock, …) re-exported alongside WRNexusJS-aware helpers that compile .wrn components, hydrate server HTML in a DOM, invoke API route handlers, and boot the real app on an ephemeral port for integration tests.
bun add @wrnexus/test
Private package — the machine must be authenticated to the wrnexus npm org
(a read token in ~/.npmrc). Requires Bun (Node is not supported).
API
Re-exported test primitives
For one-import DX, the following are re-exported straight from bun:test:
test, expect, describe, it, beforeEach, afterEach, beforeAll, afterAll, mock, spyOn.
createContext is also re-exported from @wrnexus/core.
renderComponent(source, props?)
function renderComponent(source: string, props?: Record<string, unknown>): Promise<string>;
Compiles a .wrn component source string (via @wrnexus/compiler) and renders it to an HTML string with the given props. Throws if the compiled module has no render export.
mountHtml(html)
function mountHtml(html: string): {
document: Document;
window: unknown;
querySelector: (sel: string) => Element | null;
querySelectorAll: (sel: string) => Element[];
};
Mounts server-rendered html in a happy-dom window with the reactive runtime hydrated, so you can test data-scope / data-text / data-for / data-show behaviour. Returns the window plus document and query helpers; assert on those.
happy-domis loaded lazily (viarequire), so importing this package never
requires it unless you actually call mountHtml.
callRoute(handler, request)
function callRoute(
handler: (ctx: Context) => Response | Promise<Response>,
request: Request,
): Promise<Response>;
Calls an API route handler with a Context built from a Request (using createContext). Returns the handler's Response.
createHarness(projectRoot, options?)
function createHarness(projectRoot: string, options?: HarnessOptions): Promise<Harness>;
interface HarnessOptions {
/** Config/env profile. Default "test". */
profile?: string;
}
interface Harness {
/** Base URL of the ephemeral test server. */
url: string;
/** Fetch a path on the app (relative to `url`). */
fetch(path: string, init?: RequestInit): Promise<Response>;
/** The scanned router (pages/api/realtime/components). */
router: unknown;
/** Stop the server. */
close(): void;
}
Boots the app at projectRoot on an ephemeral port (port: 0) for integration tests covering pages, API routes, middleware, and the full request pipeline. Loads env and app config for the given profile (default "test") so it picks up your test database/env. The server runs in development mode with HMR disabled. Remember to await app.close() when done.
Usage
The CLI supports focused suites by file or directory convention:
wrnexus test unit # *.unit.test.ts or test/unit/**
wrnexus test component # *.component.test.ts or test/component/**
wrnexus test api # *.api.test.ts or test/api/**
wrnexus test accessibility # *.a11y.test.ts / *.accessibility.test.ts
wrnexus test performance # *.performance.test.ts / *.benchmark.test.ts
wrnexus test browser # Playwright project when configured
wrnexus test visual # Playwright tests tagged @visual
Pass the application directory after the level, for example wrnexus test component examples/basic-app. A focused command fails clearly when no matching suite exists instead of silently running unrelated tests.
import { test, expect, renderComponent, mountHtml, createHarness } from "@wrnexus/test";
test("counter renders its label", async () => {
const html = await renderComponent(SRC, { start: 3, label: "Hits" });
expect(html).toContain("Hits");
});
test("reactive scope hydrates", () => {
const { querySelector } = mountHtml(serverHtml);
expect(querySelector("[data-text]")?.textContent).toBe("3");
});
test("home page responds", async () => {
const app = await createHarness("examples/basic-app");
const res = await app.fetch("/");
expect(res.status).toBe(200);
await app.close();
});
Calling an API route handler directly:
import { test, expect, callRoute } from "@wrnexus/test";
import { GET } from "../app/api/health.ts";
test("health endpoint", async () => {
const res = await callRoute(GET, new Request("http://test/api/health"));
expect(res.status).toBe(200);
});
Requirements / Notes
- Bun-only. Runs under
bun test(viawrnexus test); uses Bun's module mountHtmlrequireshappy-domto be available in the workspace (loaded- Works with the rest of the WRNexusJS toolchain:
loading and the bun:test runtime.
lazily; it's a dev dependency, not a runtime dependency of this package).
[@wrnexus/compiler](../compiler) (compiles .wrn sources), [@wrnexus/core](../core) (Context / createContext), [@wrnexus/csr](../csr) (reactive runtime for mountHtml), [@wrnexus/dev-server](../dev-server) (startServer behind createHarness), and [@wrnexus/styles](../styles) (config/env/profile loading for the harness).
Complete TypeScript API
Generated from the exact installed package declarations.
import { ProblemDetails, Context } from '@wrnexus/core';
export { createContext } from '@wrnexus/core';
export { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, mock, spyOn, test } from 'bun:test';
interface TestRequestOptions extends Omit<RequestInit, "body"> {
body?: BodyInit | Record<string, unknown> | URLSearchParams | FormData | null;
baseUrl?: string;
}
/** Build a web-standard Request with convenient JSON/FormData handling. */
declare function testRequest(path?: string, options?: TestRequestOptions): Request;
/** Create a complete Context suitable for middleware and route unit tests. */
declare function testContext(path?: string, options?: TestRequestOptions): Context;
interface JsonResponse<T> {
response: Response;
body: T;
}
declare function readJsonResponse<T = unknown>(response: Response): Promise<JsonResponse<T>>;
declare function expectProblem(response: Response, status?: number): Promise<ProblemDetails>;
interface Deferred<T> {
promise: Promise<T>;
resolve(value: T | PromiseLike<T>): void;
reject(reason?: unknown): void;
}
declare function deferred<T>(): Deferred<T>;
interface WaitForOptions {
timeoutMs?: number;
intervalMs?: number;
signal?: AbortSignal;
}
/** Poll a condition without depending on fake timers or a browser runtime. */
declare function waitFor(condition: () => boolean | Promise<boolean>, options?: WaitForOptions): Promise<void>;
declare class MemoryCookieJar {
#private;
apply(response: Response): void;
header(): string;
request(path: string, options?: TestRequestOptions): Request;
clear(): void;
}
interface TransactionalDatabase {
tx<T>(callback: (transaction: TransactionalDatabase) => Promise<T>): Promise<T>;
}
/** Run test work in a real transaction and always force rollback. */
declare function withDatabaseRollback<T>(db: TransactionalDatabase, run: (transaction: TransactionalDatabase) => T | Promise<T>): Promise<T>;
declare function createFactory<T extends Record<string, unknown>>(build: (sequence: number) => T): {
build(overrides?: Partial<T>): T;
buildMany(count: number, overrides?: Partial<T>): T[];
reset(): void;
};
interface BrowserArtifactPage {
screenshot(options: {
path: string;
fullPage?: boolean;
}): Promise<unknown>;
context(): {
tracing?: {
stop(options: {
path: string;
}): Promise<unknown>;
};
};
}
declare function captureBrowserArtifacts(page: BrowserArtifactPage, testName: string, options?: {
root?: string;
screenshot?: boolean;
trace?: boolean;
}): Promise<{
screenshot?: string;
trace?: string;
}>;
/**
* @wrnexus/test — testing utilities for WRNexusJS apps. Runs on `bun test` (via
* `wrnexus test`). Import everything from one place:
*
* import { test, expect, renderComponent, mountHtml, createHarness } from "@wrnexus/test";
*
* test("counter renders its label", async () => {
* const html = await renderComponent(SRC, { start: 3, label: "Hits" });
* expect(html).toContain("Hits");
* });
*
* test("home page responds", async () => {
* const app = await createHarness("examples/basic-app");
* const res = await app.fetch("/");
* expect(res.status).toBe(200);
* await app.close();
* });
*/
/** Compile a `.wrn` component source + render it to HTML with the given props. */
declare function renderComponent(source: string, props?: Record<string, unknown>): Promise<string>;
/**
* Mount server-rendered HTML in a happy-dom window with the reactive runtime
* hydrated, so you can test `data-scope`/`data-text`/`data-for`/`data-show`
* behaviour. Returns the window; assert on `win.document`.
*/
declare function mountHtml(html: string): {
document: Document;
window: unknown;
querySelector: (sel: string) => Element | null;
querySelectorAll: (sel: string) => Element[];
};
/** Call an API route handler with a `Context` built from a Request. */
declare function callRoute(handler: (ctx: Context) => Response | Promise<Response>, request: Request): Promise<Response>;
interface Harness {
/** Base URL of the ephemeral test server. */
url: string;
/** Fetch a path on the app (relative to `url`). */
fetch(path: string, init?: RequestInit): Promise<Response>;
/** The scanned router (pages/api/realtime/components). */
router: unknown;
/** Stop the server. */
close(): void;
}
interface HarnessOptions {
/** Config/env profile. Default "test". */
profile?: string;
}
/**
* Boot the app on an ephemeral port for integration tests (pages, API routes,
* middleware, the full pipeline). Uses the "test" profile by default so it picks
* up your test database/env. Remember to `await app.close()`.
*/
declare function createHarness(projectRoot: string, options?: HarnessOptions): Promise<Harness>;
export { type BrowserArtifactPage, type Deferred, type Harness, type HarnessOptions, type JsonResponse, MemoryCookieJar, type TestRequestOptions, type TransactionalDatabase, type WaitForOptions, callRoute, captureBrowserArtifacts, createFactory, createHarness, deferred, expectProblem, mountHtml, readJsonResponse, renderComponent, testContext, testRequest, waitFor, withDatabaseRollback };
Examples
Copy-ready examples from the installed package documentation.
The CLI supports focused suites by file or directory convention
wrnexus test unit # *.unit.test.ts or test/unit/**
wrnexus test component # *.component.test.ts or test/component/**
wrnexus test api # *.api.test.ts or test/api/**
wrnexus test accessibility # *.a11y.test.ts / *.accessibility.test.ts
wrnexus test performance # *.performance.test.ts / *.benchmark.test.ts
wrnexus test browser # Playwright project when configured
wrnexus test visual # Playwright tests tagged @visualsuite exists instead of silently running unrelated tests.
import { test, expect, renderComponent, mountHtml, createHarness } from "@wrnexus/test";
test("counter renders its label", async () => {
const html = await renderComponent(SRC, { start: 3, label: "Hits" });
expect(html).toContain("Hits");
});
test("reactive scope hydrates", () => {
const { querySelector } = mountHtml(serverHtml);
expect(querySelector("[data-text]")?.textContent).toBe("3");
});
test("home page responds", async () => {
const app = await createHarness("examples/basic-app");
const res = await app.fetch("/");
expect(res.status).toBe(200);
await app.close();
});Calling an API route handler directly
import { test, expect, callRoute } from "@wrnexus/test";
import { GET } from "../app/api/health.ts";
test("health endpoint", async () => {
const res = await callRoute(GET, new Request("http://test/api/health"));
expect(res.status).toBe(200);
});