@synnaxlabs/x 0.41.0 → 0.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/.turbo/turbo-build.log +23 -23
  2. package/dist/binary.cjs +1 -1
  3. package/dist/binary.js +2 -2
  4. package/dist/{bounds-M-SZ3X1Z.cjs → bounds-BQo7rvs9.cjs} +1 -1
  5. package/dist/{bounds-DQrjn60Q.js → bounds-Bn5_l4Z3.js} +10 -9
  6. package/dist/bounds.cjs +1 -1
  7. package/dist/bounds.js +1 -1
  8. package/dist/compare.cjs +1 -1
  9. package/dist/compare.js +1 -1
  10. package/dist/deep.cjs +1 -1
  11. package/dist/deep.js +84 -77
  12. package/dist/{dimensions-PWy5QZoM.cjs → dimensions-D2QGoNXO.cjs} +1 -1
  13. package/dist/dimensions.cjs +1 -1
  14. package/dist/{external-CvWr1nhS.cjs → external-DWQITF5_.cjs} +1 -1
  15. package/dist/index-BywOGO8U.js +1074 -0
  16. package/dist/index-CYYjI7Uf.cjs +1 -0
  17. package/dist/index-C_6NXBlg.cjs +3 -0
  18. package/dist/{index-BVC_8Cg9.js → index-QGplUHuy.js} +1 -1
  19. package/dist/index.cjs +3 -3
  20. package/dist/index.js +702 -243
  21. package/dist/record.js +3 -1
  22. package/dist/{scale-DL9VFGhL.cjs → scale-BtZINJ-A.cjs} +1 -1
  23. package/dist/{scale-DQwBWnwc.js → scale-DfJe9755.js} +1 -1
  24. package/dist/scale.cjs +1 -1
  25. package/dist/scale.js +1 -1
  26. package/dist/{series-D0zxMWxP.js → series-B9JERcqi.js} +571 -492
  27. package/dist/series-DqJ6f97G.cjs +11 -0
  28. package/dist/spatial.cjs +1 -1
  29. package/dist/spatial.js +2 -2
  30. package/dist/src/binary/{encoder.d.ts → codec.d.ts} +14 -8
  31. package/dist/src/binary/codec.d.ts.map +1 -0
  32. package/dist/src/binary/codec.spec.d.ts +2 -0
  33. package/dist/src/binary/codec.spec.d.ts.map +1 -0
  34. package/dist/src/binary/index.d.ts +1 -1
  35. package/dist/src/binary/index.d.ts.map +1 -1
  36. package/dist/src/breaker/breaker.d.ts +14 -21
  37. package/dist/src/breaker/breaker.d.ts.map +1 -1
  38. package/dist/src/change/change.d.ts +5 -18
  39. package/dist/src/change/change.d.ts.map +1 -1
  40. package/dist/src/color/color.d.ts +126 -0
  41. package/dist/src/color/color.d.ts.map +1 -0
  42. package/dist/src/color/color.spec.d.ts +2 -0
  43. package/dist/src/color/color.spec.d.ts.map +1 -0
  44. package/dist/src/color/external.d.ts +5 -0
  45. package/dist/src/color/external.d.ts.map +1 -0
  46. package/dist/src/color/gradient.d.ts +18 -0
  47. package/dist/src/color/gradient.d.ts.map +1 -0
  48. package/dist/src/color/index.d.ts +2 -0
  49. package/dist/src/color/index.d.ts.map +1 -0
  50. package/dist/src/color/palette.d.ts +19 -0
  51. package/dist/src/color/palette.d.ts.map +1 -0
  52. package/dist/src/color/transformColorsToHex.d.ts +6 -0
  53. package/dist/src/color/transformColorsToHex.d.ts.map +1 -0
  54. package/dist/src/control/control.d.ts +69 -74
  55. package/dist/src/control/control.d.ts.map +1 -1
  56. package/dist/src/deep/merge.d.ts +1 -1
  57. package/dist/src/deep/merge.d.ts.map +1 -1
  58. package/dist/src/errors/errors.d.ts +127 -7
  59. package/dist/src/errors/errors.d.ts.map +1 -1
  60. package/dist/src/errors/errors.spec.d.ts +2 -0
  61. package/dist/src/errors/errors.spec.d.ts.map +1 -0
  62. package/dist/src/index.d.ts +4 -0
  63. package/dist/src/index.d.ts.map +1 -1
  64. package/dist/src/jsonrpc/jsonrpc.d.ts +10 -7
  65. package/dist/src/jsonrpc/jsonrpc.d.ts.map +1 -1
  66. package/dist/src/kv/types.d.ts +1 -7
  67. package/dist/src/kv/types.d.ts.map +1 -1
  68. package/dist/src/migrate/migrate.d.ts +1 -1
  69. package/dist/src/notation/notation.d.ts +5 -1
  70. package/dist/src/notation/notation.d.ts.map +1 -1
  71. package/dist/src/record.d.ts +2 -1
  72. package/dist/src/record.d.ts.map +1 -1
  73. package/dist/src/replace.d.ts +2 -0
  74. package/dist/src/replace.d.ts.map +1 -0
  75. package/dist/src/runtime/os.d.ts +9 -1
  76. package/dist/src/runtime/os.d.ts.map +1 -1
  77. package/dist/src/singleton/define.d.ts +9 -0
  78. package/dist/src/singleton/define.d.ts.map +1 -0
  79. package/dist/src/singleton/define.spec.d.ts +2 -0
  80. package/dist/src/singleton/define.spec.d.ts.map +1 -0
  81. package/dist/src/singleton/index.d.ts +2 -0
  82. package/dist/src/singleton/index.d.ts.map +1 -0
  83. package/dist/src/spatial/base.d.ts +74 -70
  84. package/dist/src/spatial/base.d.ts.map +1 -1
  85. package/dist/src/spatial/box/box.d.ts +18 -76
  86. package/dist/src/spatial/box/box.d.ts.map +1 -1
  87. package/dist/src/spatial/dimensions/dimensions.d.ts +5 -29
  88. package/dist/src/spatial/dimensions/dimensions.d.ts.map +1 -1
  89. package/dist/src/spatial/direction/direction.d.ts +9 -1
  90. package/dist/src/spatial/direction/direction.d.ts.map +1 -1
  91. package/dist/src/spatial/location/location.d.ts +43 -22
  92. package/dist/src/spatial/location/location.d.ts.map +1 -1
  93. package/dist/src/spatial/scale/scale.d.ts +12 -120
  94. package/dist/src/spatial/scale/scale.d.ts.map +1 -1
  95. package/dist/src/spatial/xy/xy.d.ts +5 -29
  96. package/dist/src/spatial/xy/xy.d.ts.map +1 -1
  97. package/dist/src/sync/index.d.ts +2 -0
  98. package/dist/src/sync/index.d.ts.map +1 -0
  99. package/dist/src/sync/mutex.d.ts +8 -0
  100. package/dist/src/sync/mutex.d.ts.map +1 -0
  101. package/dist/src/telem/gl.d.ts +4 -1
  102. package/dist/src/telem/gl.d.ts.map +1 -1
  103. package/dist/src/telem/series.d.ts +46 -125
  104. package/dist/src/telem/series.d.ts.map +1 -1
  105. package/dist/src/telem/telem.d.ts +101 -86
  106. package/dist/src/telem/telem.d.ts.map +1 -1
  107. package/dist/src/toArray.d.ts +1 -1
  108. package/dist/src/toArray.d.ts.map +1 -1
  109. package/dist/src/zod/util.d.ts.map +1 -1
  110. package/dist/telem.cjs +1 -1
  111. package/dist/telem.js +1 -1
  112. package/dist/toArray.cjs +1 -1
  113. package/dist/toArray.js +1 -1
  114. package/dist/zod.cjs +1 -1
  115. package/package.json +5 -2
  116. package/src/binary/codec.spec.ts +370 -0
  117. package/src/binary/{encoder.ts → codec.ts} +55 -11
  118. package/src/binary/index.ts +1 -1
  119. package/src/breaker/breaker.spec.ts +16 -25
  120. package/src/breaker/breaker.ts +36 -19
  121. package/src/color/color.spec.ts +673 -0
  122. package/src/color/color.ts +317 -0
  123. package/src/color/external.ts +13 -0
  124. package/src/color/gradient.ts +78 -0
  125. package/src/color/index.ts +10 -0
  126. package/src/color/palette.ts +28 -0
  127. package/src/color/transformColorsToHex.ts +30 -0
  128. package/src/control/control.ts +30 -22
  129. package/src/deep/merge.spec.ts +60 -0
  130. package/src/deep/merge.ts +13 -8
  131. package/src/errors/errors.spec.ts +152 -0
  132. package/src/errors/errors.ts +225 -10
  133. package/src/index.ts +4 -0
  134. package/src/jsonrpc/jsonrpc.ts +12 -8
  135. package/src/migrate/migrate.ts +2 -2
  136. package/src/primitive.ts +1 -1
  137. package/src/record.ts +5 -1
  138. package/src/replace.ts +1 -0
  139. package/src/singleton/define.spec.ts +93 -0
  140. package/src/singleton/define.ts +27 -0
  141. package/src/singleton/index.ts +10 -0
  142. package/src/sync/index.ts +1 -0
  143. package/src/sync/mutex.ts +16 -0
  144. package/src/telem/series.spec.ts +32 -0
  145. package/src/telem/series.ts +54 -19
  146. package/src/telem/telem.spec.ts +151 -10
  147. package/src/telem/telem.ts +126 -73
  148. package/src/toArray.ts +2 -2
  149. package/src/zod/util.spec.ts +17 -1
  150. package/src/zod/util.ts +4 -2
  151. package/tsconfig.tsbuildinfo +1 -1
  152. package/dist/index-BG3Scw3G.cjs +0 -1
  153. package/dist/index-C3QzbIwt.js +0 -101
  154. package/dist/index-CnclyYpG.cjs +0 -3
  155. package/dist/series-BMma2b5q.cjs +0 -11
  156. package/dist/src/binary/encoder.d.ts.map +0 -1
  157. package/dist/src/binary/encoder.spec.d.ts +0 -2
  158. package/dist/src/binary/encoder.spec.d.ts.map +0 -1
  159. package/src/binary/encoder.spec.ts +0 -174
