1import { describe, expect, it } from "vitest";
2import { BLOCK_COLORS, STATUS_COLORS } from "@factory/shared/constants";
3import {
4 BLOCK_COLOR_KEYS,
5 DARK_TOKENS,
6 LIGHT_TOKENS,
7 STATUS_COLOR_KEYS,
8 THEME_TOKENS,
9 TOKEN_NAMES,
10 cssTokensFile,
11 cssVariables,
12} from "./tokens.js";
13
14describe("tokens", () => {
15 it("emits both themes with equal key sets", () => {
16 expect(Object.keys(THEME_TOKENS.light).sort()).toEqual(Object.keys(THEME_TOKENS.dark).sort());
17 expect(THEME_TOKENS.light).not.toHaveProperty("undefined");
18 });
19
20 it("covers every semantic token in both themes", () => {
21 for (const name of TOKEN_NAMES) {
22 expect(THEME_TOKENS.light[name], `light missing ${name}`).toBeTruthy();
23 expect(THEME_TOKENS.dark[name], `dark missing ${name}`).toBeTruthy();
24 }
25 });
26
27 it("uses Notion ink for light primary", () => {
28 expect(THEME_TOKENS.light["primary"]).toBe("#37352F");
29 expect(THEME_TOKENS.light["foreground"]).toBe("#37352F");
30 });
31
32 it("maps all 18 shared block colors to CSS vars in both themes", () => {
33 expect(BLOCK_COLOR_KEYS).toHaveLength(18);
34 for (const c of BLOCK_COLORS) {
35 const key = `block-color-${c.token}`;
36 expect(BLOCK_COLOR_KEYS).toContain(key);
37 expect(THEME_TOKENS.light[key]).toBe(c.hex);
38 expect(THEME_TOKENS.dark[key]).toBe(c.hex);
39 }
40 });
41
42 it("maps all 9 shared status colors to CSS vars", () => {
43 expect(STATUS_COLOR_KEYS).toHaveLength(9);
44 for (const s of STATUS_COLORS) {
45 expect(THEME_TOKENS.light[`status-${s}`]).toBeTruthy();
46 expect(THEME_TOKENS.dark[`status-${s}`]).toBeTruthy();
47 }
48 });
49
50 it("cssVariables(light) renders :root with all keys, deterministic order", () => {
51 const css = cssVariables("light");
52 expect(css.startsWith(":root {")).toBe(true);
53 expect(css.endsWith("}")).toBe(true);
54 for (const key of [...TOKEN_NAMES, ...BLOCK_COLOR_KEYS, ...STATUS_COLOR_KEYS]) {
55 expect(css).toContain(`--${key}:`);
56 }
57
58 expect(css.indexOf("--background:")).toBeGreaterThan(-1);
59 expect(css.indexOf("--background:")).toBeLessThan(css.indexOf("--focus-ring:"));
60 expect(css).toBe(cssVariables("light"));
61 });
62
63 it('cssVariables(dark) renders [data-theme="dark"]', () => {
64 const css = cssVariables("dark");
65 expect(css.startsWith('[data-theme="dark"] {')).toBe(true);
66 for (const key of TOKEN_NAMES) {
67 expect(css).toContain(`--${key}:`);
68 }
69 });
70
71 it("cssTokensFile contains both themes + block/status vars", () => {
72 const file = cssTokensFile();
73 expect(file).toContain(":root {");
74 expect(file).toContain('[data-theme="dark"] {');
75
76
77 expect(file).toContain("--block-color-blue:");
78 expect(file).toContain("--block-color-blue_bg:");
79 expect(file).toContain("--status-red:");
80 expect(file.endsWith("\n")).toBe(true);
81
82 const light = file.slice(file.indexOf(":root {"), file.indexOf('[data-theme="dark"]'));
83 const dark = file.slice(file.indexOf('[data-theme="dark"]'));
84 expect((light.match(/--[\w-]+:/g) ?? []).length).toBe((dark.match(/--[\w-]+:/g) ?? []).length);
85 });
86
87 it("light/dark token maps are plain string records", () => {
88 expect(LIGHT_TOKENS.background).toBe("#FFFFFF");
89 expect(DARK_TOKENS.background).toBe("#191919");
90 });
91});
92