Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions app/(Minitool_one)/__tests__/BarGenerationModal.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import React from "react";
import { render, fireEvent, screen, act } from "@testing-library/react-native";
import { Alert } from "react-native";
import useBarGenerationModal from "../modals/BarGenerationModal";

// Tiny harness: hooks can't be rendered directly, so wrap and expose handlers.
const Harness = React.forwardRef((props, ref) => {
const modal = useBarGenerationModal(props);
React.useImperativeHandle(ref, () => modal, [modal]);
return modal.renderModal();
});

describe("BarGenerationModal (useBarGenerationModal)", () => {
let alertSpy;

beforeEach(() => {
jest.clearAllMocks();
alertSpy = jest.spyOn(Alert, "alert").mockImplementation(() => {});
});

afterEach(() => {
alertSpy.mockRestore();
});

describe("Visibility", () => {
test("modal is hidden by default", () => {
render(<Harness onBarAdded={jest.fn()} currentBarCount={0} />);
expect(screen.queryByText("Add a New Battery")).toBeNull();
});

test("handleOpenModal makes the modal visible", () => {
const ref = React.createRef();
render(<Harness ref={ref} onBarAdded={jest.fn()} currentBarCount={0} />);
act(() => ref.current.handleOpenModal());
expect(screen.getByText("Add a New Battery")).toBeTruthy();
});

test("does not open when bar count is at the limit, alerts the user", () => {
const ref = React.createRef();
render(
<Harness
ref={ref}
onBarAdded={jest.fn()}
currentBarCount={20}
MAX_BAR_COUNT={20}
/>,
);
act(() => ref.current.handleOpenModal());
expect(screen.queryByText("Add a New Battery")).toBeNull();
expect(alertSpy).toHaveBeenCalledWith(
"Limit Reached",
expect.stringContaining("more than 20 batteries"),
);
});
});

describe("Form interaction", () => {
test("renders inputs, brand selector and action buttons", () => {
const ref = React.createRef();
render(<Harness ref={ref} onBarAdded={jest.fn()} currentBarCount={0} />);
act(() => ref.current.handleOpenModal());
expect(screen.getByText("Lifespan (1-130):")).toBeTruthy();
expect(screen.getByText("Brand:")).toBeTruthy();
expect(screen.getByText("Tough Cell")).toBeTruthy();
expect(screen.getByText("Always Ready")).toBeTruthy();
expect(screen.getByText("Cancel")).toBeTruthy();
expect(screen.getByText("Add Bar")).toBeTruthy();
});

test("changing the brand selection updates state", () => {
const ref = React.createRef();
render(<Harness ref={ref} onBarAdded={jest.fn()} currentBarCount={0} />);
act(() => ref.current.handleOpenModal());

fireEvent.press(screen.getByText("Always Ready"));
expect(ref.current.barBrandInput).toBe("Always Ready");
});

test("typing in the lifespan input updates state", () => {
const ref = React.createRef();
render(<Harness ref={ref} onBarAdded={jest.fn()} currentBarCount={0} />);
act(() => ref.current.handleOpenModal());

const input = screen.UNSAFE_getAllByType(
require("react-native").TextInput,
)[0];
fireEvent.changeText(input, "75");
expect(ref.current.barLifespanInput).toBe("75");
});
});

describe("Add Bar", () => {
test("submits a valid bar and closes the modal", () => {
const onBarAdded = jest.fn();
const ref = React.createRef();
render(<Harness ref={ref} onBarAdded={onBarAdded} currentBarCount={0} />);
act(() => ref.current.handleOpenModal());

const input = screen.UNSAFE_getAllByType(
require("react-native").TextInput,
)[0];
fireEvent.changeText(input, "85");
fireEvent.press(screen.getByText("Add Bar"));

expect(onBarAdded).toHaveBeenCalledWith({
brand: "Tough Cell",
lifespan: 85,
});
expect(screen.queryByText("Add a New Battery")).toBeNull();
});

test("rejects out-of-range lifespan with an alert", () => {
const onBarAdded = jest.fn();
const ref = React.createRef();
render(<Harness ref={ref} onBarAdded={onBarAdded} currentBarCount={0} />);
act(() => ref.current.handleOpenModal());

const input = screen.UNSAFE_getAllByType(
require("react-native").TextInput,
)[0];
fireEvent.changeText(input, "999");
fireEvent.press(screen.getByText("Add Bar"));

expect(onBarAdded).not.toHaveBeenCalled();
expect(alertSpy).toHaveBeenCalledWith(
"Invalid Lifespan",
expect.stringContaining("between 1 and 130"),
);
// Modal stays open so the user can correct it
expect(screen.getByText("Add a New Battery")).toBeTruthy();
});

test("rejects non-numeric lifespan with an alert", () => {
const onBarAdded = jest.fn();
const ref = React.createRef();
render(<Harness ref={ref} onBarAdded={onBarAdded} currentBarCount={0} />);
act(() => ref.current.handleOpenModal());

const input = screen.UNSAFE_getAllByType(
require("react-native").TextInput,
)[0];
fireEvent.changeText(input, "abc");
fireEvent.press(screen.getByText("Add Bar"));

expect(onBarAdded).not.toHaveBeenCalled();
expect(alertSpy).toHaveBeenCalled();
});
});

describe("Cancel", () => {
test("Cancel closes the modal and fires onClose", () => {
const onClose = jest.fn();
const ref = React.createRef();
render(
<Harness
ref={ref}
onBarAdded={jest.fn()}
onClose={onClose}
currentBarCount={0}
/>,
);
act(() => ref.current.handleOpenModal());
fireEvent.press(screen.getByText("Cancel"));
expect(screen.queryByText("Add a New Battery")).toBeNull();
expect(onClose).toHaveBeenCalledTimes(1);
});
});
});
67 changes: 67 additions & 0 deletions app/(Minitool_one)/__tests__/BarInfoModal.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react-native";
import BarInfoModal from "../modals/BarInfoModal";

