Skip to content

Commit d54ac07

Browse files
43081jdanielroe
andauthored
test: add color util tests (#889)
Co-authored-by: Daniel Roe <daniel@roe.dev>
1 parent 27eba70 commit d54ac07

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

test/unit/app/utils/colors.spec.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { lightenHex, oklchToHex, transparentizeOklch } from '../../../../app/utils/colors'
3+
4+
describe('lightenHex', () => {
5+
it('lightens a color by the default factor (0.5)', () => {
6+
expect(lightenHex('#000000')).toBe('#808080')
7+
})
8+
9+
it('lightens a color by a custom factor', () => {
10+
expect(lightenHex('#000000', 0.25)).toBe('#404040')
11+
expect(lightenHex('#000000', 0.75)).toBe('#bfbfbf')
12+
expect(lightenHex('#000000', 1)).toBe('#ffffff')
13+
})
14+
15+
it('returns white when factor is 1', () => {
16+
expect(lightenHex('#ff0000', 1)).toBe('#ffffff')
17+
expect(lightenHex('#123456', 1)).toBe('#ffffff')
18+
})
19+
20+
it('returns the original color when factor is 0', () => {
21+
expect(lightenHex('#ff0000', 0)).toBe('#ff0000')
22+
expect(lightenHex('#123456', 0)).toBe('#123456')
23+
})
24+
25+
it('lightens each channel independently', () => {
26+
// #ff0000 at 0.5 -> r: 255+(0)*0.5=255, g: 0+(255)*0.5=128, b: 0+(255)*0.5=128
27+
expect(lightenHex('#ff0000', 0.5)).toBe('#ff8080')
28+
})
29+
30+
it('returns the input unchanged for an invalid hex', () => {
31+
expect(lightenHex('not-a-color')).toBe('not-a-color')
32+
})
33+
34+
it('handles hex without leading #', () => {
35+
expect(lightenHex('000000', 0.5)).toBe('#808080')
36+
})
37+
})
38+
39+
describe('oklchToHex', () => {
40+
it('converts pure black', () => {
41+
expect(oklchToHex('oklch(0 0 0)')).toMatchInlineSnapshot(`"#000000"`)
42+
})
43+
44+
it('converts pure white', () => {
45+
expect(oklchToHex('oklch(1 0 0)')).toMatchInlineSnapshot(`"#ffffff"`)
46+
})
47+
48+
it('converts a saturated red', () => {
49+
expect(oklchToHex('oklch(0.628 0.258 29.23)')).toMatchInlineSnapshot(`"#ff0000"`)
50+
})
51+
52+
it('converts a saturated green', () => {
53+
expect(oklchToHex('oklch(0.866 0.295 142.5)')).toMatchInlineSnapshot(`"#00ff00"`)
54+
})
55+
56+
it('converts a saturated blue', () => {
57+
expect(oklchToHex('oklch(0.452 0.313 264.05)')).toMatchInlineSnapshot(`"#0000ff"`)
58+
})
59+
60+
it('converts the neutral fallback color', () => {
61+
expect(oklchToHex('oklch(0.633 0 0)')).toMatchInlineSnapshot(`"#8a8a8a"`)
62+
})
63+
64+
it('returns null for null input', () => {
65+
expect(oklchToHex(null)).toBeNull()
66+
})
67+
68+
it('returns undefined for undefined input', () => {
69+
expect(oklchToHex(undefined)).toBeUndefined()
70+
})
71+
72+
it('throws on invalid format', () => {
73+
expect(() => oklchToHex('not-a-color')).toThrow('Invalid OKLCH color format')
74+
})
75+
})
76+
77+
describe('transparentizeOklch', () => {
78+
it('makes a color 50% transparent', () => {
79+
expect(transparentizeOklch('oklch(0.5 0.2 120)', 0.5)).toMatchInlineSnapshot(
80+
`"oklch(0.5 0.2 120 / 0.5)"`,
81+
)
82+
})
83+
84+
it('makes a color fully transparent (factor 1)', () => {
85+
expect(transparentizeOklch('oklch(0.5 0.2 120)', 1)).toMatchInlineSnapshot(
86+
`"oklch(0.5 0.2 120 / 0)"`,
87+
)
88+
})
89+
90+
it('keeps a color fully opaque (factor 0)', () => {
91+
expect(transparentizeOklch('oklch(0.5 0.2 120)', 0)).toMatchInlineSnapshot(
92+
`"oklch(0.5 0.2 120 / 1)"`,
93+
)
94+
})
95+
96+
it('reduces existing alpha', () => {
97+
expect(transparentizeOklch('oklch(0.5 0.2 120 / 0.8)', 0.5)).toMatchInlineSnapshot(
98+
`"oklch(0.5 0.2 120 / 0.4)"`,
99+
)
100+
})
101+
102+
it('handles percentage lightness and alpha', () => {
103+
expect(transparentizeOklch('oklch(50% 0.2 120 / 80%)', 0.5)).toMatchInlineSnapshot(
104+
`"oklch(0.5 0.2 120 / 0.4)"`,
105+
)
106+
})
107+
108+
it('clamps factor to [0, 1]', () => {
109+
expect(transparentizeOklch('oklch(0.5 0.2 120)', 2)).toMatchInlineSnapshot(
110+
`"oklch(0.5 0.2 120 / 0)"`,
111+
)
112+
expect(transparentizeOklch('oklch(0.5 0.2 120)', -1)).toMatchInlineSnapshot(
113+
`"oklch(0.5 0.2 120 / 1)"`,
114+
)
115+
})
116+
117+
it('returns fallback for null input', () => {
118+
expect(transparentizeOklch(null, 0.5)).toBe('oklch(0 0 0 / 0)')
119+
})
120+
121+
it('returns fallback for undefined input', () => {
122+
expect(transparentizeOklch(undefined, 0.5)).toBe('oklch(0 0 0 / 0)')
123+
})
124+
125+
it('returns fallback for empty string', () => {
126+
expect(transparentizeOklch('', 0.5)).toBe('oklch(0 0 0 / 0)')
127+
})
128+
129+
it('returns fallback for invalid format', () => {
130+
expect(transparentizeOklch('not-a-color', 0.5)).toBe('oklch(0 0 0 / 0)')
131+
})
132+
133+
it('uses custom fallback', () => {
134+
expect(transparentizeOklch(null, 0.5, 'oklch(1 0 0 / 0)')).toBe('oklch(1 0 0 / 0)')
135+
})
136+
})

0 commit comments

Comments
 (0)