diff --git a/app/index.tsx b/app/index.tsx index b88823e..bed4639 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -78,7 +78,7 @@ const IndexScreen: React.FC = () => { const loadedState = await loadState(slot); if (loadedState) { setPlayerCount(loadedState.playerCount); - setBuyInAmount(loadedState.buyInAmount); + setBuyInAmount(loadedState.buyInAmount ?? 20); setNumberOfChips(loadedState.numberOfChips); setTotalChipsCount(loadedState.totalChipsCount); setSelectedCurrency(loadedState.selectedCurrency || "$"); diff --git a/components/__tests__/BuyInSelector.test.tsx b/components/__tests__/BuyInSelector.test.tsx index b20d528..ce29dbf 100644 --- a/components/__tests__/BuyInSelector.test.tsx +++ b/components/__tests__/BuyInSelector.test.tsx @@ -2,6 +2,7 @@ import React from "react"; import { fireEvent, render } from "@testing-library/react-native"; import BuyInSelector from "@/components/BuyInSelector"; +// Mocking vector icons to prevent errors jest.mock("@expo/vector-icons", () => { const { Text } = require("react-native"); return { @@ -10,63 +11,73 @@ jest.mock("@expo/vector-icons", () => { }); describe("BuyInSelector Component", () => { - let setBuyInAmount; - let getByText; - let getByPlaceholderText; - let queryByText; + let setBuyInAmount: jest.Mock; - // Render the component with the necessary props + // Render the component and return query methods const renderComponent = (selectedCurrency = "$") => { - const result = render( + const utils = render( ); - getByText = result.getByText; - getByPlaceholderText = result.getByPlaceholderText; - queryByText = result.queryByText; + return { + ...utils, + getByText: utils.getByText, + getByPlaceholderText: utils.getByPlaceholderText, + queryByText: utils.queryByText, + }; }; beforeEach(() => { setBuyInAmount = jest.fn(); - renderComponent(); }); it("renders the buy-in options and input correctly", () => { + const { getByText, getByPlaceholderText, queryByText } = renderComponent(); + expect(getByText("$ 10")).toBeTruthy(); expect(getByText("$ 25")).toBeTruthy(); expect(getByText("$ 50")).toBeTruthy(); expect(getByPlaceholderText("Enter custom buy-in")).toBeTruthy(); - - // Check default selection with a more flexible approach - expect(queryByText(/Selected Buy-in.*None/)).toBeTruthy(); + expect(queryByText(/Selected Buy-in:.*None/i)).toBeTruthy(); }); it("sets a predefined buy-in amount correctly", () => { + const { getByText } = renderComponent(); + fireEvent.press(getByText("$ 25")); expect(setBuyInAmount).toHaveBeenCalledWith(25); }); it("sets a custom buy-in amount correctly", () => { + const { getByPlaceholderText } = renderComponent(); + fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "100"); expect(setBuyInAmount).toHaveBeenCalledWith(100); }); it("resets custom amount if invalid input is entered", () => { + const { getByPlaceholderText } = renderComponent(); + fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "-10"); - expect(setBuyInAmount).toHaveBeenCalledWith(25); + expect(setBuyInAmount).toHaveBeenCalledWith(25); // Default reset + fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "abc"); expect(setBuyInAmount).toHaveBeenCalledWith(25); }); it("clears the custom amount when selecting a predefined option", () => { + const { getByPlaceholderText, getByText } = renderComponent(); + fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "100"); fireEvent.press(getByText("$ 50")); expect(setBuyInAmount).toHaveBeenCalledWith(50); }); it("handles valid and invalid input for custom amount correctly", () => { + const { getByPlaceholderText } = renderComponent(); + fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "75"); expect(setBuyInAmount).toHaveBeenCalledWith(75); @@ -78,33 +89,40 @@ describe("BuyInSelector Component", () => { }); it("triggers state update every time a buy-in option is clicked, even if it's the same", () => { + const { getByText } = renderComponent(); + fireEvent.press(getByText("$ 25")); fireEvent.press(getByText("$ 25")); expect(setBuyInAmount).toHaveBeenCalledTimes(2); }); it("resets to default buy-in when custom input is cleared", () => { + const { getByPlaceholderText } = renderComponent(); const input = getByPlaceholderText("Enter custom buy-in"); + fireEvent.changeText(input, "75"); expect(setBuyInAmount).toHaveBeenCalledWith(75); + fireEvent.changeText(input, ""); expect(setBuyInAmount).toHaveBeenCalledWith(25); }); it("updates state correctly when selecting predefined buy-in after entering a custom amount", () => { + const { getByPlaceholderText, getByText } = renderComponent(); + fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "200"); expect(setBuyInAmount).toHaveBeenCalledWith(200); + fireEvent.press(getByText("$ 10")); expect(setBuyInAmount).toHaveBeenCalledWith(10); }); it("displays selected currency correctly", () => { - renderComponent("€"); + const { getByText, queryByText } = renderComponent("€"); + expect(getByText("€ 10")).toBeTruthy(); expect(getByText("€ 25")).toBeTruthy(); expect(getByText("€ 50")).toBeTruthy(); - - // Check default selection text with a flexible regex - expect(queryByText(/Selected Buy-in.*None/)).toBeTruthy(); + expect(queryByText(/Selected Buy-in:.*None/i)).toBeTruthy(); }); }); diff --git a/components/__tests__/ChipDetection.test.tsx b/components/__tests__/ChipDetection.test.tsx index 45ecb5e..2dac26c 100644 --- a/components/__tests__/ChipDetection.test.tsx +++ b/components/__tests__/ChipDetection.test.tsx @@ -17,11 +17,10 @@ jest.mock("expo-image-picker", () => ({ describe("ChipDetection", () => { beforeEach(() => { jest.clearAllMocks(); - global.fetch = jest.fn(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ + jest.spyOn(global, "fetch").mockImplementation(() => + Promise.resolve( + new Response( + JSON.stringify({ choices: [ { message: { @@ -34,10 +33,16 @@ describe("ChipDetection", () => { }, ], }), - }) + { status: 200, headers: { "Content-Type": "application/json" } } + ) + ) ); }); + afterEach(() => { + jest.restoreAllMocks(); // Reset all mocks to prevent test contamination + }); + it("renders correctly", () => { const { getByText } = render( @@ -48,7 +53,7 @@ describe("ChipDetection", () => { }); it("picks an image from the library", async () => { - ImagePicker.launchImageLibraryAsync.mockResolvedValueOnce({ + (ImagePicker.launchImageLibraryAsync as jest.Mock).mockResolvedValueOnce({ canceled: false, assets: [{ uri: "test-uri", base64: "test-base64" }], }); @@ -62,10 +67,12 @@ describe("ChipDetection", () => { }); it("takes a photo with the camera", async () => { - ImagePicker.requestCameraPermissionsAsync.mockResolvedValueOnce({ + ( + ImagePicker.requestCameraPermissionsAsync as jest.Mock + ).mockResolvedValueOnce({ granted: true, }); - ImagePicker.launchCameraAsync.mockResolvedValueOnce({ + (ImagePicker.launchCameraAsync as jest.Mock).mockResolvedValueOnce({ canceled: false, assets: [{ uri: "test-camera-uri", base64: "test-camera-base64" }], }); @@ -81,7 +88,9 @@ describe("ChipDetection", () => { }); it("handles camera permission denied", async () => { - ImagePicker.requestCameraPermissionsAsync.mockResolvedValueOnce({ + ( + ImagePicker.requestCameraPermissionsAsync as jest.Mock + ).mockResolvedValueOnce({ granted: false, }); @@ -98,16 +107,18 @@ describe("ChipDetection", () => { }); it("displays error message on image processing failure", async () => { - ImagePicker.launchImageLibraryAsync.mockResolvedValueOnce({ + (ImagePicker.launchImageLibraryAsync as jest.Mock).mockResolvedValueOnce({ canceled: false, assets: [{ uri: "test-uri", base64: "test-base64" }], }); - global.fetch.mockImplementationOnce(() => - Promise.resolve({ - ok: false, - json: () => Promise.resolve({ choices: [] }), - }) + jest.spyOn(global, "fetch").mockImplementationOnce(() => + Promise.resolve( + new Response(JSON.stringify({ choices: [] }), { + status: 400, + headers: { "Content-Type": "application/json" }, + }) + ) ); const { getByText } = render( @@ -121,16 +132,15 @@ describe("ChipDetection", () => { }); it("handles valid API response correctly", async () => { - ImagePicker.launchImageLibraryAsync.mockResolvedValueOnce({ + (ImagePicker.launchImageLibraryAsync as jest.Mock).mockResolvedValueOnce({ canceled: false, assets: [{ uri: "test-uri", base64: "test-base64" }], }); - global.fetch.mockImplementationOnce(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ + jest.spyOn(global, "fetch").mockImplementationOnce(() => + Promise.resolve( + new Response( + JSON.stringify({ choices: [ { message: { @@ -139,7 +149,9 @@ describe("ChipDetection", () => { }, ], }), - }) + { status: 200, headers: { "Content-Type": "application/json" } } + ) + ) ); const { getByText } = render(