Skip to content

Commit 330ad1d

Browse files
committed
test: add program visitor test file
Adds comprehensive tests for Program.exit behavior: - StyleSheet.create injection and placement - tw/twStyle import removal - useColorScheme hook injection (dark:/light: modifiers) - useWindowDimensions hook injection (w-screen/h-screen) - Combined scenarios with multiple imports and hooks 14 tests covering all Program visitor responsibilities.
1 parent 2479eeb commit 330ad1d

File tree

1 file changed

+325
-0
lines changed

1 file changed

+325
-0
lines changed
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
import { describe, expect, it } from "vitest";
2+
import { transform } from "../../../../test/helpers/babelTransform.js";
3+
4+
describe("program visitor - Program.exit behavior", () => {
5+
describe("StyleSheet injection", () => {
6+
it("should inject StyleSheet.create when className is used", () => {
7+
const input = `
8+
import { View } from 'react-native';
9+
10+
function MyComponent() {
11+
return <View className="m-4 p-2 bg-blue-500" />;
12+
}
13+
`;
14+
15+
const output = transform(input, undefined, true);
16+
17+
// Should have StyleSheet import
18+
expect(output).toContain("StyleSheet");
19+
20+
// Should inject StyleSheet.create
21+
expect(output).toContain("StyleSheet.create");
22+
expect(output).toContain("_twStyles");
23+
24+
// Should have the styles
25+
expect(output).toMatch(/margin:\s*16/);
26+
expect(output).toMatch(/padding:\s*8/);
27+
expect(output).toMatch(/backgroundColor:/);
28+
});
29+
30+
it("should not inject StyleSheet when no className is used", () => {
31+
const input = `
32+
import { View } from 'react-native';
33+
34+
function MyComponent() {
35+
return <View />;
36+
}
37+
`;
38+
39+
const output = transform(input, undefined, true);
40+
41+
// Should not add StyleSheet import
42+
expect(output).not.toContain("StyleSheet");
43+
expect(output).not.toContain("_twStyles");
44+
});
45+
46+
it("should inject styles at top of file after imports", () => {
47+
const input = `
48+
import React from 'react';
49+
import { View } from 'react-native';
50+
51+
function MyComponent() {
52+
return <View className="m-4" />;
53+
}
54+
55+
function AnotherComponent() {
56+
return <View className="p-2" />;
57+
}
58+
`;
59+
60+
const output = transform(input, undefined, true);
61+
62+
// StyleSheet.create should come before component definitions
63+
const styleSheetIndex = output.indexOf("StyleSheet.create");
64+
const firstComponentIndex = output.indexOf("function MyComponent");
65+
66+
expect(styleSheetIndex).toBeGreaterThan(0);
67+
expect(styleSheetIndex).toBeLessThan(firstComponentIndex);
68+
});
69+
});
70+
71+
describe("tw import removal", () => {
72+
it("should remove tw import when tw tagged template is used", () => {
73+
const input = `
74+
import { tw } from '@mgcrea/react-native-tailwind';
75+
import { View } from 'react-native';
76+
77+
function MyComponent() {
78+
const styles = tw\`m-4 p-2\`;
79+
return <View style={styles.style} />;
80+
}
81+
`;
82+
83+
const output = transform(input, undefined, true);
84+
85+
// Should not have tw import anymore
86+
expect(output).not.toMatch(/import.*tw.*from ['"]@mgcrea\/react-native-tailwind['"]/);
87+
88+
// Should have StyleSheet instead
89+
expect(output).toContain("StyleSheet");
90+
});
91+
92+
it("should remove twStyle import when twStyle call is used", () => {
93+
const input = `
94+
import { twStyle } from '@mgcrea/react-native-tailwind';
95+
import { View } from 'react-native';
96+
97+
function MyComponent() {
98+
const styles = twStyle('m-4 p-2');
99+
return <View style={styles.style} />;
100+
}
101+
`;
102+
103+
const output = transform(input, undefined, true);
104+
105+
// Should not have twStyle import anymore
106+
expect(output).not.toMatch(/import.*twStyle.*from ['"]@mgcrea\/react-native-tailwind['"]/);
107+
108+
// Should have StyleSheet instead
109+
expect(output).toContain("StyleSheet");
110+
});
111+
});
112+
113+
describe("hook injection - color scheme", () => {
114+
it("should inject useColorScheme hook when dark: modifier is used", () => {
115+
const input = `
116+
import { View } from 'react-native';
117+
118+
function MyComponent() {
119+
return <View className="bg-white dark:bg-gray-900" />;
120+
}
121+
`;
122+
123+
const output = transform(input, undefined, true);
124+
125+
// Should import useColorScheme
126+
expect(output).toContain("useColorScheme");
127+
128+
// Should inject hook call
129+
expect(output).toContain("_twColorScheme");
130+
expect(output).toContain("useColorScheme()");
131+
132+
// Should use in conditional
133+
expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/);
134+
});
135+
136+
it("should inject useColorScheme hook when light: modifier is used", () => {
137+
const input = `
138+
import { View } from 'react-native';
139+
140+
function MyComponent() {
141+
return <View className="bg-gray-900 light:bg-white" />;
142+
}
143+
`;
144+
145+
const output = transform(input, undefined, true);
146+
147+
// Should import and inject hook
148+
expect(output).toContain("useColorScheme");
149+
expect(output).toContain("_twColorScheme");
150+
151+
// Should use in conditional
152+
expect(output).toMatch(/_twColorScheme\s*===\s*['"]light['"]/);
153+
});
154+
155+
it("should inject hook only once for multiple color scheme modifiers", () => {
156+
const input = `
157+
import { View } from 'react-native';
158+
159+
function MyComponent() {
160+
return (
161+
<>
162+
<View className="dark:bg-gray-900" />
163+
<View className="light:bg-white" />
164+
<View className="dark:text-white" />
165+
</>
166+
);
167+
}
168+
`;
169+
170+
const output = transform(input, undefined, true);
171+
172+
// Count hook injections (should be exactly 1)
173+
const hookMatches = output.match(/_twColorScheme\s*=\s*useColorScheme\(\)/g) ?? [];
174+
expect(hookMatches.length).toBe(1);
175+
});
176+
177+
it("should use custom color scheme hook when configured", () => {
178+
const input = `
179+
import { View } from 'react-native';
180+
181+
function MyComponent() {
182+
return <View className="dark:bg-gray-900" />;
183+
}
184+
`;
185+
186+
const output = transform(
187+
input,
188+
{
189+
colorScheme: {
190+
importFrom: "@react-navigation/native",
191+
importName: "useTheme",
192+
},
193+
},
194+
true,
195+
);
196+
197+
// Should import from custom source
198+
expect(output).toContain("@react-navigation/native");
199+
expect(output).toContain("useTheme");
200+
201+
// Should use custom hook
202+
expect(output).toContain("useTheme()");
203+
});
204+
});
205+
206+
describe("hook injection - window dimensions", () => {
207+
it("should inject useWindowDimensions hook when w-screen is used", () => {
208+
const input = `
209+
import { View } from 'react-native';
210+
211+
function MyComponent() {
212+
return <View className="w-screen bg-white" />;
213+
}
214+
`;
215+
216+
const output = transform(input, undefined, true);
217+
218+
// Should import useWindowDimensions
219+
expect(output).toContain("useWindowDimensions");
220+
221+
// Should inject hook call
222+
expect(output).toContain("_twDimensions");
223+
expect(output).toContain("useWindowDimensions()");
224+
225+
// Should use in inline style
226+
expect(output).toMatch(/width:\s*_twDimensions\.width/);
227+
});
228+
229+
it("should inject useWindowDimensions hook when h-screen is used", () => {
230+
const input = `
231+
import { View } from 'react-native';
232+
233+
function MyComponent() {
234+
return <View className="h-screen bg-white" />;
235+
}
236+
`;
237+
238+
const output = transform(input, undefined, true);
239+
240+
// Should import and inject hook
241+
expect(output).toContain("useWindowDimensions");
242+
expect(output).toContain("_twDimensions");
243+
244+
// Should use in inline style
245+
expect(output).toMatch(/height:\s*_twDimensions\.height/);
246+
});
247+
248+
it("should inject hook only once for multiple w-screen/h-screen uses", () => {
249+
const input = `
250+
import { View } from 'react-native';
251+
252+
function MyComponent() {
253+
return (
254+
<>
255+
<View className="w-screen" />
256+
<View className="h-screen" />
257+
<View className="w-screen h-screen" />
258+
</>
259+
);
260+
}
261+
`;
262+
263+
const output = transform(input, undefined, true);
264+
265+
// Count hook injections (should be exactly 1)
266+
const hookMatches = output.match(/_twDimensions\s*=\s*useWindowDimensions\(\)/g) ?? [];
267+
expect(hookMatches.length).toBe(1);
268+
});
269+
});
270+
271+
describe("combined scenarios", () => {
272+
it("should inject color scheme and platform imports together", () => {
273+
const input = `
274+
import { View } from 'react-native';
275+
276+
function MyComponent() {
277+
return (
278+
<View className="ios:p-4 android:p-2 dark:bg-gray-900 light:bg-white" />
279+
);
280+
}
281+
`;
282+
283+
const output = transform(input, undefined, true);
284+
285+
// Should have all imports
286+
expect(output).toContain("StyleSheet");
287+
expect(output).toContain("Platform");
288+
expect(output).toContain("useColorScheme");
289+
290+
// Should have color scheme hook
291+
expect(output).toContain("_twColorScheme");
292+
expect(output).toContain("useColorScheme()");
293+
294+
// Should have platform select
295+
expect(output).toContain("Platform.select");
296+
});
297+
298+
it("should inject window dimensions and color scheme hooks in separate components", () => {
299+
const input = `
300+
import { View } from 'react-native';
301+
302+
function ComponentA() {
303+
return <View className="w-screen h-screen" />;
304+
}
305+
306+
function ComponentB() {
307+
return <View className="dark:bg-gray-900 light:bg-white" />;
308+
}
309+
`;
310+
311+
const output = transform(input, undefined, true);
312+
313+
// Should have both hooks
314+
expect(output).toContain("useColorScheme");
315+
expect(output).toContain("useWindowDimensions");
316+
317+
// Should have both hook calls
318+
expect(output).toContain("_twColorScheme");
319+
expect(output).toContain("_twDimensions");
320+
321+
// Should have StyleSheet
322+
expect(output).toContain("StyleSheet.create");
323+
});
324+
});
325+
});

0 commit comments

Comments
 (0)