describe("BarInfoModal", () => {
const baseProps = {
visible: true,
barData: { brand: "Tough Cell", lifespan: 95 },
onClose: jest.fn(),
onDelete: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
});

test("renders nothing when barData is null", () => {
const { toJSON } = render(<BarInfoModal {...baseProps} barData={null} />);
expect(toJSON()).toBeNull();
});

test("renders title, brand, lifespan and action buttons when visible", () => {
render(<BarInfoModal {...baseProps} />);
expect(screen.getByText("Battery Information")).toBeTruthy();
expect(screen.getByText("Company:")).toBeTruthy();
expect(screen.getByText("Tough Cell")).toBeTruthy();
expect(screen.getByText("Lifespan (hours):")).toBeTruthy();
expect(screen.getByText("95")).toBeTruthy();
expect(screen.getByText("Remove This Battery")).toBeTruthy();
expect(screen.getByText("Close")).toBeTruthy();
});

test("renders 'Always Ready' brand correctly", () => {
render(
<BarInfoModal
{...baseProps}
barData={{ brand: "Always Ready", lifespan: 42 }}
/>,
);
expect(screen.getByText("Always Ready")).toBeTruthy();
expect(screen.getByText("42")).toBeTruthy();
});

test("pressing 'Close' calls onClose", () => {
const onClose = jest.fn();
render(<BarInfoModal {...baseProps} onClose={onClose} />);
fireEvent.press(screen.getByText("Close"));
expect(onClose).toHaveBeenCalledTimes(1);
});

test("pressing 'Remove This Battery' triggers onDelete then onClose", () => {
const onDelete = jest.fn();
const onClose = jest.fn();
render(
<BarInfoModal {...baseProps} onDelete={onDelete} onClose={onClose} />,
);
fireEvent.press(screen.getByText("Remove This Battery"));
expect(onDelete).toHaveBeenCalledTimes(1);
expect(onClose).toHaveBeenCalledTimes(1);
});

test("does not surface modal contents when visible=false", () => {
render(<BarInfoModal {...baseProps} visible={false} />);
expect(screen.queryByText("Battery Information")).toBeNull();
expect(screen.queryByText("Remove This Battery")).toBeNull();
});
});
112 changes: 112 additions & 0 deletions app/(Minitool_one)/__tests__/BatteryBar.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React from "react";
import { render, fireEvent } from "@testing-library/react-native";
import Svg, { Rect, Circle } from "react-native-svg";
import BatteryBar from "../minitool_one_components/BatteryBar";