@@ -0,0 +1,673 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { describe, expect, test } from "vitest";
11
+
12
+ import { color } from "@/color";
13
+
14
+ describe("color.Color", () => {
15
+ describe("constructor", () => {
16
+ test("from hex", () => {
17
+ const c = color.construct("#7a2c26");
18
+ expect(color.rValue(c)).toEqual(122);
19
+ expect(color.gValue(c)).toEqual(44);
20
+ expect(color.bValue(c)).toEqual(38);
21
+ });
22
+ test("from hex with alpha", () => {
23
+ const c = color.construct("#7a2c26", 0.5);
24
+ expect(color.rValue(c)).toEqual(122);
25
+ expect(color.gValue(c)).toEqual(44);
26
+ expect(color.bValue(c)).toEqual(38);
27
+ expect(color.aValue(c)).toEqual(0.5);
28
+ });
29
+
30
+ describe("from eight digit hex", () => {
31
+ test("case 1", () => {
32
+ const c = color.construct("#7a2c26ff");
33
+ expect(color.rValue(c)).toEqual(122);
34
+ expect(color.gValue(c)).toEqual(44);
35
+ expect(color.bValue(c)).toEqual(38);
36
+ expect(color.aValue(c)).toEqual(1);
37
+ });
38
+ test("case 2", () => {
39
+ const c = color.construct("#7a2c2605");
40
+ expect(color.rValue(c)).toEqual(122);
41
+ expect(color.gValue(c)).toEqual(44);
42
+ expect(color.bValue(c)).toEqual(38);
43
+ expect(color.aValue(c)).toEqual(5 / 255);
44
+ });
45
+ });
46
+
47
+ test("from rgb", () => {
48
+ const c = color.construct([122, 44, 38]);
49
+ expect(color.rValue(c)).toEqual(122);
50
+ expect(color.gValue(c)).toEqual(44);
51
+ expect(color.bValue(c)).toEqual(38);
52
+ });
53
+ test("from rgba", () => {
54
+ const c = color.construct([122, 44, 38, 0.5]);
55
+ expect(color.rValue(c)).toEqual(122);
56
+ expect(color.gValue(c)).toEqual(44);
57
+ expect(color.bValue(c)).toEqual(38);
58
+ expect(color.aValue(c)).toEqual(0.5);
59
+ });
60
+ test("from c", () => {
61
+ const c = color.construct(color.construct("#7a2c26"));
62
+ expect(color.rValue(c)).toEqual(122);
63
+ expect(color.gValue(c)).toEqual(44);
64
+ expect(color.bValue(c)).toEqual(38);
65
+ });
66
+ });
67
+
68
+ describe("to hex", () => {
69
+ test("without alpha", () => {
70
+ const c = color.construct("#7a2c26");
71
+ expect(color.hex(c)).toEqual("#7a2c26");
72
+ });
73
+ test("with alpha", () => {
74
+ const c = color.construct("#7a2c26", 0.5);
75
+ expect(color.hex(c)).toEqual("#7a2c267f");
76
+ });
77
+ });
78
+
79
+ describe("to RGBA255", () => {
80
+ test("with alpha", () => {
81
+ const c = color.construct("#7a2c26", 0.5);
82
+ const expected = [122, 44, 38, 0.5];
83
+ expect(color.construct(c)).toEqual(expected);
84
+ });
85
+ test("without alpha", () => {
86
+ const c = color.construct("#7a2c26");
87
+ const expected = [122, 44, 38, 1];
88
+ expect(color.construct(c)).toEqual(expected);
89
+ });
90
+ });
91
+
92
+ describe("to RGBA1", () => {
93
+ test("with alpha", () => {
94
+ const c = color.construct("#7a2c26", 0.5);
95
+ const expected = [122 / 255, 44 / 255, 38 / 255, 0.5];
96
+ expected.forEach((v, i) => {
97
+ expect(color.rgba1(c)[i]).toBeCloseTo(v);
98
+ });
99
+ });
100
+ test("without alpha", () => {
101
+ const c = color.construct("#7a2c26");
102
+ const expected = [122 / 255, 44 / 255, 38 / 255, 1];
103
+ expected.forEach((v, i) => {
104
+ expect(color.rgba1(c)[i]).toBeCloseTo(v);
105
+ });
106
+ });
107
+ });
108
+
109
+ describe("luminance", () => {
110
+ const tests: Array<[string, number]> = [
111
+ ["#000000", 0],
112
+ ["#ffffff", 1],
113
+ ];
114
+ tests.forEach(([hex, expected]) => {
115
+ test(hex, () => {
116
+ const c = color.construct(hex);
117
+ expect(color.luminance(c)).toBeCloseTo(expected);
118
+ });
119
+ });
120
+ });
121
+
122
+ describe("contrast", () => {
123
+ const tests: Array<[string, string, number]> = [
124
+ ["#000000", "#ffffff", 3],
125
+ ["#ffffff", "#000000", 3],
126
+ ["#000000", "#000000", 1],
127
+ ["#ffffff", "#ffffff", 1],
128
+ ];
129
+ tests.forEach(([hex1, hex2, expected]) => {
130
+ test(`${hex1} ${hex2}`, () => {
131
+ const c1 = color.construct(hex1);
132
+ const c2 = color.construct(hex2);
133
+ expect(color.contrast(c1, c2)).toBeCloseTo(expected);
134
+ });
135
+ });
136
+ test("pick c with highest contrast", () => {
137
+ const c = color.construct("#000000");
138
+ const c1 = color.construct("#ffffff");
139
+ const c2 = color.construct("#0000ff");
140
+ expect(color.pickByContrast(c, c1, c2)).toEqual(c1);
141
+ });
142
+ });
143
+
144
+ describe("grayness", () => {
145
+ const tests: Array<[string, number]> = [
146
+ ["#000000", 1],
147
+ ["#ffffff", 1],
148
+ ["#0000ff", 0],
149
+ ["#00ff00", 0],
150
+ ["#ff0000", 0],
151
+ ["#ffff00", 0],
152
+ ["#fefed4", 0.834],
153
+ ["#5c6670", 0.92],
154
+ ["#d3c5c5", 0.945],
155
+ ];
156
+ tests.forEach(([hex, expected]) => {
157
+ test(hex, () => {
158
+ const c = color.construct(hex);
159
+ expect(color.grayness(c)).toBeCloseTo(expected);
160
+ });
161
+ });
162
+ });
163
+
164
+ describe("fromHSLA", () => {
165
+ const tests: Array<
166
+ [string, [number, number, number, number], [number, number, number, number]]
167
+ > = [
168
+ ["Red", [0, 100, 50, 1], [255, 0, 0, 1]],
169
+ ["Green", [120, 100, 50, 1], [0, 255, 0, 1]],
170
+ ["Blue", [240, 100, 50, 1], [0, 0, 255, 1]],
171
+ ["Yellow", [60, 100, 50, 1], [255, 255, 0, 1]],
172
+ ["Cyan", [180, 100, 50, 1], [0, 255, 255, 1]],
173
+ ["Magenta", [300, 100, 50, 1], [255, 0, 255, 1]],
174
+ ["Black", [0, 0, 0, 1], [0, 0, 0, 1]],
175
+ ["White", [0, 0, 100, 1], [255, 255, 255, 1]],
176
+ ["Mid Gray", [0, 0, 50, 1], [128, 128, 128, 1]],
177
+ ["Semi-transparent Red", [0, 100, 50, 0.5], [255, 0, 0, 0.5]],
178
+ ];
179
+
180
+ tests.forEach(([name, hsla, expected]) => {
181
+ test(name, () => {
182
+ const result = color.fromHSLA(hsla);
183
+ expect(result).toEqual(expected);
184
+ });
185
+ });
186
+
187
+ test("converts HSLA to RGBA", () => {
188
+ const hsla: color.HSLA = [0, 100, 50, 1]; // Red in HSLA
189
+ const expected: color.RGBA = [255, 0, 0, 1]; // Red in RGBA
190
+ expect(color.fromHSLA(hsla)).toEqual(expected);
191
+ });
192
+
193
+ test("handles hue wrapping properly", () => {
194
+ const hsla1: color.HSLA = [0, 100, 50, 1]; // 0 degrees = red
195
+ const hsla2: color.HSLA = [360, 100, 50, 1]; // 360 degrees = red
196
+ expect(color.fromHSLA(hsla1)).toEqual(color.fromHSLA(hsla2));
197
+ });
198
+
199
+ test("handles zero saturation (grayscale)", () => {
200
+ // For zero saturation, hue doesn't matter, only lightness
201
+ const white: color.HSLA = [0, 0, 100, 1];
202
+ const black: color.HSLA = [0, 0, 0, 1];
203
+ const gray: color.HSLA = [0, 0, 50, 1];
204
+
205
+ expect(color.fromHSLA(white)).toEqual([255, 255, 255, 1]);
206
+ expect(color.fromHSLA(black)).toEqual([0, 0, 0, 1]);
207
+ expect(color.fromHSLA(gray)).toEqual([128, 128, 128, 1]);
208
+ });
209
+
210
+ test("preserves alpha value", () => {
211
+ const transparent: color.HSLA = [0, 100, 50, 0]; // Transparent red
212
+ const semiTransparent: color.HSLA = [0, 100, 50, 0.5]; // Semi-transparent red
213
+
214
+ expect(color.fromHSLA(transparent)[3]).toEqual(0);
215
+ expect(color.fromHSLA(semiTransparent)[3]).toEqual(0.5);
216
+ });
217
+ });
218
+
219
+ describe("hsla", () => {
220
+ const tests: Array<
221
+ [string, [number, number, number, number], [number, number, number, number]]
222
+ > = [
223
+ // Test primary colors
224
+ ["Red", [255, 0, 0, 1], [0, 100, 50, 1]],
225
+ ["Green", [0, 255, 0, 1], [120, 100, 50, 1]],
226
+ ["Blue", [0, 0, 255, 1], [240, 100, 50, 1]],
227
+
228
+ // Test secondary colors
229
+ ["Yellow", [255, 255, 0, 1], [60, 100, 50, 1]],
230
+ ["Cyan", [0, 255, 255, 1], [180, 100, 50, 1]],
231
+ ["Magenta", [255, 0, 255, 1], [300, 100, 50, 1]],
232
+
233
+ // Test shades of gray
234
+ ["Black", [0, 0, 0, 1], [0, 0, 0, 1]],
235
+ ["White", [255, 255, 255, 1], [0, 0, 100, 1]],
236
+ ["Mid Gray", [128, 128, 128, 1], [0, 0, 50, 1]],
237
+
238
+ // Test different alpha values
239
+ ["Transparent Red", [255, 0, 0, 0], [0, 100, 50, 0]],
240
+ ["Semi-transparent Blue", [0, 0, 255, 0.5], [240, 100, 50, 0.5]],
241
+
242
+ // Test different lightness levels
243
+ ["Dark Red", [128, 0, 0, 1], [0, 100, 25, 1]],
244
+ ["Light Blue", [128, 128, 255, 1], [240, 100, 75, 1]],
245
+
246
+ // Test different saturation levels
247
+ ["Desaturated Red", [191, 64, 64, 1], [0, 50, 50, 1]],
248
+ ["Slightly Saturated Green", [96, 159, 96, 1], [120, 25, 50, 1]],
249
+ ];
250
+
251
+ tests.forEach(([name, rgba, expected]) => {
252
+ test(name, () => {
253
+ const result = color.hsla(rgba);
254
+ // Note: Due to potential rounding differences in conversion,
255
+ // we use toBeCloseTo for HSL values with precision 0
256
+ for (let i = 0; i < 3; i++) expect(result[i]).toBeCloseTo(expected[i], 0);
257
+
258
+ // Alpha should match exactly
259
+ expect(result[3]).toEqual(expected[3]);
260
+ });
261
+ });
262
+
263
+ test("handles hex color input", () => {
264
+ // Red in hex
265
+ const hexColor = "#ff0000";
266
+ const expected = [0, 100, 50, 1];
267
+ const result = color.hsla(hexColor);
268
+
269
+ for (let i = 0; i < 3; i++) expect(result[i]).toBeCloseTo(expected[i], 0);
270
+
271
+ expect(result[3]).toEqual(expected[3]);
272
+ });
273
+
274
+ test("handles RGB array input", () => {
275
+ // Green as RGB array
276
+ const rgbColor: color.RGB = [0, 255, 0];
277
+ const expected = [120, 100, 50, 1];
278
+ const result = color.hsla(rgbColor);
279
+
280
+ for (let i = 0; i < 3; i++) expect(result[i]).toBeCloseTo(expected[i], 0);
281
+
282
+ expect(result[3]).toEqual(1); // Default alpha
283
+ });
284
+
285
+ test("preserves original color after round-trip conversion", () => {
286
+ const originalColors: color.RGBA[] = [
287
+ [255, 0, 0, 1], // Red
288
+ [0, 255, 0, 1], // Green
289
+ [0, 0, 255, 1], // Blue
290
+ [255, 255, 0, 1], // Yellow
291
+ [0, 255, 255, 1], // Cyan
292
+ [255, 0, 255, 1], // Magenta
293
+ [128, 128, 128, 1], // Gray
294
+ [255, 255, 255, 0.5], // Semi-transparent white
295
+ ];
296
+
297
+ for (const original of originalColors) {
298
+ const hsla = color.hsla(original);
299
+ const converted = color.fromHSLA(hsla);
300
+
301
+ // Compare RGB values with some tolerance for rounding
302
+ for (let i = 0; i < 3; i++) expect(converted[i]).toBeCloseTo(original[i], 0);
303
+
304
+ // Alpha should match exactly
305
+ expect(converted[3]).toEqual(original[3]);
306
+ }
307
+ });
308
+
309
+ test("handles achromatic colors correctly", () => {
310
+ // For achromatic colors (black, white, gray),
311
+ // hue is 0 and saturation is 0
312
+ const gray: color.RGBA = [100, 100, 100, 1];
313
+ const result = color.hsla(gray);
314
+
315
+ expect(result[0]).toEqual(0); // Hue
316
+ expect(result[1]).toEqual(0); // Saturation
317
+ expect(result[2]).toBeCloseTo(39, 0); // Lightness ~39%
318
+ expect(result[3]).toEqual(1); // Alpha
319
+ });
320
+ });
321
+
322
+ describe("setAlpha", () => {
323
+ test("sets alpha on RGB color", () => {
324
+ const rgb: color.RGB = [255, 0, 0];
325
+ const result = color.setAlpha(rgb, 0.5);
326
+
327
+ expect(result).toEqual([255, 0, 0, 0.5]);
328
+ });
329
+
330
+ test("sets alpha on RGBA color", () => {
331
+ const rgba: color.RGBA = [0, 255, 0, 1];
332
+ const result = color.setAlpha(rgba, 0.3);
333
+
334
+ expect(result).toEqual([0, 255, 0, 0.3]);
335
+ });
336
+
337
+ test("sets alpha on hex color", () => {
338
+ const hex = "#0000ff";
339
+ const result = color.setAlpha(hex, 0.7);
340
+
341
+ expect(result).toEqual([0, 0, 255, 0.7]);
342
+ });
343
+
344
+ test("overrides existing alpha in RGBA color", () => {
345
+ const rgba: color.RGBA = [128, 128, 128, 0.2];
346
+ const result = color.setAlpha(rgba, 0.8);
347
+
348
+ expect(result).toEqual([128, 128, 128, 0.8]);
349
+ });
350
+
351
+ test("handles alpha value of 0", () => {
352
+ const color1 = color.construct("#ff0000");
353
+ const result = color.setAlpha(color1, 0);
354
+
355
+ expect(result[3]).toEqual(0);
356
+ });
357
+
358
+ test("handles alpha value of 1", () => {
359
+ const color1 = color.construct("#ff0000", 0.5);
360
+ const result = color.setAlpha(color1, 1);
361
+
362
+ expect(result[3]).toEqual(1);
363
+ });
364
+
365
+ test("converts percentage (>1) alpha values to 0-1 range", () => {
366
+ const color1 = color.construct("#ff0000");
367
+ const result = color.setAlpha(color1, 50);
368
+
369
+ expect(result[3]).toEqual(0.5);
370
+ });
371
+
372
+ test("throws error for alpha values > 100", () => {
373
+ const color1 = color.construct("#ff0000");
374
+ expect(() => color.setAlpha(color1, 101)).toThrow();
375
+ });
376
+
377
+ test("preserves RGB values when setting alpha", () => {
378
+ const originalColor: color.RGBA = [123, 45, 67, 0.8];
379
+ const result = color.setAlpha(originalColor, 0.4);
380
+
381
+ expect(result[0]).toEqual(123);
382
+ expect(result[1]).toEqual(45);
383
+ expect(result[2]).toEqual(67);
384
+ });
385
+
386
+ test("return type is Color (RGBA format)", () => {
387
+ const hex = "#123456";
388
+ const result = color.setAlpha(hex, 0.5);
389
+
390
+ expect(Array.isArray(result)).toBe(true);
391
+ expect(result.length).toEqual(4);
392
+ expect(typeof result[3]).toBe("number");
393
+ });
394
+ });
395
+
396
+ describe("rgbaCSS", () => {
397
+ test("converts RGB array to CSS rgba string", () => {
398
+ const rgb: color.RGB = [255, 0, 0];
399
+ expect(color.rgbaCSS(rgb)).toEqual("rgba(255, 0, 0, 1)");
400
+ });
401
+
402
+ test("converts RGBA array to CSS rgba string", () => {
403
+ const rgba: color.RGBA = [0, 255, 0, 0.5];
404
+ expect(color.rgbaCSS(rgba)).toEqual("rgba(0, 255, 0, 0.5)");
405
+ });
406
+
407
+ test("converts hex to CSS rgba string", () => {
408
+ const hex = "#0000ff";
409
+ expect(color.rgbaCSS(hex)).toEqual("rgba(0, 0, 255, 1)");
410
+ });
411
+
412
+ test("converts hex with alpha to CSS rgba string", () => {
413
+ const hex = "#ff000080";
414
+ expect(color.rgbaCSS(hex)).toEqual("rgba(255, 0, 0, 0.5019607843137255)");
415
+ });
416
+
417
+ test("handles Color object", () => {
418
+ const c = color.construct([128, 128, 128, 0.7]);
419
+ expect(color.rgbaCSS(c)).toEqual("rgba(128, 128, 128, 0.7)");
420
+ });
421
+ });
422
+
423
+ describe("rgbCSS", () => {
424
+ test("converts RGB array to CSS rgb string", () => {
425
+ const rgb: color.RGB = [255, 0, 0];
426
+ expect(color.rgbCSS(rgb)).toEqual("rgb(255, 0, 0)");
427
+ });
428
+
429
+ test("converts RGBA array to CSS rgb string (ignores alpha)", () => {
430
+ const rgba: color.RGBA = [0, 255, 0, 0.5];
431
+ expect(color.rgbCSS(rgba)).toEqual("rgb(0, 255, 0)");
432
+ });
433
+
434
+ test("converts hex to CSS rgb string", () => {
435
+ const hex = "#0000ff";
436
+ expect(color.rgbCSS(hex)).toEqual("rgb(0, 0, 255)");
437
+ });
438
+
439
+ test("handles Color object", () => {
440
+ const c = color.construct([128, 128, 128, 0.7]);
441
+ expect(color.rgbCSS(c)).toEqual("rgb(128, 128, 128)");
442
+ });
443
+ });
444
+
445
+ describe("rgbString", () => {
446
+ test("converts RGB array to comma-separated string", () => {
447
+ const rgb: color.RGB = [255, 0, 0];
448
+ expect(color.rgbString(rgb)).toEqual("255, 0, 0");
449
+ });
450
+
451
+ test("converts RGBA array to comma-separated string (ignores alpha)", () => {
452
+ const rgba: color.RGBA = [0, 255, 0, 0.5];
453
+ expect(color.rgbString(rgba)).toEqual("0, 255, 0");
454
+ });
455
+
456
+ test("converts hex to comma-separated string", () => {
457
+ const hex = "#0000ff";
458
+ expect(color.rgbString(hex)).toEqual("0, 0, 255");
459
+ });
460
+
461
+ test("handles Color object", () => {
462
+ const c = color.construct([128, 128, 128, 0.7]);
463
+ expect(color.rgbString(c)).toEqual("128, 128, 128");
464
+ });
465
+ });
466
+
467
+ describe("equals", () => {
468
+ test("same RGB values are equal", () => {
469
+ const c1: color.RGB = [255, 0, 0];
470
+ const c2: color.RGB = [255, 0, 0];
471
+ expect(color.equals(c1, c2)).toBe(true);
472
+ });
473
+
474
+ test("different RGB values are not equal", () => {
475
+ const c1: color.RGB = [255, 0, 0];
476
+ const c2: color.RGB = [0, 255, 0];
477
+ expect(color.equals(c1, c2)).toBe(false);
478
+ });
479
+
480
+ test("RGBA and RGB with default alpha are equal", () => {
481
+ const c1: color.RGB = [255, 0, 0];
482
+ const c2: color.RGBA = [255, 0, 0, 1];
483
+ expect(color.equals(c1, c2)).toBe(true);
484
+ });
485
+
486
+ test("different alpha values are not equal", () => {
487
+ const c1: color.RGBA = [255, 0, 0, 1];
488
+ const c2: color.RGBA = [255, 0, 0, 0.5];
489
+ expect(color.equals(c1, c2)).toBe(false);
490
+ });
491
+
492
+ test("hex and RGB with same values are equal", () => {
493
+ const c1 = "#ff0000";
494
+ const c2: color.RGB = [255, 0, 0];
495
+ expect(color.equals(c1, c2)).toBe(true);
496
+ });
497
+
498
+ test("eight-digit hex and RGBA with same values are equal", () => {
499
+ const c1 = "#ff000080";
500
+ const c2: color.RGBA = [255, 0, 0, 0.5019607843137255];
501
+ expect(color.equals(c1, c2)).toBe(true);
502
+ });
503
+
504
+ test("undefined values return false", () => {
505
+ const c1: color.RGB = [255, 0, 0];
506
+ expect(color.equals(c1, undefined)).toBe(false);
507
+ expect(color.equals(undefined, c1)).toBe(false);
508
+ expect(color.equals(undefined, undefined)).toBe(false);
509
+ });
510
+
511
+ test("comparing color with itself returns true", () => {
512
+ const c = color.construct("#ff0000");
513
+ expect(color.equals(c, c)).toBe(true);
514
+ });
515
+ });
516
+
517
+ describe("cssString", () => {
518
+ test("returns undefined for undefined input", () => {
519
+ expect(color.cssString(undefined)).toBeUndefined();
520
+ });
521
+
522
+ test("returns undefined for null input", () => {
523
+ expect(color.cssString(null as any)).toBeUndefined();
524
+ });
525
+
526
+ test("converts RGB array to rgba CSS string", () => {
527
+ const rgb: color.RGB = [255, 0, 0];
528
+ expect(color.cssString(rgb)).toEqual("rgba(255, 0, 0, 1)");
529
+ });
530
+
531
+ test("converts RGBA array to rgba CSS string", () => {
532
+ const rgba: color.RGBA = [0, 255, 0, 0.5];
533
+ expect(color.cssString(rgba)).toEqual("rgba(0, 255, 0, 0.5)");
534
+ });
535
+
536
+ test("converts hex to rgba CSS string", () => {
537
+ const hex = "#0000ff";
538
+ expect(color.cssString(hex)).toEqual("rgba(0, 0, 255, 1)");
539
+ });
540
+
541
+ test("returns CSS variables as-is", () => {
542
+ const cssVar = "var(--primary-color)";
543
+ expect(color.cssString(cssVar)).toEqual(cssVar);
544
+ });
545
+
546
+ test("handles eight-digit hex with alpha", () => {
547
+ const hex = "#00ff0080";
548
+ expect(color.cssString(hex)).toEqual("rgba(0, 255, 0, 0.5019607843137255)");
549
+ });
550
+
551
+ test("handles Color object", () => {
552
+ const c = color.construct([128, 128, 128, 0.7]);
553
+ expect(color.cssString(c)).toEqual("rgba(128, 128, 128, 0.7)");
554
+ });
555
+
556
+ test("throws error for invalid color format", () => {
557
+ expect(() => color.cssString({} as any)).toThrow();
558
+ expect(() => color.cssString([1, 2] as any)).toThrow();
559
+ expect(() => color.cssString([300, 0, 0] as any)).toThrow();
560
+ });
561
+ });
562
+
563
+ describe("isCrude", () => {
564
+ test("RGB array is a crude color", () => {
565
+ const rgb: color.RGB = [255, 0, 0];
566
+ expect(color.isCrude(rgb)).toBe(true);
567
+ });
568
+
569
+ test("RGBA array is a crude color", () => {
570
+ const rgba: color.RGBA = [0, 255, 0, 0.5];
571
+ expect(color.isCrude(rgba)).toBe(true);
572
+ });
573
+
574
+ test("hex string is a crude color", () => {
575
+ expect(color.isCrude("#ff0000")).toBe(true);
576
+ });
577
+
578
+ test("hex string with hash is a crude color", () => {
579
+ expect(color.isCrude("#00ff00")).toBe(true);
580
+ });
581
+
582
+ test("eight-digit hex is a crude color", () => {
583
+ expect(color.isCrude("#0000ff80")).toBe(true);
584
+ });
585
+
586
+ test("Color object is a crude color", () => {
587
+ const c = color.construct("#ff0000");
588
+ expect(color.isCrude(c)).toBe(true);
589
+ });
590
+
591
+ test("rejects invalid hex strings", () => {
592
+ expect(color.isCrude("#xyz")).toBe(false);
593
+ expect(color.isCrude("#12345")).toBe(false);
594
+ expect(color.isCrude("#1234567")).toBe(false);
595
+ expect(color.isCrude("#123456789")).toBe(false);
596
+ });
597
+
598
+ test("rejects invalid RGB arrays", () => {
599
+ expect(color.isCrude([255])).toBe(false);
600
+ expect(color.isCrude([255, 0])).toBe(false);
601
+ expect(color.isCrude([255, 0, 0, 0.5, 1])).toBe(false);
602
+ expect(color.isCrude([-1, 0, 0])).toBe(false);
603
+ expect(color.isCrude([0, 256, 0])).toBe(false);
604
+ });
605
+
606
+ test("rejects invalid RGBA arrays", () => {
607
+ expect(color.isCrude([255, 0, 0, 1.1])).toBe(false);
608
+ expect(color.isCrude([255, 0, 0, -0.1])).toBe(false);
609
+ });
610
+
611
+ test("rejects non-color values", () => {
612
+ expect(color.isCrude(null)).toBe(false);
613
+ expect(color.isCrude(undefined)).toBe(false);
614
+ expect(color.isCrude({})).toBe(false);
615
+ expect(color.isCrude("not a color")).toBe(false);
616
+ expect(color.isCrude(123)).toBe(false);
617
+ expect(color.isCrude(true)).toBe(false);
618
+ });
619
+ });
620
+
621
+ describe("isColor", () => {
622
+ test("valid RGBA array is a Color", () => {
623
+ const rgba: color.RGBA = [0, 255, 0, 0.5];
624
+ expect(color.isColor(rgba)).toBe(true);
625
+ });
626
+
627
+ test("RGB array with 3 elements is not a Color", () => {
628
+ const rgb: color.RGB = [255, 0, 0];
629
+ expect(color.isColor(rgb)).toBe(false);
630
+ });
631
+
632
+ test("constructed Color is a Color", () => {
633
+ const c = color.construct("#ff0000");
634
+ expect(color.isColor(c)).toBe(true);
635
+ });
636
+
637
+ test("hex string is not a Color", () => {
638
+ expect(color.isColor("#ff0000")).toBe(false);
639
+ });
640
+
641
+ test("rejects invalid RGBA arrays", () => {
642
+ expect(color.isColor([255, 0, 0])).toBe(false); // Missing alpha
643
+ expect(color.isColor([255, 0, 0, 0, 0])).toBe(false); // Too many elements
644
+ expect(color.isColor([255, 0, -1, 1])).toBe(false); // Negative value
645
+ expect(color.isColor([255, 0, 256, 1])).toBe(false); // Value > 255
646
+ expect(color.isColor([255, 0, 0, 1.1])).toBe(false); // Alpha > 1
647
+ expect(color.isColor([255, 0, 0, -0.1])).toBe(false); // Alpha < 0
648
+ });
649
+
650
+ test("rejects non-array values", () => {
651
+ expect(color.isColor(null)).toBe(false);
652
+ expect(color.isColor(undefined)).toBe(false);
653
+ expect(color.isColor({})).toBe(false);
654
+ expect(color.isColor("rgba(255,0,0,1)")).toBe(false);
655
+ expect(color.isColor(123)).toBe(false);
656
+ });
657
+
658
+ test("validates RGB values are within range", () => {
659
+ expect(color.isColor([0, 0, 0, 0])).toBe(true);
660
+ expect(color.isColor([255, 255, 255, 1])).toBe(true);
661
+ expect(color.isColor([300, 0, 0, 1])).toBe(false);
662
+ expect(color.isColor([0, -10, 0, 1])).toBe(false);
663
+ });
664
+
665
+ test("validates alpha is within 0-1 range", () => {
666
+ expect(color.isColor([0, 0, 0, 0])).toBe(true);
667
+ expect(color.isColor([0, 0, 0, 1])).toBe(true);
668
+ expect(color.isColor([0, 0, 0, 0.5])).toBe(true);
669
+ expect(color.isColor([0, 0, 0, -0.1])).toBe(false);
670
+ expect(color.isColor([0, 0, 0, 1.1])).toBe(false);
671
+ });
672
+ });
673
+ });