Skip to content

Commit be4364d

Browse files
authored
test: add coverage for tokens, castHelpers, useResizable, tutorialsData, vaultSwitch
Add 73 new tests across 5 files covering design tokens, demo cast helpers, useResizable hook, tutorials data, and Electron vault switch orchestration.
1 parent 74847aa commit be4364d

5 files changed

Lines changed: 784 additions & 0 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
jest.mock("../logger", () => ({
2+
logMessage: jest.fn()
3+
}));
4+
5+
jest.mock("../vaults", () => ({
6+
setActiveVaultId: jest.fn()
7+
}));
8+
9+
jest.mock("../server", () => ({
10+
initializeBackendServer: jest.fn().mockResolvedValue(undefined),
11+
stopServer: jest.fn().mockResolvedValue(undefined)
12+
}));
13+
14+
jest.mock("../shortcuts", () => ({
15+
setupWorkflowShortcuts: jest.fn().mockResolvedValue(undefined)
16+
}));
17+
18+
jest.mock("../window", () => ({
19+
reloadMainWindow: jest.fn()
20+
}));
21+
22+
import { applyVaultSwitch } from "../vaultSwitch";
23+
import { setActiveVaultId } from "../vaults";
24+
import { initializeBackendServer, stopServer } from "../server";
25+
import { setupWorkflowShortcuts } from "../shortcuts";
26+
import { reloadMainWindow } from "../window";
27+
import { logMessage } from "../logger";
28+
29+
describe("applyVaultSwitch", () => {
30+
beforeEach(() => {
31+
jest.clearAllMocks();
32+
jest.useFakeTimers();
33+
});
34+
35+
afterEach(() => {
36+
jest.useRealTimers();
37+
});
38+
39+
it("persists the vault id", async () => {
40+
const promise = applyVaultSwitch("vault-42");
41+
await jest.advanceTimersByTimeAsync(300);
42+
await promise;
43+
44+
expect(setActiveVaultId).toHaveBeenCalledWith("vault-42");
45+
});
46+
47+
it("logs the switch", async () => {
48+
const promise = applyVaultSwitch("vault-42");
49+
await jest.advanceTimersByTimeAsync(300);
50+
await promise;
51+
52+
expect(logMessage).toHaveBeenCalledWith(
53+
expect.stringContaining("vault-42")
54+
);
55+
});
56+
57+
it("stops the server", async () => {
58+
const promise = applyVaultSwitch("vault-42");
59+
await jest.advanceTimersByTimeAsync(300);
60+
await promise;
61+
62+
expect(stopServer).toHaveBeenCalled();
63+
});
64+
65+
it("does not restart the backend before the delay elapses", async () => {
66+
const promise = applyVaultSwitch("vault-42");
67+
68+
expect(initializeBackendServer).not.toHaveBeenCalled();
69+
70+
await jest.advanceTimersByTimeAsync(300);
71+
await promise;
72+
73+
expect(initializeBackendServer).toHaveBeenCalled();
74+
});
75+
76+
it("re-registers workflow shortcuts", async () => {
77+
const promise = applyVaultSwitch("vault-42");
78+
await jest.advanceTimersByTimeAsync(300);
79+
await promise;
80+
81+
expect(setupWorkflowShortcuts).toHaveBeenCalled();
82+
});
83+
84+
it("reloads the main window", async () => {
85+
const promise = applyVaultSwitch("vault-42");
86+
await jest.advanceTimersByTimeAsync(300);
87+
await promise;
88+
89+
expect(reloadMainWindow).toHaveBeenCalled();
90+
});
91+
92+
it("calls all steps in the correct order", async () => {
93+
const order: string[] = [];
94+
(setActiveVaultId as jest.Mock).mockImplementation(() => order.push("setVault"));
95+
(stopServer as jest.Mock).mockImplementation(async () => order.push("stop"));
96+
(initializeBackendServer as jest.Mock).mockImplementation(async () => order.push("start"));
97+
(setupWorkflowShortcuts as jest.Mock).mockImplementation(async () => order.push("shortcuts"));
98+
(reloadMainWindow as jest.Mock).mockImplementation(() => order.push("reload"));
99+
100+
const promise = applyVaultSwitch("v1");
101+
await jest.advanceTimersByTimeAsync(300);
102+
await promise;
103+
104+
expect(order).toEqual(["setVault", "stop", "start", "shortcuts", "reload"]);
105+
});
106+
});
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { TUTORIALS, getTutorial } from "../tutorialsData";
2+
import type { Tutorial } from "../tutorialsData";
3+
4+
describe("tutorialsData", () => {
5+
describe("TUTORIALS", () => {
6+
it("is a non-empty array", () => {
7+
expect(TUTORIALS.length).toBeGreaterThan(0);
8+
});
9+
10+
it("every entry has all required fields", () => {
11+
const requiredKeys: (keyof Tutorial)[] = [
12+
"id",
13+
"title",
14+
"tagline",
15+
"description",
16+
"level",
17+
"durationLabel",
18+
"video",
19+
"poster",
20+
"accent",
21+
"learn"
22+
];
23+
for (const tutorial of TUTORIALS) {
24+
for (const key of requiredKeys) {
25+
expect(tutorial).toHaveProperty(key);
26+
}
27+
}
28+
});
29+
30+
it("every id is unique", () => {
31+
const ids = TUTORIALS.map((t) => t.id);
32+
expect(new Set(ids).size).toBe(ids.length);
33+
});
34+
35+
it("every learn array is non-empty", () => {
36+
for (const tutorial of TUTORIALS) {
37+
expect(tutorial.learn.length).toBeGreaterThan(0);
38+
}
39+
});
40+
41+
it("every video path starts with /tutorials/", () => {
42+
for (const tutorial of TUTORIALS) {
43+
expect(tutorial.video).toMatch(/^\/tutorials\//);
44+
}
45+
});
46+
47+
it("every poster path starts with /tutorials/", () => {
48+
for (const tutorial of TUTORIALS) {
49+
expect(tutorial.poster).toMatch(/^\/tutorials\//);
50+
}
51+
});
52+
53+
it("every accent is a valid hex color", () => {
54+
for (const tutorial of TUTORIALS) {
55+
expect(tutorial.accent).toMatch(/^#[0-9a-fA-F]{6}$/);
56+
}
57+
});
58+
59+
it("every durationLabel matches M:SS format", () => {
60+
for (const tutorial of TUTORIALS) {
61+
expect(tutorial.durationLabel).toMatch(/^\d+:\d{2}$/);
62+
}
63+
});
64+
});
65+
66+
describe("getTutorial", () => {
67+
it("returns the matching tutorial by id", () => {
68+
const first = TUTORIALS[0];
69+
const result = getTutorial(first.id);
70+
expect(result).toBe(first);
71+
});
72+
73+
it("returns the last tutorial when it exists", () => {
74+
const last = TUTORIALS[TUTORIALS.length - 1];
75+
const result = getTutorial(last.id);
76+
expect(result).toBe(last);
77+
});
78+
79+
it("falls back to the first tutorial for an unknown id", () => {
80+
const result = getTutorial("nonexistent-tutorial-id");
81+
expect(result).toBe(TUTORIALS[0]);
82+
});
83+
84+
it("falls back to the first tutorial when id is null", () => {
85+
const result = getTutorial(null);
86+
expect(result).toBe(TUTORIALS[0]);
87+
});
88+
89+
it("falls back to the first tutorial when id is undefined", () => {
90+
const result = getTutorial(undefined);
91+
expect(result).toBe(TUTORIALS[0]);
92+
});
93+
});
94+
});
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import {
2+
FONT_WEIGHT,
3+
FONT_SIZE_SANS,
4+
FONT_SIZE_MONO,
5+
TYPOGRAPHY,
6+
MOTION,
7+
reducedMotion,
8+
Z_INDEX,
9+
BORDER_RADIUS
10+
} from "../tokens";
11+
12+
describe("design token constants", () => {
13+
describe("FONT_WEIGHT", () => {
14+
it("defines the three sanctioned weights", () => {
15+
expect(FONT_WEIGHT.normal).toBe(400);
16+
expect(FONT_WEIGHT.medium).toBe(500);
17+
expect(FONT_WEIGHT.semibold).toBe(600);
18+
});
19+
20+
it("contains exactly three entries", () => {
21+
expect(Object.keys(FONT_WEIGHT)).toHaveLength(3);
22+
});
23+
});
24+
25+
describe("FONT_SIZE_SANS", () => {
26+
it("maps each role to a CSS variable", () => {
27+
expect(FONT_SIZE_SANS.title).toBe("var(--fontSizeBig)");
28+
expect(FONT_SIZE_SANS.body).toBe("var(--fontSizeNormal)");
29+
expect(FONT_SIZE_SANS.label).toBe("var(--fontSizeSmall)");
30+
expect(FONT_SIZE_SANS.caption).toBe("var(--fontSizeSmaller)");
31+
});
32+
33+
it("contains exactly four sizes", () => {
34+
expect(Object.keys(FONT_SIZE_SANS)).toHaveLength(4);
35+
});
36+
});
37+
38+
describe("FONT_SIZE_MONO", () => {
39+
it("maps each role to a CSS variable", () => {
40+
expect(FONT_SIZE_MONO.code).toBe("var(--fontSizeSmall)");
41+
expect(FONT_SIZE_MONO.strong).toBe("var(--fontSizeSmall)");
42+
expect(FONT_SIZE_MONO.label).toBe("var(--fontSizeSmall)");
43+
expect(FONT_SIZE_MONO.caption).toBe("var(--fontSizeSmaller)");
44+
});
45+
46+
it("contains exactly four sizes", () => {
47+
expect(Object.keys(FONT_SIZE_MONO)).toHaveLength(4);
48+
});
49+
});
50+
51+
describe("TYPOGRAPHY", () => {
52+
it("has sans and mono families", () => {
53+
expect(TYPOGRAPHY).toHaveProperty("sans");
54+
expect(TYPOGRAPHY).toHaveProperty("mono");
55+
});
56+
57+
it("sans has exactly four roles", () => {
58+
expect(Object.keys(TYPOGRAPHY.sans)).toEqual([
59+
"title",
60+
"body",
61+
"label",
62+
"caption"
63+
]);
64+
});
65+
66+
it("mono has exactly four roles", () => {
67+
expect(Object.keys(TYPOGRAPHY.mono)).toEqual([
68+
"code",
69+
"strong",
70+
"label",
71+
"caption"
72+
]);
73+
});
74+
75+
it("every style has fontSize, fontWeight, fontFamily, lineHeight", () => {
76+
const required = ["fontSize", "fontWeight", "fontFamily", "lineHeight"];
77+
for (const family of Object.values(TYPOGRAPHY)) {
78+
for (const style of Object.values(family)) {
79+
for (const key of required) {
80+
expect(style).toHaveProperty(key);
81+
}
82+
}
83+
}
84+
});
85+
86+
it("sans uses fontFamily1, mono uses fontFamily2", () => {
87+
for (const style of Object.values(TYPOGRAPHY.sans)) {
88+
expect(style.fontFamily).toBe("var(--fontFamily1)");
89+
}
90+
for (const style of Object.values(TYPOGRAPHY.mono)) {
91+
expect(style.fontFamily).toBe("var(--fontFamily2)");
92+
}
93+
});
94+
95+
it("all weights are from the sanctioned set", () => {
96+
const allowed = new Set(Object.values(FONT_WEIGHT));
97+
for (const family of Object.values(TYPOGRAPHY)) {
98+
for (const style of Object.values(family)) {
99+
expect(allowed).toContain(style.fontWeight);
100+
}
101+
}
102+
});
103+
});
104+
105+
describe("MOTION", () => {
106+
it("defines fast/normal/slow durations", () => {
107+
expect(MOTION.fast).toBe("120ms ease");
108+
expect(MOTION.normal).toBe("200ms ease");
109+
expect(MOTION.slow).toBe("350ms ease");
110+
});
111+
112+
it("defines property shorthands", () => {
113+
expect(MOTION.border).toContain("border-color");
114+
expect(MOTION.opacity).toContain("opacity");
115+
expect(MOTION.transform).toContain("transform");
116+
expect(MOTION.shadow).toContain("box-shadow");
117+
expect(MOTION.background).toContain("background-color");
118+
expect(MOTION.all).toContain("all");
119+
});
120+
121+
it("defines keyframe loop tiers", () => {
122+
expect(MOTION.spin).toBe("1s linear");
123+
expect(MOTION.pulse).toBe("2s ease-in-out");
124+
});
125+
126+
it("defines none for reduced motion overrides", () => {
127+
expect(MOTION.none).toBe("none");
128+
});
129+
});
130+
131+
describe("reducedMotion", () => {
132+
it("wraps overrides in a prefers-reduced-motion media query", () => {
133+
const result = reducedMotion({ transition: "none" });
134+
expect(result).toEqual({
135+
"@media (prefers-reduced-motion: reduce)": { transition: "none" }
136+
});
137+
});
138+
139+
it("passes through multiple overrides", () => {
140+
const overrides = { animation: "none", opacity: 0.6 };
141+
const result = reducedMotion(overrides);
142+
expect(
143+
result["@media (prefers-reduced-motion: reduce)"]
144+
).toEqual(overrides);
145+
});
146+
147+
it("handles empty overrides", () => {
148+
const result = reducedMotion({});
149+
expect(result).toEqual({
150+
"@media (prefers-reduced-motion: reduce)": {}
151+
});
152+
});
153+
});
154+
155+
describe("Z_INDEX", () => {
156+
it("defines the stacking scale", () => {
157+
expect(Z_INDEX.base).toBe(0);
158+
expect(Z_INDEX.raised).toBe(1);
159+
expect(Z_INDEX.dropdown).toBe(10);
160+
expect(Z_INDEX.sticky).toBe(20);
161+
expect(Z_INDEX.overlay).toBe(100);
162+
expect(Z_INDEX.modal).toBe(200);
163+
expect(Z_INDEX.tooltip).toBe(300);
164+
expect(Z_INDEX.toast).toBe(400);
165+
});
166+
167+
it("values are strictly ascending", () => {
168+
const values = Object.values(Z_INDEX);
169+
for (let i = 1; i < values.length; i++) {
170+
expect(values[i]).toBeGreaterThan(values[i - 1]);
171+
}
172+
});
173+
});
174+
175+
describe("BORDER_RADIUS", () => {
176+
it("maps each size to a CSS variable", () => {
177+
expect(BORDER_RADIUS.xs).toBe("var(--rounded-xs)");
178+
expect(BORDER_RADIUS.sm).toBe("var(--rounded-sm)");
179+
expect(BORDER_RADIUS.md).toBe("var(--rounded-md)");
180+
expect(BORDER_RADIUS.lg).toBe("var(--rounded-lg)");
181+
expect(BORDER_RADIUS.xl).toBe("var(--rounded-xl)");
182+
expect(BORDER_RADIUS.xxl).toBe("var(--rounded-xxl)");
183+
expect(BORDER_RADIUS.circle).toBe("var(--rounded-circle)");
184+
expect(BORDER_RADIUS.pill).toBe("var(--rounded-pill)");
185+
});
186+
187+
it("contains exactly eight entries", () => {
188+
expect(Object.keys(BORDER_RADIUS)).toHaveLength(8);
189+
});
190+
});
191+
});

0 commit comments

Comments
 (0)