// useSharedValue is mocked globally to return { value }
const makeSV = (v) => ({ value: v });

const renderInSvg = (ui) => render(<Svg>{ui}</Svg>);

const baseProps = {
index: 0,
chartWidth: 500,
rangeStartX: makeSV(0),
rangeEndX: makeSV(100),
tool: false,
dotsOnly: false,
TOP_BUFFER: 10,
MAX_LIFESPAN: 130,
onBarPress: jest.fn(),
};

describe("BatteryBar", () => {
beforeEach(() => {
jest.clearAllMocks();
});

test("renders both a bar (Rect) and a dot (Circle) by default", () => {
const { UNSAFE_queryAllByType } = renderInSvg(
<BatteryBar
{...baseProps}
item={{ brand: "Tough Cell", lifespan: 90 }}
/>,
);
expect(UNSAFE_queryAllByType(Rect).length).toBeGreaterThanOrEqual(1);
expect(UNSAFE_queryAllByType(Circle).length).toBe(1);
});

test("renders only the dot (no Rect bar) when dotsOnly is true", () => {
const { UNSAFE_queryAllByType } = renderInSvg(
<BatteryBar
{...baseProps}
dotsOnly
item={{ brand: "Tough Cell", lifespan: 90 }}
/>,
);
expect(UNSAFE_queryAllByType(Rect).length).toBe(0);
expect(UNSAFE_queryAllByType(Circle).length).toBe(1);
});

test("uses Tough Cell green color for Tough Cell brand", () => {
const { UNSAFE_queryAllByType } = renderInSvg(
<BatteryBar
{...baseProps}
item={{ brand: "Tough Cell", lifespan: 60 }}
/>,
);
const rect = UNSAFE_queryAllByType(Rect)[0];
expect(rect.props.fill).toBe("#33cc33");
});

test("uses Always Ready purple color for Always Ready brand", () => {
const { UNSAFE_queryAllByType } = renderInSvg(
<BatteryBar
{...baseProps}
item={{ brand: "Always Ready", lifespan: 60 }}
/>,
);
const rect = UNSAFE_queryAllByType(Rect)[0];
expect(rect.props.fill).toBe("#cc00ff");
});

test("bar width scales with lifespan / MAX_LIFESPAN * chartWidth", () => {
const { UNSAFE_queryAllByType } = renderInSvg(
<BatteryBar
{...baseProps}
chartWidth={1000}
MAX_LIFESPAN={100}
item={{ brand: "Tough Cell", lifespan: 50 }}
/>,
);
const rect = UNSAFE_queryAllByType(Rect)[0];
expect(rect.props.width).toBe(500);
});

test("pressing the bar invokes onBarPress with index and item", () => {
const onBarPress = jest.fn();
const item = { brand: "Tough Cell", lifespan: 70 };
const { UNSAFE_queryAllByType } = renderInSvg(
<BatteryBar
{...baseProps}
index={3}
item={item}
onBarPress={onBarPress}
/>,
);
const rect = UNSAFE_queryAllByType(Rect)[0];
fireEvent.press(rect);
expect(onBarPress).toHaveBeenCalledWith(3, item);
});

test("exposes a stable testID per index", () => {
const { getByTestId } = renderInSvg(
<BatteryBar
{...baseProps}
index={5}
item={{ brand: "Tough Cell", lifespan: 50 }}
/>,
);
expect(getByTestId("battery-bar-5")).toBeTruthy();
});
});
Loading