Skip to content

Commit 2a02c2d

Browse files
committed
enhance: Color Utils implementation and tests
- Make sure to use if instead of switch statements to prevent fall through issues. - Add tests for Color Utils - Add invariant error handling
1 parent 965e790 commit 2a02c2d

File tree

3 files changed

+134
-24
lines changed

3 files changed

+134
-24
lines changed

.lintstagedrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"{src/**/*,test/**/*}": ["npx prettier --write .", "npx eslint src/ --fix"]
2+
"src/**/*": ["npx prettier --write .", "npx eslint src/ --fix"]
33
}

src/tests/ColorUtils.test.ts

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { describe, expect, it } from 'vitest';
2+
import ColorUtils from '../utils/ColorUtils';
3+
4+
describe('ColorUtils', () => {
5+
describe('convertHexColorToBytes', () => {
6+
it('should throw if hex color has wrong value', () => {
7+
// @ts-expect-error testing invalid param type
8+
expect(() => ColorUtils.convertHexColorToBytes()).toThrowError(
9+
'Expected hexColor param to be a string instead got undefined',
10+
);
11+
expect(() => ColorUtils.convertHexColorToBytes('A')).toThrowError(
12+
'Expected hexColor to be of length 3, 4, 6 or 8 with 0-9 A-F characters, instead got A with length 1',
13+
);
14+
expect(() => ColorUtils.convertHexColorToBytes('red')).toThrowError(
15+
'Expected hexColor to be of length 3, 4, 6 or 8 with 0-9 A-F characters, instead got red with length 3',
16+
);
17+
expect(() => ColorUtils.convertHexColorToBytes('##FFF')).toThrowError(
18+
'Expected hexColor to be of length 3, 4, 6 or 8 with 0-9 A-F characters, instead got #FFF with length 4',
19+
);
20+
});
21+
22+
it('should return bytes for hex color with alpha', () => {
23+
expect(ColorUtils.convertHexColorToBytes('#FFFFFFFF')).toEqual([
24+
255, 255, 255, 255,
25+
]);
26+
expect(ColorUtils.convertHexColorToBytes('#00000000')).toEqual([
27+
0, 0, 0, 0,
28+
]);
29+
expect(ColorUtils.convertHexColorToBytes('#AABBCCDD')).toEqual([
30+
170, 187, 204, 221,
31+
]);
32+
});
33+
34+
it('should return bytes with default alpha for hex color without alpha', () => {
35+
expect(ColorUtils.convertHexColorToBytes('#FFFFFF')).toEqual([
36+
255, 255, 255, 255,
37+
]);
38+
expect(ColorUtils.convertHexColorToBytes('#000000')).toEqual([
39+
0, 0, 0, 255,
40+
]);
41+
expect(ColorUtils.convertHexColorToBytes('#AABBCC')).toEqual([
42+
170, 187, 204, 255,
43+
]);
44+
});
45+
46+
it('should return bytes for short hex color with alpha', () => {
47+
expect(ColorUtils.convertHexColorToBytes('#FFFF')).toEqual([
48+
255, 255, 255, 255,
49+
]);
50+
expect(ColorUtils.convertHexColorToBytes('#0000')).toEqual([0, 0, 0, 0]);
51+
expect(ColorUtils.convertHexColorToBytes('#ABCD')).toEqual([
52+
170, 187, 204, 221,
53+
]);
54+
});
55+
56+
it('should return bytes with default alpha for short hex color without alpha', () => {
57+
expect(ColorUtils.convertHexColorToBytes('#FFF')).toEqual([
58+
255, 255, 255, 255,
59+
]);
60+
expect(ColorUtils.convertHexColorToBytes('#000')).toEqual([0, 0, 0, 255]);
61+
expect(ColorUtils.convertHexColorToBytes('#ABC')).toEqual([
62+
170, 187, 204, 255,
63+
]);
64+
});
65+
66+
it('should allow to use hex color without #', () => {
67+
expect(ColorUtils.convertHexColorToBytes('FFF')).toEqual([
68+
255, 255, 255, 255,
69+
]);
70+
expect(ColorUtils.convertHexColorToBytes('000')).toEqual([0, 0, 0, 255]);
71+
expect(ColorUtils.convertHexColorToBytes('ABC')).toEqual([
72+
170, 187, 204, 255,
73+
]);
74+
75+
expect(ColorUtils.convertHexColorToBytes('FFFA')).toEqual([
76+
255, 255, 255, 170,
77+
]);
78+
expect(ColorUtils.convertHexColorToBytes('000A')).toEqual([0, 0, 0, 170]);
79+
expect(ColorUtils.convertHexColorToBytes('ABCA')).toEqual([
80+
170, 187, 204, 170,
81+
]);
82+
83+
expect(ColorUtils.convertHexColorToBytes('FFFFFF')).toEqual([
84+
255, 255, 255, 255,
85+
]);
86+
expect(ColorUtils.convertHexColorToBytes('0A0A0A')).toEqual([
87+
10, 10, 10, 255,
88+
]);
89+
expect(ColorUtils.convertHexColorToBytes('0A0B0C')).toEqual([
90+
10, 11, 12, 255,
91+
]);
92+
93+
expect(ColorUtils.convertHexColorToBytes('FFFFFF0F')).toEqual([
94+
255, 255, 255, 15,
95+
]);
96+
expect(ColorUtils.convertHexColorToBytes('0000000A')).toEqual([
97+
0, 0, 0, 10,
98+
]);
99+
expect(ColorUtils.convertHexColorToBytes('42424242')).toEqual([
100+
66, 66, 66, 66,
101+
]);
102+
});
103+
});
104+
});

