release: WRNexusJS 0.8.0
This commit is contained in:
@@ -2,12 +2,50 @@ import { test, expect, afterEach } from "bun:test";
|
||||
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { loadAppConfig, resolveProfile, loadEnv, renderStyles } from "../src/index.ts";
|
||||
import {
|
||||
loadAppConfig,
|
||||
resolveProfile,
|
||||
loadEnv,
|
||||
renderStyles,
|
||||
resolveCompatibility,
|
||||
validateAppConfig,
|
||||
explainAppConfig,
|
||||
} from "../src/index.ts";
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.WRNEXUS_PROFILE;
|
||||
});
|
||||
|
||||
test("compatibility dates pin behavior and reject invalid or future policies", () => {
|
||||
expect(
|
||||
resolveCompatibility({ compatibilityDate: "2026-08-02", frameworkBehaviour: 1 }),
|
||||
).toMatchObject({ needsUpgrade: false, future: false, effectiveBehaviour: 1 });
|
||||
expect(validateAppConfig({ compatibilityDate: "2026-02-31" })).toContainEqual(
|
||||
expect.objectContaining({ path: "compatibilityDate", severity: "error" }),
|
||||
);
|
||||
expect(validateAppConfig({ frameworkBehaviour: 2 })).toContainEqual(
|
||||
expect.objectContaining({ severity: "error" }),
|
||||
);
|
||||
});
|
||||
|
||||
test("observability config requires bounded sampling and a valid OTLP endpoint", () => {
|
||||
expect(validateAppConfig({ observability: { sampleRate: Number.NaN } })).toContainEqual(
|
||||
expect.objectContaining({ path: "observability.sampleRate", severity: "error" }),
|
||||
);
|
||||
expect(
|
||||
validateAppConfig({ observability: { exporter: "otlp", endpoint: "collector:4318" } }),
|
||||
).toContainEqual(expect.objectContaining({ path: "observability.endpoint", severity: "error" }));
|
||||
expect(
|
||||
validateAppConfig({
|
||||
observability: {
|
||||
exporter: "otlp",
|
||||
endpoint: "https://collector.example/v1/traces",
|
||||
serviceName: "api",
|
||||
},
|
||||
}),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
test("resolveProfile: explicit > WRNEXUS_PROFILE > mode default", () => {
|
||||
delete process.env.WRNEXUS_PROFILE;
|
||||
expect(resolveProfile({ mode: "development" })).toBe("development");
|
||||
@@ -47,6 +85,77 @@ test("loadAppConfig deep-merges the active profile and strips `profiles`", async
|
||||
expect(uat.db!.driver).toBe("sqlite");
|
||||
});
|
||||
|
||||
test("config layers compose recursively with deterministic application precedence", async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-layers-"));
|
||||
try {
|
||||
mkdirSync(join(root, "layers", "base"), { recursive: true });
|
||||
mkdirSync(join(root, "layers", "company"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(root, "layers", "base", "wrnexus.layer.mjs"),
|
||||
`export default {
|
||||
port: 1000, head: ["<meta name='base'>"], seo: { siteName: "Foundation", title: "Base" },
|
||||
profiles: { production: { port: 7000 } }
|
||||
}`,
|
||||
);
|
||||
writeFileSync(
|
||||
join(root, "layers", "company", "wrnexus.layer.mjs"),
|
||||
`export default {
|
||||
extends: ["../base"], head: "<meta name='company'>", seo: { title: "Company" }
|
||||
}`,
|
||||
);
|
||||
writeFileSync(
|
||||
join(root, "wrnexus.config.mjs"),
|
||||
`export default {
|
||||
extends: ["./layers/company"], port: 3000, head: ["<meta name='app'>"], seo: { title: "App" },
|
||||
profiles: { production: { port: 8000 } }
|
||||
}`,
|
||||
);
|
||||
|
||||
const config = await loadAppConfig(root, "production");
|
||||
expect(config.port).toBe(8000);
|
||||
expect((await loadAppConfig(root, "development")).port).toBe(3000);
|
||||
expect(config.seo).toEqual({ siteName: "Foundation", title: "App" });
|
||||
expect(config.head).toEqual([
|
||||
"<meta name='base'>",
|
||||
"<meta name='company'>",
|
||||
"<meta name='app'>",
|
||||
]);
|
||||
expect(config.extends).toBeUndefined();
|
||||
const explained = await explainAppConfig(root, "production");
|
||||
expect(explained.sources.filter((source) => source.includes("wrnexus.layer")).length).toBe(2);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("config layers resolve packages and reject dependency cycles", async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-package-layer-"));
|
||||
try {
|
||||
const pkg = join(root, "node_modules", "company-layer");
|
||||
mkdirSync(pkg, { recursive: true });
|
||||
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "app" }));
|
||||
writeFileSync(
|
||||
join(pkg, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "company-layer",
|
||||
wrnexus: { layer: "./foundation.mjs" },
|
||||
}),
|
||||
);
|
||||
writeFileSync(join(pkg, "foundation.mjs"), `export default { port: 4400 }`);
|
||||
writeFileSync(join(root, "wrnexus.config.mjs"), `export default { extends: "company-layer" }`);
|
||||
expect((await loadAppConfig(root)).port).toBe(4400);
|
||||
|
||||
const cycle = join(root, "cycle");
|
||||
mkdirSync(cycle);
|
||||
writeFileSync(join(cycle, "a.mjs"), `export default { extends: "./b.mjs" }`);
|
||||
writeFileSync(join(cycle, "b.mjs"), `export default { extends: "./a.mjs" }`);
|
||||
writeFileSync(join(cycle, "wrnexus.config.mjs"), `export default { extends: "./a.mjs" }`);
|
||||
await expect(loadAppConfig(cycle)).rejects.toThrow("WRN-CONFIG-LAYER-CYCLE");
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("loadEnv layers .env files by precedence; real env always wins", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "wire-env-"));
|
||||
writeFileSync(join(dir, ".env"), 'BASE=1\nSHARED=base\n# a comment\nQUOTED="hi there"\n');
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { defineThemeTokens, themeVar } from "../src/index.ts";
|
||||
|
||||
test("typed theme token helpers preserve custom tokens and emit CSS variables", () => {
|
||||
const tokens = defineThemeTokens({
|
||||
"color-primary": "#2563eb",
|
||||
"space-product-card": "1rem",
|
||||
});
|
||||
expect(tokens["space-product-card"]).toBe("1rem");
|
||||
expect(themeVar("color-primary")).toBe("var(--wire-color-primary)");
|
||||
expect(themeVar("color-text", "#111")).toBe("var(--wire-color-text, #111)");
|
||||
});
|
||||
Reference in New Issue
Block a user