import React, { useContext } from "react"; import { View, Text } from "react-native"; import { render, fireEvent, waitFor, act } from "@testing-library/react-native"; import { MeasurementsPortal, MeasurementsPortalContextProvider, MeasurementPortalContext, } from "../MeasurementsPortal"; // Mock uuid jest.mock("uuid", () => ({ v4: jest.fn(() => "mock-uuid-key"), })); describe("MeasurementsPortal", () => { const mockOnLayout = jest.fn(); const MockComponent = jest.fn(() => ); beforeEach(() => { jest.clearAllMocks(); }); it("renders with correct testID", () => { const { getByTestId } = render( ); expect(getByTestId("MeasurementsPortal")).toBeTruthy(); }); it("renders the passed Component", () => { const { getByTestId } = render( ); expect(getByTestId("mock-component")).toBeTruthy(); expect(MockComponent).toHaveBeenCalled(); }); it("passes componentProps to the Component", () => { const componentProps = { testProp: "test-value", anotherProp: 123, }; render( ); expect(MockComponent).toHaveBeenCalledWith(componentProps, {}); }); it("calls onLayout when layout changes", () => { const { getByTestId } = render( ); const container = getByTestId("MeasurementsPortal"); const wrapper = container.children[0] as any; const layoutEvent = { nativeEvent: { layout: { width: 100, height: 200 }, }, }; fireEvent(wrapper, "layout", layoutEvent); expect(mockOnLayout).toHaveBeenCalledWith(layoutEvent); }); it("applies correct container styles", () => { const { getByTestId } = render( ); const container = getByTestId("MeasurementsPortal"); expect(container.props.style).toEqual({ position: "absolute", opacity: 0, top: 0, left: 0, right: 0, bottom: 0, }); }); it("renders wrapper with onLayout", () => { const { getByTestId } = render( ); const container = getByTestId("MeasurementsPortal"); const wrapper = container.children[0] as any; expect(wrapper.props.onLayout).toBe(mockOnLayout); }); it("works with different Component types", () => { const CustomComponent = ({ title }: { title: string }) => ( {title} ); const props = { title: "Test Title" }; const { getByTestId, getByText } = render( ); expect(getByTestId("custom-text")).toBeTruthy(); expect(getByText("Test Title")).toBeTruthy(); }); it("handles component without componentProps", () => { render( ); expect(MockComponent).toHaveBeenCalledWith({}, {}); }); }); describe("MeasurementsPortalContextProvider", () => { const TestConsumer = () => { const context = useContext(MeasurementPortalContext); return ( {context?.measuringInProgress ? "measuring" : "idle"} ); }; it("provides context to children", () => { const { getByTestId } = render( ); expect(getByTestId("test-consumer")).toBeTruthy(); expect(getByTestId("measuring-status")).toBeTruthy(); }); it("initially sets measuringInProgress to false", () => { const { getByText } = render( ); expect(getByText("idle")).toBeTruthy(); }); it("renders MeasurementsPortal with default View component", () => { const { getByTestId } = render( ); expect(getByTestId("MeasurementsPortal")).toBeTruthy(); }); describe("measureComponent function", () => { const TestMeasureComponent = () => { const context = useContext(MeasurementPortalContext); const [result, setResult] = React.useState(null); const handleMeasure = async () => { if (context?.measureComponent) { const measurement = await context.measureComponent(View, { index: 0, onLoadFinished: () => {}, }); setResult(measurement); } }; return ( {context?.measuringInProgress ? "measuring" : "idle"} Measure {result ? ( {result.width}x{result.height} ) : null} ); }; it("sets measuringInProgress to true when measureComponent is called", async () => { const { getByTestId, getByText } = render( ); expect(getByText("idle")).toBeTruthy(); await act(async () => { fireEvent.press(getByTestId("measure-button")); }); expect(getByText("measuring")).toBeTruthy(); }); it("can measure a component through the context", async () => { const TestMeasureComponent = () => { const context = useContext(MeasurementPortalContext); const [measured, setMeasured] = React.useState(false); React.useEffect(() => { if (context?.measureComponent) { context .measureComponent(View, { index: 5, onLoadFinished: () => {}, }) .then(() => { setMeasured(true); }); } }, [context]); return ( {measured ? "measured" : "not-measured"} ); }; const { getByText } = render( ); // Initially should be not measured expect(getByText("not-measured")).toBeTruthy(); }); it("handles error in onLoadFinished", async () => { const ErrorComponent = ({ onLoadFinished }: any) => { React.useEffect(() => { // Simulate error setTimeout( () => onLoadFinished({ error: new Error("Test error") }), 10 ); }, [onLoadFinished]); return ; }; const TestErrorFlow = () => { const context = useContext(MeasurementPortalContext); const [result, setResult] = React.useState(null); const handleMeasure = React.useCallback(async () => { if (context?.measureComponent) { const measurement = await context.measureComponent(ErrorComponent, { index: 1, onLoadFinished: () => {}, }); setResult(measurement); } }, [context]); React.useEffect(() => { handleMeasure(); }, [handleMeasure]); return ( {result ? ( {result.width}x{result.height} ) : null} ); }; const { queryByText } = render( ); // Wait for error handling await waitFor(() => { expect(queryByText("0x0")).toBeTruthy(); }); }); }); }); describe("MeasurementPortalContext", () => { it("returns null when used outside of provider", () => { const TestComponent = () => { const context = useContext(MeasurementPortalContext); return ( {context ? "has-context" : "no-context"} ); }; const { getByText } = render(); expect(getByText("no-context")).toBeTruthy(); }); it("returns context value when used inside provider", () => { const TestComponent = () => { const context = useContext(MeasurementPortalContext); return ( {context ? "has-context" : "no-context"} ); }; const { getByText } = render( ); expect(getByText("has-context")).toBeTruthy(); }); });