Files
copilotkit--copilotkit/packages/web-components/src/threads-drawer/__tests__/generated-tokens.test.ts
T
2026-07-13 12:58:18 +08:00

122 lines
4.1 KiB
TypeScript

/**
* Guards the build-time token-sync (anti-drift) contract.
*
* The drawer's bundled default token values are DERIVED from react-core's
* canonical theme by `scripts/generate-tokens.ts` and checked in as
* `generated-tokens.ts`. These tests assert (a) the generated file exposes the
* tokens the shadow-DOM CSS depends on, and (b) the checked-in values still
* match what the canonical theme currently declares — i.e. the generated file
* has not drifted from `react-core/.../globals.css`.
*/
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { expect, test } from "vitest";
import { GENERATED_DRAWER_TOKEN_DEFAULTS } from "../generated-tokens";
import type { GeneratedDrawerTokenKey } from "../generated-tokens";
import {
extractLightThemeBlock,
parseCustomProperties,
} from "../token-extraction";
const dirname = path.dirname(fileURLToPath(import.meta.url));
// __tests__ -> drawer -> src -> web-components -> packages -> repo root
const repoRoot = path.resolve(dirname, "../../../../../");
const GLOBALS_CSS = path.join(
repoRoot,
"packages/react-core/src/v2/styles/globals.css",
);
/** Maps drawer token keys back to the react-core custom property they derive from. */
const DERIVATION: Record<GeneratedDrawerTokenKey, string> = {
bg: "background",
fg: "foreground",
surface: "card",
"surface-fg": "card-foreground",
muted: "muted",
"muted-fg": "muted-foreground",
accent: "accent",
"accent-fg": "accent-foreground",
primary: "primary",
"primary-fg": "primary-foreground",
danger: "destructive",
border: "border",
ring: "ring",
radius: "radius",
};
function readCanonicalLightTokens(): Map<string, string> {
const css = readFileSync(GLOBALS_CSS, "utf8");
// Route through the SAME canonical parser the build script uses, rather than a
// local naive first-`}` scan: a reimplementation here could pass while the real
// extractor truncates (or vice-versa), defeating the drift guarantee.
return parseCustomProperties(extractLightThemeBlock(css, GLOBALS_CSS));
}
test("generated defaults expose every token the drawer CSS references", () => {
const expectedKeys: GeneratedDrawerTokenKey[] = [
"bg",
"fg",
"surface",
"surface-fg",
"muted",
"muted-fg",
"accent",
"accent-fg",
"primary",
"primary-fg",
"danger",
"border",
"ring",
"radius",
];
for (const key of expectedKeys) {
expect(GENERATED_DRAWER_TOKEN_DEFAULTS[key]).toBeTypeOf("string");
expect(GENERATED_DRAWER_TOKEN_DEFAULTS[key].length).toBeGreaterThan(0);
}
});
test("checked-in generated defaults have not drifted from react-core's canonical theme", () => {
const canonical = readCanonicalLightTokens();
for (const [drawerKey, reactCoreVar] of Object.entries(DERIVATION)) {
const canonicalValue = canonical.get(reactCoreVar);
expect(
canonicalValue,
`react-core theme is missing --${reactCoreVar}; regenerate tokens`,
).toBeDefined();
expect(
GENERATED_DRAWER_TOKEN_DEFAULTS[drawerKey as GeneratedDrawerTokenKey],
`drawer token "${drawerKey}" drifted from react-core --${reactCoreVar}; run "pnpm run gen:tokens"`,
).toBe(canonicalValue);
}
});
// --- Fix 8: brace-balanced block extraction -------------------------------
test("extractLightThemeBlock captures the full block past a nested brace", () => {
const css = [
"[data-copilotkit] {",
" --background: #fff;",
" @media (min-width: 0) { --inner: 1; }",
" --foreground: #000;",
"}",
".other { --x: 1; }",
].join("\n");
const block = extractLightThemeBlock(css);
// a naive first-`}` scan would stop at the @media close and drop --foreground
expect(block).toContain("--background: #fff;");
expect(block).toContain("--foreground: #000;");
// and must NOT bleed into the sibling rule that follows the matching close
expect(block).not.toContain("--x: 1;");
});
test("extractLightThemeBlock throws on unbalanced braces rather than truncating", () => {
const css = "[data-copilotkit] {\n --background: #fff;\n { --orphan: 1;\n";
expect(() => extractLightThemeBlock(css)).toThrow(/Unbalanced braces/);
});