/* eslint-disable @typescript-eslint/no-empty-function */ import { transformSync } from "@babel/core"; import { describe, expect, it, vi } from "vitest"; import babelPlugin, { type PluginOptions } from "./plugin.js"; /** * Helper to transform code with the Babel plugin */ function transform(code: string, options?: PluginOptions, includeJsx = false) { const presets = includeJsx ? ["@babel/preset-react", ["@babel/preset-typescript", { isTSX: true, allExtensions: true }]] : []; const result = transformSync(code, { presets, plugins: [[babelPlugin, options]], filename: "test.tsx", configFile: false, babelrc: false, }); return result?.code ?? ""; } describe("Babel plugin - tw template tag transformation", () => { it("should transform simple tw template literal", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; const styles = tw\`bg-blue-500 m-4\`; `; const output = transform(input); // Should have StyleSheet import (either ESM or CommonJS) expect(output).toMatch(/import.*StyleSheet.*from "react-native"|require\("react-native"\)/); expect(output).toContain("StyleSheet"); // Should have _twStyles definition expect(output).toContain("_twStyles"); expect(output).toContain("StyleSheet.create"); // Should transform tw call to object with style property expect(output).toContain("style:"); expect(output).toContain("_twStyles._bg_blue_500_m_4"); // Should remove tw import expect(output).not.toContain("from '@mgcrea/react-native-tailwind'"); }); it("should transform tw with state modifiers", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; const styles = tw\`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300\`; `; const output = transform(input); // Should have base style expect(output).toContain("style:"); expect(output).toContain("_bg_blue_500"); // Should have activeStyle expect(output).toContain("activeStyle:"); expect(output).toContain("_active_bg_blue_700"); // Should have disabledStyle expect(output).toContain("disabledStyle:"); expect(output).toContain("_disabled_bg_gray_300"); // Should create StyleSheet with all styles expect(output).toContain("backgroundColor:"); }); it("should inject StyleSheet.create after imports", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; import { View } from 'react-native'; const styles = tw\`m-4\`; `; const output = transform(input); // Find the position of imports and StyleSheet.create const viewImportPos = output.indexOf('require("react-native")'); const styleSheetCreatePos = output.indexOf("_twStyles"); // StyleSheet.create should come after imports expect(styleSheetCreatePos).toBeGreaterThan(viewImportPos); }); it("should handle tw in object literals", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; const sizeVariants = { sm: { container: tw\`h-9 px-3\`, text: tw\`text-sm\`, }, }; `; const output = transform(input); // Should define _twStyles before object literal const twStylesPos = output.indexOf("_twStyles"); const sizeVariantsPos = output.indexOf("sizeVariants"); expect(twStylesPos).toBeGreaterThan(0); expect(twStylesPos).toBeLessThan(sizeVariantsPos); // Should have both styles expect(output).toContain("_h_9_px_3"); expect(output).toContain("_text_sm"); }); it("should handle empty tw template literal", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; const styles = tw\`\`; `; const output = transform(input); // Should replace with empty style object expect(output).toContain("style:"); expect(output).toContain("{}"); }); it("should preserve other imports from the same package", () => { const input = ` import { tw, TwStyle, COLORS } from '@mgcrea/react-native-tailwind'; const styles = tw\`m-4\`; `; const output = transform(input); // Should remove tw but keep other imports expect(output).not.toContain('"tw"'); expect(output).toContain("TwStyle"); expect(output).toContain("COLORS"); }); it("should handle renamed tw import", () => { const input = ` import { tw as customTw } from '@mgcrea/react-native-tailwind'; const styles = customTw\`m-4 p-2\`; `; const output = transform(input); // Should still transform the renamed import expect(output).toContain("_twStyles"); expect(output).toContain("_m_4_p_2"); expect(output).not.toContain("customTw"); }); it("should handle multiple tw calls", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; const style1 = tw\`bg-red-500\`; const style2 = tw\`bg-blue-500\`; const style3 = tw\`bg-green-500\`; `; const output = transform(input); // Should have all three styles in StyleSheet expect(output).toContain("_bg_red_500"); expect(output).toContain("_bg_blue_500"); expect(output).toContain("_bg_green_500"); // Should have StyleSheet.create with all styles expect(output).toContain("StyleSheet.create"); }); it("should use custom stylesIdentifier option", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; const styles = tw\`m-4\`; `; const output = transform(input, { stylesIdentifier: "myStyles" }); // Should use custom identifier expect(output).toContain("myStyles"); expect(output).toContain("myStyles._m_4"); expect(output).not.toContain("_twStyles"); }); }); describe("Babel plugin - twStyle function transformation", () => { it("should transform twStyle function call", () => { const input = ` import { twStyle } from '@mgcrea/react-native-tailwind'; const styles = twStyle('bg-blue-500 m-4'); `; const output = transform(input); // Should transform to object with style property expect(output).toContain("style:"); expect(output).toContain("_twStyles._bg_blue_500_m_4"); }); it("should transform twStyle with modifiers", () => { const input = ` import { twStyle } from '@mgcrea/react-native-tailwind'; const styles = twStyle('bg-blue-500 active:bg-blue-700'); `; const output = transform(input); expect(output).toContain("style:"); expect(output).toContain("activeStyle:"); }); it("should handle empty twStyle call", () => { const input = ` import { twStyle } from '@mgcrea/react-native-tailwind'; const styles = twStyle(''); `; const output = transform(input); // Should replace with undefined expect(output).toContain("undefined"); }); }); // Note: JSX tests require @babel/preset-react describe("Babel plugin - className transformation (existing behavior)", () => { it("should still transform className props", () => { const input = ` import { View } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Enable JSX // Should have StyleSheet expect(output).toContain("StyleSheet.create"); expect(output).toContain("_twStyles"); // Should replace className with style expect(output).not.toContain("className"); expect(output).toContain("style:"); }); it("should work with both tw and className in same file", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; import { View } from 'react-native'; const styles = tw\`bg-red-500\`; export function Component() { return ; } `; const output = transform(input, undefined, true); // Enable JSX // Should have both styles in StyleSheet expect(output).toContain("_bg_red_500"); expect(output).toContain("_m_4_p_2"); }); it("should merge className with function-based style prop", () => { const input = ` import { TextInput } from 'react-native'; export function Component() { return ( [ baseStyles, focused && focusedStyles, ]} /> ); } `; const output = transform(input, undefined, true); // Enable JSX // Should have StyleSheet with className styles expect(output).toContain("StyleSheet.create"); // Style keys are sorted alphabetically: bg-gray-100 comes before border expect(output).toContain("_bg_gray_100_border_border_gray_300"); // Should create a wrapper function that merges both // The wrapper should call the original function and merge results expect(output).toContain("_state"); expect(output).toContain("_twStyles._bg_gray_100_border_border_gray_300"); // Should not have className in output expect(output).not.toContain("className"); // Should have a function that accepts state and returns an array expect(output).toMatch(/_state\s*=>/); }); it("should merge dynamic className with function-based style prop", () => { const input = ` import { TextInput } from 'react-native'; export function Component({ isError }) { return ( [ baseStyles, focused && focusedStyles, ]} /> ); } `; const output = transform(input, undefined, true); // Enable JSX // Should have StyleSheet with both className styles expect(output).toContain("StyleSheet.create"); expect(output).toContain("_border"); expect(output).toContain("_border_red_500"); expect(output).toContain("_border_gray_300"); // Should create a wrapper function that merges dynamic styles with function result expect(output).toContain("_state"); // Should not have className in output expect(output).not.toContain("className"); }); it('should transform className={"..."} (string literal in expression container)', () => { const input = ` import { View } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should have StyleSheet expect(output).toContain("StyleSheet.create"); expect(output).toContain("_twStyles"); // Should replace className with style expect(output).not.toContain("className"); expect(output).toContain("style:"); // Should have the expected style keys expect(output).toContain("_flex_row_items_center_justify_start"); }); it('should transform className={"..."} with modifiers', () => { const input = ` import { Pressable } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should have StyleSheet with both base and active styles expect(output).toContain("_bg_blue_500_p_4"); expect(output).toContain("_active_bg_blue_700"); // Should have style function for active modifier (Pressable uses 'pressed' parameter) expect(output).toMatch(/(pressed|_state)/); // Should not have className in output expect(output).not.toContain("className"); }); it('should transform className={"..."} with platform modifiers', () => { const input = ` import { View } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should have Platform import expect(output).toContain("Platform"); expect(output).toMatch(/from ['"]react-native['"]/); // Match both single and double quotes // Should have Platform.select expect(output).toContain("Platform.select"); // Should have platform-specific styles expect(output).toContain("_ios_p_6"); expect(output).toContain("_android_p_8"); // Should not have className in output expect(output).not.toContain("className"); }); it('should handle empty className={""}', () => { const input = ` import { View } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should remove empty className attribute entirely expect(output).not.toContain("className"); expect(output).not.toContain("style="); }); }); describe("Babel plugin - placeholder: modifier transformation", () => { it("should transform placeholder:text-{color} to placeholderTextColor prop", () => { const input = ` import { TextInput } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input, undefined, true); // Should have placeholderTextColor prop with correct hex value (from custom palette) expect(output).toContain('placeholderTextColor: "#99a1af"'); // Should still have style for border-2 expect(output).toContain("StyleSheet.create"); expect(output).toContain("_border_2"); // Should not have className in output expect(output).not.toContain("className"); }); it("should support placeholder colors with opacity", () => { const input = ` import { TextInput } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should have 8-digit hex with alpha channel (custom palette red-500, uppercased) expect(output).toContain('placeholderTextColor: "#FB2C3680"'); }); it("should support arbitrary placeholder colors", () => { const input = ` import { TextInput } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); expect(output).toContain('placeholderTextColor: "#ff0000"'); }); it("should combine placeholder: with other modifiers", () => { const input = ` import { TextInput } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input, undefined, true); // Should have placeholderTextColor prop (custom palette gray-400) expect(output).toContain('placeholderTextColor: "#99a1af"'); // Should have focus: modifier handling (style function) expect(output).toContain("focused"); expect(output).toMatch(/style[\s\S]*=>/); // Style function // Should not have className expect(output).not.toContain("className"); }); it("should handle multiple placeholder: classes (last wins)", () => { const input = ` import { TextInput } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input, undefined, true); // Blue should win (last color, custom palette blue-500) expect(output).toContain('placeholderTextColor: "#2b7fff"'); }); it("should ignore non-text utilities in placeholder: modifier", () => { const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const input = ` import { TextInput } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input, undefined, true); // Should still have the valid text color (custom palette gray-400) expect(output).toContain('placeholderTextColor: "#99a1af"'); // Should not have font-bold anywhere expect(output).not.toContain("fontWeight"); consoleSpy.mockRestore(); }); it.skip("should work with custom colors", () => { // Note: This test would require setting up a tailwind.config file // For now, we'll skip custom color testing in Babel tests // Custom colors are tested in the parser tests }); it("should not transform placeholder: on non-TextInput elements", () => { const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const input = ` import { View } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should not have placeholderTextColor prop (View doesn't support it) expect(output).not.toContain("placeholderTextColor"); // Should warn about unsupported modifier // (The warning happens because View doesn't support any modifiers) consoleSpy.mockRestore(); }); }); describe("Babel plugin - platform modifier transformation", () => { it("should transform platform modifiers to Platform.select()", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input, undefined, true); // Should import Platform from react-native expect(output).toContain("Platform"); expect(output).toMatch(/import.*Platform.*from ['"]react-native['"]/); // Should generate Platform.select() expect(output).toContain("Platform.select"); // Should have base padding style expect(output).toContain("_p_4"); // Should have iOS and Android specific styles expect(output).toContain("_ios_p_6"); expect(output).toContain("_android_p_8"); // Should have correct style values in StyleSheet.create expect(output).toMatch(/padding:\s*16/); // p-4 expect(output).toMatch(/padding:\s*24/); // p-6 (ios) expect(output).toMatch(/padding:\s*32/); // p-8 (android) }); it("should support multiple platform modifiers on same element", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input, undefined, true); // Should have Platform import expect(output).toContain("Platform"); // Should have base styles (combined key) expect(output).toContain("_bg_white_p_4"); // Should have iOS specific styles (combined key for multiple ios: modifiers) expect(output).toContain("_ios_bg_blue_50_p_6"); // Should have Android specific styles (combined key for multiple android: modifiers) expect(output).toContain("_android_bg_green_50_p_8"); // Should contain Platform.select with both platforms expect(output).toMatch(/Platform\.select\s*\(\s*\{[\s\S]*ios:/); expect(output).toMatch(/Platform\.select\s*\(\s*\{[\s\S]*android:/); }); it("should support web platform modifier", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input, undefined, true); // Should have Platform.select with web expect(output).toContain("Platform.select"); expect(output).toContain("web:"); expect(output).toContain("_web_p_2"); }); it("should work with platform modifiers on all components", () => { const input = ` import React from 'react'; import { View, Text, ScrollView } from 'react-native'; export function Component() { return ( Platform text ); } `; const output = transform(input, undefined, true); // Should work on View - check for Platform.select separately (not checking style= format) expect(output).toContain("Platform.select"); // Should work on Text expect(output).toContain("_ios_text_lg"); expect(output).toContain("_android_text_xl"); // Should work on ScrollView contentContainerStyle expect(output).toContain("contentContainerStyle"); }); it("should combine platform modifiers with state modifiers", () => { const input = ` import React from 'react'; import { Pressable, Text } from 'react-native'; export function Component() { return ( Button ); } `; const output = transform(input, undefined, true); // Should have Platform.select for platform modifiers expect(output).toContain("Platform.select"); expect(output).toContain("_ios_shadow_md"); expect(output).toContain("_android_shadow_sm"); // Should have state modifier function for active expect(output).toMatch(/\(\s*\{\s*pressed\s*\}\s*\)\s*=>/); expect(output).toContain("pressed"); expect(output).toContain("_active_bg_blue_700"); // Should have base styles expect(output).toContain("_bg_blue_500"); expect(output).toContain("_p_4"); }); it("should handle platform-specific colors", () => { const input = ` import React from 'react'; import { View, Text } from 'react-native'; export function Component() { return ( Platform colors ); } `; const output = transform(input, undefined, true); // Should have color values in StyleSheet expect(output).toMatch(/#[0-9A-F]{6}/i); // Hex color format // Should have platform-specific color classes expect(output).toContain("_ios_text_blue_900"); expect(output).toContain("_android_text_green_900"); }); it("should only add Platform import once when needed", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ( <> ); } `; const output = transform(input, undefined, true); // Should have Platform import expect(output).toContain("Platform"); // Count how many times Platform is imported (should be once) const platformImports = output.match(/import.*Platform.*from ['"]react-native['"]/g); expect(platformImports).toHaveLength(1); }); it("should merge with existing Platform import", () => { const input = ` import React from 'react'; import { View, Platform } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should still use Platform.select expect(output).toContain("Platform.select"); // Should not duplicate Platform import - Platform appears in import and Platform.select calls expect(output).toMatch(/Platform.*react-native/); }); it("should handle platform modifiers without base classes", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should only have Platform.select, no base style expect(output).toContain("Platform.select"); expect(output).toContain("_ios_p_6"); expect(output).toContain("_android_p_8"); // Should not have generic padding without platform prefix // Check that non-platform-prefixed style keys don't exist expect(output).not.toMatch(/(? { it("should transform dark: modifier to conditional expression", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input, undefined, true); // Should import useColorScheme expect(output).toContain("useColorScheme"); expect(output).toMatch(/import.*useColorScheme.*from ['"]react-native['"]/); // Should inject colorScheme hook in component expect(output).toContain("_twColorScheme"); expect(output).toContain("useColorScheme()"); // Should have base bg-white style expect(output).toContain("_bg_white"); // Should have dark:bg-gray-900 style expect(output).toContain("_dark_bg_gray_900"); // Should generate conditional: _twColorScheme === 'dark' && ... expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/); }); it("should support both dark: and light: modifiers", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input, undefined, true); // Should have all three styles expect(output).toContain("_bg_gray_100"); expect(output).toContain("_dark_bg_gray_900"); expect(output).toContain("_light_bg_white"); // Should have both conditionals expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/); expect(output).toMatch(/_twColorScheme\s*===\s*['"]light['"]/); }); it("should inject hook once for multiple elements with color scheme modifiers", () => { const input = ` import React from 'react'; import { View, Text } from 'react-native'; export function Component() { return ( <> ); } `; const output = transform(input, undefined, true); // Count occurrences of useColorScheme() call - should be exactly 1 const hookCallMatches = output.match(/=\s*useColorScheme\(\)/g); expect(hookCallMatches).toHaveLength(1); // Should have color scheme variable expect(output).toContain("_twColorScheme"); }); it("should work with color scheme and platform modifiers together", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input, undefined, true); // Should have Platform import expect(output).toContain("Platform"); // Should have useColorScheme import expect(output).toContain("useColorScheme"); // Should have Platform.select for ios: expect(output).toContain("Platform.select"); expect(output).toContain("_ios_p_6"); // Should have color scheme conditional for dark: expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/); expect(output).toContain("_dark_bg_gray_900"); }); it("should only add useColorScheme import once when needed", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ( <> ); } `; const output = transform(input, undefined, true); // Count useColorScheme imports const importMatches = output.match(/import.*useColorScheme.*from ['"]react-native['"]/g); expect(importMatches).toHaveLength(1); }); it("should merge with existing useColorScheme import", () => { const input = ` import React from 'react'; import { View, useColorScheme } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should still use useColorScheme expect(output).toContain("useColorScheme"); // Should inject hook call expect(output).toContain("_twColorScheme"); expect(output).toContain("useColorScheme()"); }); it("should work with concise arrow functions", () => { const input = ` import React from 'react'; import { View } from 'react-native'; const Component = () => ; `; const output = transform(input, undefined, true); // Should inject useColorScheme import expect(output).toContain("useColorScheme"); // Should convert concise arrow to block statement and inject hook expect(output).toContain("_twColorScheme"); expect(output).toContain("useColorScheme()"); expect(output).toContain("return"); // Should have the style expect(output).toContain("_dark_bg_gray_900"); expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/); }); it("should inject hook at component level when dark: used in nested callback", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { const items = [1, 2, 3]; return ( {items.map(item => ( ))} ); } `; const output = transform(input, undefined, true); // Should inject hook at Component level (not in map callback) expect(output).toContain("_twColorScheme"); expect(output).toContain("useColorScheme()"); // Hook should be injected in Component function, not in map callback // Count occurrences - should be exactly 1 at Component level const hookCallMatches = output.match(/=\s*useColorScheme\(\)/g); expect(hookCallMatches).toHaveLength(1); // Should still generate conditional expression expect(output).toContain("_dark_bg_gray_900"); expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/); }); it("should handle dynamic expressions with dark:/light: modifiers", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component({ isActive }) { return ( ); } `; const output = transform(input, undefined, true); // Should inject useColorScheme expect(output).toContain("useColorScheme"); expect(output).toContain("_twColorScheme"); // Should have both dark styles expect(output).toContain("_dark_bg_blue_500"); expect(output).toContain("_dark_bg_gray_900"); // Should have conditional expressions for color scheme expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/); }); it("should handle dynamic expressions with platform modifiers", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component({ isLarge }) { return ( ); } `; const output = transform(input, undefined, true); // Should inject Platform import expect(output).toContain("Platform"); // Should have both ios styles expect(output).toContain("_ios_p_8"); expect(output).toContain("_ios_p_6"); // Should have Platform.select expect(output).toContain("Platform.select"); }); it("should skip color scheme modifiers when used outside component scope", () => { // Suppress console.warn for this test const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const input = ` import React from 'react'; import { View } from 'react-native'; // Class component - no function component scope class MyComponent extends React.Component { render() { return ; } } `; const output = transform(input, undefined, true); // Should warn about invalid context expect(consoleWarnSpy).toHaveBeenCalledWith( expect.stringContaining("dark:/light: modifiers require a function component scope"), ); // Should NOT inject useColorScheme import (no valid component scope) expect(output).not.toContain("useColorScheme"); // Should NOT have _twColorScheme variable reference (would cause ReferenceError) expect(output).not.toContain("_twColorScheme"); // Should NOT have dark: style conditional (skipped due to no component scope) expect(output).not.toContain("_dark_bg_gray_900"); // Should still transform base classes (p-4) expect(output).toContain("_p_4"); consoleWarnSpy.mockRestore(); }); }); describe("Babel plugin - custom color scheme hook import", () => { it("should use custom import source for color scheme hook", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ; } `; const output = transform( input, { colorScheme: { importFrom: "@/hooks/useColorScheme", importName: "useColorScheme", }, }, true, ); // Should import from custom source expect(output).toContain('from "@/hooks/useColorScheme"'); expect(output).not.toContain('useColorScheme } from "react-native"'); // Should inject hook call expect(output).toContain("_twColorScheme = useColorScheme()"); // Should have conditional styling expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/); }); it("should use custom hook name", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ; } `; const output = transform( input, { colorScheme: { importFrom: "@react-navigation/native", importName: "useTheme", }, }, true, ); // Should import useTheme from React Navigation expect(output).toContain('from "@react-navigation/native"'); expect(output).toContain("useTheme"); // Should call useTheme hook expect(output).toContain("_twColorScheme = useTheme()"); // Should have conditional styling expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/); }); it("should merge custom hook with existing import from same source", () => { const input = ` import React from 'react'; import { View, Text } from 'react-native'; import { useNavigation } from '@react-navigation/native'; export function Component() { const navigation = useNavigation(); return ( navigation.navigate('Home')}>Go Home ); } `; const output = transform( input, { colorScheme: { importFrom: "@react-navigation/native", importName: "useTheme", }, }, true, ); // Should merge with existing import (both useNavigation and useTheme in same import) expect(output).toMatch( /import\s+\{\s*useNavigation[^}]*useTheme[^}]*\}\s+from\s+['"]@react-navigation\/native['"]/, ); expect(output).toContain("useNavigation()"); expect(output).toContain("useTheme()"); // Should only have one import from that source const importCount = (output.match(/@react-navigation\/native/g) ?? []).length; expect(importCount).toBe(1); }); it("should not duplicate custom hook if already imported", () => { const input = ` import React from 'react'; import { View } from 'react-native'; import { useColorScheme } from '@/hooks/useColorScheme'; export function Component() { return ; } `; const output = transform( input, { colorScheme: { importFrom: "@/hooks/useColorScheme", importName: "useColorScheme", }, }, true, ); // Should not add duplicate import const importMatches = output.match(/import.*useColorScheme.*from ['"]@\/hooks\/useColorScheme['"]/g); expect(importMatches).toHaveLength(1); // Should still inject hook call expect(output).toContain("_twColorScheme = useColorScheme()"); }); it("should use react-native by default when no custom config provided", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should use default react-native import (can be single or double quotes) expect(output).toMatch(/useColorScheme\s*}\s*from\s+['"]react-native['"]/); expect(output).not.toContain("@/hooks"); expect(output).not.toContain("@react-navigation"); // Should inject hook call with default name expect(output).toContain("_twColorScheme = useColorScheme()"); }); it("should create separate import when only type-only import exists", () => { const input = ` import React from 'react'; import { View } from 'react-native'; import type { NavigationProp } from '@react-navigation/native'; export function Component() { return ; } `; const output = transform( input, { colorScheme: { importFrom: "@react-navigation/native", importName: "useTheme", }, }, true, ); // TypeScript preset strips type-only imports, but the important thing is: // 1. useTheme hook is imported (not skipped thinking it was already imported) // 2. Hook is correctly called in the component expect(output).toMatch(/import\s+\{\s*useTheme\s*\}\s+from\s+['"]@react-navigation\/native['"]/); expect(output).toContain("_twColorScheme = useTheme()"); }); it("should use aliased identifier when hook is already imported with alias", () => { const input = ` import React from 'react'; import { View, Text } from 'react-native'; import { useTheme as navTheme } from '@react-navigation/native'; export function Component() { const theme = navTheme(); return ( {theme.dark ? 'Dark' : 'Light'} ); } `; const output = transform( input, { colorScheme: { importFrom: "@react-navigation/native", importName: "useTheme", }, }, true, ); // Should not add duplicate import const importMatches = output.match( /import\s+\{[^}]*useTheme[^}]*\}\s+from\s+['"]@react-navigation\/native['"]/g, ); expect(importMatches).toHaveLength(1); // Should still have the aliased import expect(output).toMatch(/useTheme\s+as\s+navTheme/); // Should call the aliased name (navTheme), not the export name (useTheme) // Both the user's code and our injected hook should use navTheme expect(output).toContain("_twColorScheme = navTheme()"); expect(output).not.toContain("_twColorScheme = useTheme()"); }); it("should not treat type-only imports as having the hook", () => { const input = ` import React from 'react'; import { View } from 'react-native'; import type { useColorScheme } from 'react-native'; export function Component() { return ; } `; const output = transform(input, undefined, true); // Should add a VALUE import for useColorScheme (type import doesn't count) expect(output).toMatch(/import\s+\{[^}]*useColorScheme[^}]*\}\s+from\s+['"]react-native['"]/); // Should inject the hook expect(output).toContain("_twColorScheme = useColorScheme()"); // Should have both type-only and value imports in output // (TypeScript preset keeps type imports for type checking) const colorSchemeMatches = output.match(/useColorScheme/g); expect(colorSchemeMatches).toBeTruthy(); if (colorSchemeMatches) { expect(colorSchemeMatches.length).toBeGreaterThanOrEqual(2); // At least in import and hook call } }); it("should handle both type-only and aliased imports together", () => { const input = ` import React from 'react'; import { View, Text } from 'react-native'; import type { Theme } from '@react-navigation/native'; import { useTheme as getNavTheme } from '@react-navigation/native'; export function Component() { const theme = getNavTheme(); return ( {theme.dark ? 'Dark Mode' : 'Light Mode'} ); } `; const output = transform( input, { colorScheme: { importFrom: "@react-navigation/native", importName: "useTheme", }, }, true, ); // TypeScript preset strips type-only imports // The important thing is: should not add duplicate import, and should use aliased name expect(output).toMatch( /import\s+\{[^}]*useTheme\s+as\s+getNavTheme[^}]*\}\s+from\s+['"]@react-navigation\/native['"]/, ); // Should not add duplicate import - useTheme should only appear in the aliased import const useThemeImports = output.match( /import\s+\{[^}]*useTheme[^}]*\}\s+from\s+['"]@react-navigation\/native['"]/g, ); expect(useThemeImports).toHaveLength(1); // Should call the aliased name for both user code and our injected hook expect(output).toContain("_twColorScheme = getNavTheme()"); expect(output).not.toContain("_twColorScheme = useTheme()"); }); }); describe("Babel plugin - import injection", () => { it("should not add StyleSheet import to files without className usage", () => { const input = ` import { View, Text } from 'react-native'; function MyComponent() { return Hello; } `; const output = transform(input, undefined, true); // Should not mutate the import by adding StyleSheet // Count occurrences of "StyleSheet" in output const styleSheetCount = (output.match(/StyleSheet/g) ?? []).length; expect(styleSheetCount).toBe(0); // Should not have _twStyles definition expect(output).not.toContain("_twStyles"); expect(output).not.toContain("StyleSheet.create"); // Original imports should remain unchanged expect(output).toContain("View"); expect(output).toContain("Text"); }); it("should add StyleSheet import only when className is used", () => { const input = ` import { View } from 'react-native'; function MyComponent() { return ; } `; const output = transform(input, undefined, true); // Should have StyleSheet import (both single and double quotes) expect(output).toMatch(/import.*StyleSheet.*from ['"]react-native['"]|require\(['"]react-native['"]\)/); // Should have _twStyles definition expect(output).toContain("_twStyles"); expect(output).toContain("StyleSheet.create"); }); it("should add Platform import only when platform modifiers are used", () => { const input = ` import { View } from 'react-native'; function MyComponent() { return ; } `; const output = transform(input, undefined, true); // Should have Platform import expect(output).toContain("Platform"); // Should have StyleSheet import too expect(output).toContain("StyleSheet"); // Should use Platform.select expect(output).toContain("Platform.select"); }); it("should not add Platform import without platform modifiers", () => { const input = ` import { View } from 'react-native'; function MyComponent() { return ; } `; const output = transform(input, undefined, true); // Should not have Platform import const platformCount = (output.match(/Platform/g) ?? []).length; expect(platformCount).toBe(0); // Should still have StyleSheet expect(output).toContain("StyleSheet"); }); }); describe("Babel plugin - scheme: modifier", () => { it.skip("should expand scheme: modifier into dark: and light: modifiers", () => { // Note: This test requires tailwind.config.js with custom colors defined // The scheme: modifier expands to dark: and light: modifiers which require // the color variants to exist in customColors (e.g., systemGray-dark, systemGray-light) // // Integration test should be done in a real project with tailwind.config.js const input = ` import { View } from 'react-native'; function MyComponent() { return ; } `; const output = transform(input, undefined, true); // Should generate both dark and light variants expect(output).toContain("_dark_text_systemGray_dark"); expect(output).toContain("_light_text_systemGray_light"); // Should inject useColorScheme hook expect(output).toContain("useColorScheme"); expect(output).toContain("_twColorScheme"); // Should have conditional expressions expect(output).toContain("_twColorScheme === 'dark'"); expect(output).toContain("_twColorScheme === 'light'"); }); }); describe("Babel plugin - color scheme modifiers in tw/twStyle", () => { it("should transform tw with dark: modifier inside component", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`bg-white dark:bg-gray-900\`; return null; } `; const output = transform(input); // Should inject useColorScheme hook expect(output).toContain("useColorScheme"); expect(output).toContain("_twColorScheme"); // Should generate style array with conditionals expect(output).toContain("style: ["); expect(output).toContain('_twColorScheme === "dark"'); expect(output).toContain("_twStyles._dark_bg_gray_900"); expect(output).toContain("_twStyles._bg_white"); // Should have StyleSheet.create expect(output).toContain("StyleSheet.create"); }); it("should transform twStyle with light: modifier inside component", () => { const input = ` import { twStyle } from '@mgcrea/react-native-tailwind'; export const MyComponent = () => { const buttonStyles = twStyle('text-gray-900 light:text-gray-100'); return null; }; `; const output = transform(input); // Should inject useColorScheme hook expect(output).toContain("useColorScheme"); expect(output).toContain("_twColorScheme"); // Should generate style array with conditionals expect(output).toContain("style: ["); expect(output).toContain('_twColorScheme === "light"'); expect(output).toContain("_twStyles._light_text_gray_100"); expect(output).toContain("_twStyles._text_gray_900"); }); it("should transform tw with both dark: and light: modifiers", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`bg-blue-500 dark:bg-blue-900 light:bg-blue-100\`; return null; } `; const output = transform(input); // Should have both conditionals expect(output).toContain('_twColorScheme === "dark"'); expect(output).toContain('_twColorScheme === "light"'); expect(output).toContain("_twStyles._dark_bg_blue_900"); expect(output).toContain("_twStyles._light_bg_blue_100"); expect(output).toContain("_twStyles._bg_blue_500"); }); it("should combine color scheme modifiers with state modifiers", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`bg-white dark:bg-gray-900 active:bg-blue-500\`; return null; } `; const output = transform(input); // Should have color scheme conditionals in style array expect(output).toContain("style: ["); expect(output).toContain('_twColorScheme === "dark"'); // Should have activeStyle property (separate from color scheme) expect(output).toContain("activeStyle:"); expect(output).toContain("_twStyles._active_bg_blue_500"); }); it("should warn if tw with color scheme modifiers used outside component", () => { const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const input = ` import { tw } from '@mgcrea/react-native-tailwind'; const globalStyles = tw\`bg-white dark:bg-gray-900\`; `; const output = transform(input); // Should warn about usage outside component expect(consoleWarnSpy).toHaveBeenCalledWith( expect.stringContaining("Color scheme modifiers (dark:, light:) in tw/twStyle calls"), ); // Should not inject hook (no component scope) expect(output).not.toContain("useColorScheme"); // Should still generate styles but without runtime conditionals expect(output).toContain("_twStyles"); consoleWarnSpy.mockRestore(); }); it("should handle tw with only dark: modifier (no base class)", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`dark:bg-gray-900\`; return null; } `; const output = transform(input); // Should still generate style array expect(output).toContain("style: ["); expect(output).toContain('_twColorScheme === "dark"'); expect(output).toContain("_twStyles._dark_bg_gray_900"); }); it("should work with custom color scheme hook import", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; import { useTheme } from '@react-navigation/native'; function MyComponent() { const styles = tw\`bg-white dark:bg-gray-900\`; return null; } `; const options: PluginOptions = { colorScheme: { importFrom: "@react-navigation/native", importName: "useTheme", }, }; const output = transform(input, options); // Should use existing import (not duplicate) const themeImportCount = (output.match(/useTheme/g) ?? []).length; // Should appear in import statement and hook call expect(themeImportCount).toBeGreaterThanOrEqual(2); // Should call the custom hook expect(output).toContain("useTheme()"); }); it("should generate both style array and darkStyle/lightStyle properties", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`bg-white dark:bg-gray-900 light:bg-gray-50\`; return null; } `; const output = transform(input); // Should have runtime conditional in style array expect(output).toContain("style: ["); expect(output).toContain('_twColorScheme === "dark"'); expect(output).toContain('_twColorScheme === "light"'); // Should ALSO have darkStyle and lightStyle properties for manual access expect(output).toContain("darkStyle:"); expect(output).toContain("lightStyle:"); expect(output).toContain("_twStyles._dark_bg_gray_900"); expect(output).toContain("_twStyles._light_bg_gray_50"); }); it("should allow accessing raw color values from darkStyle/lightStyle", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const btnStyles = tw\`bg-blue-500 dark:bg-blue-900\`; // User can access raw hex for Reanimated const darkBgColor = btnStyles.darkStyle?.backgroundColor; return null; } `; const output = transform(input); // Should have darkStyle property available expect(output).toContain("darkStyle:"); expect(output).toContain("_twStyles._dark_bg_blue_900"); // The actual usage line should be preserved (TypeScript/Babel doesn't remove it) expect(output).toContain("btnStyles.darkStyle"); }); // Platform modifier tests for tw/twStyle it("should transform tw with ios: modifier", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`bg-white ios:p-6\`; return null; } `; const output = transform(input); // Should add Platform import expect(output).toContain("Platform"); expect(output).toContain('from "react-native"'); // Should generate style array with Platform.select() expect(output).toContain("style: ["); expect(output).toContain("Platform.select"); expect(output).toContain("ios:"); expect(output).toContain("_twStyles._ios_p_6"); expect(output).toContain("_twStyles._bg_white"); // Should have StyleSheet.create expect(output).toContain("StyleSheet.create"); }); it("should transform twStyle with android: modifier", () => { const input = ` import { twStyle } from '@mgcrea/react-native-tailwind'; export const MyComponent = () => { const buttonStyles = twStyle('bg-blue-500 android:p-8'); return null; }; `; const output = transform(input); // Should add Platform import expect(output).toContain("Platform"); // Should generate style array with Platform.select() expect(output).toContain("style: ["); expect(output).toContain("Platform.select"); expect(output).toContain("android:"); expect(output).toContain("_twStyles._android_p_8"); expect(output).toContain("_twStyles._bg_blue_500"); }); it("should transform tw with multiple platform modifiers", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`bg-white ios:p-6 android:p-8 web:p-4\`; return null; } `; const output = transform(input); // Should generate Platform.select() with all platforms expect(output).toContain("Platform.select"); expect(output).toContain("ios:"); expect(output).toContain("android:"); expect(output).toContain("web:"); expect(output).toContain("_twStyles._ios_p_6"); expect(output).toContain("_twStyles._android_p_8"); expect(output).toContain("_twStyles._web_p_4"); }); it("should combine platform modifiers with color-scheme modifiers", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`bg-white ios:p-6 dark:bg-gray-900\`; return null; } `; const output = transform(input); // Should have both Platform and useColorScheme expect(output).toContain("Platform"); expect(output).toContain("useColorScheme"); expect(output).toContain("_twColorScheme"); // Should have both conditionals in style array expect(output).toContain("Platform.select"); expect(output).toContain('_twColorScheme === "dark"'); }); it("should generate iosStyle/androidStyle/webStyle properties for manual access", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`bg-white ios:p-6 android:p-8 web:p-4\`; return null; } `; const output = transform(input); // Should have separate platform style properties expect(output).toContain("iosStyle:"); expect(output).toContain("_twStyles._ios_p_6"); expect(output).toContain("androidStyle:"); expect(output).toContain("_twStyles._android_p_8"); expect(output).toContain("webStyle:"); expect(output).toContain("_twStyles._web_p_4"); // Should also have runtime Platform.select() in style array expect(output).toContain("Platform.select"); }); it("should work with only platform modifiers (no base class)", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`ios:p-6 android:p-8\`; return null; } `; const output = transform(input); // Should generate Platform.select() even without base classes expect(output).toContain("Platform.select"); expect(output).toContain("_twStyles._ios_p_6"); expect(output).toContain("_twStyles._android_p_8"); }); it("should allow accessing platform-specific styles manually", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const btnStyles = tw\`bg-blue-500 ios:p-6\`; const iosPadding = btnStyles.iosStyle; return null; } `; const output = transform(input); // Should have iosStyle property available expect(output).toContain("iosStyle:"); expect(output).toContain("_twStyles._ios_p_6"); // The actual usage line should be preserved expect(output).toContain("btnStyles.iosStyle"); }); it("should combine state modifiers with platform modifiers", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; function MyComponent() { const styles = tw\`bg-white active:bg-blue-500 ios:p-6\`; return null; } `; const output = transform(input); // Should have both activeStyle and platform modifiers expect(output).toContain("activeStyle:"); expect(output).toContain("_twStyles._active_bg_blue_500"); expect(output).toContain("Platform.select"); expect(output).toContain("_twStyles._ios_p_6"); }); }); describe("Babel plugin - window dimensions (w-screen/h-screen)", () => { it("should inject useWindowDimensions hook for w-screen in function component", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function MyComponent() { return ; } `; const output = transform(input, undefined, true); // Should import useWindowDimensions expect(output).toContain("useWindowDimensions"); expect(output).toMatch(/import.*useWindowDimensions.*from ['"]react-native['"]/); // Should inject hook call expect(output).toContain("_twDimensions"); expect(output).toContain("useWindowDimensions()"); // Should generate inline style with width expect(output).toMatch(/width:\s*_twDimensions\.width/); // Should have StyleSheet for static styles expect(output).toContain("_twStyles"); expect(output).toContain("backgroundColor:"); }); it("should inject useWindowDimensions hook for h-screen in function component", () => { const input = ` import React from 'react'; import { View } from 'react-native'; export function MyComponent() { return ; } `; const output = transform(input, undefined, true); // Should import useWindowDimensions expect(output).toContain("useWindowDimensions"); // Should inject hook call expect(output).toContain("_twDimensions"); expect(output).toContain("useWindowDimensions()"); // Should generate inline style with height expect(output).toMatch(/height:\s*_twDimensions\.height/); }); it("should handle both w-screen and h-screen together", () => { const input = ` import React from 'react'; import { View } from 'react-native'; function FullScreenView() { return ; } `; const output = transform(input, undefined, true); // Should inject hook expect(output).toContain("useWindowDimensions()"); // Should generate inline style with both dimensions expect(output).toMatch(/width:\s*_twDimensions\.width/); expect(output).toMatch(/height:\s*_twDimensions\.height/); // Should have StyleSheet for static styles expect(output).toContain("backgroundColor:"); }); it("should handle w-screen/h-screen with arrow function component", () => { const input = ` import React from 'react'; import { View } from 'react-native'; const MyComponent = () => { return ; }; `; const output = transform(input, undefined, true); // Should inject hook expect(output).toContain("useWindowDimensions()"); expect(output).toMatch(/width:\s*_twDimensions\.width/); }); it("should handle concise arrow function and inject hook", () => { const input = ` import React from 'react'; import { View } from 'react-native'; const MyComponent = () => ; `; const output = transform(input, undefined, true); // Should convert concise arrow to block statement and inject hook expect(output).toContain("useWindowDimensions()"); expect(output).toContain("return"); expect(output).toMatch(/width:\s*_twDimensions\.width/); }); it("should merge useWindowDimensions with existing react-native import", () => { const input = ` import React from 'react'; import { View, Text } from 'react-native'; function MyComponent() { return ; } `; const output = transform(input, undefined, true); // Should merge useWindowDimensions into existing import (not create separate import) // The key is that useWindowDimensions should be imported expect(output).toContain("useWindowDimensions"); }); it("should warn when w-screen/h-screen used outside function component", () => { const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const input = ` import React from 'react'; import { View } from 'react-native'; class MyComponent extends React.Component { render() { return ; } } `; transform(input, undefined, true); // Should warn about usage in class component expect(consoleWarnSpy).toHaveBeenCalledWith( expect.stringContaining("w-screen/h-screen classes require a function component scope"), ); consoleWarnSpy.mockRestore(); }); it("should combine w-screen with other static classes", () => { const input = ` import React from 'react'; import { View } from 'react-native'; function MyComponent() { return ; } `; const output = transform(input, undefined, true); // Should have static styles in StyleSheet expect(output).toContain("padding:"); expect(output).toContain("backgroundColor:"); expect(output).toContain("borderRadius:"); // Should have runtime dimension expect(output).toMatch(/width:\s*_twDimensions\.width/); // Should combine as array expect(output).toContain("_twStyles"); expect(output).toContain("_twDimensions"); }); it("should inject hook only once even with multiple w-screen/h-screen uses", () => { const input = ` import React from 'react'; import { View } from 'react-native'; function MyComponent() { return ( <> ); } `; const output = transform(input, undefined, true); // Count occurrences of hook injection (should be exactly 1) const hookMatches = output.match(/_twDimensions\s*=\s*useWindowDimensions\(\)/g) ?? []; expect(hookMatches.length).toBe(1); }); it("should handle aliased useWindowDimensions import", () => { const input = ` import React from 'react'; import { View, useWindowDimensions as useDims } from 'react-native'; function MyComponent() { const dims = useDims(); return ; } `; const output = transform(input, undefined, true); // Should use the aliased name expect(output).toContain("useDims()"); // Should still generate _twDimensions variable for our use expect(output).toContain("_twDimensions"); }); }); describe("Babel plugin - w-screen/h-screen error handling", () => { it("should error when w-screen is combined with dark: modifier", () => { const input = ` import { View } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with color scheme modifiers/, ); }); it("should error when h-screen is combined with light: modifier", () => { const input = ` import { View } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with color scheme modifiers/, ); }); it("should error when w-screen is combined with active: modifier", () => { const input = ` import { Pressable } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with state modifiers/, ); }); it("should error when h-screen is combined with hover: modifier", () => { const input = ` import { Pressable } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with state modifiers/, ); }); it("should error when w-screen is combined with ios: modifier", () => { const input = ` import { View } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with.*platform modifiers/, ); }); it("should error when h-screen is combined with android: modifier", () => { const input = ` import { View } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with.*platform modifiers/, ); }); it("should error when w-screen is used in tw`` call", () => { const input = ` import { tw } from '@mgcrea/react-native-tailwind'; export function MyComponent() { const styles = tw\`w-screen bg-white\`; return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen are not supported in tw.*or twStyle/, ); }); it("should error when h-screen is used in twStyle() call", () => { const input = ` import { twStyle } from '@mgcrea/react-native-tailwind'; export function MyComponent() { const styles = twStyle('h-screen p-4'); return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen are not supported in tw.*or twStyle/, ); }); it("should properly merge w-screen with Pressable style function", () => { const input = ` import { Pressable } from 'react-native'; export function MyComponent() { return ( [pressed && { opacity: 0.5 }]} /> ); } `; const output = transform(input, undefined, true); // Should inject useWindowDimensions hook expect(output).toContain("useWindowDimensions"); expect(output).toContain("_twDimensions"); // Should wrap the style function properly expect(output).toContain("_state"); // Should contain the runtime dimension access expect(output).toContain("_twDimensions.width"); // Should call the existing function expect(output).toContain("pressed"); }); it("should properly merge h-screen with arrow function style", () => { const input = ` import { Pressable } from 'react-native'; export function MyComponent() { return ( state.pressed ? { opacity: 0.8 } : null} /> ); } `; const output = transform(input, undefined, true); // Should inject useWindowDimensions hook expect(output).toContain("useWindowDimensions"); expect(output).toContain("_twDimensions"); // Should wrap the style function expect(output).toContain("_state"); // Should contain the runtime dimension access expect(output).toContain("_twDimensions.height"); // Should call the original function expect(output).toContain("state.pressed"); }); it("should error when w-screen is in base classes with dark: modifier", () => { const input = ` import { View } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with modifiers/, ); }); it("should error when w-screen is in base classes with ios: modifier", () => { const input = ` import { View } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with modifiers/, ); }); it("should error when h-screen is in base classes with active: modifier", () => { const input = ` import { Pressable } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with.*modifiers/, ); }); it("should error when w-screen is in base classes with multiple modifiers", () => { const input = ` import { View } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with modifiers/, ); }); it("should error when h-screen is in base classes with light: modifier", () => { const input = ` import { View } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with modifiers/, ); }); it("should error when w-screen is in base classes with android: modifier", () => { const input = ` import { View } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with modifiers/, ); }); it("should error when h-screen is in base classes with web: modifier", () => { const input = ` import { View } from 'react-native'; export function MyComponent() { return ; } `; expect(() => transform(input, undefined, true)).toThrow( /w-screen and h-screen cannot be combined with modifiers/, ); }); });