release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+16
View File
@@ -103,6 +103,22 @@ Remember to `await app.close()` when done.
## Usage
The CLI supports focused suites by file or directory convention:
```bash
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.
```ts
import { test, expect, renderComponent, mountHtml, createHarness } from "@wrnexus/test";
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/test",
"version": "0.7.0",
"version": "0.8.0",
"private": true,
"type": "module",
"main": "src/index.ts",
+2
View File
@@ -190,3 +190,5 @@ export {
MemoryCookieJar,
} 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";
+67
View File
@@ -0,0 +1,67 @@
import { mkdirSync } from "node:fs";
import { join, resolve } from "node:path";
export interface TransactionalDatabase {
tx<T>(callback: (transaction: TransactionalDatabase) => Promise<T>): Promise<T>;
}
const ROLLBACK = Symbol("wrnexus-test-rollback");
/** Run test work in a real transaction and always force rollback. */
export async function withDatabaseRollback<T>(
db: TransactionalDatabase,
run: (transaction: TransactionalDatabase) => T | Promise<T>,
): Promise<T> {
let output!: T;
try {
await db.tx(async (transaction) => {
output = await run(transaction);
throw ROLLBACK;
});
} catch (error) {
if (error !== ROLLBACK) throw error;
}
return output;
}
export function createFactory<T extends Record<string, unknown>>(build: (sequence: number) => T) {
let sequence = 0;
return {
build(overrides: Partial<T> = {}): T {
return { ...build(++sequence), ...overrides };
},
buildMany(count: number, overrides: Partial<T> = {}): T[] {
if (!Number.isInteger(count) || count < 0 || count > 10_000)
throw new RangeError("Factory count must be between 0 and 10000");
return Array.from({ length: count }, () => ({ ...build(++sequence), ...overrides }));
},
reset(): void {
sequence = 0;
},
};
}
export interface BrowserArtifactPage {
screenshot(options: { path: string; fullPage?: boolean }): Promise<unknown>;
context(): { tracing?: { stop(options: { path: string }): Promise<unknown> } };
}
export async function captureBrowserArtifacts(
page: BrowserArtifactPage,
testName: string,
options: { root?: string; screenshot?: boolean; trace?: boolean } = {},
): Promise<{ screenshot?: string; trace?: string }> {
const safe = testName.replace(/[^A-Za-z0-9_.-]+/g, "-").slice(0, 120) || "test";
const root = resolve(options.root ?? join("test-results", "wrnexus"));
mkdirSync(root, { recursive: true });
const output: { screenshot?: string; trace?: string } = {};
if (options.screenshot !== false) {
output.screenshot = join(root, `${safe}.png`);
await page.screenshot({ path: output.screenshot, fullPage: true });
}
if (options.trace && page.context().tracing) {
output.trace = join(root, `${safe}.zip`);
await page.context().tracing!.stop({ path: output.trace });
}
return output;
}
+63
View File
@@ -0,0 +1,63 @@
import { expect, test } from "bun:test";
import { captureBrowserArtifacts, createFactory, withDatabaseRollback } from "../src/index.ts";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
test("factories are deterministic, bounded and override-safe", () => {
const users = createFactory((sequence) => ({
id: sequence,
email: `user${sequence}@test.local`,
}));
expect(users.build().id).toBe(1);
expect(users.build({ email: "custom@test.local" }).email).toBe("custom@test.local");
users.reset();
expect(users.buildMany(2).map((user) => user.id)).toEqual([1, 2]);
expect(() => users.buildMany(10_001)).toThrow();
});
test("database isolation always rolls back while returning the test value", async () => {
let rolledBack = false;
const db = {
async tx<T>(run: (transaction: any) => Promise<T>): Promise<T> {
try {
return await run(db);
} catch (error) {
rolledBack = true;
throw error;
}
},
};
expect(await withDatabaseRollback(db, async () => 42)).toBe(42);
expect(rolledBack).toBeTrue();
});
test("browser artifact helper writes safe screenshot and trace paths", async () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-browser-artifacts-"));
const calls: string[] = [];
try {
const result = await captureBrowserArtifacts(
{
async screenshot({ path }) {
calls.push(path);
},
context() {
return {
tracing: {
async stop({ path }) {
calls.push(path);
},
},
};
},
},
"user / unsafe name",
{ root, trace: true },
);
expect(result.screenshot).toEndWith("user-unsafe-name.png");
expect(result.trace).toEndWith("user-unsafe-name.zip");
expect(calls).toHaveLength(2);
} finally {
rmSync(root, { recursive: true, force: true });
}
});