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();
});
});