diff --git a/apps/mobile/jest.setup.js b/apps/mobile/jest.setup.js index f76afc3..fe9d681 100644 --- a/apps/mobile/jest.setup.js +++ b/apps/mobile/jest.setup.js @@ -1,11 +1,19 @@ -import 'react-native-gesture-handler/jestSetup' +try { + require("react-native-gesture-handler/jestSetup"); +} catch { + // Package may be absent in minimal test environments +} -jest.mock('react-native-reanimated', () => { - const Reanimated = require('react-native-reanimated/mock') - Reanimated.default.call = () => {} - return Reanimated -}) +jest.mock( + "react-native-reanimated", + () => { + const Reanimated = require("react-native-reanimated/mock"); + Reanimated.default.call = () => {}; + return Reanimated; + }, + { virtual: true }, +); -jest.mock('@expo/vector-icons', () => ({ - Ionicons: 'Ionicons', -})) \ No newline at end of file +jest.mock("@expo/vector-icons", () => ({ + Ionicons: "Ionicons", +})); diff --git a/apps/mobile/src/api/__tests__/gyms.test.ts b/apps/mobile/src/api/__tests__/gyms.test.ts new file mode 100644 index 0000000..44b23f0 --- /dev/null +++ b/apps/mobile/src/api/__tests__/gyms.test.ts @@ -0,0 +1,55 @@ +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; +import { gymsApi } from "../gyms"; +import { apiClient, withAuth } from "../client"; + +jest.mock("../client", () => ({ + apiClient: { + get: jest.fn(), + patch: jest.fn(), + }, + withAuth: jest.fn((token?: string | null) => + token ? { headers: { Authorization: `Bearer ${token}` } } : {}, + ), +})); + +describe("gymsApi", () => { + const getMock = apiClient.get as any; + const patchMock = apiClient.patch as any; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("returns array payload from getGyms", async () => { + getMock.mockResolvedValue({ + data: [{ id: "gym_1", name: "Gym One" }], + }); + + const result = await gymsApi.getGyms("token_1"); + + expect(result).toEqual([{ id: "gym_1", name: "Gym One" }]); + expect(withAuth).toHaveBeenCalledWith("token_1"); + }); + + it("returns nested data payload from getGyms", async () => { + getMock.mockResolvedValue({ + data: { data: [{ id: "gym_2", name: "Gym Two" }] }, + }); + + const result = await gymsApi.getGyms(null); + + expect(result).toEqual([{ id: "gym_2", name: "Gym Two" }]); + }); + + it("patches selected gym for current user", async () => { + patchMock.mockResolvedValue({}); + + await gymsApi.updateUserGym("gym_2", "token_2"); + + expect(apiClient.patch).toHaveBeenCalledWith( + "/api/users/gym", + { gymId: "gym_2" }, + expect.any(Object), + ); + }); +}); diff --git a/apps/mobile/src/api/__tests__/recommendations.test.ts b/apps/mobile/src/api/__tests__/recommendations.test.ts new file mode 100644 index 0000000..f1cd079 --- /dev/null +++ b/apps/mobile/src/api/__tests__/recommendations.test.ts @@ -0,0 +1,68 @@ +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; +import { + approveRecommendation, + generateRecommendation, + getRecommendations, +} from "../recommendations"; +import { apiClient, withAuth } from "../client"; + +jest.mock("../client", () => ({ + apiClient: { + get: jest.fn(), + post: jest.fn(), + }, + withAuth: jest.fn((token?: string | null) => + token ? { headers: { Authorization: `Bearer ${token}` } } : {}, + ), +})); + +describe("recommendations api", () => { + const getMock = apiClient.get as any; + const postMock = apiClient.post as any; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("returns normalized list from standardized response", async () => { + getMock.mockResolvedValue({ + data: { + success: true, + data: [{ id: "rec_1", status: "pending" }], + }, + }); + + const result = await getRecommendations("user_1", "token_1"); + + expect(result).toEqual([{ id: "rec_1", status: "pending" }]); + expect(withAuth).toHaveBeenCalledWith("token_1"); + }); + + it("falls back to legacy response for generate", async () => { + postMock.mockResolvedValue({ + data: { id: "rec_2", status: "pending" }, + }); + + const result = await generateRecommendation({ userId: "user_1" }, null); + + expect(result).toEqual({ id: "rec_2", status: "pending" }); + }); + + it("sends approval payload without approvedBy", async () => { + postMock.mockResolvedValue({ + data: { + success: true, + data: { id: "rec_3", status: "approved" }, + }, + }); + + const result = await approveRecommendation("rec_3", "token_3"); + + expect(result).toEqual({ id: "rec_3", status: "approved" }); + expect(apiClient.post).toHaveBeenCalledWith( + "/api/recommendations/approve", + { recommendationId: "rec_3", status: "approved" }, + expect.any(Object), + ); + }); +});