@applicaster/quick-brick-core
Version:
Core package for Applicaster's Quick Brick App
233 lines (195 loc) • 6.53 kB
JSX
import React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { render, fireEvent, act } from "@testing-library/react-native";
import { useErrorStore } from "../store";
import { ErrorBoundary } from "../index";
jest.mock(
"@applicaster/zapp-react-native-ui-components/Components/ErrorScreen",
() => ({
// eslint-disable-next-line react/prop-types
ErrorScreen: ({ dismissError, message, buttons }) => (
<View testID="error-screen">
<Text testID="error-message">{message || "Default error message"}</Text>
{/* eslint-disable-next-line react/prop-types */}
{buttons?.map((button, index) => (
<TouchableOpacity
key={index}
onPress={button.onPress}
testID={`button-${index}`}
>
<Text>{button.text}</Text>
</TouchableOpacity>
))}
{dismissError ? (
<TouchableOpacity onPress={dismissError} testID="dismiss-button">
<Text>Dismiss Error</Text>
</TouchableOpacity>
) : null}
</View>
),
})
);
jest.mock("@applicaster/zapp-react-native-bridge/QuickBrick", () => ({
sendQuickBrickEvent: jest.fn(),
QUICK_BRICK_EVENTS: {
FORCE_APP_RELOAD: "force_app_reload",
},
}));
jest.mock("@applicaster/zapp-react-native-utils/logger", () => ({
coreLogger: {
addSubsystem: () => ({
error: jest.fn(),
}),
},
}));
// eslint-disable-next-line react/prop-types
const TestComponent = ({ error, errorMessage = "error !!" }) => {
if (error) {
throw new Error(errorMessage);
}
return (
<View>
<Text>all is good</Text>
</View>
);
};
describe("<ErrorBoundary />", () => {
// Clear all error states between tests
beforeEach(() => {
useErrorStore.getState().dismissError();
});
describe("when there is no error", () => {
it("renders children correctly", () => {
const { getByText } = render(
<ErrorBoundary>
<TestComponent />
</ErrorBoundary>
);
expect(getByText("all is good")).toBeTruthy();
});
});
describe("when an error is caught", () => {
const _consoleLogs = {
error: console.error,
log: console.log,
};
beforeAll(() => {
console.error = jest.fn();
console.log = jest.fn();
});
afterAll(() => {
console.error = _consoleLogs.error;
console.log = _consoleLogs.log;
});
it("renders error state with default values (recoverable error)", () => {
const { getByTestId } = render(
<ErrorBoundary>
<TestComponent error />
</ErrorBoundary>
);
expect(getByTestId("error-screen")).toBeTruthy();
expect(getByTestId("dismiss-button")).toBeTruthy(); // Should be recoverable by default
});
it("handles non-recoverable errors (no dismiss button)", () => {
const { getByTestId, queryByTestId } = render(
<ErrorBoundary recoverable={false}>
<TestComponent error />
</ErrorBoundary>
);
expect(getByTestId("error-screen")).toBeTruthy();
expect(queryByTestId("dismiss-button")).toBeFalsy(); // Should not be recoverable
});
it("shows custom error message from caught error", () => {
const customErrorMessage = "Custom error message for testing";
const { getByTestId } = render(
<ErrorBoundary>
<TestComponent error errorMessage={customErrorMessage} />
</ErrorBoundary>
);
// The error info should be passed to the ErrorScreen
expect(getByTestId("error-message").props.children).toBeTruthy();
});
it("can dismiss the error and restore the UI", () => {
// First render with error
const { getByTestId, queryByText, rerender } = render(
<ErrorBoundary>
<TestComponent error />
</ErrorBoundary>
);
// Verify error screen is shown
expect(getByTestId("error-screen")).toBeTruthy();
// Dismiss the error
fireEvent.press(getByTestId("dismiss-button"));
// Force rerender to see the change
rerender(
<ErrorBoundary>
<TestComponent />
</ErrorBoundary>
);
// Now the regular content should be visible
expect(queryByText("all is good")).toBeTruthy();
});
});
describe("error store functionality", () => {
it("properly sets error state", () => {
const error = new Error("Test error");
const errorInfo = "Test error info";
act(() => {
useErrorStore.getState().setError(error, errorInfo, true);
});
const state = useErrorStore.getState();
expect(state.hasError).toBe(true);
expect(state.error).toBe(error);
expect(state.info).toBe(errorInfo);
expect(state.recoverable).toBe(true);
});
it("properly resets error state", () => {
// First set an error
act(() => {
useErrorStore
.getState()
.setError(new Error("Test error"), "Info", true);
});
// Then dismiss it
act(() => {
useErrorStore.getState().dismissError();
});
const state = useErrorStore.getState();
expect(state.hasError).toBe(false);
expect(state.error).toBeUndefined();
expect(state.info).toBeUndefined();
expect(state.recoverable).toBe(true); // Default value when reset
});
it("remembers recoverable state", () => {
act(() => {
useErrorStore
.getState()
.setError(new Error("Test error"), "Info", false);
});
const state = useErrorStore.getState();
expect(state.recoverable).toBe(false);
});
});
describe("integration with ErrorScreen", () => {
it("renders restart app button for non-recoverable errors", () => {
const { getAllByTestId } = render(
<ErrorBoundary recoverable={false}>
<TestComponent error />
</ErrorBoundary>
);
// Should have the restart app button
const buttons = getAllByTestId(/button-/);
expect(buttons.length).toBeGreaterThan(0);
});
it("passes error info to the ErrorScreen component", () => {
const customErrorMessage = "Very specific error message";
const { getByTestId } = render(
<ErrorBoundary>
<TestComponent error errorMessage={customErrorMessage} />
</ErrorBoundary>
);
// The error info should be visible in the UI
expect(getByTestId("error-message")).toBeTruthy();
});
});
});