@mgcrea/react-native-tailwind 0.12.1 → 0.14.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 +45 -2031
- package/dist/babel/index.cjs +1726 -1094
- package/dist/babel/plugin/componentScope.d.ts +26 -0
- package/dist/babel/plugin/componentScope.ts +87 -0
- package/dist/babel/plugin/state.d.ts +123 -0
- package/dist/babel/plugin/state.ts +185 -0
- package/dist/babel/plugin/visitors/className.d.ts +11 -0
- package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +285 -572
- package/dist/babel/plugin/visitors/className.ts +652 -0
- package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
- package/dist/babel/plugin/visitors/imports.d.ts +11 -0
- package/dist/babel/plugin/visitors/imports.test.ts +88 -0
- package/dist/babel/plugin/visitors/imports.ts +116 -0
- package/dist/babel/plugin/visitors/program.d.ts +15 -0
- package/dist/babel/plugin/visitors/program.test.ts +325 -0
- package/dist/babel/plugin/visitors/program.ts +116 -0
- package/dist/babel/plugin/visitors/tw.d.ts +16 -0
- package/dist/babel/plugin/visitors/tw.test.ts +771 -0
- package/dist/babel/plugin/visitors/tw.ts +148 -0
- package/dist/babel/plugin.d.ts +3 -96
- package/dist/babel/plugin.test.ts +470 -0
- package/dist/babel/plugin.ts +28 -963
- package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
- package/dist/babel/utils/componentSupport.test.ts +20 -7
- package/dist/babel/utils/componentSupport.ts +2 -0
- package/dist/babel/utils/directionalModifierProcessing.d.ts +34 -0
- package/dist/babel/utils/directionalModifierProcessing.ts +99 -0
- package/dist/babel/utils/modifierProcessing.ts +21 -0
- package/dist/babel/utils/platformModifierProcessing.ts +11 -0
- package/dist/babel/utils/styleInjection.d.ts +31 -0
- package/dist/babel/utils/styleInjection.ts +253 -7
- package/dist/babel/utils/twProcessing.d.ts +2 -0
- package/dist/babel/utils/twProcessing.ts +103 -3
- package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
- package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
- package/dist/components/TouchableOpacity.d.ts +35 -0
- package/dist/components/TouchableOpacity.js +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +1 -0
- package/dist/config/markers.d.ts +5 -0
- package/dist/config/markers.js +1 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.js +1 -1
- package/dist/parser/borders.d.ts +3 -1
- package/dist/parser/borders.js +1 -1
- package/dist/parser/borders.test.js +1 -1
- package/dist/parser/colors.js +1 -1
- package/dist/parser/colors.test.js +1 -1
- package/dist/parser/index.d.ts +2 -2
- package/dist/parser/index.js +1 -1
- package/dist/parser/layout.js +1 -1
- package/dist/parser/layout.test.js +1 -1
- package/dist/parser/modifiers.d.ts +32 -2
- package/dist/parser/modifiers.js +1 -1
- package/dist/parser/modifiers.test.js +1 -1
- package/dist/parser/sizing.js +1 -1
- package/dist/parser/spacing.d.ts +1 -1
- package/dist/parser/spacing.js +1 -1
- package/dist/parser/spacing.test.js +1 -1
- package/dist/parser/typography.test.js +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +4 -4
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +4 -4
- package/package.json +6 -6
- package/src/babel/plugin/componentScope.ts +87 -0
- package/src/babel/plugin/state.ts +185 -0
- package/src/babel/plugin/visitors/className.test.ts +1625 -0
- package/src/babel/plugin/visitors/className.ts +652 -0
- package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
- package/src/babel/plugin/visitors/imports.test.ts +88 -0
- package/src/babel/plugin/visitors/imports.ts +116 -0
- package/src/babel/plugin/visitors/program.test.ts +325 -0
- package/src/babel/plugin/visitors/program.ts +116 -0
- package/src/babel/plugin/visitors/tw.test.ts +771 -0
- package/src/babel/plugin/visitors/tw.ts +148 -0
- package/src/babel/plugin.ts +28 -963
- package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
- package/src/babel/utils/componentSupport.test.ts +20 -7
- package/src/babel/utils/componentSupport.ts +2 -0
- package/src/babel/utils/directionalModifierProcessing.ts +99 -0
- package/src/babel/utils/modifierProcessing.ts +21 -0
- package/src/babel/utils/platformModifierProcessing.ts +11 -0
- package/src/babel/utils/styleInjection.ts +253 -7
- package/src/babel/utils/twProcessing.ts +103 -3
- package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
- package/src/components/TouchableOpacity.tsx +71 -0
- package/src/components/index.ts +3 -0
- package/src/config/markers.ts +5 -0
- package/src/index.ts +4 -5
- package/src/parser/borders.test.ts +162 -0
- package/src/parser/borders.ts +67 -9
- package/src/parser/colors.test.ts +249 -0
- package/src/parser/colors.ts +38 -0
- package/src/parser/index.ts +4 -2
- package/src/parser/layout.test.ts +74 -0
- package/src/parser/layout.ts +94 -0
- package/src/parser/modifiers.test.ts +206 -0
- package/src/parser/modifiers.ts +62 -3
- package/src/parser/sizing.ts +11 -0
- package/src/parser/spacing.test.ts +66 -0
- package/src/parser/spacing.ts +15 -5
- package/src/parser/typography.test.ts +8 -0
- package/src/parser/typography.ts +4 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { transform } from "../../../../test/helpers/babelTransform.js";
|
|
4
|
+
import type { PluginOptions } from "../state.js";
|
|
5
|
+
|
|
6
|
+
describe("tw visitor - template tag transformation", () => {
|
|
7
|
+
it("should transform simple tw template literal", () => {
|
|
8
|
+
const input = `
|
|
9
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
10
|
+
const styles = tw\`bg-blue-500 m-4\`;
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
const output = transform(input);
|
|
14
|
+
|
|
15
|
+
// Should have StyleSheet import (either ESM or CommonJS)
|
|
16
|
+
expect(output).toMatch(/import.*StyleSheet.*from "react-native"|require\("react-native"\)/);
|
|
17
|
+
expect(output).toContain("StyleSheet");
|
|
18
|
+
|
|
19
|
+
// Should have _twStyles definition
|
|
20
|
+
expect(output).toContain("_twStyles");
|
|
21
|
+
expect(output).toContain("StyleSheet.create");
|
|
22
|
+
|
|
23
|
+
// Should transform tw call to object with style property
|
|
24
|
+
expect(output).toContain("style:");
|
|
25
|
+
expect(output).toContain("_twStyles._bg_blue_500_m_4");
|
|
26
|
+
|
|
27
|
+
// Should remove tw import
|
|
28
|
+
expect(output).not.toContain("from '@mgcrea/react-native-tailwind'");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should transform tw with state modifiers", () => {
|
|
32
|
+
const input = `
|
|
33
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
34
|
+
const styles = tw\`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300\`;
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const output = transform(input);
|
|
38
|
+
|
|
39
|
+
// Should have base style
|
|
40
|
+
expect(output).toContain("style:");
|
|
41
|
+
expect(output).toContain("_bg_blue_500");
|
|
42
|
+
|
|
43
|
+
// Should have activeStyle
|
|
44
|
+
expect(output).toContain("activeStyle:");
|
|
45
|
+
expect(output).toContain("_active_bg_blue_700");
|
|
46
|
+
|
|
47
|
+
// Should have disabledStyle
|
|
48
|
+
expect(output).toContain("disabledStyle:");
|
|
49
|
+
expect(output).toContain("_disabled_bg_gray_300");
|
|
50
|
+
|
|
51
|
+
// Should create StyleSheet with all styles
|
|
52
|
+
expect(output).toContain("backgroundColor:");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should inject StyleSheet.create after imports", () => {
|
|
56
|
+
const input = `
|
|
57
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
58
|
+
import { View } from 'react-native';
|
|
59
|
+
|
|
60
|
+
const styles = tw\`m-4\`;
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const output = transform(input);
|
|
64
|
+
|
|
65
|
+
// Find the position of imports and StyleSheet.create
|
|
66
|
+
const viewImportPos = output.indexOf('require("react-native")');
|
|
67
|
+
const styleSheetCreatePos = output.indexOf("_twStyles");
|
|
68
|
+
|
|
69
|
+
// StyleSheet.create should come after imports
|
|
70
|
+
expect(styleSheetCreatePos).toBeGreaterThan(viewImportPos);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should handle tw in object literals", () => {
|
|
74
|
+
const input = `
|
|
75
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
76
|
+
|
|
77
|
+
const sizeVariants = {
|
|
78
|
+
sm: {
|
|
79
|
+
container: tw\`h-9 px-3\`,
|
|
80
|
+
text: tw\`text-sm\`,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
const output = transform(input);
|
|
86
|
+
|
|
87
|
+
// Should define _twStyles before object literal
|
|
88
|
+
const twStylesPos = output.indexOf("_twStyles");
|
|
89
|
+
const sizeVariantsPos = output.indexOf("sizeVariants");
|
|
90
|
+
|
|
91
|
+
expect(twStylesPos).toBeGreaterThan(0);
|
|
92
|
+
expect(twStylesPos).toBeLessThan(sizeVariantsPos);
|
|
93
|
+
|
|
94
|
+
// Should have both styles
|
|
95
|
+
expect(output).toContain("_h_9_px_3");
|
|
96
|
+
expect(output).toContain("_text_sm");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should handle empty tw template literal", () => {
|
|
100
|
+
const input = `
|
|
101
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
102
|
+
const styles = tw\`\`;
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
const output = transform(input);
|
|
106
|
+
|
|
107
|
+
// Should replace with empty style object
|
|
108
|
+
expect(output).toContain("style:");
|
|
109
|
+
expect(output).toContain("{}");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should preserve other imports from the same package", () => {
|
|
113
|
+
const input = `
|
|
114
|
+
import { tw, TwStyle, COLORS } from '@mgcrea/react-native-tailwind';
|
|
115
|
+
const styles = tw\`m-4\`;
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
const output = transform(input);
|
|
119
|
+
|
|
120
|
+
// Should remove tw but keep other imports
|
|
121
|
+
expect(output).not.toContain('"tw"');
|
|
122
|
+
expect(output).toContain("TwStyle");
|
|
123
|
+
expect(output).toContain("COLORS");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should handle renamed tw import", () => {
|
|
127
|
+
const input = `
|
|
128
|
+
import { tw as customTw } from '@mgcrea/react-native-tailwind';
|
|
129
|
+
const styles = customTw\`m-4 p-2\`;
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
const output = transform(input);
|
|
133
|
+
|
|
134
|
+
// Should still transform the renamed import
|
|
135
|
+
expect(output).toContain("_twStyles");
|
|
136
|
+
expect(output).toContain("_m_4_p_2");
|
|
137
|
+
expect(output).not.toContain("customTw");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should handle multiple tw calls", () => {
|
|
141
|
+
const input = `
|
|
142
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
143
|
+
const style1 = tw\`bg-red-500\`;
|
|
144
|
+
const style2 = tw\`bg-blue-500\`;
|
|
145
|
+
const style3 = tw\`bg-green-500\`;
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
const output = transform(input);
|
|
149
|
+
|
|
150
|
+
// Should have all three styles in StyleSheet
|
|
151
|
+
expect(output).toContain("_bg_red_500");
|
|
152
|
+
expect(output).toContain("_bg_blue_500");
|
|
153
|
+
expect(output).toContain("_bg_green_500");
|
|
154
|
+
|
|
155
|
+
// Should have StyleSheet.create with all styles
|
|
156
|
+
expect(output).toContain("StyleSheet.create");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("should use custom stylesIdentifier option", () => {
|
|
160
|
+
const input = `
|
|
161
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
162
|
+
const styles = tw\`m-4\`;
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
const output = transform(input, { stylesIdentifier: "myStyles" });
|
|
166
|
+
|
|
167
|
+
// Should use custom identifier
|
|
168
|
+
expect(output).toContain("myStyles");
|
|
169
|
+
expect(output).toContain("myStyles._m_4");
|
|
170
|
+
expect(output).not.toContain("_twStyles");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("twStyle visitor - function transformation", () => {
|
|
175
|
+
it("should transform twStyle function call", () => {
|
|
176
|
+
const input = `
|
|
177
|
+
import { twStyle } from '@mgcrea/react-native-tailwind';
|
|
178
|
+
const styles = twStyle('bg-blue-500 m-4');
|
|
179
|
+
`;
|
|
180
|
+
|
|
181
|
+
const output = transform(input);
|
|
182
|
+
|
|
183
|
+
// Should transform to object with style property
|
|
184
|
+
expect(output).toContain("style:");
|
|
185
|
+
expect(output).toContain("_twStyles._bg_blue_500_m_4");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should transform twStyle with modifiers", () => {
|
|
189
|
+
const input = `
|
|
190
|
+
import { twStyle } from '@mgcrea/react-native-tailwind';
|
|
191
|
+
const styles = twStyle('bg-blue-500 active:bg-blue-700');
|
|
192
|
+
`;
|
|
193
|
+
|
|
194
|
+
const output = transform(input);
|
|
195
|
+
|
|
196
|
+
expect(output).toContain("style:");
|
|
197
|
+
expect(output).toContain("activeStyle:");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should handle empty twStyle call", () => {
|
|
201
|
+
const input = `
|
|
202
|
+
import { twStyle } from '@mgcrea/react-native-tailwind';
|
|
203
|
+
const styles = twStyle('');
|
|
204
|
+
`;
|
|
205
|
+
|
|
206
|
+
const output = transform(input);
|
|
207
|
+
|
|
208
|
+
// Should replace with undefined
|
|
209
|
+
expect(output).toContain("undefined");
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("tw/twStyle - color scheme modifiers", () => {
|
|
214
|
+
it("should transform tw with dark: modifier inside component", () => {
|
|
215
|
+
const input = `
|
|
216
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
217
|
+
|
|
218
|
+
function MyComponent() {
|
|
219
|
+
const styles = tw\`bg-white dark:bg-gray-900\`;
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
`;
|
|
223
|
+
|
|
224
|
+
const output = transform(input);
|
|
225
|
+
|
|
226
|
+
// Should inject useColorScheme hook
|
|
227
|
+
expect(output).toContain("useColorScheme");
|
|
228
|
+
expect(output).toContain("_twColorScheme");
|
|
229
|
+
|
|
230
|
+
// Should generate style array with conditionals
|
|
231
|
+
expect(output).toContain("style: [");
|
|
232
|
+
expect(output).toContain('_twColorScheme === "dark"');
|
|
233
|
+
expect(output).toContain("_twStyles._dark_bg_gray_900");
|
|
234
|
+
expect(output).toContain("_twStyles._bg_white");
|
|
235
|
+
|
|
236
|
+
// Should have StyleSheet.create
|
|
237
|
+
expect(output).toContain("StyleSheet.create");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should transform twStyle with light: modifier inside component", () => {
|
|
241
|
+
const input = `
|
|
242
|
+
import { twStyle } from '@mgcrea/react-native-tailwind';
|
|
243
|
+
|
|
244
|
+
export const MyComponent = () => {
|
|
245
|
+
const buttonStyles = twStyle('text-gray-900 light:text-gray-100');
|
|
246
|
+
return null;
|
|
247
|
+
};
|
|
248
|
+
`;
|
|
249
|
+
|
|
250
|
+
const output = transform(input);
|
|
251
|
+
|
|
252
|
+
// Should inject useColorScheme hook
|
|
253
|
+
expect(output).toContain("useColorScheme");
|
|
254
|
+
expect(output).toContain("_twColorScheme");
|
|
255
|
+
|
|
256
|
+
// Should generate style array with conditionals
|
|
257
|
+
expect(output).toContain("style: [");
|
|
258
|
+
expect(output).toContain('_twColorScheme === "light"');
|
|
259
|
+
expect(output).toContain("_twStyles._light_text_gray_100");
|
|
260
|
+
expect(output).toContain("_twStyles._text_gray_900");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should transform tw with both dark: and light: modifiers", () => {
|
|
264
|
+
const input = `
|
|
265
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
266
|
+
|
|
267
|
+
function MyComponent() {
|
|
268
|
+
const styles = tw\`bg-blue-500 dark:bg-blue-900 light:bg-blue-100\`;
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
`;
|
|
272
|
+
|
|
273
|
+
const output = transform(input);
|
|
274
|
+
|
|
275
|
+
// Should have both conditionals
|
|
276
|
+
expect(output).toContain('_twColorScheme === "dark"');
|
|
277
|
+
expect(output).toContain('_twColorScheme === "light"');
|
|
278
|
+
expect(output).toContain("_twStyles._dark_bg_blue_900");
|
|
279
|
+
expect(output).toContain("_twStyles._light_bg_blue_100");
|
|
280
|
+
expect(output).toContain("_twStyles._bg_blue_500");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should combine color scheme modifiers with state modifiers", () => {
|
|
284
|
+
const input = `
|
|
285
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
286
|
+
|
|
287
|
+
function MyComponent() {
|
|
288
|
+
const styles = tw\`bg-white dark:bg-gray-900 active:bg-blue-500\`;
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
`;
|
|
292
|
+
|
|
293
|
+
const output = transform(input);
|
|
294
|
+
|
|
295
|
+
// Should have color scheme conditionals in style array
|
|
296
|
+
expect(output).toContain("style: [");
|
|
297
|
+
expect(output).toContain('_twColorScheme === "dark"');
|
|
298
|
+
|
|
299
|
+
// Should have activeStyle property (separate from color scheme)
|
|
300
|
+
expect(output).toContain("activeStyle:");
|
|
301
|
+
expect(output).toContain("_twStyles._active_bg_blue_500");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should warn if tw with color scheme modifiers used outside component", () => {
|
|
305
|
+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
306
|
+
|
|
307
|
+
const input = `
|
|
308
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
309
|
+
|
|
310
|
+
const globalStyles = tw\`bg-white dark:bg-gray-900\`;
|
|
311
|
+
`;
|
|
312
|
+
|
|
313
|
+
const output = transform(input);
|
|
314
|
+
|
|
315
|
+
// Should warn about usage outside component
|
|
316
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
317
|
+
expect.stringContaining("Color scheme modifiers (dark:, light:) in tw/twStyle calls"),
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// Should not inject hook (no component scope)
|
|
321
|
+
expect(output).not.toContain("useColorScheme");
|
|
322
|
+
|
|
323
|
+
// Should still generate styles but without runtime conditionals
|
|
324
|
+
expect(output).toContain("_twStyles");
|
|
325
|
+
|
|
326
|
+
consoleWarnSpy.mockRestore();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("should handle tw with only dark: modifier (no base class)", () => {
|
|
330
|
+
const input = `
|
|
331
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
332
|
+
|
|
333
|
+
function MyComponent() {
|
|
334
|
+
const styles = tw\`dark:bg-gray-900\`;
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
`;
|
|
338
|
+
|
|
339
|
+
const output = transform(input);
|
|
340
|
+
|
|
341
|
+
// Should still generate style array
|
|
342
|
+
expect(output).toContain("style: [");
|
|
343
|
+
expect(output).toContain('_twColorScheme === "dark"');
|
|
344
|
+
expect(output).toContain("_twStyles._dark_bg_gray_900");
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("should work with custom color scheme hook import", () => {
|
|
348
|
+
const input = `
|
|
349
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
350
|
+
import { useTheme } from '@react-navigation/native';
|
|
351
|
+
|
|
352
|
+
function MyComponent() {
|
|
353
|
+
const styles = tw\`bg-white dark:bg-gray-900\`;
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
`;
|
|
357
|
+
|
|
358
|
+
const options: PluginOptions = {
|
|
359
|
+
colorScheme: {
|
|
360
|
+
importFrom: "@react-navigation/native",
|
|
361
|
+
importName: "useTheme",
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const output = transform(input, options);
|
|
366
|
+
|
|
367
|
+
// Should use existing import (not duplicate)
|
|
368
|
+
const themeImportCount = (output.match(/useTheme/g) ?? []).length;
|
|
369
|
+
// Should appear in import statement and hook call
|
|
370
|
+
expect(themeImportCount).toBeGreaterThanOrEqual(2);
|
|
371
|
+
|
|
372
|
+
// Should call the custom hook
|
|
373
|
+
expect(output).toContain("useTheme()");
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("should generate both style array and darkStyle/lightStyle properties", () => {
|
|
377
|
+
const input = `
|
|
378
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
379
|
+
|
|
380
|
+
function MyComponent() {
|
|
381
|
+
const styles = tw\`bg-white dark:bg-gray-900 light:bg-gray-50\`;
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
`;
|
|
385
|
+
|
|
386
|
+
const output = transform(input);
|
|
387
|
+
|
|
388
|
+
// Should have runtime conditional in style array
|
|
389
|
+
expect(output).toContain("style: [");
|
|
390
|
+
expect(output).toContain('_twColorScheme === "dark"');
|
|
391
|
+
expect(output).toContain('_twColorScheme === "light"');
|
|
392
|
+
|
|
393
|
+
// Should ALSO have darkStyle and lightStyle properties for manual access
|
|
394
|
+
expect(output).toContain("darkStyle:");
|
|
395
|
+
expect(output).toContain("lightStyle:");
|
|
396
|
+
expect(output).toContain("_twStyles._dark_bg_gray_900");
|
|
397
|
+
expect(output).toContain("_twStyles._light_bg_gray_50");
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it("should allow accessing raw color values from darkStyle/lightStyle", () => {
|
|
401
|
+
const input = `
|
|
402
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
403
|
+
|
|
404
|
+
function MyComponent() {
|
|
405
|
+
const btnStyles = tw\`bg-blue-500 dark:bg-blue-900\`;
|
|
406
|
+
// User can access raw hex for Reanimated
|
|
407
|
+
const darkBgColor = btnStyles.darkStyle?.backgroundColor;
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
`;
|
|
411
|
+
|
|
412
|
+
const output = transform(input);
|
|
413
|
+
|
|
414
|
+
// Should have darkStyle property available
|
|
415
|
+
expect(output).toContain("darkStyle:");
|
|
416
|
+
expect(output).toContain("_twStyles._dark_bg_blue_900");
|
|
417
|
+
|
|
418
|
+
// The actual usage line should be preserved (TypeScript/Babel doesn't remove it)
|
|
419
|
+
expect(output).toContain("btnStyles.darkStyle");
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe("tw/twStyle - platform modifiers", () => {
|
|
424
|
+
it("should transform tw with ios: modifier", () => {
|
|
425
|
+
const input = `
|
|
426
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
427
|
+
|
|
428
|
+
function MyComponent() {
|
|
429
|
+
const styles = tw\`bg-white ios:p-6\`;
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
`;
|
|
433
|
+
|
|
434
|
+
const output = transform(input);
|
|
435
|
+
|
|
436
|
+
// Should add Platform import
|
|
437
|
+
expect(output).toContain("Platform");
|
|
438
|
+
expect(output).toContain('from "react-native"');
|
|
439
|
+
|
|
440
|
+
// Should generate style array with Platform.select()
|
|
441
|
+
expect(output).toContain("style: [");
|
|
442
|
+
expect(output).toContain("Platform.select");
|
|
443
|
+
expect(output).toContain("ios:");
|
|
444
|
+
expect(output).toContain("_twStyles._ios_p_6");
|
|
445
|
+
expect(output).toContain("_twStyles._bg_white");
|
|
446
|
+
|
|
447
|
+
// Should have StyleSheet.create
|
|
448
|
+
expect(output).toContain("StyleSheet.create");
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("should transform twStyle with android: modifier", () => {
|
|
452
|
+
const input = `
|
|
453
|
+
import { twStyle } from '@mgcrea/react-native-tailwind';
|
|
454
|
+
|
|
455
|
+
export const MyComponent = () => {
|
|
456
|
+
const buttonStyles = twStyle('bg-blue-500 android:p-8');
|
|
457
|
+
return null;
|
|
458
|
+
};
|
|
459
|
+
`;
|
|
460
|
+
|
|
461
|
+
const output = transform(input);
|
|
462
|
+
|
|
463
|
+
// Should add Platform import
|
|
464
|
+
expect(output).toContain("Platform");
|
|
465
|
+
|
|
466
|
+
// Should generate style array with Platform.select()
|
|
467
|
+
expect(output).toContain("style: [");
|
|
468
|
+
expect(output).toContain("Platform.select");
|
|
469
|
+
expect(output).toContain("android:");
|
|
470
|
+
expect(output).toContain("_twStyles._android_p_8");
|
|
471
|
+
expect(output).toContain("_twStyles._bg_blue_500");
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("should transform tw with multiple platform modifiers", () => {
|
|
475
|
+
const input = `
|
|
476
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
477
|
+
|
|
478
|
+
function MyComponent() {
|
|
479
|
+
const styles = tw\`bg-white ios:p-6 android:p-8 web:p-4\`;
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
`;
|
|
483
|
+
|
|
484
|
+
const output = transform(input);
|
|
485
|
+
|
|
486
|
+
// Should generate Platform.select() with all platforms
|
|
487
|
+
expect(output).toContain("Platform.select");
|
|
488
|
+
expect(output).toContain("ios:");
|
|
489
|
+
expect(output).toContain("android:");
|
|
490
|
+
expect(output).toContain("web:");
|
|
491
|
+
expect(output).toContain("_twStyles._ios_p_6");
|
|
492
|
+
expect(output).toContain("_twStyles._android_p_8");
|
|
493
|
+
expect(output).toContain("_twStyles._web_p_4");
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("should combine platform modifiers with color-scheme modifiers", () => {
|
|
497
|
+
const input = `
|
|
498
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
499
|
+
|
|
500
|
+
function MyComponent() {
|
|
501
|
+
const styles = tw\`bg-white ios:p-6 dark:bg-gray-900\`;
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
`;
|
|
505
|
+
|
|
506
|
+
const output = transform(input);
|
|
507
|
+
|
|
508
|
+
// Should have both Platform and useColorScheme
|
|
509
|
+
expect(output).toContain("Platform");
|
|
510
|
+
expect(output).toContain("useColorScheme");
|
|
511
|
+
expect(output).toContain("_twColorScheme");
|
|
512
|
+
|
|
513
|
+
// Should have both conditionals in style array
|
|
514
|
+
expect(output).toContain("Platform.select");
|
|
515
|
+
expect(output).toContain('_twColorScheme === "dark"');
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it("should generate iosStyle/androidStyle/webStyle properties for manual access", () => {
|
|
519
|
+
const input = `
|
|
520
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
521
|
+
|
|
522
|
+
function MyComponent() {
|
|
523
|
+
const styles = tw\`bg-white ios:p-6 android:p-8 web:p-4\`;
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
`;
|
|
527
|
+
|
|
528
|
+
const output = transform(input);
|
|
529
|
+
|
|
530
|
+
// Should have separate platform style properties
|
|
531
|
+
expect(output).toContain("iosStyle:");
|
|
532
|
+
expect(output).toContain("_twStyles._ios_p_6");
|
|
533
|
+
expect(output).toContain("androidStyle:");
|
|
534
|
+
expect(output).toContain("_twStyles._android_p_8");
|
|
535
|
+
expect(output).toContain("webStyle:");
|
|
536
|
+
expect(output).toContain("_twStyles._web_p_4");
|
|
537
|
+
|
|
538
|
+
// Should also have runtime Platform.select() in style array
|
|
539
|
+
expect(output).toContain("Platform.select");
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it("should work with only platform modifiers (no base class)", () => {
|
|
543
|
+
const input = `
|
|
544
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
545
|
+
|
|
546
|
+
function MyComponent() {
|
|
547
|
+
const styles = tw\`ios:p-6 android:p-8\`;
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
`;
|
|
551
|
+
|
|
552
|
+
const output = transform(input);
|
|
553
|
+
|
|
554
|
+
// Should generate Platform.select() even without base classes
|
|
555
|
+
expect(output).toContain("Platform.select");
|
|
556
|
+
expect(output).toContain("_twStyles._ios_p_6");
|
|
557
|
+
expect(output).toContain("_twStyles._android_p_8");
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("should allow accessing platform-specific styles manually", () => {
|
|
561
|
+
const input = `
|
|
562
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
563
|
+
|
|
564
|
+
function MyComponent() {
|
|
565
|
+
const btnStyles = tw\`bg-blue-500 ios:p-6\`;
|
|
566
|
+
const iosPadding = btnStyles.iosStyle;
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
`;
|
|
570
|
+
|
|
571
|
+
const output = transform(input);
|
|
572
|
+
|
|
573
|
+
// Should have iosStyle property available
|
|
574
|
+
expect(output).toContain("iosStyle:");
|
|
575
|
+
expect(output).toContain("_twStyles._ios_p_6");
|
|
576
|
+
|
|
577
|
+
// The actual usage line should be preserved
|
|
578
|
+
expect(output).toContain("btnStyles.iosStyle");
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("should combine state modifiers with platform modifiers", () => {
|
|
582
|
+
const input = `
|
|
583
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
584
|
+
|
|
585
|
+
function MyComponent() {
|
|
586
|
+
const styles = tw\`bg-white active:bg-blue-500 ios:p-6\`;
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
`;
|
|
590
|
+
|
|
591
|
+
const output = transform(input);
|
|
592
|
+
|
|
593
|
+
// Should have both activeStyle and platform modifiers
|
|
594
|
+
expect(output).toContain("activeStyle:");
|
|
595
|
+
expect(output).toContain("_twStyles._active_bg_blue_500");
|
|
596
|
+
expect(output).toContain("Platform.select");
|
|
597
|
+
expect(output).toContain("_twStyles._ios_p_6");
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
describe("tw/twStyle - integration with className", () => {
|
|
602
|
+
it("should work with both tw and className in same file", () => {
|
|
603
|
+
const input = `
|
|
604
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
605
|
+
import { View } from 'react-native';
|
|
606
|
+
|
|
607
|
+
const styles = tw\`bg-red-500\`;
|
|
608
|
+
|
|
609
|
+
export function Component() {
|
|
610
|
+
return <View className="m-4 p-2" />;
|
|
611
|
+
}
|
|
612
|
+
`;
|
|
613
|
+
|
|
614
|
+
const output = transform(input, undefined, true); // Enable JSX
|
|
615
|
+
|
|
616
|
+
// Should have both styles in StyleSheet
|
|
617
|
+
expect(output).toContain("_bg_red_500");
|
|
618
|
+
expect(output).toContain("_m_4_p_2");
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
describe("tw visitor - directional modifiers (RTL/LTR)", () => {
|
|
623
|
+
it("should transform rtl: modifier in tw template", () => {
|
|
624
|
+
const input = `
|
|
625
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
626
|
+
|
|
627
|
+
function MyComponent() {
|
|
628
|
+
const styles = tw\`p-4 rtl:mr-4\`;
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
`;
|
|
632
|
+
|
|
633
|
+
const output = transform(input);
|
|
634
|
+
|
|
635
|
+
// Should import I18nManager
|
|
636
|
+
expect(output).toContain("I18nManager");
|
|
637
|
+
|
|
638
|
+
// Should declare _twIsRTL variable
|
|
639
|
+
expect(output).toContain("_twIsRTL");
|
|
640
|
+
expect(output).toContain("I18nManager.isRTL");
|
|
641
|
+
|
|
642
|
+
// Should have style array with conditional
|
|
643
|
+
expect(output).toContain("style:");
|
|
644
|
+
expect(output).toContain("_twStyles._p_4");
|
|
645
|
+
expect(output).toMatch(/_twIsRTL\s*&&\s*_twStyles\._rtl_mr_4/);
|
|
646
|
+
|
|
647
|
+
// Should have rtlStyle property
|
|
648
|
+
expect(output).toContain("rtlStyle:");
|
|
649
|
+
expect(output).toContain("_twStyles._rtl_mr_4");
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it("should transform ltr: modifier with negated conditional", () => {
|
|
653
|
+
const input = `
|
|
654
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
655
|
+
|
|
656
|
+
function MyComponent() {
|
|
657
|
+
const styles = tw\`p-4 ltr:ml-4\`;
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
`;
|
|
661
|
+
|
|
662
|
+
const output = transform(input);
|
|
663
|
+
|
|
664
|
+
// Should import I18nManager
|
|
665
|
+
expect(output).toContain("I18nManager");
|
|
666
|
+
|
|
667
|
+
// Should have negated conditional for LTR (!_twIsRTL)
|
|
668
|
+
expect(output).toMatch(/!\s*_twIsRTL\s*&&\s*_twStyles\._ltr_ml_4/);
|
|
669
|
+
|
|
670
|
+
// Should have ltrStyle property
|
|
671
|
+
expect(output).toContain("ltrStyle:");
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it("should combine rtl: and ltr: modifiers", () => {
|
|
675
|
+
const input = `
|
|
676
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
677
|
+
|
|
678
|
+
function MyComponent() {
|
|
679
|
+
const styles = tw\`rtl:mr-4 ltr:ml-4\`;
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
`;
|
|
683
|
+
|
|
684
|
+
const output = transform(input);
|
|
685
|
+
|
|
686
|
+
// Should have both conditionals
|
|
687
|
+
expect(output).toMatch(/_twIsRTL\s*&&\s*_twStyles\._rtl_mr_4/);
|
|
688
|
+
expect(output).toMatch(/!\s*_twIsRTL\s*&&\s*_twStyles\._ltr_ml_4/);
|
|
689
|
+
|
|
690
|
+
// Should have both style properties
|
|
691
|
+
expect(output).toContain("rtlStyle:");
|
|
692
|
+
expect(output).toContain("ltrStyle:");
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it("should combine directional modifiers with platform modifiers", () => {
|
|
696
|
+
const input = `
|
|
697
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
698
|
+
|
|
699
|
+
function MyComponent() {
|
|
700
|
+
const styles = tw\`p-4 ios:p-6 rtl:mr-4\`;
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
`;
|
|
704
|
+
|
|
705
|
+
const output = transform(input);
|
|
706
|
+
|
|
707
|
+
// Should have Platform import
|
|
708
|
+
expect(output).toContain("Platform");
|
|
709
|
+
|
|
710
|
+
// Should have I18nManager import
|
|
711
|
+
expect(output).toContain("I18nManager");
|
|
712
|
+
|
|
713
|
+
// Should have both modifiers in style array
|
|
714
|
+
expect(output).toContain("Platform.select");
|
|
715
|
+
expect(output).toMatch(/_twIsRTL\s*&&/);
|
|
716
|
+
|
|
717
|
+
// Should have iosStyle and rtlStyle properties
|
|
718
|
+
expect(output).toContain("iosStyle:");
|
|
719
|
+
expect(output).toContain("rtlStyle:");
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
it("should combine directional modifiers with state modifiers", () => {
|
|
723
|
+
const input = `
|
|
724
|
+
import { tw } from '@mgcrea/react-native-tailwind';
|
|
725
|
+
|
|
726
|
+
function MyComponent() {
|
|
727
|
+
const styles = tw\`bg-white active:bg-blue-500 rtl:pr-4\`;
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
`;
|
|
731
|
+
|
|
732
|
+
const output = transform(input);
|
|
733
|
+
|
|
734
|
+
// Should have I18nManager import
|
|
735
|
+
expect(output).toContain("I18nManager");
|
|
736
|
+
|
|
737
|
+
// Should have directional conditional
|
|
738
|
+
expect(output).toMatch(/_twIsRTL\s*&&/);
|
|
739
|
+
|
|
740
|
+
// Should have activeStyle property
|
|
741
|
+
expect(output).toContain("activeStyle:");
|
|
742
|
+
expect(output).toContain("_twStyles._active_bg_blue_500");
|
|
743
|
+
|
|
744
|
+
// Should have rtlStyle property
|
|
745
|
+
expect(output).toContain("rtlStyle:");
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it("should work with twStyle function for RTL modifiers", () => {
|
|
749
|
+
const input = `
|
|
750
|
+
import { twStyle } from '@mgcrea/react-native-tailwind';
|
|
751
|
+
|
|
752
|
+
function MyComponent() {
|
|
753
|
+
const styles = twStyle('p-4 rtl:mr-4 ltr:ml-4');
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
`;
|
|
757
|
+
|
|
758
|
+
const output = transform(input);
|
|
759
|
+
|
|
760
|
+
// Should import I18nManager
|
|
761
|
+
expect(output).toContain("I18nManager");
|
|
762
|
+
|
|
763
|
+
// Should have both conditionals
|
|
764
|
+
expect(output).toMatch(/_twIsRTL\s*&&/);
|
|
765
|
+
expect(output).toMatch(/!\s*_twIsRTL\s*&&/);
|
|
766
|
+
|
|
767
|
+
// Should have both style properties
|
|
768
|
+
expect(output).toContain("rtlStyle:");
|
|
769
|
+
expect(output).toContain("ltrStyle:");
|
|
770
|
+
});
|
|
771
|
+
});
|