Skip to content

Commit 0b094c8

Browse files
add unit tests for HSB color space
1 parent 451d4b9 commit 0b094c8

1 file changed

Lines changed: 248 additions & 0 deletions

File tree

test/unit/color/hsb.js

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/**
2+
* Tests for the HSB (Hue, Saturation, Brightness) color space.
3+
*
4+
* The HSB color space is defined in src/color/color_spaces/hsb.js and provides
5+
* conversions between RGB (base) and HSB color representations.
6+
*
7+
* HSB ranges:
8+
* - Hue: 0-360 (degrees on color wheel)
9+
* - Saturation: 0-100 (percentage)
10+
* - Brightness: 0-100 (percentage)
11+
*
12+
* RGB ranges (in colorjs.io):
13+
* - R, G, B: 0-1 (normalized)
14+
*/
15+
16+
import HSBSpace from '../../../src/color/color_spaces/hsb.js';
17+
18+
// Helper function to compare arrays with tolerance
19+
const arrayApproximately = (arr1, arr2, delta = 0.01) => {
20+
if (arr1.length !== arr2.length) return false;
21+
for (let i = 0; i < arr1.length; i++) {
22+
if (Math.abs(arr1[i] - arr2[i]) > delta) return false;
23+
}
24+
return true;
25+
};
26+
27+
suite('color/HSB Color Space', function() {
28+
const accuracy = 0.01;
29+
30+
suite('HSB to RGB conversion (toBase)', function() {
31+
suite('Primary Colors', function() {
32+
test('pure red: HSB(0, 100, 100) → RGB(1, 0, 0)', function() {
33+
const result = HSBSpace.toBase([0, 100, 100]);
34+
assert.approximately(result[0], 1, accuracy);
35+
assert.approximately(result[1], 0, accuracy);
36+
assert.approximately(result[2], 0, accuracy);
37+
});
38+
39+
test('pure green: HSB(120, 100, 100) → RGB(0, 1, 0)', function() {
40+
const result = HSBSpace.toBase([120, 100, 100]);
41+
assert.approximately(result[0], 0, accuracy);
42+
assert.approximately(result[1], 1, accuracy);
43+
assert.approximately(result[2], 0, accuracy);
44+
});
45+
46+
test('pure blue: HSB(240, 100, 100) → RGB(0, 0, 1)', function() {
47+
const result = HSBSpace.toBase([240, 100, 100]);
48+
assert.approximately(result[0], 0, accuracy);
49+
assert.approximately(result[1], 0, accuracy);
50+
assert.approximately(result[2], 1, accuracy);
51+
});
52+
});
53+
54+
suite('Secondary Colors', function() {
55+
test('yellow: HSB(60, 100, 100) → RGB(1, 1, 0)', function() {
56+
const result = HSBSpace.toBase([60, 100, 100]);
57+
assert.approximately(result[0], 1, accuracy);
58+
assert.approximately(result[1], 1, accuracy);
59+
assert.approximately(result[2], 0, accuracy);
60+
});
61+
62+
test('cyan: HSB(180, 100, 100) → RGB(0, 1, 1)', function() {
63+
const result = HSBSpace.toBase([180, 100, 100]);
64+
assert.approximately(result[0], 0, accuracy);
65+
assert.approximately(result[1], 1, accuracy);
66+
assert.approximately(result[2], 1, accuracy);
67+
});
68+
69+
test('magenta: HSB(300, 100, 100) → RGB(1, 0, 1)', function() {
70+
const result = HSBSpace.toBase([300, 100, 100]);
71+
assert.approximately(result[0], 1, accuracy);
72+
assert.approximately(result[1], 0, accuracy);
73+
assert.approximately(result[2], 1, accuracy);
74+
});
75+
});
76+
77+
suite('Grayscale (Saturation = 0)', function() {
78+
test('white: HSB(0, 0, 100) → RGB(1, 1, 1)', function() {
79+
const result = HSBSpace.toBase([0, 0, 100]);
80+
assert.approximately(result[0], 1, accuracy);
81+
assert.approximately(result[1], 1, accuracy);
82+
assert.approximately(result[2], 1, accuracy);
83+
});
84+
85+
test('black: HSB(0, 0, 0) → RGB(0, 0, 0)', function() {
86+
const result = HSBSpace.toBase([0, 0, 0]);
87+
assert.approximately(result[0], 0, accuracy);
88+
assert.approximately(result[1], 0, accuracy);
89+
assert.approximately(result[2], 0, accuracy);
90+
});
91+
92+
test('mid gray: HSB(0, 0, 50) → RGB(0.5, 0.5, 0.5)', function() {
93+
const result = HSBSpace.toBase([0, 0, 50]);
94+
assert.approximately(result[0], 0.5, accuracy);
95+
assert.approximately(result[1], 0.5, accuracy);
96+
assert.approximately(result[2], 0.5, accuracy);
97+
});
98+
99+
test('grayscale ignores hue: HSB(180, 0, 50) → RGB(0.5, 0.5, 0.5)', function() {
100+
const result = HSBSpace.toBase([180, 0, 50]);
101+
assert.approximately(result[0], 0.5, accuracy);
102+
assert.approximately(result[1], 0.5, accuracy);
103+
assert.approximately(result[2], 0.5, accuracy);
104+
});
105+
});
106+
107+
suite('Partial Saturation', function() {
108+
test('50% saturation red: HSB(0, 50, 100) → RGB(1, 0.5, 0.5)', function() {
109+
const result = HSBSpace.toBase([0, 50, 100]);
110+
assert.approximately(result[0], 1, accuracy);
111+
assert.approximately(result[1], 0.5, accuracy);
112+
assert.approximately(result[2], 0.5, accuracy);
113+
});
114+
115+
test('50% brightness red: HSB(0, 100, 50) → RGB(0.5, 0, 0)', function() {
116+
const result = HSBSpace.toBase([0, 100, 50]);
117+
assert.approximately(result[0], 0.5, accuracy);
118+
assert.approximately(result[1], 0, accuracy);
119+
assert.approximately(result[2], 0, accuracy);
120+
});
121+
});
122+
123+
suite('Edge Cases', function() {
124+
test('hue at 360 should be same as 0 (red)', function() {
125+
const result = HSBSpace.toBase([360, 100, 100]);
126+
assert.approximately(result[0], 1, accuracy);
127+
assert.approximately(result[1], 0, accuracy);
128+
assert.approximately(result[2], 0, accuracy);
129+
});
130+
131+
test('brightness 0 produces black regardless of hue/saturation', function() {
132+
const result = HSBSpace.toBase([120, 100, 0]);
133+
assert.approximately(result[0], 0, accuracy);
134+
assert.approximately(result[1], 0, accuracy);
135+
assert.approximately(result[2], 0, accuracy);
136+
});
137+
});
138+
});
139+
140+
suite('RGB to HSB conversion (fromBase)', function() {
141+
suite('Primary Colors', function() {
142+
test('pure red: RGB(1, 0, 0) → HSB(0, 100, 100)', function() {
143+
const result = HSBSpace.fromBase([1, 0, 0]);
144+
assert.approximately(result[0], 0, accuracy);
145+
assert.approximately(result[1], 100, accuracy);
146+
assert.approximately(result[2], 100, accuracy);
147+
});
148+
149+
test('pure green: RGB(0, 1, 0) → HSB(120, 100, 100)', function() {
150+
const result = HSBSpace.fromBase([0, 1, 0]);
151+
assert.approximately(result[0], 120, accuracy);
152+
assert.approximately(result[1], 100, accuracy);
153+
assert.approximately(result[2], 100, accuracy);
154+
});
155+
156+
test('pure blue: RGB(0, 0, 1) → HSB(240, 100, 100)', function() {
157+
const result = HSBSpace.fromBase([0, 0, 1]);
158+
assert.approximately(result[0], 240, accuracy);
159+
assert.approximately(result[1], 100, accuracy);
160+
assert.approximately(result[2], 100, accuracy);
161+
});
162+
});
163+
164+
suite('Secondary Colors', function() {
165+
test('yellow: RGB(1, 1, 0) → HSB(60, 100, 100)', function() {
166+
const result = HSBSpace.fromBase([1, 1, 0]);
167+
assert.approximately(result[0], 60, accuracy);
168+
assert.approximately(result[1], 100, accuracy);
169+
assert.approximately(result[2], 100, accuracy);
170+
});
171+
172+
test('cyan: RGB(0, 1, 1) → HSB(180, 100, 100)', function() {
173+
const result = HSBSpace.fromBase([0, 1, 1]);
174+
assert.approximately(result[0], 180, accuracy);
175+
assert.approximately(result[1], 100, accuracy);
176+
assert.approximately(result[2], 100, accuracy);
177+
});
178+
179+
test('magenta: RGB(1, 0, 1) → HSB(300, 100, 100)', function() {
180+
const result = HSBSpace.fromBase([1, 0, 1]);
181+
assert.approximately(result[0], 300, accuracy);
182+
assert.approximately(result[1], 100, accuracy);
183+
assert.approximately(result[2], 100, accuracy);
184+
});
185+
});
186+
187+
suite('Grayscale', function() {
188+
test('white: RGB(1, 1, 1) → HSB(0, 0, 100)', function() {
189+
const result = HSBSpace.fromBase([1, 1, 1]);
190+
assert.approximately(result[0], 0, accuracy);
191+
assert.approximately(result[1], 0, accuracy);
192+
assert.approximately(result[2], 100, accuracy);
193+
});
194+
195+
test('black: RGB(0, 0, 0) → HSB(0, 0, 0)', function() {
196+
const result = HSBSpace.fromBase([0, 0, 0]);
197+
assert.approximately(result[0], 0, accuracy);
198+
assert.approximately(result[1], 0, accuracy);
199+
assert.approximately(result[2], 0, accuracy);
200+
});
201+
202+
test('mid gray: RGB(0.5, 0.5, 0.5) → HSB(0, 0, 50)', function() {
203+
const result = HSBSpace.fromBase([0.5, 0.5, 0.5]);
204+
assert.approximately(result[0], 0, accuracy);
205+
assert.approximately(result[1], 0, accuracy);
206+
assert.approximately(result[2], 50, accuracy);
207+
});
208+
});
209+
210+
suite('Partial Values', function() {
211+
test('dark red: RGB(0.5, 0, 0) → HSB(0, 100, 50)', function() {
212+
const result = HSBSpace.fromBase([0.5, 0, 0]);
213+
assert.approximately(result[0], 0, accuracy);
214+
assert.approximately(result[1], 100, accuracy);
215+
assert.approximately(result[2], 50, accuracy);
216+
});
217+
218+
test('light red (pink): RGB(1, 0.5, 0.5) → HSB(0, 50, 100)', function() {
219+
const result = HSBSpace.fromBase([1, 0.5, 0.5]);
220+
assert.approximately(result[0], 0, accuracy);
221+
assert.approximately(result[1], 50, accuracy);
222+
assert.approximately(result[2], 100, accuracy);
223+
});
224+
});
225+
});
226+
227+
suite('Round-trip conversion', function() {
228+
test('HSB → RGB → HSB should preserve values', function() {
229+
const originalHSB = [210, 75, 80];
230+
const rgb = HSBSpace.toBase(originalHSB);
231+
const resultHSB = HSBSpace.fromBase(rgb);
232+
233+
assert.approximately(resultHSB[0], originalHSB[0], 0.1);
234+
assert.approximately(resultHSB[1], originalHSB[1], 0.1);
235+
assert.approximately(resultHSB[2], originalHSB[2], 0.1);
236+
});
237+
238+
test('RGB → HSB → RGB should preserve values', function() {
239+
const originalRGB = [0.7, 0.3, 0.5];
240+
const hsb = HSBSpace.fromBase(originalRGB);
241+
const resultRGB = HSBSpace.toBase(hsb);
242+
243+
assert.approximately(resultRGB[0], originalRGB[0], 0.01);
244+
assert.approximately(resultRGB[1], originalRGB[1], 0.01);
245+
assert.approximately(resultRGB[2], originalRGB[2], 0.01);
246+
});
247+
});
248+
});

0 commit comments

Comments
 (0)