Skip to content

Commit 9ea0834

Browse files
committed
test: add comprehensive tests for RTL support
Add test coverage for: - I18nManager aliased imports (import { I18nManager as RTL }) - Directive preservation ('use client', 'use strict') for both I18nManager variable and StyleSheet.create - text-start/text-end automatic expansion to directional modifiers - ltr:/rtl: modifier code generation with I18nManager.isRTL conditionals
1 parent 51596f6 commit 9ea0834

File tree

1 file changed

+313
-0
lines changed

1 file changed

+313
-0
lines changed

src/babel/plugin/visitors/className.test.ts

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,35 @@ describe("className visitor - basic transformation", () => {
7676
expect(output).toMatch(/_state\s*=>/);
7777
});
7878

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+
79108
it("should merge dynamic className with function-based style prop", () => {
80109
const input = `
81110
import { TextInput } from 'react-native';
@@ -1310,3 +1339,287 @@ describe("className visitor - directional border colors", () => {
13101339
expect(output).toMatch(/isError\s*\?\s*_twStyles\._border_t_red_500/);
13111340
});
13121341
});
1342+
1343+
describe("className visitor - directional modifiers (RTL/LTR)", () => {
1344+
it("should transform rtl: modifier and inject I18nManager import", () => {
1345+
const input = `
1346+
import { View } from 'react-native';
1347+
export function Component() {
1348+
return <View className="rtl:mr-4" />;
1349+
}
1350+
`;
1351+
1352+
const output = transform(input, undefined, true);
1353+
1354+
// Should import I18nManager
1355+
expect(output).toContain("I18nManager");
1356+
1357+
// Should declare _twIsRTL variable
1358+
expect(output).toContain("_twIsRTL");
1359+
expect(output).toContain("I18nManager.isRTL");
1360+
1361+
// Should have StyleSheet with rtl style
1362+
expect(output).toContain("StyleSheet.create");
1363+
expect(output).toContain("_rtl_mr_4");
1364+
1365+
// Should have conditional for RTL
1366+
expect(output).toMatch(/_twIsRTL\s*&&\s*_twStyles\._rtl_mr_4/);
1367+
});
1368+
1369+
it("should transform ltr: modifier with negated conditional", () => {
1370+
const input = `
1371+
import { View } from 'react-native';
1372+
export function Component() {
1373+
return <View className="ltr:ml-4" />;
1374+
}
1375+
`;
1376+
1377+
const output = transform(input, undefined, true);
1378+
1379+
// Should import I18nManager
1380+
expect(output).toContain("I18nManager");
1381+
1382+
// Should have StyleSheet with ltr style
1383+
expect(output).toContain("_ltr_ml_4");
1384+
1385+
// Should have negated conditional for LTR (!_twIsRTL)
1386+
expect(output).toMatch(/!\s*_twIsRTL\s*&&\s*_twStyles\._ltr_ml_4/);
1387+
});
1388+
1389+
it("should combine rtl: and ltr: modifiers", () => {
1390+
const input = `
1391+
import { View } from 'react-native';
1392+
export function Component() {
1393+
return <View className="rtl:mr-4 ltr:ml-4" />;
1394+
}
1395+
`;
1396+
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");
1402+
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/);
1406+
});
1407+
1408+
it("should combine directional modifiers with base classes", () => {
1409+
const input = `
1410+
import { View } from 'react-native';
1411+
export function Component() {
1412+
return <View className="p-4 bg-white rtl:pr-8 ltr:pl-8" />;
1413+
}
1414+
`;
1415+
1416+
const output = transform(input, undefined, true);
1417+
1418+
// Should have base style
1419+
expect(output).toContain("_bg_white_p_4");
1420+
1421+
// Should have directional styles
1422+
expect(output).toContain("_rtl_pr_8");
1423+
expect(output).toContain("_ltr_pl_8");
1424+
1425+
// Should generate an array with base and conditional styles
1426+
expect(output).toMatch(/style:\s*\[/);
1427+
});
1428+
1429+
it("should combine directional modifiers with platform modifiers", () => {
1430+
const input = `
1431+
import { View } from 'react-native';
1432+
export function Component() {
1433+
return <View className="p-4 ios:p-6 rtl:mr-4" />;
1434+
}
1435+
`;
1436+
1437+
const output = transform(input, undefined, true);
1438+
1439+
// Should have Platform import
1440+
expect(output).toContain("Platform");
1441+
1442+
// Should have I18nManager import
1443+
expect(output).toContain("I18nManager");
1444+
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");
1449+
1450+
// Should have Platform.select
1451+
expect(output).toContain("Platform.select");
1452+
1453+
// Should have RTL conditional
1454+
expect(output).toMatch(/_twIsRTL\s*&&/);
1455+
});
1456+
1457+
it("should not add I18nManager import if already present", () => {
1458+
const input = `
1459+
import { View, I18nManager } from 'react-native';
1460+
export function Component() {
1461+
return <View className="rtl:mr-4" />;
1462+
}
1463+
`;
1464+
1465+
const output = transform(input, undefined, true);
1466+
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[^}]*\}/);
1473+
});
1474+
1475+
it("should work with directional logical properties", () => {
1476+
const input = `
1477+
import { View } from 'react-native';
1478+
export function Component() {
1479+
return <View className="rtl:ms-4 ltr:me-4" />;
1480+
}
1481+
`;
1482+
1483+
const output = transform(input, undefined, true);
1484+
1485+
// Should have logical property styles
1486+
expect(output).toContain("_rtl_ms_4");
1487+
expect(output).toContain("_ltr_me_4");
1488+
1489+
// Should contain marginStart and marginEnd in the StyleSheet
1490+
expect(output).toContain("marginStart");
1491+
expect(output).toContain("marginEnd");
1492+
});
1493+
1494+
it("should combine directional modifiers with color scheme modifiers", () => {
1495+
const input = `
1496+
import { View } from 'react-native';
1497+
export function Component() {
1498+
return <View className="bg-white dark:bg-gray-900 rtl:pr-4" />;
1499+
}
1500+
`;
1501+
1502+
const output = transform(input, undefined, true);
1503+
1504+
// Should have useColorScheme
1505+
expect(output).toContain("useColorScheme");
1506+
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*&&/);
1518+
});
1519+
1520+
it("should handle aliased I18nManager import", () => {
1521+
const input = `
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" />;
1527+
}
1528+
`;
1529+
1530+
const output = transform(input, undefined, true);
1531+
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*[,}]/);
1538+
});
1539+
1540+
it("should preserve 'use client' directive when injecting I18nManager variable", () => {
1541+
const input = `
1542+
'use client';
1543+
import { View } from 'react-native';
1544+
export function Component() {
1545+
return <View className="rtl:mr-4" />;
1546+
}
1547+
`;
1548+
1549+
const output = transform(input, undefined, true);
1550+
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);
1557+
1558+
// I18nManager variable should come after imports, not before 'use client'
1559+
expect(output).toContain("_twIsRTL");
1560+
expect(output).toContain("I18nManager.isRTL");
1561+
});
1562+
1563+
it("should preserve 'use strict' directive when injecting I18nManager variable", () => {
1564+
const input = `
1565+
'use strict';
1566+
import { View } from 'react-native';
1567+
export function Component() {
1568+
return <View className="rtl:mr-4" />;
1569+
}
1570+
`;
1571+
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);
1580+
1581+
// I18nManager variable should work correctly
1582+
expect(output).toContain("_twIsRTL");
1583+
expect(output).toContain("I18nManager.isRTL");
1584+
});
1585+
1586+
it("should expand text-start to directional modifiers", () => {
1587+
const input = `
1588+
import { Text } from 'react-native';
1589+
export function Component() {
1590+
return <Text className="text-start" />;
1591+
}
1592+
`;
1593+
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");
1598+
1599+
// Should have both ltr and rtl styles
1600+
expect(output).toContain("_ltr_text_left");
1601+
expect(output).toContain("_rtl_text_right");
1602+
1603+
// Should have conditionals for both
1604+
expect(output).toMatch(/_twIsRTL\s*&&/);
1605+
expect(output).toMatch(/!\s*_twIsRTL\s*&&/);
1606+
});
1607+
1608+
it("should expand text-end to directional modifiers", () => {
1609+
const input = `
1610+
import { Text } from 'react-native';
1611+
export function Component() {
1612+
return <Text className="text-end" />;
1613+
}
1614+
`;
1615+
1616+
const output = transform(input, undefined, true);
1617+
1618+
// Should have I18nManager import
1619+
expect(output).toContain("I18nManager");
1620+
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");
1624+
});
1625+
});

0 commit comments

Comments
 (0)