@arolariu/components 0.4.1 → 0.5.0
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.
- package/changelog.md +15 -0
- package/dist/components/ui/card.d.ts +2 -1
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/card.js +7 -1
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/collapsible.js +1 -1
- package/dist/components/ui/collapsible.js.map +1 -1
- package/dist/components/ui/command.d.ts.map +1 -1
- package/dist/components/ui/command.js +11 -6
- package/dist/components/ui/command.js.map +1 -1
- package/dist/components/ui/field.d.ts +26 -0
- package/dist/components/ui/field.d.ts.map +1 -0
- package/dist/index.css +28 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/lib/color-conversion-utilities.d.ts +82 -0
- package/dist/lib/color-conversion-utilities.d.ts.map +1 -0
- package/dist/lib/color-conversion-utilities.js +94 -0
- package/dist/lib/color-conversion-utilities.js.map +1 -0
- package/package.json +9 -2
- package/src/components/ui/card.tsx +10 -1
- package/src/components/ui/command.tsx +2 -1
- package/src/hooks/useIsMobile.test.tsx +96 -0
- package/src/hooks/useWindowSize.test.tsx +57 -0
- package/src/index.test.ts +537 -0
- package/src/index.ts +30 -1
- package/src/lib/color-conversion-utilities.test.ts +225 -0
- package/src/lib/color-conversion-utilities.ts +165 -0
- package/src/lib/utilities.test.ts +37 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for color conversion utilities.
|
|
3
|
+
* @module lib/color-conversion-utilities.test
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {describe, expect, it} from "vitest";
|
|
7
|
+
import {
|
|
8
|
+
adjustHexColorLightness,
|
|
9
|
+
calculateComplementaryHexColor,
|
|
10
|
+
convertHexToHslString,
|
|
11
|
+
convertHslToHexString,
|
|
12
|
+
parseHslStringToComponents,
|
|
13
|
+
validateHexColorFormat,
|
|
14
|
+
} from "./color-conversion-utilities";
|
|
15
|
+
|
|
16
|
+
describe("color-conversion-utilities", () => {
|
|
17
|
+
describe("convertHexToHslString", () => {
|
|
18
|
+
it("should convert cyan-500 (#06b6d4) to HSL", () => {
|
|
19
|
+
const result = convertHexToHslString("#06b6d4");
|
|
20
|
+
// Note: Actual computed value due to floating point math
|
|
21
|
+
expect(result).toBe("189 94% 43%");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should convert pink-500 (#ec4899) to HSL", () => {
|
|
25
|
+
const result = convertHexToHslString("#ec4899");
|
|
26
|
+
expect(result).toBe("330 81% 60%");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should handle hex without # prefix", () => {
|
|
30
|
+
const result = convertHexToHslString("06b6d4");
|
|
31
|
+
// Note: Same color as with # prefix, same computed HSL
|
|
32
|
+
expect(result).toBe("189 94% 43%");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should convert black (#000000) to HSL", () => {
|
|
36
|
+
const result = convertHexToHslString("#000000");
|
|
37
|
+
expect(result).toBe("0 0% 0%");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should convert white (#ffffff) to HSL", () => {
|
|
41
|
+
const result = convertHexToHslString("#ffffff");
|
|
42
|
+
expect(result).toBe("0 0% 100%");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should convert pure red (#ff0000) to HSL", () => {
|
|
46
|
+
const result = convertHexToHslString("#ff0000");
|
|
47
|
+
expect(result).toBe("0 100% 50%");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should convert pure green (#00ff00) to HSL", () => {
|
|
51
|
+
const result = convertHexToHslString("#00ff00");
|
|
52
|
+
expect(result).toBe("120 100% 50%");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should convert pure blue (#0000ff) to HSL", () => {
|
|
56
|
+
const result = convertHexToHslString("#0000ff");
|
|
57
|
+
expect(result).toBe("240 100% 50%");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should handle high lightness colors (l > 0.5)", () => {
|
|
61
|
+
// Light pink has l > 0.5, which triggers the other saturation calculation branch
|
|
62
|
+
const result = convertHexToHslString("#ffb6c1");
|
|
63
|
+
expect(result).toBe("351 100% 86%");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should handle colors where max is green", () => {
|
|
67
|
+
// Green dominant color
|
|
68
|
+
const result = convertHexToHslString("#90ee90");
|
|
69
|
+
expect(result).toBe("120 73% 75%");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should handle colors where g < b in red-dominant colors", () => {
|
|
73
|
+
// Red with more blue than green (magenta-ish)
|
|
74
|
+
const result = convertHexToHslString("#ff00aa");
|
|
75
|
+
expect(result).toBe("320 100% 50%");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("convertHslToHexString", () => {
|
|
80
|
+
it("should convert HSL to cyan-500", () => {
|
|
81
|
+
// Note: HSL to hex conversion may not perfectly round-trip due to rounding
|
|
82
|
+
const result = convertHslToHexString(189, 94, 43);
|
|
83
|
+
expect(result.toLowerCase()).toBe("#07b6d5");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should convert black HSL to hex", () => {
|
|
87
|
+
const result = convertHslToHexString(0, 0, 0);
|
|
88
|
+
expect(result.toLowerCase()).toBe("#000000");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should convert white HSL to hex", () => {
|
|
92
|
+
const result = convertHslToHexString(0, 0, 100);
|
|
93
|
+
expect(result.toLowerCase()).toBe("#ffffff");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should convert pure red HSL to hex", () => {
|
|
97
|
+
const result = convertHslToHexString(0, 100, 50);
|
|
98
|
+
expect(result.toLowerCase()).toBe("#ff0000");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should handle all hue ranges", () => {
|
|
102
|
+
// Yellow range (60)
|
|
103
|
+
expect(convertHslToHexString(60, 100, 50).toLowerCase()).toBe("#ffff00");
|
|
104
|
+
// Cyan range (180)
|
|
105
|
+
expect(convertHslToHexString(180, 100, 50).toLowerCase()).toBe("#00ffff");
|
|
106
|
+
// Magenta range (300)
|
|
107
|
+
expect(convertHslToHexString(300, 100, 50).toLowerCase()).toBe("#ff00ff");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should handle all hue sectors correctly", () => {
|
|
111
|
+
// Sector 0-60: red range
|
|
112
|
+
expect(convertHslToHexString(30, 100, 50).toLowerCase()).toBe("#ff8000");
|
|
113
|
+
// Sector 60-120: yellow-green range
|
|
114
|
+
expect(convertHslToHexString(90, 100, 50).toLowerCase()).toBe("#80ff00");
|
|
115
|
+
// Sector 120-180: green-cyan range
|
|
116
|
+
expect(convertHslToHexString(150, 100, 50).toLowerCase()).toBe("#00ff80");
|
|
117
|
+
// Sector 180-240: cyan-blue range
|
|
118
|
+
expect(convertHslToHexString(210, 100, 50).toLowerCase()).toBe("#0080ff");
|
|
119
|
+
// Sector 240-300: blue-magenta range
|
|
120
|
+
expect(convertHslToHexString(270, 100, 50).toLowerCase()).toBe("#8000ff");
|
|
121
|
+
// Sector 300-360: magenta-red range
|
|
122
|
+
expect(convertHslToHexString(330, 100, 50).toLowerCase()).toBe("#ff0080");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("validateHexColorFormat", () => {
|
|
127
|
+
it("should return true for valid hex with #", () => {
|
|
128
|
+
expect(validateHexColorFormat("#06b6d4")).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should return true for valid hex without #", () => {
|
|
132
|
+
expect(validateHexColorFormat("06b6d4")).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should return true for uppercase hex", () => {
|
|
136
|
+
expect(validateHexColorFormat("#FFFFFF")).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should return false for 3-digit hex", () => {
|
|
140
|
+
expect(validateHexColorFormat("#FFF")).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should return false for invalid characters", () => {
|
|
144
|
+
expect(validateHexColorFormat("#GGGGGG")).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should return false for empty string", () => {
|
|
148
|
+
expect(validateHexColorFormat("")).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should return false for random string", () => {
|
|
152
|
+
expect(validateHexColorFormat("invalid")).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("calculateComplementaryHexColor", () => {
|
|
157
|
+
it("should return inverse of black as white", () => {
|
|
158
|
+
const result = calculateComplementaryHexColor("#000000");
|
|
159
|
+
expect(result.toLowerCase()).toBe("#ffffff");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should return inverse of white as black", () => {
|
|
163
|
+
const result = calculateComplementaryHexColor("#ffffff");
|
|
164
|
+
expect(result.toLowerCase()).toBe("#000000");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should return cyan for red", () => {
|
|
168
|
+
const result = calculateComplementaryHexColor("#ff0000");
|
|
169
|
+
expect(result.toLowerCase()).toBe("#00ffff");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should handle hex without #", () => {
|
|
173
|
+
const result = calculateComplementaryHexColor("ff0000");
|
|
174
|
+
expect(result.toLowerCase()).toBe("#00ffff");
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("adjustHexColorLightness", () => {
|
|
179
|
+
it("should lighten a color", () => {
|
|
180
|
+
const result = adjustHexColorLightness("#000000", 50);
|
|
181
|
+
expect(result).not.toBe("#000000");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should darken a color", () => {
|
|
185
|
+
const result = adjustHexColorLightness("#ffffff", -50);
|
|
186
|
+
expect(result).not.toBe("#ffffff");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("should not exceed 100% lightness", () => {
|
|
190
|
+
const result = adjustHexColorLightness("#ffffff", 100);
|
|
191
|
+
// Should cap at white
|
|
192
|
+
expect(result.toLowerCase()).toBe("#ffffff");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should not go below 0% lightness", () => {
|
|
196
|
+
const result = adjustHexColorLightness("#000000", -100);
|
|
197
|
+
// Should cap at black
|
|
198
|
+
expect(result.toLowerCase()).toBe("#000000");
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("parseHslStringToComponents", () => {
|
|
203
|
+
it("should parse valid HSL string", () => {
|
|
204
|
+
const result = parseHslStringToComponents("187 94% 43%");
|
|
205
|
+
expect(result).toEqual({hue: 187, saturation: 94, lightness: 43});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should parse HSL with zero values", () => {
|
|
209
|
+
const result = parseHslStringToComponents("0 0% 0%");
|
|
210
|
+
expect(result).toEqual({hue: 0, saturation: 0, lightness: 0});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should return null for invalid format", () => {
|
|
214
|
+
expect(parseHslStringToComponents("invalid")).toBeNull();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should return null for empty string", () => {
|
|
218
|
+
expect(parseHslStringToComponents("")).toBeNull();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should return null for partial HSL", () => {
|
|
222
|
+
expect(parseHslStringToComponents("187 94%")).toBeNull();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utility functions for color conversion and manipulation.
|
|
3
|
+
* Provides hex-to-HSL conversion and color validation for CSS custom properties.
|
|
4
|
+
* @module lib/color-conversion-utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Converts a hexadecimal color code to an HSL string for CSS variables.
|
|
9
|
+
* The output format matches Tailwind CSS HSL variable format: "h s% l%"
|
|
10
|
+
*
|
|
11
|
+
* @param hexColor - Hex color code (e.g., "#06b6d4" or "06b6d4")
|
|
12
|
+
* @returns HSL values as "h s% l%" string suitable for CSS variables
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* convertHexToHslString("#06b6d4"); // "187 94% 43%"
|
|
17
|
+
* convertHexToHslString("#ec4899"); // "330 81% 60%"
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function convertHexToHslString(hexColor: string): string {
|
|
21
|
+
// Remove # if present
|
|
22
|
+
const cleanHex = hexColor.replace("#", "");
|
|
23
|
+
|
|
24
|
+
// Parse RGB values
|
|
25
|
+
const r = Number.parseInt(cleanHex.slice(0, 2), 16) / 255;
|
|
26
|
+
const g = Number.parseInt(cleanHex.slice(2, 4), 16) / 255;
|
|
27
|
+
const b = Number.parseInt(cleanHex.slice(4, 6), 16) / 255;
|
|
28
|
+
|
|
29
|
+
const max = Math.max(r, g, b);
|
|
30
|
+
const min = Math.min(r, g, b);
|
|
31
|
+
let h = 0;
|
|
32
|
+
let s = 0;
|
|
33
|
+
const l = (max + min) / 2;
|
|
34
|
+
|
|
35
|
+
if (max !== min) {
|
|
36
|
+
const d = max - min;
|
|
37
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
38
|
+
|
|
39
|
+
// max is always r, g, or b - no default case needed
|
|
40
|
+
if (max === r) {
|
|
41
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
42
|
+
} else if (max === g) {
|
|
43
|
+
h = ((b - r) / d + 2) / 6;
|
|
44
|
+
} else {
|
|
45
|
+
h = ((r - g) / d + 4) / 6;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Converts HSL color values to a hexadecimal color string.
|
|
54
|
+
*
|
|
55
|
+
* @param hue - Hue value (0-360)
|
|
56
|
+
* @param saturation - Saturation percentage (0-100)
|
|
57
|
+
* @param lightness - Lightness percentage (0-100)
|
|
58
|
+
* @returns Hex color code (e.g., "#06b6d4")
|
|
59
|
+
*/
|
|
60
|
+
export function convertHslToHexString(hue: number, saturation: number, lightness: number): string {
|
|
61
|
+
const sNorm = saturation / 100;
|
|
62
|
+
const lNorm = lightness / 100;
|
|
63
|
+
|
|
64
|
+
const c = (1 - Math.abs(2 * lNorm - 1)) * sNorm;
|
|
65
|
+
const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));
|
|
66
|
+
const m = lNorm - c / 2;
|
|
67
|
+
|
|
68
|
+
const getRgb = (): [number, number, number] => {
|
|
69
|
+
if (hue >= 0 && hue < 60) return [c, x, 0];
|
|
70
|
+
if (hue >= 60 && hue < 120) return [x, c, 0];
|
|
71
|
+
if (hue >= 120 && hue < 180) return [0, c, x];
|
|
72
|
+
if (hue >= 180 && hue < 240) return [0, x, c];
|
|
73
|
+
if (hue >= 240 && hue < 300) return [x, 0, c];
|
|
74
|
+
return [c, 0, x];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const rgb = getRgb();
|
|
78
|
+
|
|
79
|
+
const toHex = (n: number) =>
|
|
80
|
+
Math.round((n + m) * 255)
|
|
81
|
+
.toString(16)
|
|
82
|
+
.padStart(2, "0");
|
|
83
|
+
|
|
84
|
+
return `#${toHex(rgb[0])}${toHex(rgb[1])}${toHex(rgb[2])}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validates whether a string is a valid 6-digit hexadecimal color code.
|
|
89
|
+
*
|
|
90
|
+
* @param hexColor - String to validate
|
|
91
|
+
* @returns True if valid 6-digit hex color (with or without #)
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* validateHexColorFormat("#06b6d4"); // true
|
|
96
|
+
* validateHexColorFormat("06b6d4"); // true
|
|
97
|
+
* validateHexColorFormat("#FFF"); // false (3-digit not supported)
|
|
98
|
+
* validateHexColorFormat("invalid"); // false
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function validateHexColorFormat(hexColor: string): boolean {
|
|
102
|
+
return /^#?[\dA-Fa-f]{6}$/u.test(hexColor);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Generates the complementary (inverse) color for a given hex color.
|
|
107
|
+
*
|
|
108
|
+
* @param hexColor - Hex color code
|
|
109
|
+
* @returns Complementary hex color code
|
|
110
|
+
*/
|
|
111
|
+
export function calculateComplementaryHexColor(hexColor: string): string {
|
|
112
|
+
const cleanHex = hexColor.replace("#", "");
|
|
113
|
+
const r = 255 - Number.parseInt(cleanHex.slice(0, 2), 16);
|
|
114
|
+
const g = 255 - Number.parseInt(cleanHex.slice(2, 4), 16);
|
|
115
|
+
const b = 255 - Number.parseInt(cleanHex.slice(4, 6), 16);
|
|
116
|
+
|
|
117
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Adjusts the lightness of a hexadecimal color by a specified amount.
|
|
122
|
+
*
|
|
123
|
+
* @param hexColor - Hex color code
|
|
124
|
+
* @param lightnessAdjustment - Amount to adjust lightness (-100 to 100)
|
|
125
|
+
* @returns Adjusted hex color code
|
|
126
|
+
*/
|
|
127
|
+
export function adjustHexColorLightness(hexColor: string, lightnessAdjustment: number): string {
|
|
128
|
+
const hsl = convertHexToHslString(hexColor);
|
|
129
|
+
const [h, s, l] = hsl.split(" ").map((v, i) => (i === 0 ? Number.parseInt(v, 10) : Number.parseInt(v.replace("%", ""), 10)));
|
|
130
|
+
|
|
131
|
+
const newL = Math.max(0, Math.min(100, (l ?? 50) + lightnessAdjustment));
|
|
132
|
+
return convertHslToHexString(h ?? 0, s ?? 50, newL);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Parses an HSL CSS variable string into its numeric components.
|
|
137
|
+
*
|
|
138
|
+
* @param hslString - HSL string in format "h s% l%"
|
|
139
|
+
* @returns Object with hue, saturation, lightness values or null if invalid
|
|
140
|
+
*/
|
|
141
|
+
export function parseHslStringToComponents(hslString: string): {hue: number; saturation: number; lightness: number} | null {
|
|
142
|
+
const pattern = /^(?<hue>\d+)\s+(?<sat>\d+)%\s+(?<light>\d+)%$/u;
|
|
143
|
+
const match = pattern.exec(hslString);
|
|
144
|
+
if (!match?.groups) return null;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
hue: Number.parseInt(match.groups["hue"] ?? "0", 10),
|
|
148
|
+
saturation: Number.parseInt(match.groups["sat"] ?? "0", 10),
|
|
149
|
+
lightness: Number.parseInt(match.groups["light"] ?? "0", 10),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Legacy aliases for backwards compatibility (deprecated)
|
|
154
|
+
/** @deprecated Use convertHexToHslString instead */
|
|
155
|
+
export const hexToHsl = convertHexToHslString;
|
|
156
|
+
/** @deprecated Use convertHslToHexString instead */
|
|
157
|
+
export const hslToHex = convertHslToHexString;
|
|
158
|
+
/** @deprecated Use validateHexColorFormat instead */
|
|
159
|
+
export const isValidHexColor = validateHexColorFormat;
|
|
160
|
+
/** @deprecated Use calculateComplementaryHexColor instead */
|
|
161
|
+
export const getComplementaryColor = calculateComplementaryHexColor;
|
|
162
|
+
/** @deprecated Use adjustHexColorLightness instead */
|
|
163
|
+
export const adjustLightness = adjustHexColorLightness;
|
|
164
|
+
/** @deprecated Use parseHslStringToComponents instead */
|
|
165
|
+
export const parseHslString = parseHslStringToComponents;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {describe, expect, it} from "vitest";
|
|
2
|
+
import {cn} from "./utilities";
|
|
3
|
+
|
|
4
|
+
describe("cn utility", () => {
|
|
5
|
+
it("should merge class names", () => {
|
|
6
|
+
expect(cn("class1", "class2")).toBe("class1 class2");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("should handle conditional classes", () => {
|
|
10
|
+
expect(cn("class1", true && "class2", false && "class3")).toBe("class1 class2");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should handle null and undefined", () => {
|
|
14
|
+
expect(cn("class1", null, undefined)).toBe("class1");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should merge tailwind classes correctly", () => {
|
|
18
|
+
expect(cn("px-2", "px-4")).toBe("px-4");
|
|
19
|
+
expect(cn("text-red-500", "text-blue-500")).toBe("text-blue-500");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should handle arrays of classes", () => {
|
|
23
|
+
expect(cn(["class1", "class2"])).toBe("class1 class2");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should handle objects of classes", () => {
|
|
27
|
+
expect(cn({class1: true, class2: false, class3: true})).toBe("class1 class3");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should handle complex nested structures", () => {
|
|
31
|
+
expect(cn("base", ["nested1", {nested2: true}], {obj1: false, obj2: true})).toBe("base nested1 nested2 obj2");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should handle empty input", () => {
|
|
35
|
+
expect(cn()).toBe("");
|
|
36
|
+
});
|
|
37
|
+
});
|