@mgcrea/react-native-tailwind 0.8.0 → 0.8.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.
@@ -552,7 +552,7 @@ var TAILWIND_COLORS = {
552
552
  function flattenColors(colors, prefix = "") {
553
553
  const result = {};
554
554
  for (const [key, value] of Object.entries(colors)) {
555
- const newKey = prefix ? `${prefix}-${key}` : key;
555
+ const newKey = key === "DEFAULT" && prefix ? prefix : prefix ? `${prefix}-${key}` : key;
556
556
  if (typeof value === "string") {
557
557
  result[newKey] = value;
558
558
  } else if (typeof value === "object" && value !== null) {
@@ -0,0 +1,294 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ DEFAULT_CLASS_ATTRIBUTES,
4
+ buildAttributeMatchers,
5
+ getTargetStyleProp,
6
+ isAttributeSupported,
7
+ } from "./attributeMatchers";
8
+
9
+ describe("DEFAULT_CLASS_ATTRIBUTES", () => {
10
+ it("should contain standard className attributes", () => {
11
+ expect(DEFAULT_CLASS_ATTRIBUTES).toEqual([
12
+ "className",
13
+ "contentContainerClassName",
14
+ "columnWrapperClassName",
15
+ "ListHeaderComponentClassName",
16
+ "ListFooterComponentClassName",
17
+ ]);
18
+ });
19
+
20
+ it("should be a readonly array", () => {
21
+ // TypeScript compile-time check - if this compiles, the const assertion works
22
+ const _typeCheck: readonly string[] = DEFAULT_CLASS_ATTRIBUTES;
23
+ expect(_typeCheck).toBeDefined();
24
+ });
25
+ });
26
+
27
+ describe("buildAttributeMatchers", () => {
28
+ it("should separate exact matches from patterns", () => {
29
+ const attributes = ["className", "containerClassName", "*Style", "custom*"];
30
+ const result = buildAttributeMatchers(attributes);
31
+
32
+ // Exact matches should be in a Set
33
+ expect(result.exactMatches).toBeInstanceOf(Set);
34
+ expect(result.exactMatches.has("className")).toBe(true);
35
+ expect(result.exactMatches.has("containerClassName")).toBe(true);
36
+ expect(result.exactMatches.size).toBe(2);
37
+
38
+ // Patterns should be RegExp objects
39
+ expect(result.patterns).toHaveLength(2);
40
+ expect(result.patterns[0]).toBeInstanceOf(RegExp);
41
+ expect(result.patterns[1]).toBeInstanceOf(RegExp);
42
+ });
43
+
44
+ it("should handle only exact matches", () => {
45
+ const attributes = ["className", "customClass", "anotherClass"];
46
+ const result = buildAttributeMatchers(attributes);
47
+
48
+ expect(result.exactMatches.size).toBe(3);
49
+ expect(result.patterns).toHaveLength(0);
50
+ });
51
+
52
+ it("should handle only patterns", () => {
53
+ const attributes = ["*ClassName", "container*", "*Style*"];
54
+ const result = buildAttributeMatchers(attributes);
55
+
56
+ expect(result.exactMatches.size).toBe(0);
57
+ expect(result.patterns).toHaveLength(3);
58
+ });
59
+
60
+ it("should handle empty array", () => {
61
+ const result = buildAttributeMatchers([]);
62
+
63
+ expect(result.exactMatches.size).toBe(0);
64
+ expect(result.patterns).toHaveLength(0);
65
+ });
66
+
67
+ it("should convert glob patterns to regex correctly", () => {
68
+ const attributes = ["*ClassName", "container*", "*custom*"];
69
+ const result = buildAttributeMatchers(attributes);
70
+
71
+ // Test that patterns match expected strings
72
+ expect(result.patterns[0]?.test("myClassName")).toBe(true);
73
+ expect(result.patterns[0]?.test("fooClassName")).toBe(true);
74
+ expect(result.patterns[0]?.test("className")).toBe(false); // Doesn't start with anything
75
+
76
+ expect(result.patterns[1]?.test("containerStyle")).toBe(true);
77
+ expect(result.patterns[1]?.test("containerFoo")).toBe(true);
78
+ expect(result.patterns[1]?.test("myContainer")).toBe(false);
79
+
80
+ expect(result.patterns[2]?.test("mycustomattr")).toBe(true);
81
+ expect(result.patterns[2]?.test("customattr")).toBe(true);
82
+ expect(result.patterns[2]?.test("attrcustom")).toBe(true);
83
+ });
84
+
85
+ it("should handle multiple wildcards in same pattern", () => {
86
+ const attributes = ["*custom*Class*"];
87
+ const result = buildAttributeMatchers(attributes);
88
+
89
+ expect(result.patterns[0]?.test("mycustomFooClassName")).toBe(true);
90
+ expect(result.patterns[0]?.test("customClassName")).toBe(true);
91
+ expect(result.patterns[0]?.test("foocustombarClassbaz")).toBe(true);
92
+ });
93
+ });
94
+
95
+ describe("isAttributeSupported", () => {
96
+ it("should match exact attributes", () => {
97
+ const { exactMatches, patterns } = buildAttributeMatchers(["className", "customClass"]);
98
+
99
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
100
+ expect(isAttributeSupported("customClass", exactMatches, patterns)).toBe(true);
101
+ expect(isAttributeSupported("otherClass", exactMatches, patterns)).toBe(false);
102
+ });
103
+
104
+ it("should match pattern-based attributes", () => {
105
+ const { exactMatches, patterns } = buildAttributeMatchers(["*ClassName", "container*"]);
106
+
107
+ expect(isAttributeSupported("myClassName", exactMatches, patterns)).toBe(true);
108
+ expect(isAttributeSupported("fooClassName", exactMatches, patterns)).toBe(true);
109
+ expect(isAttributeSupported("containerStyle", exactMatches, patterns)).toBe(true);
110
+ expect(isAttributeSupported("containerFoo", exactMatches, patterns)).toBe(true);
111
+ expect(isAttributeSupported("randomAttr", exactMatches, patterns)).toBe(false);
112
+ });
113
+
114
+ it("should match both exact and pattern attributes", () => {
115
+ const { exactMatches, patterns } = buildAttributeMatchers(["className", "*Style"]);
116
+
117
+ // Exact match
118
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
119
+
120
+ // Pattern match
121
+ expect(isAttributeSupported("containerStyle", exactMatches, patterns)).toBe(true);
122
+ expect(isAttributeSupported("customStyle", exactMatches, patterns)).toBe(true);
123
+
124
+ // No match
125
+ expect(isAttributeSupported("otherAttr", exactMatches, patterns)).toBe(false);
126
+ });
127
+
128
+ it("should prioritize exact matches (performance)", () => {
129
+ // Even if a pattern would match, exact match should work
130
+ const { exactMatches, patterns } = buildAttributeMatchers(["className", "*Name"]);
131
+
132
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
133
+ });
134
+
135
+ it("should handle empty matchers", () => {
136
+ const { exactMatches, patterns } = buildAttributeMatchers([]);
137
+
138
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(false);
139
+ expect(isAttributeSupported("anyAttr", exactMatches, patterns)).toBe(false);
140
+ });
141
+
142
+ it("should be case-sensitive", () => {
143
+ const { exactMatches, patterns } = buildAttributeMatchers(["className"]);
144
+
145
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
146
+ expect(isAttributeSupported("ClassName", exactMatches, patterns)).toBe(false);
147
+ expect(isAttributeSupported("classname", exactMatches, patterns)).toBe(false);
148
+ });
149
+
150
+ it("should match default React Native FlatList attributes", () => {
151
+ const { exactMatches, patterns } = buildAttributeMatchers([...DEFAULT_CLASS_ATTRIBUTES]);
152
+
153
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
154
+ expect(isAttributeSupported("contentContainerClassName", exactMatches, patterns)).toBe(true);
155
+ expect(isAttributeSupported("columnWrapperClassName", exactMatches, patterns)).toBe(true);
156
+ expect(isAttributeSupported("ListHeaderComponentClassName", exactMatches, patterns)).toBe(true);
157
+ expect(isAttributeSupported("ListFooterComponentClassName", exactMatches, patterns)).toBe(true);
158
+ });
159
+
160
+ it("should work with complex glob patterns", () => {
161
+ const { exactMatches, patterns } = buildAttributeMatchers(["*Container*Class*"]);
162
+
163
+ expect(isAttributeSupported("myContainerFooClassName", exactMatches, patterns)).toBe(true);
164
+ expect(isAttributeSupported("ContainerClassName", exactMatches, patterns)).toBe(true);
165
+ expect(isAttributeSupported("fooContainerBarClassBaz", exactMatches, patterns)).toBe(true);
166
+ expect(isAttributeSupported("ContainerStyle", exactMatches, patterns)).toBe(false);
167
+ });
168
+ });
169
+
170
+ describe("getTargetStyleProp", () => {
171
+ it("should convert className to style", () => {
172
+ expect(getTargetStyleProp("className")).toBe("style");
173
+ });
174
+
175
+ it("should convert *ClassName to *Style", () => {
176
+ expect(getTargetStyleProp("contentContainerClassName")).toBe("contentContainerStyle");
177
+ expect(getTargetStyleProp("columnWrapperClassName")).toBe("columnWrapperStyle");
178
+ expect(getTargetStyleProp("ListHeaderComponentClassName")).toBe("ListHeaderComponentStyle");
179
+ expect(getTargetStyleProp("ListFooterComponentClassName")).toBe("ListFooterComponentStyle");
180
+ });
181
+
182
+ it("should handle custom className attributes", () => {
183
+ expect(getTargetStyleProp("customClassName")).toBe("customStyle");
184
+ expect(getTargetStyleProp("myCustomClassName")).toBe("myCustomStyle");
185
+ expect(getTargetStyleProp("fooBarClassName")).toBe("fooBarStyle");
186
+ });
187
+
188
+ it("should return style for attributes not ending in ClassName", () => {
189
+ expect(getTargetStyleProp("customClass")).toBe("style");
190
+ expect(getTargetStyleProp("class")).toBe("style");
191
+ expect(getTargetStyleProp("myAttr")).toBe("style");
192
+ expect(getTargetStyleProp("")).toBe("style");
193
+ });
194
+
195
+ it("should handle edge cases", () => {
196
+ // Attribute IS exactly "ClassName"
197
+ expect(getTargetStyleProp("ClassName")).toBe("Style");
198
+
199
+ // Multiple "ClassName" occurrences (only last one replaced)
200
+ expect(getTargetStyleProp("classNameClassName")).toBe("classNameStyle");
201
+ });
202
+
203
+ it("should be case-sensitive", () => {
204
+ expect(getTargetStyleProp("classname")).toBe("style");
205
+ expect(getTargetStyleProp("CLASSNAME")).toBe("style");
206
+ expect(getTargetStyleProp("classNamee")).toBe("style");
207
+ });
208
+
209
+ it("should handle all default attributes correctly", () => {
210
+ const expectedMappings = [
211
+ ["className", "style"],
212
+ ["contentContainerClassName", "contentContainerStyle"],
213
+ ["columnWrapperClassName", "columnWrapperStyle"],
214
+ ["ListHeaderComponentClassName", "ListHeaderComponentStyle"],
215
+ ["ListFooterComponentClassName", "ListFooterComponentStyle"],
216
+ ] as const;
217
+
218
+ for (const [input, expected] of expectedMappings) {
219
+ expect(getTargetStyleProp(input)).toBe(expected);
220
+ }
221
+ });
222
+ });
223
+
224
+ describe("Integration - Real-world scenarios", () => {
225
+ it("should handle FlatList with multiple className attributes", () => {
226
+ const { exactMatches, patterns } = buildAttributeMatchers([...DEFAULT_CLASS_ATTRIBUTES]);
227
+
228
+ // All FlatList className props should be supported
229
+ const flatListAttrs = [
230
+ ["className", "style"],
231
+ ["contentContainerClassName", "contentContainerStyle"],
232
+ ["columnWrapperClassName", "columnWrapperStyle"],
233
+ ["ListHeaderComponentClassName", "ListHeaderComponentStyle"],
234
+ ["ListFooterComponentClassName", "ListFooterComponentStyle"],
235
+ ] as const;
236
+
237
+ for (const [attr, expectedStyle] of flatListAttrs) {
238
+ expect(isAttributeSupported(attr, exactMatches, patterns)).toBe(true);
239
+ expect(getTargetStyleProp(attr)).toBe(expectedStyle);
240
+ }
241
+ });
242
+
243
+ it("should support custom wildcard pattern for all *ClassName attributes", () => {
244
+ const { exactMatches, patterns } = buildAttributeMatchers(["*ClassName"]);
245
+
246
+ // Should match any attribute ending in ClassName (with at least one char before)
247
+ // Note: The regex ^.*ClassName$ requires at least one character due to .*
248
+ expect(isAttributeSupported("myCustomClassName", exactMatches, patterns)).toBe(true);
249
+ expect(isAttributeSupported("fooBarBazClassName", exactMatches, patterns)).toBe(true);
250
+ expect(isAttributeSupported("xClassName", exactMatches, patterns)).toBe(true);
251
+
252
+ // Edge case: bare "className" needs at least one char before it for .* to match
253
+ // If you want to include "className", add it explicitly or use a different pattern
254
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(false);
255
+
256
+ // Should convert to corresponding style prop
257
+ expect(getTargetStyleProp("myCustomClassName")).toBe("myCustomStyle");
258
+
259
+ // Should not match non-className attributes
260
+ expect(isAttributeSupported("myCustomClass", exactMatches, patterns)).toBe(false);
261
+ });
262
+
263
+ it("should support combining exact matches with patterns", () => {
264
+ const { exactMatches, patterns } = buildAttributeMatchers([
265
+ "className",
266
+ "customClass",
267
+ "*ClassName",
268
+ "container*",
269
+ ]);
270
+
271
+ // Exact matches
272
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
273
+ expect(isAttributeSupported("customClass", exactMatches, patterns)).toBe(true);
274
+
275
+ // Pattern matches
276
+ expect(isAttributeSupported("myClassName", exactMatches, patterns)).toBe(true);
277
+ expect(isAttributeSupported("containerStyle", exactMatches, patterns)).toBe(true);
278
+
279
+ // No matches
280
+ expect(isAttributeSupported("randomAttr", exactMatches, patterns)).toBe(false);
281
+ });
282
+
283
+ it("should handle empty configuration gracefully", () => {
284
+ const { exactMatches, patterns } = buildAttributeMatchers([]);
285
+
286
+ // Nothing should be supported
287
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(false);
288
+ expect(isAttributeSupported("anything", exactMatches, patterns)).toBe(false);
289
+
290
+ // getTargetStyleProp should still work
291
+ expect(getTargetStyleProp("className")).toBe("style");
292
+ expect(getTargetStyleProp("customClassName")).toBe("customStyle");
293
+ });
294
+ });