@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
|
@@ -1,236 +1,8 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
2
|
-
import { transformSync } from "@babel/core";
|
|
3
2
|
import { describe, expect, it, vi } from "vitest";
|
|
4
|
-
import
|
|
3
|
+
import { transform } from "../../../../test/helpers/babelTransform.js";
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
* Helper to transform code with the Babel plugin
|
|
8
|
-
*/
|
|
9
|
-
function transform(code: string, options?: PluginOptions, includeJsx = false) {
|
|
10
|
-
const presets = includeJsx
|
|
11
|
-
? ["@babel/preset-react", ["@babel/preset-typescript", { isTSX: true, allExtensions: true }]]
|
|
12
|
-
: [];
|
|
13
|
-
|
|
14
|
-
const result = transformSync(code, {
|
|
15
|
-
presets,
|
|
16
|
-
plugins: [[babelPlugin, options]],
|
|
17
|
-
filename: "test.tsx",
|
|
18
|
-
configFile: false,
|
|
19
|
-
babelrc: false,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return result?.code ?? "";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
describe("Babel plugin - tw template tag transformation", () => {
|
|
26
|
-
it("should transform simple tw template literal", () => {
|
|
27
|
-
const input = `
|
|
28
|
-
import { tw } from '@mgcrea/react-native-tailwind';
|
|
29
|
-
const styles = tw\`bg-blue-500 m-4\`;
|
|
30
|
-
`;
|
|
31
|
-
|
|
32
|
-
const output = transform(input);
|
|
33
|
-
|
|
34
|
-
// Should have StyleSheet import (either ESM or CommonJS)
|
|
35
|
-
expect(output).toMatch(/import.*StyleSheet.*from "react-native"|require\("react-native"\)/);
|
|
36
|
-
expect(output).toContain("StyleSheet");
|
|
37
|
-
|
|
38
|
-
// Should have _twStyles definition
|
|
39
|
-
expect(output).toContain("_twStyles");
|
|
40
|
-
expect(output).toContain("StyleSheet.create");
|
|
41
|
-
|
|
42
|
-
// Should transform tw call to object with style property
|
|
43
|
-
expect(output).toContain("style:");
|
|
44
|
-
expect(output).toContain("_twStyles._bg_blue_500_m_4");
|
|
45
|
-
|
|
46
|
-
// Should remove tw import
|
|
47
|
-
expect(output).not.toContain("from '@mgcrea/react-native-tailwind'");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("should transform tw with state modifiers", () => {
|
|
51
|
-
const input = `
|
|
52
|
-
import { tw } from '@mgcrea/react-native-tailwind';
|
|
53
|
-
const styles = tw\`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300\`;
|
|
54
|
-
`;
|
|
55
|
-
|
|
56
|
-
const output = transform(input);
|
|
57
|
-
|
|
58
|
-
// Should have base style
|
|
59
|
-
expect(output).toContain("style:");
|
|
60
|
-
expect(output).toContain("_bg_blue_500");
|
|
61
|
-
|
|
62
|
-
// Should have activeStyle
|
|
63
|
-
expect(output).toContain("activeStyle:");
|
|
64
|
-
expect(output).toContain("_active_bg_blue_700");
|
|
65
|
-
|
|
66
|
-
// Should have disabledStyle
|
|
67
|
-
expect(output).toContain("disabledStyle:");
|
|
68
|
-
expect(output).toContain("_disabled_bg_gray_300");
|
|
69
|
-
|
|
70
|
-
// Should create StyleSheet with all styles
|
|
71
|
-
expect(output).toContain("backgroundColor:");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("should inject StyleSheet.create after imports", () => {
|
|
75
|
-
const input = `
|
|
76
|
-
import { tw } from '@mgcrea/react-native-tailwind';
|
|
77
|
-
import { View } from 'react-native';
|
|
78
|
-
|
|
79
|
-
const styles = tw\`m-4\`;
|
|
80
|
-
`;
|
|
81
|
-
|
|
82
|
-
const output = transform(input);
|
|
83
|
-
|
|
84
|
-
// Find the position of imports and StyleSheet.create
|
|
85
|
-
const viewImportPos = output.indexOf('require("react-native")');
|
|
86
|
-
const styleSheetCreatePos = output.indexOf("_twStyles");
|
|
87
|
-
|
|
88
|
-
// StyleSheet.create should come after imports
|
|
89
|
-
expect(styleSheetCreatePos).toBeGreaterThan(viewImportPos);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("should handle tw in object literals", () => {
|
|
93
|
-
const input = `
|
|
94
|
-
import { tw } from '@mgcrea/react-native-tailwind';
|
|
95
|
-
|
|
96
|
-
const sizeVariants = {
|
|
97
|
-
sm: {
|
|
98
|
-
container: tw\`h-9 px-3\`,
|
|
99
|
-
text: tw\`text-sm\`,
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
`;
|
|
103
|
-
|
|
104
|
-
const output = transform(input);
|
|
105
|
-
|
|
106
|
-
// Should define _twStyles before object literal
|
|
107
|
-
const twStylesPos = output.indexOf("_twStyles");
|
|
108
|
-
const sizeVariantsPos = output.indexOf("sizeVariants");
|
|
109
|
-
|
|
110
|
-
expect(twStylesPos).toBeGreaterThan(0);
|
|
111
|
-
expect(twStylesPos).toBeLessThan(sizeVariantsPos);
|
|
112
|
-
|
|
113
|
-
// Should have both styles
|
|
114
|
-
expect(output).toContain("_h_9_px_3");
|
|
115
|
-
expect(output).toContain("_text_sm");
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("should handle empty tw template literal", () => {
|
|
119
|
-
const input = `
|
|
120
|
-
import { tw } from '@mgcrea/react-native-tailwind';
|
|
121
|
-
const styles = tw\`\`;
|
|
122
|
-
`;
|
|
123
|
-
|
|
124
|
-
const output = transform(input);
|
|
125
|
-
|
|
126
|
-
// Should replace with empty style object
|
|
127
|
-
expect(output).toContain("style:");
|
|
128
|
-
expect(output).toContain("{}");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("should preserve other imports from the same package", () => {
|
|
132
|
-
const input = `
|
|
133
|
-
import { tw, TwStyle, COLORS } from '@mgcrea/react-native-tailwind';
|
|
134
|
-
const styles = tw\`m-4\`;
|
|
135
|
-
`;
|
|
136
|
-
|
|
137
|
-
const output = transform(input);
|
|
138
|
-
|
|
139
|
-
// Should remove tw but keep other imports
|
|
140
|
-
expect(output).not.toContain('"tw"');
|
|
141
|
-
expect(output).toContain("TwStyle");
|
|
142
|
-
expect(output).toContain("COLORS");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("should handle renamed tw import", () => {
|
|
146
|
-
const input = `
|
|
147
|
-
import { tw as customTw } from '@mgcrea/react-native-tailwind';
|
|
148
|
-
const styles = customTw\`m-4 p-2\`;
|
|
149
|
-
`;
|
|
150
|
-
|
|
151
|
-
const output = transform(input);
|
|
152
|
-
|
|
153
|
-
// Should still transform the renamed import
|
|
154
|
-
expect(output).toContain("_twStyles");
|
|
155
|
-
expect(output).toContain("_m_4_p_2");
|
|
156
|
-
expect(output).not.toContain("customTw");
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("should handle multiple tw calls", () => {
|
|
160
|
-
const input = `
|
|
161
|
-
import { tw } from '@mgcrea/react-native-tailwind';
|
|
162
|
-
const style1 = tw\`bg-red-500\`;
|
|
163
|
-
const style2 = tw\`bg-blue-500\`;
|
|
164
|
-
const style3 = tw\`bg-green-500\`;
|
|
165
|
-
`;
|
|
166
|
-
|
|
167
|
-
const output = transform(input);
|
|
168
|
-
|
|
169
|
-
// Should have all three styles in StyleSheet
|
|
170
|
-
expect(output).toContain("_bg_red_500");
|
|
171
|
-
expect(output).toContain("_bg_blue_500");
|
|
172
|
-
expect(output).toContain("_bg_green_500");
|
|
173
|
-
|
|
174
|
-
// Should have StyleSheet.create with all styles
|
|
175
|
-
expect(output).toContain("StyleSheet.create");
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it("should use custom stylesIdentifier option", () => {
|
|
179
|
-
const input = `
|
|
180
|
-
import { tw } from '@mgcrea/react-native-tailwind';
|
|
181
|
-
const styles = tw\`m-4\`;
|
|
182
|
-
`;
|
|
183
|
-
|
|
184
|
-
const output = transform(input, { stylesIdentifier: "myStyles" });
|
|
185
|
-
|
|
186
|
-
// Should use custom identifier
|
|
187
|
-
expect(output).toContain("myStyles");
|
|
188
|
-
expect(output).toContain("myStyles._m_4");
|
|
189
|
-
expect(output).not.toContain("_twStyles");
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe("Babel plugin - twStyle function transformation", () => {
|
|
194
|
-
it("should transform twStyle function call", () => {
|
|
195
|
-
const input = `
|
|
196
|
-
import { twStyle } from '@mgcrea/react-native-tailwind';
|
|
197
|
-
const styles = twStyle('bg-blue-500 m-4');
|
|
198
|
-
`;
|
|
199
|
-
|
|
200
|
-
const output = transform(input);
|
|
201
|
-
|
|
202
|
-
// Should transform to object with style property
|
|
203
|
-
expect(output).toContain("style:");
|
|
204
|
-
expect(output).toContain("_twStyles._bg_blue_500_m_4");
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it("should transform twStyle with modifiers", () => {
|
|
208
|
-
const input = `
|
|
209
|
-
import { twStyle } from '@mgcrea/react-native-tailwind';
|
|
210
|
-
const styles = twStyle('bg-blue-500 active:bg-blue-700');
|
|
211
|
-
`;
|
|
212
|
-
|
|
213
|
-
const output = transform(input);
|
|
214
|
-
|
|
215
|
-
expect(output).toContain("style:");
|
|
216
|
-
expect(output).toContain("activeStyle:");
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it("should handle empty twStyle call", () => {
|
|
220
|
-
const input = `
|
|
221
|
-
import { twStyle } from '@mgcrea/react-native-tailwind';
|
|
222
|
-
const styles = twStyle('');
|
|
223
|
-
`;
|
|
224
|
-
|
|
225
|
-
const output = transform(input);
|
|
226
|
-
|
|
227
|
-
// Should replace with undefined
|
|
228
|
-
expect(output).toContain("undefined");
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// Note: JSX tests require @babel/preset-react
|
|
233
|
-
describe("Babel plugin - className transformation (existing behavior)", () => {
|
|
5
|
+
describe("className visitor - basic transformation", () => {
|
|
234
6
|
it("should still transform className props", () => {
|
|
235
7
|
const input = `
|
|
236
8
|
import { View } from 'react-native';
|
|
@@ -304,6 +76,35 @@ describe("Babel plugin - className transformation (existing behavior)", () => {
|
|
|
304
76
|
expect(output).toMatch(/_state\s*=>/);
|
|
305
77
|
});
|
|
306
78
|
|
|
79
|
+
it("should preserve 'use client' directive when injecting StyleSheet.create", () => {
|
|
80
|
+
const input = `
|
|
81
|
+
'use client';
|
|
82
|
+
import { View } from 'react-native';
|
|
83
|
+
export function Component() {
|
|
84
|
+
return <View className="m-4 p-2" />;
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const output = transform(input, undefined, true);
|
|
89
|
+
|
|
90
|
+
// 'use client' should be the first statement
|
|
91
|
+
const lines = output.split("\n").filter((l: string) => l.trim());
|
|
92
|
+
const useClientIndex = lines.findIndex(
|
|
93
|
+
(l: string) => l.includes("'use client'") || l.includes('"use client"'),
|
|
94
|
+
);
|
|
95
|
+
expect(useClientIndex).toBe(0);
|
|
96
|
+
|
|
97
|
+
// StyleSheet.create should be in the output
|
|
98
|
+
expect(output).toContain("StyleSheet.create");
|
|
99
|
+
expect(output).toContain("_twStyles");
|
|
100
|
+
|
|
101
|
+
// Imports should come after 'use client', before StyleSheet.create
|
|
102
|
+
const importIndex = lines.findIndex((l: string) => l.includes("import"));
|
|
103
|
+
const styleSheetIndex = lines.findIndex((l: string) => l.includes("StyleSheet.create"));
|
|
104
|
+
expect(importIndex).toBeGreaterThan(useClientIndex);
|
|
105
|
+
expect(styleSheetIndex).toBeGreaterThan(importIndex);
|
|
106
|
+
});
|
|
107
|
+
|
|
307
108
|
it("should merge dynamic className with function-based style prop", () => {
|
|
308
109
|
const input = `
|
|
309
110
|
import { TextInput } from 'react-native';
|
|
@@ -419,7 +220,7 @@ describe("Babel plugin - className transformation (existing behavior)", () => {
|
|
|
419
220
|
});
|
|
420
221
|
});
|
|
421
222
|
|
|
422
|
-
describe("
|
|
223
|
+
describe("className visitor - placeholder: modifier", () => {
|
|
423
224
|
it("should transform placeholder:text-{color} to placeholderTextColor prop", () => {
|
|
424
225
|
const input = `
|
|
425
226
|
import { TextInput } from 'react-native';
|
|
@@ -566,7 +367,7 @@ describe("Babel plugin - placeholder: modifier transformation", () => {
|
|
|
566
367
|
});
|
|
567
368
|
});
|
|
568
369
|
|
|
569
|
-
describe("
|
|
370
|
+
describe("className visitor - platform modifiers", () => {
|
|
570
371
|
it("should transform platform modifiers to Platform.select()", () => {
|
|
571
372
|
const input = `
|
|
572
373
|
import React from 'react';
|
|
@@ -807,7 +608,7 @@ describe("Babel plugin - platform modifier transformation", () => {
|
|
|
807
608
|
});
|
|
808
609
|
});
|
|
809
610
|
|
|
810
|
-
describe("
|
|
611
|
+
describe("className visitor - color scheme modifiers", () => {
|
|
811
612
|
it("should transform dark: modifier to conditional expression", () => {
|
|
812
613
|
const input = `
|
|
813
614
|
import React from 'react';
|
|
@@ -1107,7 +908,7 @@ describe("Babel plugin - color scheme modifier transformation", () => {
|
|
|
1107
908
|
});
|
|
1108
909
|
});
|
|
1109
910
|
|
|
1110
|
-
describe("
|
|
911
|
+
describe("className visitor - custom color scheme hook", () => {
|
|
1111
912
|
it("should use custom import source for color scheme hook", () => {
|
|
1112
913
|
const input = `
|
|
1113
914
|
import React from 'react';
|
|
@@ -1407,506 +1208,418 @@ describe("Babel plugin - custom color scheme hook import", () => {
|
|
|
1407
1208
|
});
|
|
1408
1209
|
});
|
|
1409
1210
|
|
|
1410
|
-
describe("
|
|
1411
|
-
it("should
|
|
1211
|
+
describe("className visitor - directional border colors", () => {
|
|
1212
|
+
it("should transform directional border colors with preset values", () => {
|
|
1412
1213
|
const input = `
|
|
1413
|
-
import { View
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
return <View><Text>Hello</Text></View>;
|
|
1214
|
+
import { View } from 'react-native';
|
|
1215
|
+
export function Component() {
|
|
1216
|
+
return <View className="border-t-red-500 border-l-blue-500" />;
|
|
1417
1217
|
}
|
|
1418
1218
|
`;
|
|
1419
1219
|
|
|
1420
1220
|
const output = transform(input, undefined, true);
|
|
1421
1221
|
|
|
1422
|
-
// Should
|
|
1423
|
-
|
|
1424
|
-
const styleSheetCount = (output.match(/StyleSheet/g) ?? []).length;
|
|
1425
|
-
expect(styleSheetCount).toBe(0);
|
|
1222
|
+
// Should have StyleSheet
|
|
1223
|
+
expect(output).toContain("StyleSheet.create");
|
|
1426
1224
|
|
|
1427
|
-
// Should
|
|
1428
|
-
expect(output).
|
|
1429
|
-
expect(output).
|
|
1225
|
+
// Should generate styles with borderTopColor and borderLeftColor
|
|
1226
|
+
expect(output).toMatch(/borderTopColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
|
|
1227
|
+
expect(output).toMatch(/borderLeftColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
|
|
1430
1228
|
|
|
1431
|
-
//
|
|
1432
|
-
expect(output).toContain("
|
|
1433
|
-
expect(output).toContain("Text");
|
|
1229
|
+
// Should not have className in output
|
|
1230
|
+
expect(output).not.toContain("className");
|
|
1434
1231
|
});
|
|
1435
1232
|
|
|
1436
|
-
it("should
|
|
1233
|
+
it("should combine directional border width and color", () => {
|
|
1437
1234
|
const input = `
|
|
1438
1235
|
import { View } from 'react-native';
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
return <View className="m-4 p-2" />;
|
|
1236
|
+
export function Component() {
|
|
1237
|
+
return <View className="border-l-2 border-l-red-500" />;
|
|
1442
1238
|
}
|
|
1443
1239
|
`;
|
|
1444
1240
|
|
|
1445
1241
|
const output = transform(input, undefined, true);
|
|
1446
1242
|
|
|
1447
|
-
// Should have
|
|
1448
|
-
expect(output).toMatch(/
|
|
1243
|
+
// Should have both borderLeftWidth and borderLeftColor in the StyleSheet
|
|
1244
|
+
expect(output).toMatch(/borderLeftWidth[:\s]*2/);
|
|
1245
|
+
expect(output).toMatch(/borderLeftColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
|
|
1449
1246
|
|
|
1450
|
-
// Should have
|
|
1451
|
-
expect(output).toContain("
|
|
1452
|
-
expect(output).toContain("StyleSheet.create");
|
|
1247
|
+
// Should not have className in output
|
|
1248
|
+
expect(output).not.toContain("className");
|
|
1453
1249
|
});
|
|
1454
1250
|
|
|
1455
|
-
it("should
|
|
1251
|
+
it("should support directional border colors with opacity", () => {
|
|
1456
1252
|
const input = `
|
|
1457
1253
|
import { View } from 'react-native';
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
return <View className="ios:m-4 android:m-2" />;
|
|
1254
|
+
export function Component() {
|
|
1255
|
+
return <View className="border-t-red-500/50 border-b-blue-500/80" />;
|
|
1461
1256
|
}
|
|
1462
1257
|
`;
|
|
1463
1258
|
|
|
1464
1259
|
const output = transform(input, undefined, true);
|
|
1465
1260
|
|
|
1466
|
-
// Should have
|
|
1467
|
-
expect(output).
|
|
1468
|
-
|
|
1469
|
-
// Should have StyleSheet import too
|
|
1470
|
-
expect(output).toContain("StyleSheet");
|
|
1471
|
-
|
|
1472
|
-
// Should use Platform.select
|
|
1473
|
-
expect(output).toContain("Platform.select");
|
|
1261
|
+
// Should have 8-digit hex colors with alpha channel
|
|
1262
|
+
expect(output).toMatch(/borderTopColor[:\s]*['"]#[0-9A-F]{8}['"]/i);
|
|
1263
|
+
expect(output).toMatch(/borderBottomColor[:\s]*['"]#[0-9A-F]{8}['"]/i);
|
|
1474
1264
|
});
|
|
1475
1265
|
|
|
1476
|
-
it("should
|
|
1266
|
+
it("should support directional border colors with arbitrary hex values", () => {
|
|
1477
1267
|
const input = `
|
|
1478
1268
|
import { View } from 'react-native';
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
return <View className="m-4 p-2" />;
|
|
1269
|
+
export function Component() {
|
|
1270
|
+
return <View className="border-t-[#ff0000] border-r-[#abc]" />;
|
|
1482
1271
|
}
|
|
1483
1272
|
`;
|
|
1484
1273
|
|
|
1485
1274
|
const output = transform(input, undefined, true);
|
|
1486
1275
|
|
|
1487
|
-
// Should
|
|
1488
|
-
|
|
1489
|
-
expect(
|
|
1490
|
-
|
|
1491
|
-
// Should still have StyleSheet
|
|
1492
|
-
expect(output).toContain("StyleSheet");
|
|
1276
|
+
// Should have borderTopColor and borderRightColor
|
|
1277
|
+
expect(output).toMatch(/borderTopColor[:\s]*['"]#[0-9a-fA-F]{6}['"]/);
|
|
1278
|
+
expect(output).toMatch(/borderRightColor[:\s]*['"]#[0-9a-fA-F]{6}['"]/);
|
|
1493
1279
|
});
|
|
1494
|
-
});
|
|
1495
1280
|
|
|
1496
|
-
|
|
1497
|
-
it.skip("should expand scheme: modifier into dark: and light: modifiers", () => {
|
|
1498
|
-
// Note: This test requires tailwind.config.js with custom colors defined
|
|
1499
|
-
// The scheme: modifier expands to dark: and light: modifiers which require
|
|
1500
|
-
// the color variants to exist in customColors (e.g., systemGray-dark, systemGray-light)
|
|
1501
|
-
//
|
|
1502
|
-
// Integration test should be done in a real project with tailwind.config.js
|
|
1281
|
+
it("should support all four directional border colors", () => {
|
|
1503
1282
|
const input = `
|
|
1504
1283
|
import { View } from 'react-native';
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1284
|
+
export function Component() {
|
|
1285
|
+
return (
|
|
1286
|
+
<View className="border-t-red-500 border-r-blue-500 border-b-green-500 border-l-yellow-500" />
|
|
1287
|
+
);
|
|
1508
1288
|
}
|
|
1509
1289
|
`;
|
|
1510
1290
|
|
|
1511
1291
|
const output = transform(input, undefined, true);
|
|
1512
1292
|
|
|
1513
|
-
// Should
|
|
1514
|
-
expect(output).
|
|
1515
|
-
expect(output).
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
expect(output).toContain("useColorScheme");
|
|
1519
|
-
expect(output).toContain("_twColorScheme");
|
|
1520
|
-
|
|
1521
|
-
// Should have conditional expressions
|
|
1522
|
-
expect(output).toContain("_twColorScheme === 'dark'");
|
|
1523
|
-
expect(output).toContain("_twColorScheme === 'light'");
|
|
1293
|
+
// Should have all four directional color properties
|
|
1294
|
+
expect(output).toMatch(/borderTopColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
|
|
1295
|
+
expect(output).toMatch(/borderRightColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
|
|
1296
|
+
expect(output).toMatch(/borderBottomColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
|
|
1297
|
+
expect(output).toMatch(/borderLeftColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
|
|
1524
1298
|
});
|
|
1525
|
-
});
|
|
1526
1299
|
|
|
1527
|
-
|
|
1528
|
-
it("should transform tw with dark: modifier inside component", () => {
|
|
1300
|
+
it("should combine directional widths, colors, and general border color", () => {
|
|
1529
1301
|
const input = `
|
|
1530
|
-
import {
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1302
|
+
import { View } from 'react-native';
|
|
1303
|
+
export function Component() {
|
|
1304
|
+
return (
|
|
1305
|
+
<View className="border border-gray-300 border-l-4 border-l-blue-500" />
|
|
1306
|
+
);
|
|
1535
1307
|
}
|
|
1536
1308
|
`;
|
|
1537
1309
|
|
|
1538
|
-
const output = transform(input);
|
|
1539
|
-
|
|
1540
|
-
// Should inject useColorScheme hook
|
|
1541
|
-
expect(output).toContain("useColorScheme");
|
|
1542
|
-
expect(output).toContain("_twColorScheme");
|
|
1310
|
+
const output = transform(input, undefined, true);
|
|
1543
1311
|
|
|
1544
|
-
// Should
|
|
1545
|
-
expect(output).
|
|
1546
|
-
expect(output).
|
|
1547
|
-
expect(output).toContain("_twStyles._dark_bg_gray_900");
|
|
1548
|
-
expect(output).toContain("_twStyles._bg_white");
|
|
1312
|
+
// Should have general border properties
|
|
1313
|
+
expect(output).toMatch(/borderWidth[:\s]*1/);
|
|
1314
|
+
expect(output).toMatch(/borderColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
|
|
1549
1315
|
|
|
1550
|
-
// Should have
|
|
1551
|
-
expect(output).
|
|
1316
|
+
// Should have directional left border properties
|
|
1317
|
+
expect(output).toMatch(/borderLeftWidth[:\s]*4/);
|
|
1318
|
+
expect(output).toMatch(/borderLeftColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
|
|
1552
1319
|
});
|
|
1553
1320
|
|
|
1554
|
-
it("should
|
|
1321
|
+
it("should work with dynamic className containing directional border colors", () => {
|
|
1555
1322
|
const input = `
|
|
1556
|
-
import {
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
}
|
|
1323
|
+
import { View } from 'react-native';
|
|
1324
|
+
export function Component({ isError }) {
|
|
1325
|
+
return (
|
|
1326
|
+
<View className={\`border-t-2 \${isError ? 'border-t-red-500' : 'border-t-gray-300'}\`} />
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1562
1329
|
`;
|
|
1563
1330
|
|
|
1564
|
-
const output = transform(input);
|
|
1331
|
+
const output = transform(input, undefined, true);
|
|
1565
1332
|
|
|
1566
|
-
// Should
|
|
1567
|
-
expect(output).toContain("
|
|
1568
|
-
expect(output).toContain("
|
|
1333
|
+
// Should have StyleSheet with both color options
|
|
1334
|
+
expect(output).toContain("_border_t_2");
|
|
1335
|
+
expect(output).toContain("_border_t_red_500");
|
|
1336
|
+
expect(output).toContain("_border_t_gray_300");
|
|
1569
1337
|
|
|
1570
|
-
// Should
|
|
1571
|
-
expect(output).
|
|
1572
|
-
expect(output).toContain('_twColorScheme === "light"');
|
|
1573
|
-
expect(output).toContain("_twStyles._light_text_gray_100");
|
|
1574
|
-
expect(output).toContain("_twStyles._text_gray_900");
|
|
1338
|
+
// Should have conditional expression with both styles
|
|
1339
|
+
expect(output).toMatch(/isError\s*\?\s*_twStyles\._border_t_red_500/);
|
|
1575
1340
|
});
|
|
1341
|
+
});
|
|
1576
1342
|
|
|
1577
|
-
|
|
1343
|
+
describe("className visitor - directional modifiers (RTL/LTR)", () => {
|
|
1344
|
+
it("should transform rtl: modifier and inject I18nManager import", () => {
|
|
1578
1345
|
const input = `
|
|
1579
|
-
import {
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
const styles = tw\`bg-blue-500 dark:bg-blue-900 light:bg-blue-100\`;
|
|
1583
|
-
return null;
|
|
1346
|
+
import { View } from 'react-native';
|
|
1347
|
+
export function Component() {
|
|
1348
|
+
return <View className="rtl:mr-4" />;
|
|
1584
1349
|
}
|
|
1585
1350
|
`;
|
|
1586
1351
|
|
|
1587
|
-
const output = transform(input);
|
|
1588
|
-
|
|
1589
|
-
// Should have both conditionals
|
|
1590
|
-
expect(output).toContain('_twColorScheme === "dark"');
|
|
1591
|
-
expect(output).toContain('_twColorScheme === "light"');
|
|
1592
|
-
expect(output).toContain("_twStyles._dark_bg_blue_900");
|
|
1593
|
-
expect(output).toContain("_twStyles._light_bg_blue_100");
|
|
1594
|
-
expect(output).toContain("_twStyles._bg_blue_500");
|
|
1595
|
-
});
|
|
1596
|
-
|
|
1597
|
-
it("should combine color scheme modifiers with state modifiers", () => {
|
|
1598
|
-
const input = `
|
|
1599
|
-
import { tw } from '@mgcrea/react-native-tailwind';
|
|
1352
|
+
const output = transform(input, undefined, true);
|
|
1600
1353
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
return null;
|
|
1604
|
-
}
|
|
1605
|
-
`;
|
|
1354
|
+
// Should import I18nManager
|
|
1355
|
+
expect(output).toContain("I18nManager");
|
|
1606
1356
|
|
|
1607
|
-
|
|
1357
|
+
// Should declare _twIsRTL variable
|
|
1358
|
+
expect(output).toContain("_twIsRTL");
|
|
1359
|
+
expect(output).toContain("I18nManager.isRTL");
|
|
1608
1360
|
|
|
1609
|
-
// Should have
|
|
1610
|
-
expect(output).toContain("
|
|
1611
|
-
expect(output).toContain(
|
|
1361
|
+
// Should have StyleSheet with rtl style
|
|
1362
|
+
expect(output).toContain("StyleSheet.create");
|
|
1363
|
+
expect(output).toContain("_rtl_mr_4");
|
|
1612
1364
|
|
|
1613
|
-
// Should have
|
|
1614
|
-
expect(output).
|
|
1615
|
-
expect(output).toContain("_twStyles._active_bg_blue_500");
|
|
1365
|
+
// Should have conditional for RTL
|
|
1366
|
+
expect(output).toMatch(/_twIsRTL\s*&&\s*_twStyles\._rtl_mr_4/);
|
|
1616
1367
|
});
|
|
1617
1368
|
|
|
1618
|
-
it("should
|
|
1619
|
-
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1620
|
-
|
|
1369
|
+
it("should transform ltr: modifier with negated conditional", () => {
|
|
1621
1370
|
const input = `
|
|
1622
|
-
import {
|
|
1623
|
-
|
|
1624
|
-
|
|
1371
|
+
import { View } from 'react-native';
|
|
1372
|
+
export function Component() {
|
|
1373
|
+
return <View className="ltr:ml-4" />;
|
|
1374
|
+
}
|
|
1625
1375
|
`;
|
|
1626
1376
|
|
|
1627
|
-
const output = transform(input);
|
|
1628
|
-
|
|
1629
|
-
// Should warn about usage outside component
|
|
1630
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
1631
|
-
expect.stringContaining("Color scheme modifiers (dark:, light:) in tw/twStyle calls"),
|
|
1632
|
-
);
|
|
1377
|
+
const output = transform(input, undefined, true);
|
|
1633
1378
|
|
|
1634
|
-
// Should
|
|
1635
|
-
expect(output).
|
|
1379
|
+
// Should import I18nManager
|
|
1380
|
+
expect(output).toContain("I18nManager");
|
|
1636
1381
|
|
|
1637
|
-
// Should
|
|
1638
|
-
expect(output).toContain("
|
|
1382
|
+
// Should have StyleSheet with ltr style
|
|
1383
|
+
expect(output).toContain("_ltr_ml_4");
|
|
1639
1384
|
|
|
1640
|
-
|
|
1385
|
+
// Should have negated conditional for LTR (!_twIsRTL)
|
|
1386
|
+
expect(output).toMatch(/!\s*_twIsRTL\s*&&\s*_twStyles\._ltr_ml_4/);
|
|
1641
1387
|
});
|
|
1642
1388
|
|
|
1643
|
-
it("should
|
|
1389
|
+
it("should combine rtl: and ltr: modifiers", () => {
|
|
1644
1390
|
const input = `
|
|
1645
|
-
import {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
const styles = tw\`dark:bg-gray-900\`;
|
|
1649
|
-
return null;
|
|
1391
|
+
import { View } from 'react-native';
|
|
1392
|
+
export function Component() {
|
|
1393
|
+
return <View className="rtl:mr-4 ltr:ml-4" />;
|
|
1650
1394
|
}
|
|
1651
1395
|
`;
|
|
1652
1396
|
|
|
1653
|
-
const output = transform(input);
|
|
1397
|
+
const output = transform(input, undefined, true);
|
|
1398
|
+
|
|
1399
|
+
// Should have both styles
|
|
1400
|
+
expect(output).toContain("_rtl_mr_4");
|
|
1401
|
+
expect(output).toContain("_ltr_ml_4");
|
|
1654
1402
|
|
|
1655
|
-
// Should
|
|
1656
|
-
expect(output).
|
|
1657
|
-
expect(output).
|
|
1658
|
-
expect(output).toContain("_twStyles._dark_bg_gray_900");
|
|
1403
|
+
// Should have both conditionals
|
|
1404
|
+
expect(output).toMatch(/_twIsRTL\s*&&\s*_twStyles\._rtl_mr_4/);
|
|
1405
|
+
expect(output).toMatch(/!\s*_twIsRTL\s*&&\s*_twStyles\._ltr_ml_4/);
|
|
1659
1406
|
});
|
|
1660
1407
|
|
|
1661
|
-
it("should
|
|
1408
|
+
it("should combine directional modifiers with base classes", () => {
|
|
1662
1409
|
const input = `
|
|
1663
|
-
import {
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
function MyComponent() {
|
|
1667
|
-
const styles = tw\`bg-white dark:bg-gray-900\`;
|
|
1668
|
-
return null;
|
|
1410
|
+
import { View } from 'react-native';
|
|
1411
|
+
export function Component() {
|
|
1412
|
+
return <View className="p-4 bg-white rtl:pr-8 ltr:pl-8" />;
|
|
1669
1413
|
}
|
|
1670
1414
|
`;
|
|
1671
1415
|
|
|
1672
|
-
const
|
|
1673
|
-
colorScheme: {
|
|
1674
|
-
importFrom: "@react-navigation/native",
|
|
1675
|
-
importName: "useTheme",
|
|
1676
|
-
},
|
|
1677
|
-
};
|
|
1416
|
+
const output = transform(input, undefined, true);
|
|
1678
1417
|
|
|
1679
|
-
|
|
1418
|
+
// Should have base style
|
|
1419
|
+
expect(output).toContain("_bg_white_p_4");
|
|
1680
1420
|
|
|
1681
|
-
// Should
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
expect(themeImportCount).toBeGreaterThanOrEqual(2);
|
|
1421
|
+
// Should have directional styles
|
|
1422
|
+
expect(output).toContain("_rtl_pr_8");
|
|
1423
|
+
expect(output).toContain("_ltr_pl_8");
|
|
1685
1424
|
|
|
1686
|
-
// Should
|
|
1687
|
-
expect(output).
|
|
1425
|
+
// Should generate an array with base and conditional styles
|
|
1426
|
+
expect(output).toMatch(/style:\s*\[/);
|
|
1688
1427
|
});
|
|
1689
1428
|
|
|
1690
|
-
it("should
|
|
1429
|
+
it("should combine directional modifiers with platform modifiers", () => {
|
|
1691
1430
|
const input = `
|
|
1692
|
-
import {
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
const styles = tw\`bg-white dark:bg-gray-900 light:bg-gray-50\`;
|
|
1696
|
-
return null;
|
|
1431
|
+
import { View } from 'react-native';
|
|
1432
|
+
export function Component() {
|
|
1433
|
+
return <View className="p-4 ios:p-6 rtl:mr-4" />;
|
|
1697
1434
|
}
|
|
1698
1435
|
`;
|
|
1699
1436
|
|
|
1700
|
-
const output = transform(input);
|
|
1701
|
-
|
|
1702
|
-
// Should have runtime conditional in style array
|
|
1703
|
-
expect(output).toContain("style: [");
|
|
1704
|
-
expect(output).toContain('_twColorScheme === "dark"');
|
|
1705
|
-
expect(output).toContain('_twColorScheme === "light"');
|
|
1706
|
-
|
|
1707
|
-
// Should ALSO have darkStyle and lightStyle properties for manual access
|
|
1708
|
-
expect(output).toContain("darkStyle:");
|
|
1709
|
-
expect(output).toContain("lightStyle:");
|
|
1710
|
-
expect(output).toContain("_twStyles._dark_bg_gray_900");
|
|
1711
|
-
expect(output).toContain("_twStyles._light_bg_gray_50");
|
|
1712
|
-
});
|
|
1437
|
+
const output = transform(input, undefined, true);
|
|
1713
1438
|
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
import { tw } from '@mgcrea/react-native-tailwind';
|
|
1439
|
+
// Should have Platform import
|
|
1440
|
+
expect(output).toContain("Platform");
|
|
1717
1441
|
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
// User can access raw hex for Reanimated
|
|
1721
|
-
const darkBgColor = btnStyles.darkStyle?.backgroundColor;
|
|
1722
|
-
return null;
|
|
1723
|
-
}
|
|
1724
|
-
`;
|
|
1442
|
+
// Should have I18nManager import
|
|
1443
|
+
expect(output).toContain("I18nManager");
|
|
1725
1444
|
|
|
1726
|
-
|
|
1445
|
+
// Should have all styles
|
|
1446
|
+
expect(output).toContain("_p_4");
|
|
1447
|
+
expect(output).toContain("_ios_p_6");
|
|
1448
|
+
expect(output).toContain("_rtl_mr_4");
|
|
1727
1449
|
|
|
1728
|
-
// Should have
|
|
1729
|
-
expect(output).toContain("
|
|
1730
|
-
expect(output).toContain("_twStyles._dark_bg_blue_900");
|
|
1450
|
+
// Should have Platform.select
|
|
1451
|
+
expect(output).toContain("Platform.select");
|
|
1731
1452
|
|
|
1732
|
-
//
|
|
1733
|
-
expect(output).
|
|
1453
|
+
// Should have RTL conditional
|
|
1454
|
+
expect(output).toMatch(/_twIsRTL\s*&&/);
|
|
1734
1455
|
});
|
|
1735
1456
|
|
|
1736
|
-
|
|
1737
|
-
it("should transform tw with ios: modifier", () => {
|
|
1457
|
+
it("should not add I18nManager import if already present", () => {
|
|
1738
1458
|
const input = `
|
|
1739
|
-
import {
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
const styles = tw\`bg-white ios:p-6\`;
|
|
1743
|
-
return null;
|
|
1459
|
+
import { View, I18nManager } from 'react-native';
|
|
1460
|
+
export function Component() {
|
|
1461
|
+
return <View className="rtl:mr-4" />;
|
|
1744
1462
|
}
|
|
1745
1463
|
`;
|
|
1746
1464
|
|
|
1747
|
-
const output = transform(input);
|
|
1748
|
-
|
|
1749
|
-
// Should add Platform import
|
|
1750
|
-
expect(output).toContain("Platform");
|
|
1751
|
-
expect(output).toContain('from "react-native"');
|
|
1752
|
-
|
|
1753
|
-
// Should generate style array with Platform.select()
|
|
1754
|
-
expect(output).toContain("style: [");
|
|
1755
|
-
expect(output).toContain("Platform.select");
|
|
1756
|
-
expect(output).toContain("ios:");
|
|
1757
|
-
expect(output).toContain("_twStyles._ios_p_6");
|
|
1758
|
-
expect(output).toContain("_twStyles._bg_white");
|
|
1465
|
+
const output = transform(input, undefined, true);
|
|
1759
1466
|
|
|
1760
|
-
// Should have
|
|
1761
|
-
|
|
1467
|
+
// Should have only one I18nManager import (merged, not duplicated)
|
|
1468
|
+
const i18nMatches = output.match(/I18nManager/g);
|
|
1469
|
+
// Should have I18nManager in: import, variable declaration, and style usage
|
|
1470
|
+
expect(i18nMatches).toBeTruthy();
|
|
1471
|
+
// Should not have duplicate imports
|
|
1472
|
+
expect(output).not.toMatch(/import\s*\{[^}]*I18nManager[^}]*I18nManager[^}]*\}/);
|
|
1762
1473
|
});
|
|
1763
1474
|
|
|
1764
|
-
it("should
|
|
1475
|
+
it("should work with directional logical properties", () => {
|
|
1765
1476
|
const input = `
|
|
1766
|
-
import {
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
return null;
|
|
1771
|
-
};
|
|
1477
|
+
import { View } from 'react-native';
|
|
1478
|
+
export function Component() {
|
|
1479
|
+
return <View className="rtl:ms-4 ltr:me-4" />;
|
|
1480
|
+
}
|
|
1772
1481
|
`;
|
|
1773
1482
|
|
|
1774
|
-
const output = transform(input);
|
|
1483
|
+
const output = transform(input, undefined, true);
|
|
1775
1484
|
|
|
1776
|
-
// Should
|
|
1777
|
-
expect(output).toContain("
|
|
1485
|
+
// Should have logical property styles
|
|
1486
|
+
expect(output).toContain("_rtl_ms_4");
|
|
1487
|
+
expect(output).toContain("_ltr_me_4");
|
|
1778
1488
|
|
|
1779
|
-
// Should
|
|
1780
|
-
expect(output).toContain("
|
|
1781
|
-
expect(output).toContain("
|
|
1782
|
-
expect(output).toContain("android:");
|
|
1783
|
-
expect(output).toContain("_twStyles._android_p_8");
|
|
1784
|
-
expect(output).toContain("_twStyles._bg_blue_500");
|
|
1489
|
+
// Should contain marginStart and marginEnd in the StyleSheet
|
|
1490
|
+
expect(output).toContain("marginStart");
|
|
1491
|
+
expect(output).toContain("marginEnd");
|
|
1785
1492
|
});
|
|
1786
1493
|
|
|
1787
|
-
it("should
|
|
1494
|
+
it("should combine directional modifiers with color scheme modifiers", () => {
|
|
1788
1495
|
const input = `
|
|
1789
|
-
import {
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
const styles = tw\`bg-white ios:p-6 android:p-8 web:p-4\`;
|
|
1793
|
-
return null;
|
|
1496
|
+
import { View } from 'react-native';
|
|
1497
|
+
export function Component() {
|
|
1498
|
+
return <View className="bg-white dark:bg-gray-900 rtl:pr-4" />;
|
|
1794
1499
|
}
|
|
1795
1500
|
`;
|
|
1796
1501
|
|
|
1797
|
-
const output = transform(input);
|
|
1502
|
+
const output = transform(input, undefined, true);
|
|
1503
|
+
|
|
1504
|
+
// Should have useColorScheme
|
|
1505
|
+
expect(output).toContain("useColorScheme");
|
|
1798
1506
|
|
|
1799
|
-
// Should
|
|
1800
|
-
expect(output).toContain("
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
expect(output).toContain("
|
|
1804
|
-
expect(output).toContain("
|
|
1805
|
-
expect(output).toContain("
|
|
1806
|
-
|
|
1507
|
+
// Should have I18nManager
|
|
1508
|
+
expect(output).toContain("I18nManager");
|
|
1509
|
+
|
|
1510
|
+
// Should have all styles
|
|
1511
|
+
expect(output).toContain("_bg_white");
|
|
1512
|
+
expect(output).toContain("_dark_bg_gray_900");
|
|
1513
|
+
expect(output).toContain("_rtl_pr_4");
|
|
1514
|
+
|
|
1515
|
+
// Should have both conditionals
|
|
1516
|
+
expect(output).toMatch(/_twColorScheme\s*===\s*["']dark["']/);
|
|
1517
|
+
expect(output).toMatch(/_twIsRTL\s*&&/);
|
|
1807
1518
|
});
|
|
1808
1519
|
|
|
1809
|
-
it("should
|
|
1520
|
+
it("should handle aliased I18nManager import", () => {
|
|
1810
1521
|
const input = `
|
|
1811
|
-
import {
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
const
|
|
1815
|
-
return
|
|
1522
|
+
import { View, I18nManager as RTL } from 'react-native';
|
|
1523
|
+
export function Component() {
|
|
1524
|
+
// Use RTL somewhere so TypeScript doesn't strip the unused import
|
|
1525
|
+
const isRtl = RTL.isRTL;
|
|
1526
|
+
return <View className="rtl:mr-4" />;
|
|
1816
1527
|
}
|
|
1817
1528
|
`;
|
|
1818
1529
|
|
|
1819
|
-
const output = transform(input);
|
|
1820
|
-
|
|
1821
|
-
// Should have both Platform and useColorScheme
|
|
1822
|
-
expect(output).toContain("Platform");
|
|
1823
|
-
expect(output).toContain("useColorScheme");
|
|
1824
|
-
expect(output).toContain("_twColorScheme");
|
|
1530
|
+
const output = transform(input, undefined, true);
|
|
1825
1531
|
|
|
1826
|
-
// Should
|
|
1827
|
-
expect(output).toContain("
|
|
1828
|
-
|
|
1532
|
+
// Should use the aliased identifier RTL.isRTL instead of I18nManager.isRTL
|
|
1533
|
+
expect(output).toContain("RTL.isRTL");
|
|
1534
|
+
// Should preserve the aliased import
|
|
1535
|
+
expect(output).toContain("I18nManager as RTL");
|
|
1536
|
+
// Should not add a separate I18nManager import without alias
|
|
1537
|
+
expect(output).not.toMatch(/I18nManager,|,\s*I18nManager\s*[,}]/);
|
|
1829
1538
|
});
|
|
1830
1539
|
|
|
1831
|
-
it("should
|
|
1540
|
+
it("should preserve 'use client' directive when injecting I18nManager variable", () => {
|
|
1832
1541
|
const input = `
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
function
|
|
1836
|
-
|
|
1837
|
-
return null;
|
|
1542
|
+
'use client';
|
|
1543
|
+
import { View } from 'react-native';
|
|
1544
|
+
export function Component() {
|
|
1545
|
+
return <View className="rtl:mr-4" />;
|
|
1838
1546
|
}
|
|
1839
1547
|
`;
|
|
1840
1548
|
|
|
1841
|
-
const output = transform(input);
|
|
1549
|
+
const output = transform(input, undefined, true);
|
|
1842
1550
|
|
|
1843
|
-
//
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
expect(
|
|
1849
|
-
expect(output).toContain("_twStyles._web_p_4");
|
|
1551
|
+
// 'use client' should be the first statement
|
|
1552
|
+
const lines = output.split("\n").filter((l: string) => l.trim());
|
|
1553
|
+
const useClientIndex = lines.findIndex(
|
|
1554
|
+
(l: string) => l.includes("'use client'") || l.includes('"use client"'),
|
|
1555
|
+
);
|
|
1556
|
+
expect(useClientIndex).toBe(0);
|
|
1850
1557
|
|
|
1851
|
-
//
|
|
1852
|
-
expect(output).toContain("
|
|
1558
|
+
// I18nManager variable should come after imports, not before 'use client'
|
|
1559
|
+
expect(output).toContain("_twIsRTL");
|
|
1560
|
+
expect(output).toContain("I18nManager.isRTL");
|
|
1853
1561
|
});
|
|
1854
1562
|
|
|
1855
|
-
it("should
|
|
1563
|
+
it("should preserve 'use strict' directive when injecting I18nManager variable", () => {
|
|
1856
1564
|
const input = `
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
function
|
|
1860
|
-
|
|
1861
|
-
return null;
|
|
1565
|
+
'use strict';
|
|
1566
|
+
import { View } from 'react-native';
|
|
1567
|
+
export function Component() {
|
|
1568
|
+
return <View className="rtl:mr-4" />;
|
|
1862
1569
|
}
|
|
1863
1570
|
`;
|
|
1864
1571
|
|
|
1865
|
-
const output = transform(input);
|
|
1572
|
+
const output = transform(input, undefined, true);
|
|
1573
|
+
|
|
1574
|
+
// 'use strict' should be preserved at the top
|
|
1575
|
+
const lines = output.split("\n").filter((l: string) => l.trim());
|
|
1576
|
+
const useStrictIndex = lines.findIndex(
|
|
1577
|
+
(l: string) => l.includes("'use strict'") || l.includes('"use strict"'),
|
|
1578
|
+
);
|
|
1579
|
+
expect(useStrictIndex).toBe(0);
|
|
1866
1580
|
|
|
1867
|
-
//
|
|
1868
|
-
expect(output).toContain("
|
|
1869
|
-
expect(output).toContain("
|
|
1870
|
-
expect(output).toContain("_twStyles._android_p_8");
|
|
1581
|
+
// I18nManager variable should work correctly
|
|
1582
|
+
expect(output).toContain("_twIsRTL");
|
|
1583
|
+
expect(output).toContain("I18nManager.isRTL");
|
|
1871
1584
|
});
|
|
1872
1585
|
|
|
1873
|
-
it("should
|
|
1586
|
+
it("should expand text-start to directional modifiers", () => {
|
|
1874
1587
|
const input = `
|
|
1875
|
-
import {
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
const btnStyles = tw\`bg-blue-500 ios:p-6\`;
|
|
1879
|
-
const iosPadding = btnStyles.iosStyle;
|
|
1880
|
-
return null;
|
|
1588
|
+
import { Text } from 'react-native';
|
|
1589
|
+
export function Component() {
|
|
1590
|
+
return <Text className="text-start" />;
|
|
1881
1591
|
}
|
|
1882
1592
|
`;
|
|
1883
1593
|
|
|
1884
|
-
const output = transform(input);
|
|
1594
|
+
const output = transform(input, undefined, true);
|
|
1595
|
+
|
|
1596
|
+
// Should have I18nManager import (text-start expands to ltr:/rtl: modifiers)
|
|
1597
|
+
expect(output).toContain("I18nManager");
|
|
1885
1598
|
|
|
1886
|
-
// Should have
|
|
1887
|
-
expect(output).toContain("
|
|
1888
|
-
expect(output).toContain("
|
|
1599
|
+
// Should have both ltr and rtl styles
|
|
1600
|
+
expect(output).toContain("_ltr_text_left");
|
|
1601
|
+
expect(output).toContain("_rtl_text_right");
|
|
1889
1602
|
|
|
1890
|
-
//
|
|
1891
|
-
expect(output).
|
|
1603
|
+
// Should have conditionals for both
|
|
1604
|
+
expect(output).toMatch(/_twIsRTL\s*&&/);
|
|
1605
|
+
expect(output).toMatch(/!\s*_twIsRTL\s*&&/);
|
|
1892
1606
|
});
|
|
1893
1607
|
|
|
1894
|
-
it("should
|
|
1608
|
+
it("should expand text-end to directional modifiers", () => {
|
|
1895
1609
|
const input = `
|
|
1896
|
-
import {
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
const styles = tw\`bg-white active:bg-blue-500 ios:p-6\`;
|
|
1900
|
-
return null;
|
|
1610
|
+
import { Text } from 'react-native';
|
|
1611
|
+
export function Component() {
|
|
1612
|
+
return <Text className="text-end" />;
|
|
1901
1613
|
}
|
|
1902
1614
|
`;
|
|
1903
1615
|
|
|
1904
|
-
const output = transform(input);
|
|
1616
|
+
const output = transform(input, undefined, true);
|
|
1617
|
+
|
|
1618
|
+
// Should have I18nManager import
|
|
1619
|
+
expect(output).toContain("I18nManager");
|
|
1905
1620
|
|
|
1906
|
-
//
|
|
1907
|
-
expect(output).toContain("
|
|
1908
|
-
expect(output).toContain("
|
|
1909
|
-
expect(output).toContain("Platform.select");
|
|
1910
|
-
expect(output).toContain("_twStyles._ios_p_6");
|
|
1621
|
+
// text-end expands to ltr:text-right rtl:text-left
|
|
1622
|
+
expect(output).toContain("_ltr_text_right");
|
|
1623
|
+
expect(output).toContain("_rtl_text_left");
|
|
1911
1624
|
});
|
|
1912
1625
|
});
|