first commit
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { test, expect, afterEach } from "bun:test";
|
||||
import { mkdtempSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { loadAppConfig, resolveProfile, loadEnv } from "../src/index.ts";
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.WRNEXUS_PROFILE;
|
||||
});
|
||||
|
||||
test("resolveProfile: explicit > WRNEXUS_PROFILE > mode default", () => {
|
||||
delete process.env.WRNEXUS_PROFILE;
|
||||
expect(resolveProfile({ mode: "development" })).toBe("development");
|
||||
expect(resolveProfile({ mode: "production" })).toBe("production");
|
||||
expect(resolveProfile({ explicit: "uat", mode: "production" })).toBe("uat");
|
||||
process.env.WRNEXUS_PROFILE = "test";
|
||||
expect(resolveProfile({ mode: "production" })).toBe("test");
|
||||
expect(resolveProfile({ explicit: "uat" })).toBe("uat"); // explicit still wins
|
||||
});
|
||||
|
||||
test("loadAppConfig deep-merges the active profile and strips `profiles`", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "wire-cfg-"));
|
||||
writeFileSync(
|
||||
join(dir, "wrnexus.config.mjs"),
|
||||
`export default {
|
||||
port: 3000,
|
||||
db: { driver: "sqlite", url: "file:./dev.db" },
|
||||
seo: { title: "Base", siteName: "App" },
|
||||
profiles: {
|
||||
prod: { db: { driver: "postgres", url: "postgres://prod" }, port: 8080 },
|
||||
uat: { seo: { title: "UAT" } },
|
||||
},
|
||||
};`,
|
||||
);
|
||||
|
||||
const dev = await loadAppConfig(dir, "development");
|
||||
expect(dev.db).toEqual({ driver: "sqlite", url: "file:./dev.db" });
|
||||
expect((dev as { profiles?: unknown }).profiles).toBeUndefined();
|
||||
|
||||
const prod = await loadAppConfig(dir, "prod");
|
||||
expect(prod.db).toEqual({ driver: "postgres", url: "postgres://prod" });
|
||||
expect(prod.port).toBe(8080);
|
||||
expect(prod.seo).toEqual({ title: "Base", siteName: "App" }); // untouched
|
||||
|
||||
const uat = await loadAppConfig(dir, "uat");
|
||||
expect(uat.seo).toEqual({ title: "UAT", siteName: "App" }); // deep-merged
|
||||
expect(uat.db!.driver).toBe("sqlite");
|
||||
});
|
||||
|
||||
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');
|
||||
writeFileSync(join(dir, ".env.prod"), "SHARED=prod\nPROD_ONLY=yes\nREALVAR=fromfile\n");
|
||||
writeFileSync(join(dir, ".env.prod.local"), "SHARED=local\n");
|
||||
|
||||
process.env.REALVAR = "real"; // simulate a real environment variable
|
||||
try {
|
||||
const loaded = loadEnv(dir, "prod");
|
||||
expect(loaded.BASE).toBe("1");
|
||||
expect(process.env.BASE).toBe("1");
|
||||
expect(process.env.QUOTED).toBe("hi there"); // quotes stripped
|
||||
expect(process.env.SHARED).toBe("local"); // .env.prod.local beats .env.prod beats .env
|
||||
expect(process.env.PROD_ONLY).toBe("yes");
|
||||
expect(process.env.REALVAR).toBe("real"); // real env NOT overridden by a file
|
||||
} finally {
|
||||
for (const k of ["BASE", "SHARED", "PROD_ONLY", "QUOTED", "REALVAR"]) delete process.env[k];
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { fontCspSources, renderFontHead } from "../src/index.ts";
|
||||
|
||||
test("renders subsetted Google and escaped local font markup", () => {
|
||||
const html = renderFontHead({
|
||||
google: [{ family: "Inter", weights: [700, 400] }],
|
||||
local: [
|
||||
{
|
||||
family: 'Bad"</style><script>x</script>',
|
||||
src: '/font".woff2',
|
||||
preload: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(html).toContain("family=Inter:wght@400;700");
|
||||
expect(html).not.toContain("</style><script>");
|
||||
expect(html).toContain(""");
|
||||
expect(fontCspSources({ google: [{ family: "Inter" }] })).toEqual({
|
||||
style: ["https://fonts.googleapis.com"],
|
||||
font: ["https://fonts.gstatic.com"],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import {
|
||||
resolveThemeConfig,
|
||||
resolveThemeName,
|
||||
renderThemeCss,
|
||||
renderThemeRuntime,
|
||||
} from "../src/index.ts";
|
||||
|
||||
test("resolveThemeConfig merges user tokens over built-in light/dark", () => {
|
||||
const t = resolveThemeConfig({ default: "dark", themes: { dark: { "color-primary": "#abc" } } });
|
||||
expect(t.default).toBe("dark");
|
||||
expect(t.names).toContain("light");
|
||||
expect(t.names).toContain("dark");
|
||||
expect(t.themes.dark["color-primary"]).toBe("#abc"); // overridden
|
||||
expect(t.themes.dark["color-bg"]).toBeDefined(); // built-in kept
|
||||
});
|
||||
|
||||
test("resolveThemeName validates against configured names", () => {
|
||||
const t = resolveThemeConfig();
|
||||
expect(resolveThemeName("light", t)).toBe("light");
|
||||
expect(resolveThemeName("nonsense", t)).toBe(t.default);
|
||||
expect(resolveThemeName(undefined, t)).toBe(t.default);
|
||||
});
|
||||
|
||||
test("renderThemeCss emits :root + per-theme blocks and --wire-* vars", () => {
|
||||
const t = resolveThemeConfig({
|
||||
default: "dark",
|
||||
themes: { dark: { "color-primary": "#6c8cff" } },
|
||||
});
|
||||
const css = renderThemeCss(t);
|
||||
expect(css).toContain(":root{");
|
||||
expect(css).toContain('[data-theme="dark"]{');
|
||||
expect(css).toContain("--wire-color-primary:#6c8cff");
|
||||
expect(css).toContain("color-scheme:dark"); // reserved token → native property
|
||||
});
|
||||
|
||||
test("renderThemeRuntime bakes the theme names for cycling", () => {
|
||||
const t = resolveThemeConfig();
|
||||
const js = renderThemeRuntime(t);
|
||||
expect(js).toContain("data-wire-theme-toggle");
|
||||
expect(js).toContain(JSON.stringify(t.names));
|
||||
});
|
||||
Reference in New Issue
Block a user