This project is a palette creator tool that allows users to generate and customize color palettes for their design projects.
at main 326 lines 12 kB view raw
1// oxlint-disable max-lines 2// oxlint-disable no-magic-numbers 3 4import { 5 generateAnalogous, 6 generateComplement, 7 generateHsl, 8 generateMono, 9 generateSaturations, 10 generateTriad, 11 getSaturation, 12 hexToHsl, 13 hslToRgb, 14 isAchromatic, 15 isLowSaturation, 16 rgbToHex, 17 rgbToHsl, 18 toHslString, 19} from './utils'; 20 21// oxlint-disable-next-line max-lines-per-function 22describe('utility functions', () => { 23 describe('toHslString utility', () => { 24 it('formats h,s,l into hsl string', () => { 25 expect(toHslString(180, 50, 50)).toBe('hsl(180, 50%, 50%)'); 26 expect(toHslString(0, 0, 100)).toBe('hsl(0, 0%, 100%)'); 27 expect(toHslString(360, 100, 0)).toBe('hsl(360, 100%, 0%)'); 28 }); 29 }); 30 31 describe('generateHsl utility', () => { 32 it('returns a valid hsl string', () => { 33 expect(generateHsl()).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/); 34 }); 35 }); 36 37 describe('hslToRgb utility', () => { 38 it('converts hsl to rgb', () => { 39 expect(hslToRgb('hsl(0, 0%, 0%)')).toBe('rgb(0, 0, 0)'); 40 expect(hslToRgb('hsl(0, 0%, 100%)')).toBe('rgb(255, 255, 255)'); 41 expect(hslToRgb('hsl(0, 100%, 50%)')).toBe('rgb(255, 0, 0)'); 42 expect(hslToRgb('hsl(120, 100%, 50%)')).toBe('rgb(0, 255, 0)'); 43 expect(hslToRgb('hsl(240, 100%, 50%)')).toBe('rgb(0, 0, 255)'); 44 }); 45 46 it('handles hue > 240 (triggers t > 1 in hue2rgb)', () => { 47 expect(hslToRgb('hsl(300, 100%, 50%)')).toBe('rgb(255, 0, 255)'); 48 }); 49 50 it('returns black for invalid input', () => { 51 expect(hslToRgb('invalid')).toBe('rgb(0, 0, 0)'); 52 }); 53 }); 54 55 describe('rgbToHsl utility', () => { 56 it('converts rgb to hsl', () => { 57 expect(rgbToHsl('rgb(0, 0, 0)')).toBe('hsl(0, 0%, 0%)'); 58 expect(rgbToHsl('rgb(255, 255, 255)')).toBe('hsl(0, 0%, 100%)'); 59 expect(rgbToHsl('rgb(255, 0, 0)')).toBe('hsl(0, 100%, 50%)'); 60 expect(rgbToHsl('rgb(0, 255, 0)')).toBe('hsl(120, 100%, 50%)'); 61 expect(rgbToHsl('rgb(0, 0, 255)')).toBe('hsl(240, 100%, 50%)'); 62 }); 63 64 it('handles high-lightness colors (l > 0.5 saturation branch)', () => { 65 expect(rgbToHsl('rgb(255, 128, 128)')).toBe('hsl(0, 100%, 75%)'); 66 }); 67 68 it('returns black for invalid input', () => { 69 expect(rgbToHsl('invalid')).toBe('hsl(0, 0%, 0%)'); 70 }); 71 }); 72 73 describe('rgbToHex utility', () => { 74 it('converts rgb to hex', () => { 75 expect(rgbToHex('rgb(0, 0, 0)')).toBe('#000000'); 76 expect(rgbToHex('rgb(255, 255, 255)')).toBe('#ffffff'); 77 expect(rgbToHex('rgb(255, 0, 0)')).toBe('#ff0000'); 78 expect(rgbToHex('rgb(0, 255, 0)')).toBe('#00ff00'); 79 expect(rgbToHex('rgb(0, 0, 255)')).toBe('#0000ff'); 80 }); 81 82 it('returns black for invalid input', () => { 83 expect(rgbToHex('invalid')).toBe('#000000'); 84 }); 85 }); 86 87 describe('hexToHsl utility', () => { 88 it('converts hex to hsl', () => { 89 expect(hexToHsl('000000')).toBe('hsl(0, 0%, 0%)'); 90 expect(hexToHsl('ffffff')).toBe('hsl(0, 0%, 100%)'); 91 expect(hexToHsl('ff0000')).toBe('hsl(0, 100%, 50%)'); 92 expect(hexToHsl('00ff00')).toBe('hsl(120, 100%, 50%)'); 93 expect(hexToHsl('0000ff')).toBe('hsl(240, 100%, 50%)'); 94 }); 95 }); 96 97 describe('generateComplement utility', () => { 98 it('returns array of 7 colors', () => { 99 expect(generateComplement('hsl(180, 50%, 50%)')).toHaveLength(7); 100 }); 101 102 it('generates complementary hue (180 degrees)', () => { 103 const result = generateComplement('hsl(0, 50%, 50%)'); 104 105 expect(result[0]).toBe('hsl(180, 50%, 50%)'); 106 }); 107 108 it('returns empty array for invalid input', () => { 109 expect(generateComplement('invalid')).toEqual([]); 110 }); 111 }); 112 113 describe('generateMono utility', () => { 114 it('returns array of 8 colors', () => { 115 expect(generateMono('hsl(180, 50%, 50%)')).toHaveLength(8); 116 }); 117 118 it('varies lightness while keeping hue constant', () => { 119 const result = generateMono('hsl(180, 50%, 50%)'); 120 121 expect(result[0]).toBe('hsl(180, 50%, 8%)'); 122 expect(result[7]).toBe('hsl(180, 50%, 95%)'); 123 }); 124 125 it('returns empty array for invalid input', () => { 126 expect(generateMono('invalid')).toEqual([]); 127 }); 128 }); 129 130 describe('generateTriad utility', () => { 131 it('returns array of 6 colors', () => { 132 expect(generateTriad('hsl(180, 50%, 50%)')).toHaveLength(6); 133 }); 134 135 it('generates triadic hues (120 degrees apart)', () => { 136 const result = generateTriad('hsl(0, 50%, 50%)'); 137 138 expect(result[0]).toBe('hsl(120, 50%, 50%)'); 139 expect(result[1]).toBe('hsl(240, 50%, 50%)'); 140 }); 141 142 it('clamps lightness below 0 when l - 20 < 0', () => { 143 const result = generateTriad('hsl(0, 50%, 10%)'); 144 145 expect(result[2]).toBe('hsl(120, 50%, 0%)'); 146 expect(result[3]).toBe('hsl(240, 50%, 0%)'); 147 }); 148 149 it('clamps lightness above 100 when l + 20 > 100', () => { 150 const result = generateTriad('hsl(0, 50%, 90%)'); 151 152 expect(result[4]).toBe('hsl(120, 50%, 100%)'); 153 expect(result[5]).toBe('hsl(240, 50%, 100%)'); 154 }); 155 156 it('returns empty array for invalid input', () => { 157 expect(generateTriad('invalid')).toEqual([]); 158 }); 159 }); 160 161 describe('generateAnalogous utility', () => { 162 it('returns array of 6 colors', () => { 163 expect(generateAnalogous('hsl(180, 50%, 50%)')).toHaveLength(6); 164 }); 165 166 it('generates analogous hues (30 degree steps)', () => { 167 const result = generateAnalogous('hsl(180, 50%, 50%)'); 168 169 expect(result[0]).toBe('hsl(120, 50%, 50%)'); 170 expect(result[2]).toBe('hsl(210, 50%, 50%)'); 171 }); 172 173 it('returns empty array for invalid input', () => { 174 expect(generateAnalogous('invalid')).toEqual([]); 175 }); 176 }); 177 178 describe('generateSaturations utility', () => { 179 it('returns array of 8 colors', () => { 180 expect(generateSaturations('hsl(180, 50%, 50%)')).toHaveLength(8); 181 }); 182 183 it('varies saturation while keeping hue and lightness constant', () => { 184 const result = generateSaturations('hsl(180, 50%, 50%)'); 185 186 for (const color of result) { 187 expect(color).toMatch(/^hsl\(180, \d+%, 50%\)$/); 188 } 189 }); 190 191 it('returns empty array for invalid input', () => { 192 expect(generateSaturations('invalid')).toEqual([]); 193 }); 194 }); 195 196 describe('isLowSaturation utility', () => { 197 it('returns true for s=0', () => { 198 expect(isLowSaturation(0)).toBeTruthy(); 199 }); 200 201 it('returns true at the threshold s=10', () => { 202 expect(isLowSaturation(10)).toBeTruthy(); 203 }); 204 205 it('returns false just above the threshold s=11', () => { 206 expect(isLowSaturation(11)).toBeFalsy(); 207 }); 208 209 it('returns false for s=50', () => { 210 expect(isLowSaturation(50)).toBeFalsy(); 211 }); 212 }); 213 214 describe('isAchromatic utility', () => { 215 it('returns true for pure black (s=0, l=0)', () => { 216 expect(isAchromatic(0, 0)).toBeTruthy(); 217 }); 218 219 it('returns true for pure white (s=0, l=100)', () => { 220 expect(isAchromatic(0, 100)).toBeTruthy(); 221 }); 222 223 it('returns true at the dark threshold boundary (s=5, l=8)', () => { 224 expect(isAchromatic(5, 8)).toBeTruthy(); 225 }); 226 227 it('returns true at the light threshold boundary (s=5, l=95)', () => { 228 expect(isAchromatic(5, 95)).toBeTruthy(); 229 }); 230 231 it('returns false when lightness is mid-range even at s=0', () => { 232 expect(isAchromatic(0, 50)).toBeFalsy(); 233 }); 234 235 it('returns false when saturation is above threshold', () => { 236 expect(isAchromatic(15, 0)).toBeFalsy(); 237 }); 238 }); 239 240 // oxlint-disable-next-line max-lines-per-function 241 describe('achromatic variation generation', () => { 242 const BLACK = 'hsl(0, 0%, 0%)'; 243 const WHITE = 'hsl(0, 0%, 100%)'; 244 const HSL_PATTERN = /^hsl\(\d+, \d+%, \d+%\)$/; 245 246 it('generateComplement returns 7 chromatic colors for black', () => { 247 const result = generateComplement(BLACK); 248 expect(result).toHaveLength(7); 249 for (const color of result) { 250 expect(color).toMatch(HSL_PATTERN); 251 expect(getSaturation(color)).toBeGreaterThan(0); 252 } 253 }); 254 255 it('generateComplement returns 7 chromatic colors for white', () => { 256 const result = generateComplement(WHITE); 257 expect(result).toHaveLength(7); 258 for (const color of result) { 259 expect(getSaturation(color)).toBeGreaterThan(0); 260 } 261 }); 262 263 it('generateComplement produces different results on repeated calls', () => { 264 const results = new Set( 265 Array.from({ length: 10 }, () => generateComplement(BLACK)[0]), 266 ); 267 expect(results.size).toBeGreaterThan(1); 268 }); 269 270 it('generateTriad returns 6 chromatic colors for black', () => { 271 const result = generateTriad(BLACK); 272 expect(result).toHaveLength(6); 273 for (const color of result) { 274 expect(color).toMatch(HSL_PATTERN); 275 expect(getSaturation(color)).toBeGreaterThan(0); 276 } 277 }); 278 279 it('generateTriad produces different results on repeated calls', () => { 280 const results = new Set( 281 Array.from({ length: 10 }, () => generateTriad(BLACK)[0]), 282 ); 283 expect(results.size).toBeGreaterThan(1); 284 }); 285 286 it('generateAnalogous returns 6 chromatic colors for black', () => { 287 const result = generateAnalogous(BLACK); 288 expect(result).toHaveLength(6); 289 for (const color of result) { 290 expect(color).toMatch(HSL_PATTERN); 291 expect(getSaturation(color)).toBeGreaterThan(0); 292 } 293 }); 294 295 it('generateAnalogous produces different results on repeated calls', () => { 296 const results = new Set( 297 Array.from({ length: 10 }, () => generateAnalogous(BLACK)[0]), 298 ); 299 expect(results.size).toBeGreaterThan(1); 300 }); 301 302 it('generateSaturations returns 8 chromatic colors for black', () => { 303 const result = generateSaturations(BLACK); 304 expect(result).toHaveLength(8); 305 for (const color of result) { 306 expect(color).toMatch(HSL_PATTERN); 307 expect(getSaturation(color)).toBeGreaterThan(0); 308 } 309 }); 310 311 it('generateSaturations uses normal behavior for mid-lightness low-sat gray', () => { 312 const result = generateSaturations('hsl(0, 0%, 50%)'); 313 expect(result).toHaveLength(8); 314 for (const color of result) { 315 expect(color).toMatch(/^hsl\(0, \d+%, 50%\)$/); 316 } 317 }); 318 319 it('generateSaturations produces different results on repeated calls for black', () => { 320 const results = new Set( 321 Array.from({ length: 10 }, () => generateSaturations(BLACK)[0]), 322 ); 323 expect(results.size).toBeGreaterThan(1); 324 }); 325 }); 326});