src/utils/ColorUtils.ts

+29-23
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
1+
import { invariant } from './invariant';
2+
13
export default class ColorUtils {
24
static convertHexColorToBytes(hexColor: string): number[] {
3-
const bytes: number[] = [];
5+
invariant(
6+
typeof hexColor === 'string',
7+
`Expected hexColor param to be a string instead got ${typeof hexColor}`,
8+
);
9+
410
let hex = hexColor.replace('#', '');
511

6-
if (!/^[0-9A-F]{3,8}$/i.test(hex)) {
7-
return [0, 0, 0, 0];
12+
const isHexColor =
13+
/^([0-9A-F]{3}|[0-9A-F]{4}|[0-9A-F]{6}|[0-9A-F]{8})$/i.test(hex);
14+
15+
invariant(
16+
isHexColor,
17+
`Expected hexColor to be of length 3, 4, 6 or 8 with 0-9 A-F characters, instead got ${hex} with length ${hex.length}`,
18+
);
19+
20+
const bytes: number[] = [];
21+
22+
if (hex.length === 3) {
23+
hex += 'F';
24+
} else if (hex.length === 6) {
25+
hex += 'FF';
826
}
927

10-
switch (hex.length) {
11-
// @ts-ignore
12-
case 3:
13-
hex += 'F';
14-
// Fall through
15-
case 4:
16-
bytes.push(...hex.split('').map(h => parseInt(h.repeat(2), 16)));
17-
break;
18-
// @ts-ignore
19-
case 6:
20-
hex += 'FF';
21-
// Fall through
22-
case 8:
23-
bytes.push(parseInt(hex.substr(0, 2), 16));
24-
bytes.push(parseInt(hex.substr(2, 2), 16));
25-
bytes.push(parseInt(hex.substr(4, 2), 16));
26-
bytes.push(parseInt(hex.substr(6, 2), 16));
27-
break;
28-
default:
29-
bytes.push(0, 0, 0, 0);
28+
if (hex.length === 4) {
29+
bytes.push(...hex.split('').map((h) => parseInt(h.repeat(2), 16)));
30+
} else if (hex.length === 8) {
31+
bytes.push(parseInt(hex.substring(0, 2), 16));
32+
bytes.push(parseInt(hex.substring(2, 4), 16));
33+
bytes.push(parseInt(hex.substring(4, 6), 16));
34+
bytes.push(parseInt(hex.substring(6, 8), 16));
3035
}
36+
3137
return bytes;
3238
}
3339
}

0 commit comments

Comments
 (0)