Skip to content

Commit bc475e1

Browse files
committed
test(theme): 为系统主题检测相关函数添加详细单元测试
- 添加 parseOscRgb 函数各种格式输入的测试用例,包括 rgb、rgba、短十六进制和无效输入 - 测试 themeFromOscColor 函数对于不同背景颜色的亮暗主题判断 - 验证 detectFromColorFgBg 函数对环境变量 COLORFGBG 不同值的正确处理 - 编写 detectSystemTheme 函数的主题检测结果有效性及优先级测试 - 覆盖异常及边界情况,确保函数在无环境变量或无效输入时表现稳定
1 parent 368a00a commit bc475e1

1 file changed

Lines changed: 318 additions & 0 deletions

File tree

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
import { test } from "node:test";
2+
import assert from "node:assert/strict";
3+
import {
4+
parseOscRgb,
5+
themeFromOscColor,
6+
detectFromColorFgBg,
7+
detectSystemTheme,
8+
} from "../ui/theme/detect-system-theme";
9+
10+
// ---------------------------------------------------------------------------
11+
// parseOscRgb
12+
// ---------------------------------------------------------------------------
13+
14+
test("parseOscRgb parses rgb:RR/GG/BB format", () => {
15+
const result = parseOscRgb("rgb:0000/0000/0000");
16+
assert.ok(result);
17+
assert.equal(result.r, 0);
18+
assert.equal(result.g, 0);
19+
assert.equal(result.b, 0);
20+
});
21+
22+
test("parseOscRgb parses rgb:RRRR/GGGG/BBBB format (bright white)", () => {
23+
const result = parseOscRgb("rgb:ffff/ffff/ffff");
24+
assert.ok(result);
25+
assert.equal(result.r, 1);
26+
assert.equal(result.g, 1);
27+
assert.equal(result.b, 1);
28+
});
29+
30+
test("parseOscRgb parses rgb:RR/GG/BB with mid values", () => {
31+
const result = parseOscRgb("rgb:8080/8080/8080");
32+
assert.ok(result);
33+
assert.ok(Math.abs(result.r - 0.50196) < 0.001);
34+
assert.ok(Math.abs(result.g - 0.50196) < 0.001);
35+
assert.ok(Math.abs(result.b - 0.50196) < 0.001);
36+
});
37+
38+
test("parseOscRgb parses rgb with short hex (1 digit per component)", () => {
39+
const result = parseOscRgb("rgb:f/f/f");
40+
assert.ok(result);
41+
assert.equal(result.r, 1);
42+
assert.equal(result.g, 1);
43+
assert.equal(result.b, 1);
44+
});
45+
46+
test("parseOscRgb parses rgb with 2-digit hex", () => {
47+
const result = parseOscRgb("rgb:ff/80/00");
48+
assert.ok(result);
49+
assert.equal(result.r, 1);
50+
assert.ok(Math.abs(result.g - 0.50196) < 0.001);
51+
assert.equal(result.b, 0);
52+
});
53+
54+
test("parseOscRgb parses rgba format", () => {
55+
const result = parseOscRgb("rgba:ffff/ffff/ffff/ffff");
56+
assert.ok(result);
57+
assert.equal(result.r, 1);
58+
assert.equal(result.g, 1);
59+
assert.equal(result.b, 1);
60+
});
61+
62+
test("parseOscRgb parses #RRGGBB format", () => {
63+
const result = parseOscRgb("#000000");
64+
assert.ok(result);
65+
assert.equal(result.r, 0);
66+
assert.equal(result.g, 0);
67+
assert.equal(result.b, 0);
68+
});
69+
70+
test("parseOscRgb parses #RRGGBB format (white)", () => {
71+
const result = parseOscRgb("#ffffff");
72+
assert.ok(result);
73+
assert.equal(result.r, 1);
74+
assert.equal(result.g, 1);
75+
assert.equal(result.b, 1);
76+
});
77+
78+
test("parseOscRgb parses #RRRRGGGGBBBB format", () => {
79+
const result = parseOscRgb("#ffff80800000");
80+
assert.ok(result);
81+
assert.equal(result.r, 1);
82+
assert.ok(Math.abs(result.g - 0.50196) < 0.001);
83+
assert.equal(result.b, 0);
84+
});
85+
86+
test("parseOscRgb returns undefined for invalid format", () => {
87+
assert.equal(parseOscRgb("invalid"), undefined);
88+
assert.equal(parseOscRgb(""), undefined);
89+
assert.equal(parseOscRgb("rgb:zz/zz/zz"), undefined);
90+
assert.equal(parseOscRgb("#gggggg"), undefined);
91+
});
92+
93+
test("parseOscRgb returns undefined for #RRGGBB with non-multiple-of-3 length", () => {
94+
assert.equal(parseOscRgb("#ffff"), undefined);
95+
assert.equal(parseOscRgb("#fffff"), undefined);
96+
});
97+
98+
test("parseOscRgb is case-insensitive", () => {
99+
const lower = parseOscRgb("rgb:ffff/0000/8080");
100+
const upper = parseOscRgb("RGB:FFFF/0000/8080");
101+
assert.ok(lower);
102+
assert.ok(upper);
103+
assert.equal(lower.r, upper.r);
104+
assert.equal(lower.g, upper.g);
105+
assert.equal(lower.b, upper.b);
106+
});
107+
108+
// ---------------------------------------------------------------------------
109+
// themeFromOscColor
110+
// ---------------------------------------------------------------------------
111+
112+
test("themeFromOscColor returns 'light' for white background", () => {
113+
assert.equal(themeFromOscColor("rgb:ffff/ffff/ffff"), "light");
114+
assert.equal(themeFromOscColor("#ffffff"), "light");
115+
});
116+
117+
test("themeFromOscColor returns 'dark' for black background", () => {
118+
assert.equal(themeFromOscColor("rgb:0000/0000/0000"), "dark");
119+
assert.equal(themeFromOscColor("#000000"), "dark");
120+
});
121+
122+
test("themeFromOscColor returns 'light' for bright background", () => {
123+
// Luminance of #c0c0c0 (silver) ≈ 0.53 > 0.5
124+
assert.equal(themeFromOscColor("#c0c0c0"), "light");
125+
});
126+
127+
test("themeFromOscColor returns 'dark' for dim background", () => {
128+
// Luminance of #404040 ≈ 0.04 < 0.5
129+
assert.equal(themeFromOscColor("#404040"), "dark");
130+
});
131+
132+
test("themeFromOscColor uses ITU-R BT.709 luminance weights", () => {
133+
// Pure green has highest luminance weight (0.7152)
134+
// rgb:0000/8080/0000 → luminance ≈ 0.7152 * 0.5 ≈ 0.358 → dark
135+
assert.equal(themeFromOscColor("rgb:0000/8080/0000"), "dark");
136+
137+
// Pure green bright → luminance > 0.5 → light
138+
assert.equal(themeFromOscColor("rgb:0000/ffff/0000"), "light");
139+
140+
// Pure blue has lowest weight (0.0722)
141+
// rgb:0000/0000/ffff → luminance ≈ 0.0722 → dark
142+
assert.equal(themeFromOscColor("rgb:0000/0000/ffff"), "dark");
143+
});
144+
145+
test("themeFromOscColor returns undefined for invalid input", () => {
146+
assert.equal(themeFromOscColor("invalid"), undefined);
147+
assert.equal(themeFromOscColor(""), undefined);
148+
});
149+
150+
// ---------------------------------------------------------------------------
151+
// detectFromColorFgBg
152+
// ---------------------------------------------------------------------------
153+
154+
test("detectFromColorFgBg returns 'dark' for dark background indices", () => {
155+
const original = process.env["COLORFGBG"];
156+
try {
157+
// Index 0 = black
158+
process.env["COLORFGBG"] = "15;0";
159+
assert.equal(detectFromColorFgBg(), "dark");
160+
161+
// Index 1 = red
162+
process.env["COLORFGBG"] = "15;1";
163+
assert.equal(detectFromColorFgBg(), "dark");
164+
165+
// Index 6 = cyan
166+
process.env["COLORFGBG"] = "15;6";
167+
assert.equal(detectFromColorFgBg(), "dark");
168+
169+
// Index 8 = bright black (dark gray)
170+
process.env["COLORFGBG"] = "15;8";
171+
assert.equal(detectFromColorFgBg(), "dark");
172+
} finally {
173+
if (original === undefined) {
174+
delete process.env["COLORFGBG"];
175+
} else {
176+
process.env["COLORFGBG"] = original;
177+
}
178+
}
179+
});
180+
181+
test("detectFromColorFgBg returns 'light' for light background indices", () => {
182+
const original = process.env["COLORFGBG"];
183+
try {
184+
// Index 7 = light gray
185+
process.env["COLORFGBG"] = "0;7";
186+
assert.equal(detectFromColorFgBg(), "light");
187+
188+
// Index 9 = bright red
189+
process.env["COLORFGBG"] = "0;9";
190+
assert.equal(detectFromColorFgBg(), "light");
191+
192+
// Index 15 = bright white
193+
process.env["COLORFGBG"] = "0;15";
194+
assert.equal(detectFromColorFgBg(), "light");
195+
} finally {
196+
if (original === undefined) {
197+
delete process.env["COLORFGBG"];
198+
} else {
199+
process.env["COLORFGBG"] = original;
200+
}
201+
}
202+
});
203+
204+
test("detectFromColorFgBg handles foreground;background format", () => {
205+
const original = process.env["COLORFGBG"];
206+
try {
207+
// Only the last segment (background) matters
208+
process.env["COLORFGBG"] = "0;15";
209+
assert.equal(detectFromColorFgBg(), "light");
210+
211+
process.env["COLORFGBG"] = "15;0";
212+
assert.equal(detectFromColorFgBg(), "dark");
213+
} finally {
214+
if (original === undefined) {
215+
delete process.env["COLORFGBG"];
216+
} else {
217+
process.env["COLORFGBG"] = original;
218+
}
219+
}
220+
});
221+
222+
test("detectFromColorFgBg handles single value (background only)", () => {
223+
const original = process.env["COLORFGBG"];
224+
try {
225+
process.env["COLORFGBG"] = "0";
226+
assert.equal(detectFromColorFgBg(), "dark");
227+
228+
process.env["COLORFGBG"] = "15";
229+
assert.equal(detectFromColorFgBg(), "light");
230+
} finally {
231+
if (original === undefined) {
232+
delete process.env["COLORFGBG"];
233+
} else {
234+
process.env["COLORFGBG"] = original;
235+
}
236+
}
237+
});
238+
239+
test("detectFromColorFgBg returns undefined when COLORFGBG is not set", () => {
240+
const original = process.env["COLORFGBG"];
241+
try {
242+
delete process.env["COLORFGBG"];
243+
assert.equal(detectFromColorFgBg(), undefined);
244+
} finally {
245+
if (original !== undefined) {
246+
process.env["COLORFGBG"] = original;
247+
}
248+
}
249+
});
250+
251+
test("detectFromColorFgBg returns undefined for non-numeric value", () => {
252+
const original = process.env["COLORFGBG"];
253+
try {
254+
process.env["COLORFGBG"] = "abc";
255+
assert.equal(detectFromColorFgBg(), undefined);
256+
} finally {
257+
if (original === undefined) {
258+
delete process.env["COLORFGBG"];
259+
} else {
260+
process.env["COLORFGBG"] = original;
261+
}
262+
}
263+
});
264+
265+
test("detectFromColorFgBg returns undefined for empty string", () => {
266+
const original = process.env["COLORFGBG"];
267+
try {
268+
process.env["COLORFGBG"] = "";
269+
assert.equal(detectFromColorFgBg(), undefined);
270+
} finally {
271+
if (original === undefined) {
272+
delete process.env["COLORFGBG"];
273+
} else {
274+
process.env["COLORFGBG"] = original;
275+
}
276+
}
277+
});
278+
279+
// ---------------------------------------------------------------------------
280+
// detectSystemTheme (sync entry point)
281+
// ---------------------------------------------------------------------------
282+
283+
test("detectSystemTheme returns a valid theme", () => {
284+
const result = detectSystemTheme();
285+
assert.ok(result === "light" || result === "dark");
286+
});
287+
288+
test("detectSystemTheme prefers COLORFGBG over other sources", () => {
289+
const original = process.env["COLORFGBG"];
290+
try {
291+
process.env["COLORFGBG"] = "0;15"; // light background
292+
assert.equal(detectSystemTheme(), "light");
293+
294+
process.env["COLORFGBG"] = "15;0"; // dark background
295+
assert.equal(detectSystemTheme(), "dark");
296+
} finally {
297+
if (original === undefined) {
298+
delete process.env["COLORFGBG"];
299+
} else {
300+
process.env["COLORFGBG"] = original;
301+
}
302+
}
303+
});
304+
305+
test("detectSystemTheme falls back to 'dark' when no sources available", () => {
306+
const original = process.env["COLORFGBG"];
307+
try {
308+
delete process.env["COLORFGBG"];
309+
// On non-macOS, detectMacOSTheme returns undefined → falls back to "dark"
310+
// On macOS, it depends on system settings
311+
const result = detectSystemTheme();
312+
assert.ok(result === "light" || result === "dark");
313+
} finally {
314+
if (original !== undefined) {
315+
process.env["COLORFGBG"] = original;
316+
}
317+
}
318+
});

0 commit comments

Comments
 (0)