import { transformSync } from "@babel/core"; import { describe, expect, it } from "vitest"; import babelPlugin from "../plugin.js"; /** * Helper to transform code with the Babel plugin */ function transform(code: string) { const result = transformSync(code, { presets: ["@babel/preset-react"], plugins: [babelPlugin], filename: "test.tsx", configFile: false, babelrc: false, }); return result?.code ?? ""; } describe("Style merging - mergeStyleAttribute", () => { it("should merge className with identifier style prop (object variable)", () => { const input = ` import { TouchableOpacity } from 'react-native'; export function Component() { const style = { marginHorizontal: 24 }; return ( ); } `; const output = transform(input); // Should have StyleSheet with className styles expect(output).toContain("StyleSheet.create"); expect(output).toContain("_m_4"); // Should create a simple array merge, NOT a function wrapper expect(output).toContain("[_twStyles._m_4, style]"); // Should NOT have typeof check or _state parameter expect(output).not.toContain("typeof"); expect(output).not.toContain("_state"); // Should not have className in output expect(output).not.toContain("className"); }); it("should merge className with member expression style prop", () => { const input = ` import { View } from 'react-native'; export function Component(props) { return ( ); } `; const output = transform(input); // Should have StyleSheet with className styles expect(output).toContain("StyleSheet.create"); expect(output).toContain("_bg_blue_500_p_2"); // Should create a simple array merge, NOT a function wrapper expect(output).toContain("[_twStyles._bg_blue_500_p_2, props.style]"); // Should NOT have typeof check or _state parameter expect(output).not.toContain("typeof"); expect(output).not.toContain("_state"); // Should not have className in output expect(output).not.toContain("className"); }); it("should merge className with array style prop", () => { const input = ` import { View } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input); // Should have StyleSheet with className styles expect(output).toContain("StyleSheet.create"); expect(output).toContain("_m_4"); // Should create array merge without function wrapper expect(output).toContain("_twStyles._m_4"); // Should NOT have typeof check or _state parameter expect(output).not.toContain("typeof"); expect(output).not.toContain("_state"); // Should not have className in output expect(output).not.toContain("className"); }); it("should merge className with inline object style prop", () => { const input = ` import { View } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input); // Should have StyleSheet with className styles expect(output).toContain("StyleSheet.create"); expect(output).toContain("_m_4_p_2"); // Should create array merge expect(output).toContain("_twStyles._m_4_p_2"); expect(output).toContain("backgroundColor:"); // Should not have className in output expect(output).not.toContain("className"); }); it("should merge className with inline function style prop", () => { const input = ` import { TextInput } from 'react-native'; export function Component() { return ( [ baseStyles, focused && focusedStyles, ]} /> ); } `; const output = transform(input); // Should have StyleSheet with className styles expect(output).toContain("StyleSheet.create"); expect(output).toContain("_border_border_gray_300"); // Should create a wrapper function that merges both expect(output).toContain("_state"); expect(output).toContain("_twStyles._border_border_gray_300"); // Should have a function that accepts state and returns an array expect(output).toMatch(/_state\s*=>/); // Should not have className in output expect(output).not.toContain("className"); }); }); describe("Style merging - mergeDynamicStyleAttribute", () => { it("should merge dynamic className with identifier style prop", () => { const input = ` import { View } from 'react-native'; export function Component({ isActive }) { const customStyle = { opacity: 0.8 }; return ( ); } `; const output = transform(input); // Should have StyleSheet with both className styles expect(output).toContain("StyleSheet.create"); expect(output).toContain("_p_2"); expect(output).toContain("_bg_blue_500"); expect(output).toContain("_bg_gray_300"); // Should create array merge without function wrapper expect(output).toContain("customStyle"); // Should NOT have typeof check or _state parameter for the style merge expect(output).not.toContain("typeof"); expect(output).not.toContain("_state"); // Should not have className in output expect(output).not.toContain("className"); }); it("should merge dynamic className with inline function style prop", () => { const input = ` import { TextInput } from 'react-native'; export function Component({ isError }) { return ( [ baseStyles, focused && focusedStyles, ]} /> ); } `; const output = transform(input); // 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 merge dynamic className with member expression style prop", () => { const input = ` import { View } from 'react-native'; export function Component({ variant, ...props }) { return ( ); } `; const output = transform(input); // Should have StyleSheet with both className styles expect(output).toContain("StyleSheet.create"); expect(output).toContain("_m_2"); expect(output).toContain("_bg_blue_500"); expect(output).toContain("_bg_gray_300"); // Should create array merge without function wrapper expect(output).toContain("props.style"); // Should NOT have typeof check or _state parameter for the style merge expect(output).not.toContain("typeof"); expect(output).not.toContain("_state"); // Should not have className in output expect(output).not.toContain("className"); }); }); describe("Style merging - edge cases", () => { it("should handle className without existing style prop", () => { const input = ` import { View } from 'react-native'; export function Component() { return ; } `; const output = transform(input); // Should have StyleSheet with className styles expect(output).toContain("StyleSheet.create"); expect(output).toContain("_m_4_p_2"); // Should have style prop with reference to stylesheet expect(output).toContain("style: _twStyles._m_4_p_2"); // 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); // Should not create StyleSheet expect(output).not.toContain("StyleSheet.create"); // Should preserve original style prop expect(output).toContain("style: myStyle"); // Should not have className in output expect(output).not.toContain("className"); }); it("should handle multiple components with different merge scenarios", () => { const input = ` import { View, TouchableOpacity } from 'react-native'; export function Component() { const style1 = { opacity: 0.5 }; return ( <> ); } `; const output = transform(input); // Should have StyleSheet with all className styles expect(output).toContain("StyleSheet.create"); expect(output).toContain("_m_4"); expect(output).toContain("_p_2"); expect(output).toContain("_bg_red_500"); // Should handle identifier merge expect(output).toContain("[_twStyles._m_4, style1]"); // Should handle no merge expect(output).toContain("_twStyles._p_2"); // Should handle array merge expect(output).toContain("_twStyles._bg_red_500"); // Should not have className in output expect(output).not.toContain("className"); }); }); describe("Style function merging - mergeStyleFunctionAttribute", () => { it("should merge modifier className with existing function style prop", () => { const input = ` import { Pressable } from 'react-native'; export function Component() { return ( pressed && { opacity: 0.8 }} /> ); } `; const output = transform(input); // Should create wrapper function that merges both style functions expect(output).toContain("_state"); expect(output).toMatch(/_state\s*=>/); // Should call both the new style function and existing style function expect(output).toContain("_bg_blue_500"); expect(output).toContain("_active_bg_blue_700"); // Should not have className in output expect(output).not.toContain("className"); }); it("should merge modifier className with static existing style when using Pressable", () => { const input = ` import { Pressable } from 'react-native'; export function Component() { return ( ); } `; const output = transform(input); // Should create function that wraps className styles expect(output).toContain("_state"); // Should include both the className styles and the static style expect(output).toContain("_p_4"); expect(output).toContain("_active_bg_gray_100"); expect(output).toContain("borderRadius"); // Should not have className in output expect(output).not.toContain("className"); }); });