@mgcrea/react-native-tailwind 0.6.0 → 0.7.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/README.md +437 -10
- package/dist/babel/config-loader.ts +1 -23
- package/dist/babel/index.cjs +543 -150
- package/dist/babel/index.d.ts +27 -2
- package/dist/babel/index.test.ts +268 -0
- package/dist/babel/index.ts +352 -44
- package/dist/components/Pressable.d.ts +2 -0
- package/dist/components/TextInput.d.ts +2 -0
- package/dist/config/palettes.d.ts +302 -0
- package/dist/config/palettes.js +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/parser/__snapshots__/colors.test.js.snap +242 -90
- package/dist/parser/__snapshots__/transforms.test.js.snap +58 -0
- package/dist/parser/colors.js +1 -1
- package/dist/parser/colors.test.js +1 -1
- package/dist/parser/layout.js +1 -1
- package/dist/parser/layout.test.js +1 -1
- package/dist/parser/typography.js +1 -1
- package/dist/parser/typography.test.js +1 -1
- package/dist/runtime.cjs +2 -0
- package/dist/runtime.cjs.map +7 -0
- package/dist/runtime.d.ts +139 -0
- package/dist/runtime.js +2 -0
- package/dist/runtime.js.map +7 -0
- package/dist/runtime.test.js +1 -0
- package/dist/stubs/tw.d.ts +60 -0
- package/dist/stubs/tw.js +1 -0
- package/dist/utils/flattenColors.d.ts +16 -0
- package/dist/utils/flattenColors.js +1 -0
- package/dist/utils/flattenColors.test.js +1 -0
- package/dist/utils/modifiers.d.ts +29 -0
- package/dist/utils/modifiers.js +1 -0
- package/dist/utils/modifiers.test.js +1 -0
- package/dist/utils/styleKey.test.js +1 -0
- package/package.json +15 -3
- package/src/babel/config-loader.ts +1 -23
- package/src/babel/index.test.ts +268 -0
- package/src/babel/index.ts +352 -44
- package/src/components/Pressable.tsx +1 -0
- package/src/components/TextInput.tsx +1 -0
- package/src/config/palettes.ts +304 -0
- package/src/index.ts +5 -0
- package/src/parser/colors.test.ts +47 -31
- package/src/parser/colors.ts +5 -110
- package/src/parser/layout.test.ts +35 -0
- package/src/parser/layout.ts +26 -0
- package/src/parser/typography.test.ts +10 -0
- package/src/parser/typography.ts +8 -0
- package/src/runtime.test.ts +325 -0
- package/src/runtime.ts +280 -0
- package/src/stubs/tw.ts +80 -0
- package/src/utils/flattenColors.test.ts +361 -0
- package/src/utils/flattenColors.ts +32 -0
- package/src/utils/modifiers.test.ts +286 -0
- package/src/utils/modifiers.ts +63 -0
- package/src/utils/styleKey.test.ts +168 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { SUPPORTED_MODIFIERS, hasModifiers, splitModifierClasses } from "./modifiers";
|
|
3
|
+
|
|
4
|
+
describe("SUPPORTED_MODIFIERS", () => {
|
|
5
|
+
it("should export list of supported modifiers", () => {
|
|
6
|
+
expect(SUPPORTED_MODIFIERS).toEqual(["active", "focus", "disabled"]);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("should be readonly", () => {
|
|
10
|
+
// TypeScript enforces this at compile time
|
|
11
|
+
expect(Array.isArray(SUPPORTED_MODIFIERS)).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("hasModifiers", () => {
|
|
16
|
+
it("should return true for active modifier", () => {
|
|
17
|
+
expect(hasModifiers("active:bg-blue-500")).toBe(true);
|
|
18
|
+
expect(hasModifiers("m-4 active:bg-blue-500")).toBe(true);
|
|
19
|
+
expect(hasModifiers("active:bg-blue-500 active:text-white")).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should return true for focus modifier", () => {
|
|
23
|
+
expect(hasModifiers("focus:border-blue-500")).toBe(true);
|
|
24
|
+
expect(hasModifiers("p-2 focus:border-blue-500")).toBe(true);
|
|
25
|
+
expect(hasModifiers("focus:bg-blue-500 focus:text-white")).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should return true for disabled modifier", () => {
|
|
29
|
+
expect(hasModifiers("disabled:opacity-50")).toBe(true);
|
|
30
|
+
expect(hasModifiers("bg-blue-500 disabled:opacity-50")).toBe(true);
|
|
31
|
+
expect(hasModifiers("disabled:bg-gray-300 disabled:text-gray-500")).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should return true for multiple different modifiers", () => {
|
|
35
|
+
expect(hasModifiers("active:bg-blue-700 focus:border-blue-500")).toBe(true);
|
|
36
|
+
expect(hasModifiers("bg-blue-500 active:bg-blue-700 disabled:opacity-50")).toBe(true);
|
|
37
|
+
expect(hasModifiers("focus:border-blue-500 disabled:bg-gray-300")).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should return false for no modifiers", () => {
|
|
41
|
+
expect(hasModifiers("bg-blue-500")).toBe(false);
|
|
42
|
+
expect(hasModifiers("m-4 p-2 text-white")).toBe(false);
|
|
43
|
+
expect(hasModifiers("flex items-center justify-center")).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should return false for empty string", () => {
|
|
47
|
+
expect(hasModifiers("")).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should return false for partial matches", () => {
|
|
51
|
+
expect(hasModifiers("active")).toBe(false);
|
|
52
|
+
expect(hasModifiers("focus")).toBe(false);
|
|
53
|
+
expect(hasModifiers("disabled")).toBe(false);
|
|
54
|
+
expect(hasModifiers("active-bg-blue-500")).toBe(false);
|
|
55
|
+
expect(hasModifiers("focusborder-blue-500")).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should detect modifiers anywhere in the string", () => {
|
|
59
|
+
expect(hasModifiers("bg-blue-500 active:bg-blue-700 text-white")).toBe(true);
|
|
60
|
+
expect(hasModifiers("flex items-center focus:border-blue-500 p-4")).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should handle whitespace variations", () => {
|
|
64
|
+
expect(hasModifiers(" active:bg-blue-500 ")).toBe(true);
|
|
65
|
+
expect(hasModifiers("\tactive:bg-blue-500\n")).toBe(true);
|
|
66
|
+
expect(hasModifiers("m-4 active:bg-blue-500 p-2")).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should be case-sensitive", () => {
|
|
70
|
+
expect(hasModifiers("Active:bg-blue-500")).toBe(false);
|
|
71
|
+
expect(hasModifiers("ACTIVE:bg-blue-500")).toBe(false);
|
|
72
|
+
expect(hasModifiers("Focus:border-blue-500")).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("splitModifierClasses", () => {
|
|
77
|
+
it("should split base classes without modifiers", () => {
|
|
78
|
+
const result = splitModifierClasses("m-4 p-2 bg-blue-500");
|
|
79
|
+
|
|
80
|
+
expect(result.base).toEqual(["m-4", "p-2", "bg-blue-500"]);
|
|
81
|
+
expect(result.modifiers.size).toBe(0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should split active modifier classes", () => {
|
|
85
|
+
const result = splitModifierClasses("bg-blue-500 active:bg-blue-700");
|
|
86
|
+
|
|
87
|
+
expect(result.base).toEqual(["bg-blue-500"]);
|
|
88
|
+
expect(result.modifiers.get("active")).toEqual(["bg-blue-700"]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should split focus modifier classes", () => {
|
|
92
|
+
const result = splitModifierClasses("border-gray-300 focus:border-blue-500");
|
|
93
|
+
|
|
94
|
+
expect(result.base).toEqual(["border-gray-300"]);
|
|
95
|
+
expect(result.modifiers.get("focus")).toEqual(["border-blue-500"]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should split disabled modifier classes", () => {
|
|
99
|
+
const result = splitModifierClasses("bg-blue-500 disabled:bg-gray-300");
|
|
100
|
+
|
|
101
|
+
expect(result.base).toEqual(["bg-blue-500"]);
|
|
102
|
+
expect(result.modifiers.get("disabled")).toEqual(["bg-gray-300"]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should split multiple classes with same modifier", () => {
|
|
106
|
+
const result = splitModifierClasses(
|
|
107
|
+
"bg-blue-500 active:bg-blue-700 active:text-white active:border-blue-900",
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(result.base).toEqual(["bg-blue-500"]);
|
|
111
|
+
expect(result.modifiers.get("active")).toEqual(["bg-blue-700", "text-white", "border-blue-900"]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should split multiple different modifiers", () => {
|
|
115
|
+
const result = splitModifierClasses(
|
|
116
|
+
"bg-blue-500 active:bg-blue-700 focus:border-blue-500 disabled:opacity-50",
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(result.base).toEqual(["bg-blue-500"]);
|
|
120
|
+
expect(result.modifiers.get("active")).toEqual(["bg-blue-700"]);
|
|
121
|
+
expect(result.modifiers.get("focus")).toEqual(["border-blue-500"]);
|
|
122
|
+
expect(result.modifiers.get("disabled")).toEqual(["opacity-50"]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should handle complex combination of base and modifier classes", () => {
|
|
126
|
+
const result = splitModifierClasses(
|
|
127
|
+
"m-4 p-2 bg-blue-500 text-white rounded-lg active:bg-blue-700 active:text-gray-100 focus:border-blue-500 disabled:opacity-50 disabled:bg-gray-300",
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(result.base).toEqual(["m-4", "p-2", "bg-blue-500", "text-white", "rounded-lg"]);
|
|
131
|
+
expect(result.modifiers.get("active")).toEqual(["bg-blue-700", "text-gray-100"]);
|
|
132
|
+
expect(result.modifiers.get("focus")).toEqual(["border-blue-500"]);
|
|
133
|
+
expect(result.modifiers.get("disabled")).toEqual(["opacity-50", "bg-gray-300"]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should handle empty string", () => {
|
|
137
|
+
const result = splitModifierClasses("");
|
|
138
|
+
|
|
139
|
+
expect(result.base).toEqual([]);
|
|
140
|
+
expect(result.modifiers.size).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should handle whitespace-only string", () => {
|
|
144
|
+
const result = splitModifierClasses(" ");
|
|
145
|
+
|
|
146
|
+
expect(result.base).toEqual([]);
|
|
147
|
+
expect(result.modifiers.size).toBe(0);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should handle only modifier classes", () => {
|
|
151
|
+
const result = splitModifierClasses("active:bg-blue-700 focus:border-blue-500");
|
|
152
|
+
|
|
153
|
+
expect(result.base).toEqual([]);
|
|
154
|
+
expect(result.modifiers.get("active")).toEqual(["bg-blue-700"]);
|
|
155
|
+
expect(result.modifiers.get("focus")).toEqual(["border-blue-500"]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should filter out empty strings from split", () => {
|
|
159
|
+
const result = splitModifierClasses("m-4 p-2 bg-blue-500");
|
|
160
|
+
|
|
161
|
+
expect(result.base).toEqual(["m-4", "p-2", "bg-blue-500"]);
|
|
162
|
+
expect(result.modifiers.size).toBe(0);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should handle leading and trailing whitespace", () => {
|
|
166
|
+
const result = splitModifierClasses(" m-4 active:bg-blue-700 ");
|
|
167
|
+
|
|
168
|
+
expect(result.base).toEqual(["m-4"]);
|
|
169
|
+
expect(result.modifiers.get("active")).toEqual(["bg-blue-700"]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should handle arbitrary values with modifiers", () => {
|
|
173
|
+
const result = splitModifierClasses("bg-[#ff0000] active:bg-[#00ff00] text-[14px]");
|
|
174
|
+
|
|
175
|
+
expect(result.base).toEqual(["bg-[#ff0000]", "text-[14px]"]);
|
|
176
|
+
expect(result.modifiers.get("active")).toEqual(["bg-[#00ff00]"]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should handle opacity modifiers with state modifiers", () => {
|
|
180
|
+
const result = splitModifierClasses("bg-black/50 active:bg-black/80 text-white/90");
|
|
181
|
+
|
|
182
|
+
expect(result.base).toEqual(["bg-black/50", "text-white/90"]);
|
|
183
|
+
expect(result.modifiers.get("active")).toEqual(["bg-black/80"]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("should preserve order of base classes", () => {
|
|
187
|
+
const result = splitModifierClasses("z-10 m-4 a-1 p-2");
|
|
188
|
+
|
|
189
|
+
expect(result.base).toEqual(["z-10", "m-4", "a-1", "p-2"]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should preserve order of modifier classes", () => {
|
|
193
|
+
const result = splitModifierClasses("active:z-10 active:m-4 active:a-1 active:p-2");
|
|
194
|
+
|
|
195
|
+
expect(result.modifiers.get("active")).toEqual(["z-10", "m-4", "a-1", "p-2"]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should not match modifiers without colon", () => {
|
|
199
|
+
const result = splitModifierClasses("active bg-blue-500 focus border-blue-500");
|
|
200
|
+
|
|
201
|
+
expect(result.base).toEqual(["active", "bg-blue-500", "focus", "border-blue-500"]);
|
|
202
|
+
expect(result.modifiers.size).toBe(0);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("should not match partial modifier names", () => {
|
|
206
|
+
const result = splitModifierClasses("reactive:bg-blue-500 prefocus:border-blue-500");
|
|
207
|
+
|
|
208
|
+
expect(result.base).toEqual(["reactive:bg-blue-500", "prefocus:border-blue-500"]);
|
|
209
|
+
expect(result.modifiers.size).toBe(0);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should handle modifiers at different positions", () => {
|
|
213
|
+
const result = splitModifierClasses(
|
|
214
|
+
"active:bg-blue-700 m-4 focus:border-blue-500 p-2 disabled:opacity-50",
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
expect(result.base).toEqual(["m-4", "p-2"]);
|
|
218
|
+
expect(result.modifiers.get("active")).toEqual(["bg-blue-700"]);
|
|
219
|
+
expect(result.modifiers.get("focus")).toEqual(["border-blue-500"]);
|
|
220
|
+
expect(result.modifiers.get("disabled")).toEqual(["opacity-50"]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("should return empty Map when no modifiers present", () => {
|
|
224
|
+
const result = splitModifierClasses("m-4 p-2 bg-blue-500");
|
|
225
|
+
|
|
226
|
+
expect(result.modifiers).toBeInstanceOf(Map);
|
|
227
|
+
expect(result.modifiers.size).toBe(0);
|
|
228
|
+
expect(result.modifiers.get("active")).toBeUndefined();
|
|
229
|
+
expect(result.modifiers.get("focus")).toBeUndefined();
|
|
230
|
+
expect(result.modifiers.get("disabled")).toBeUndefined();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should return Map with only present modifiers", () => {
|
|
234
|
+
const result = splitModifierClasses("bg-blue-500 active:bg-blue-700");
|
|
235
|
+
|
|
236
|
+
expect(result.modifiers.has("active")).toBe(true);
|
|
237
|
+
expect(result.modifiers.has("focus")).toBe(false);
|
|
238
|
+
expect(result.modifiers.has("disabled")).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should handle real-world button example", () => {
|
|
242
|
+
const result = splitModifierClasses(
|
|
243
|
+
"px-4 py-2 bg-blue-500 text-white rounded-lg active:bg-blue-700 disabled:bg-gray-300 disabled:text-gray-500",
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
expect(result.base).toEqual(["px-4", "py-2", "bg-blue-500", "text-white", "rounded-lg"]);
|
|
247
|
+
expect(result.modifiers.get("active")).toEqual(["bg-blue-700"]);
|
|
248
|
+
expect(result.modifiers.get("disabled")).toEqual(["bg-gray-300", "text-gray-500"]);
|
|
249
|
+
expect(result.modifiers.get("focus")).toBeUndefined();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should handle real-world input example", () => {
|
|
253
|
+
const result = splitModifierClasses(
|
|
254
|
+
"border border-gray-300 rounded p-2 focus:border-blue-500 focus:outline-none disabled:bg-gray-100 disabled:text-gray-400",
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(result.base).toEqual(["border", "border-gray-300", "rounded", "p-2"]);
|
|
258
|
+
expect(result.modifiers.get("focus")).toEqual(["border-blue-500", "outline-none"]);
|
|
259
|
+
expect(result.modifiers.get("disabled")).toEqual(["bg-gray-100", "text-gray-400"]);
|
|
260
|
+
expect(result.modifiers.get("active")).toBeUndefined();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should be consistent across multiple calls", () => {
|
|
264
|
+
const className = "m-4 active:bg-blue-700 focus:border-blue-500";
|
|
265
|
+
const result1 = splitModifierClasses(className);
|
|
266
|
+
const result2 = splitModifierClasses(className);
|
|
267
|
+
|
|
268
|
+
expect(result1.base).toEqual(result2.base);
|
|
269
|
+
expect(result1.modifiers.get("active")).toEqual(result2.modifiers.get("active"));
|
|
270
|
+
expect(result1.modifiers.get("focus")).toEqual(result2.modifiers.get("focus"));
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("should handle negative values with modifiers", () => {
|
|
274
|
+
const result = splitModifierClasses("-m-4 active:-m-8 -translate-x-2");
|
|
275
|
+
|
|
276
|
+
expect(result.base).toEqual(["-m-4", "-translate-x-2"]);
|
|
277
|
+
expect(result.modifiers.get("active")).toEqual(["-m-8"]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("should handle transform classes with modifiers", () => {
|
|
281
|
+
const result = splitModifierClasses("scale-100 active:scale-110 rotate-0 active:rotate-45");
|
|
282
|
+
|
|
283
|
+
expect(result.base).toEqual(["scale-100", "rotate-0"]);
|
|
284
|
+
expect(result.modifiers.get("active")).toEqual(["scale-110", "rotate-45"]);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for parsing state modifiers (active:, focus:, disabled:)
|
|
3
|
+
* Used by both runtime parser and Babel plugin
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Supported state modifiers for Pressable/TextInput components
|
|
7
|
+
export const SUPPORTED_MODIFIERS = ["active", "focus", "disabled"] as const;
|
|
8
|
+
export type SupportedModifier = (typeof SUPPORTED_MODIFIERS)[number];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect if a className contains any state modifiers (active:, focus:, disabled:)
|
|
12
|
+
*/
|
|
13
|
+
export function hasModifiers(className: string): boolean {
|
|
14
|
+
return SUPPORTED_MODIFIERS.some((modifier) => className.includes(`${modifier}:`));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Split className into base classes and modifier-specific classes
|
|
19
|
+
* Returns: { base: string[], modifiers: Map<modifier, string[]> }
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* splitModifierClasses('bg-blue-500 active:bg-blue-700 disabled:bg-gray-300')
|
|
23
|
+
* // Returns:
|
|
24
|
+
* // {
|
|
25
|
+
* // base: ['bg-blue-500'],
|
|
26
|
+
* // modifiers: Map {
|
|
27
|
+
* // 'active' => ['bg-blue-700'],
|
|
28
|
+
* // 'disabled' => ['bg-gray-300']
|
|
29
|
+
* // }
|
|
30
|
+
* // }
|
|
31
|
+
*/
|
|
32
|
+
export function splitModifierClasses(className: string): {
|
|
33
|
+
base: string[];
|
|
34
|
+
modifiers: Map<SupportedModifier, string[]>;
|
|
35
|
+
} {
|
|
36
|
+
const classes = className.split(/\s+/).filter(Boolean);
|
|
37
|
+
const base: string[] = [];
|
|
38
|
+
const modifiers = new Map<SupportedModifier, string[]>();
|
|
39
|
+
|
|
40
|
+
for (const cls of classes) {
|
|
41
|
+
let matched = false;
|
|
42
|
+
for (const modifier of SUPPORTED_MODIFIERS) {
|
|
43
|
+
const prefix = `${modifier}:`;
|
|
44
|
+
if (cls.startsWith(prefix)) {
|
|
45
|
+
const cleanClass = cls.slice(prefix.length);
|
|
46
|
+
if (!modifiers.has(modifier)) {
|
|
47
|
+
modifiers.set(modifier, []);
|
|
48
|
+
}
|
|
49
|
+
const modifierClasses = modifiers.get(modifier);
|
|
50
|
+
if (modifierClasses) {
|
|
51
|
+
modifierClasses.push(cleanClass);
|
|
52
|
+
}
|
|
53
|
+
matched = true;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!matched) {
|
|
58
|
+
base.push(cls);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { base, modifiers };
|
|
63
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { generateStyleKey } from "./styleKey";
|
|
3
|
+
|
|
4
|
+
describe("generateStyleKey", () => {
|
|
5
|
+
it("should generate key with leading underscore", () => {
|
|
6
|
+
expect(generateStyleKey("m-4")).toBe("_m_4");
|
|
7
|
+
expect(generateStyleKey("p-2")).toBe("_p_2");
|
|
8
|
+
expect(generateStyleKey("bg-blue-500")).toBe("_bg_blue_500");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should sort classes alphabetically for consistency", () => {
|
|
12
|
+
expect(generateStyleKey("m-4 p-2")).toBe(generateStyleKey("p-2 m-4"));
|
|
13
|
+
expect(generateStyleKey("bg-blue-500 text-white")).toBe(generateStyleKey("text-white bg-blue-500"));
|
|
14
|
+
expect(generateStyleKey("flex items-center justify-center")).toBe(
|
|
15
|
+
generateStyleKey("justify-center flex items-center"),
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should handle single class", () => {
|
|
20
|
+
expect(generateStyleKey("flex")).toBe("_flex");
|
|
21
|
+
expect(generateStyleKey("hidden")).toBe("_hidden");
|
|
22
|
+
expect(generateStyleKey("absolute")).toBe("_absolute");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should handle multiple classes", () => {
|
|
26
|
+
expect(generateStyleKey("m-4 p-2 bg-blue-500")).toBe("_bg_blue_500_m_4_p_2");
|
|
27
|
+
expect(generateStyleKey("flex items-center justify-center")).toBe("_flex_items_center_justify_center");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should replace special characters with underscores", () => {
|
|
31
|
+
expect(generateStyleKey("bg-[#ff0000]")).toBe("_bg_ff0000_");
|
|
32
|
+
expect(generateStyleKey("text-[14px]")).toBe("_text_14px_");
|
|
33
|
+
expect(generateStyleKey("m-[16px]")).toBe("_m_16px_");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should collapse multiple underscores", () => {
|
|
37
|
+
expect(generateStyleKey("bg-[#ff0000]")).toBe("_bg_ff0000_");
|
|
38
|
+
expect(generateStyleKey("w-[100%]")).toBe("_w_100_");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should handle classes with hyphens", () => {
|
|
42
|
+
expect(generateStyleKey("bg-blue-500")).toBe("_bg_blue_500");
|
|
43
|
+
expect(generateStyleKey("text-gray-700")).toBe("_text_gray_700");
|
|
44
|
+
expect(generateStyleKey("border-red-300")).toBe("_border_red_300");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should handle classes with numbers", () => {
|
|
48
|
+
expect(generateStyleKey("m-4")).toBe("_m_4");
|
|
49
|
+
expect(generateStyleKey("p-8")).toBe("_p_8");
|
|
50
|
+
expect(generateStyleKey("gap-96")).toBe("_gap_96");
|
|
51
|
+
expect(generateStyleKey("text-2xl")).toBe("_text_2xl");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should handle classes with decimals", () => {
|
|
55
|
+
expect(generateStyleKey("m-0.5")).toBe("_m_0_5");
|
|
56
|
+
expect(generateStyleKey("p-1.5")).toBe("_p_1_5");
|
|
57
|
+
expect(generateStyleKey("gap-2.5")).toBe("_gap_2_5");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should handle empty string", () => {
|
|
61
|
+
expect(generateStyleKey("")).toBe("_");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should handle whitespace-only string", () => {
|
|
65
|
+
expect(generateStyleKey(" ")).toBe("_");
|
|
66
|
+
expect(generateStyleKey("\t\n")).toBe("_");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should handle multiple consecutive spaces", () => {
|
|
70
|
+
expect(generateStyleKey("m-4 p-2 bg-blue-500")).toBe("_bg_blue_500_m_4_p_2");
|
|
71
|
+
expect(generateStyleKey("flex items-center")).toBe("_flex_items_center");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should handle leading and trailing spaces", () => {
|
|
75
|
+
expect(generateStyleKey(" m-4 p-2 ")).toBe("_m_4_p_2");
|
|
76
|
+
expect(generateStyleKey("\tflex items-center\n")).toBe("_flex_items_center");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should handle opacity modifiers", () => {
|
|
80
|
+
expect(generateStyleKey("bg-black/50")).toBe("_bg_black_50");
|
|
81
|
+
expect(generateStyleKey("text-white/80")).toBe("_text_white_80");
|
|
82
|
+
expect(generateStyleKey("border-blue-500/30")).toBe("_border_blue_500_30");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should handle state modifiers", () => {
|
|
86
|
+
expect(generateStyleKey("active:bg-blue-700")).toBe("_active_bg_blue_700");
|
|
87
|
+
expect(generateStyleKey("focus:border-blue-500")).toBe("_focus_border_blue_500");
|
|
88
|
+
expect(generateStyleKey("disabled:opacity-50")).toBe("_disabled_opacity_50");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should handle complex combinations", () => {
|
|
92
|
+
expect(generateStyleKey("m-4 p-2 bg-blue-500 text-white rounded-lg")).toBe(
|
|
93
|
+
"_bg_blue_500_m_4_p_2_rounded_lg_text_white",
|
|
94
|
+
);
|
|
95
|
+
expect(generateStyleKey("flex items-center justify-between p-4 bg-gray-100 border-b-2")).toBe(
|
|
96
|
+
"_bg_gray_100_border_b_2_flex_items_center_justify_between_p_4",
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should handle arbitrary values in complex scenarios", () => {
|
|
101
|
+
expect(generateStyleKey("m-[16px] p-[8px] bg-[#ff0000]")).toBe("_bg_ff0000_m_16px_p_8px_");
|
|
102
|
+
expect(generateStyleKey("w-[100%] h-[50px] text-[14px]")).toBe("_h_50px_text_14px_w_100_");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should produce consistent keys regardless of class order", () => {
|
|
106
|
+
const combinations = [
|
|
107
|
+
"m-4 p-2 bg-blue-500",
|
|
108
|
+
"p-2 m-4 bg-blue-500",
|
|
109
|
+
"bg-blue-500 m-4 p-2",
|
|
110
|
+
"bg-blue-500 p-2 m-4",
|
|
111
|
+
"m-4 bg-blue-500 p-2",
|
|
112
|
+
"p-2 bg-blue-500 m-4",
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const keys = combinations.map(generateStyleKey);
|
|
116
|
+
const firstKey = keys[0];
|
|
117
|
+
keys.forEach((key) => {
|
|
118
|
+
expect(key).toBe(firstKey);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should handle duplicate classes", () => {
|
|
123
|
+
// Note: duplicates will appear in output since we don't dedupe
|
|
124
|
+
expect(generateStyleKey("m-4 m-4")).toBe("_m_4_m_4");
|
|
125
|
+
expect(generateStyleKey("flex flex flex")).toBe("_flex_flex_flex");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should handle transform classes", () => {
|
|
129
|
+
expect(generateStyleKey("scale-110")).toBe("_scale_110");
|
|
130
|
+
expect(generateStyleKey("rotate-45")).toBe("_rotate_45");
|
|
131
|
+
expect(generateStyleKey("translate-x-4")).toBe("_translate_x_4");
|
|
132
|
+
expect(generateStyleKey("-rotate-90")).toBe("__rotate_90");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should handle negative values", () => {
|
|
136
|
+
expect(generateStyleKey("-m-4")).toBe("__m_4");
|
|
137
|
+
expect(generateStyleKey("-translate-x-8")).toBe("__translate_x_8");
|
|
138
|
+
expect(generateStyleKey("-rotate-180")).toBe("__rotate_180");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should be deterministic", () => {
|
|
142
|
+
const className = "m-4 p-2 bg-blue-500 text-white rounded-lg flex items-center";
|
|
143
|
+
const key1 = generateStyleKey(className);
|
|
144
|
+
const key2 = generateStyleKey(className);
|
|
145
|
+
const key3 = generateStyleKey(className);
|
|
146
|
+
|
|
147
|
+
expect(key1).toBe(key2);
|
|
148
|
+
expect(key2).toBe(key3);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should create valid JavaScript identifiers", () => {
|
|
152
|
+
// Valid identifiers start with _, $, or letter, and contain _, $, letters, or digits
|
|
153
|
+
const keys = [
|
|
154
|
+
generateStyleKey("m-4"),
|
|
155
|
+
generateStyleKey("bg-blue-500"),
|
|
156
|
+
generateStyleKey("text-[14px]"),
|
|
157
|
+
generateStyleKey("flex items-center"),
|
|
158
|
+
generateStyleKey("active:bg-red-500"),
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
keys.forEach((key) => {
|
|
162
|
+
// Should start with underscore
|
|
163
|
+
expect(key).toMatch(/^_/);
|
|
164
|
+
// Should only contain alphanumeric and underscores
|
|
165
|
+
expect(key).toMatch(/^[_a-zA-Z0-9]+$/);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|