import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { vi } from "vitest"; import { MemoryRouter, Route, Routes } from "react-router-dom"; import BookPage from "./BookPage"; import { AuthProvider } from "../contexts/AuthContext"; import i18n from "../i18n"; vi.mock("../components/ProtectedRoute", () => ({ default: ({ children }) => <>{children}, })); vi.mock("../api/client", () => ({ apiGet: vi.fn(), apiPost: vi.fn(), })); const { apiGet, apiPost } = await import("../api/client"); function renderBook(initialEntries = ["/book?salon=1"]) { return render( } /> Pay Page} /> ); } const salonFixture = { id: 1, name: "Riyadh Salon", services: [ { id: 10, name: "Cut", duration_minutes: 60, price_amount: 120, currency: "SAR" }, ], staff: [{ id: 99, name: "Mona" }], }; describe("BookPage", () => { beforeEach(async () => { vi.clearAllMocks(); apiGet.mockResolvedValue(salonFixture); await i18n.changeLanguage("en"); }); it("validates required fields", async () => { renderBook(); await screen.findByText("Riyadh Salon"); fireEvent.click(screen.getByRole("button", { name: "Confirm booking" })); expect(screen.getByText("Please fill all required fields.")).toBeInTheDocument(); }); it("submits booking and redirects to payment", async () => { apiPost.mockResolvedValueOnce({ id: 55 }); renderBook(); await screen.findByText("Riyadh Salon"); fireEvent.change(screen.getByLabelText("Service"), { target: { value: "10" } }); fireEvent.change(screen.getByLabelText("Staff"), { target: { value: "99" } }); fireEvent.change(screen.getByLabelText("Date"), { target: { value: "2026-03-14" } }); fireEvent.change(screen.getByLabelText("Time"), { target: { value: "10:30" } }); fireEvent.click(screen.getByRole("button", { name: "Confirm booking" })); await waitFor(() => { expect(screen.getByText("Pay Page")).toBeInTheDocument(); }); expect(apiPost).toHaveBeenCalledWith( "/bookings/", expect.objectContaining({ service: 10, staff: 99, start_time: "2026-03-14T10:30:00+03:00", end_time: expect.any(String), }), null ); const payload = apiPost.mock.calls[0][1]; const startMs = new Date(payload.start_time).getTime(); const endMs = new Date(payload.end_time).getTime(); expect(endMs - startMs).toBe(60 * 60 * 1000); }); it("shows API error message", async () => { apiPost.mockRejectedValueOnce(new Error("Booking failed")); renderBook(); await screen.findByText("Riyadh Salon"); fireEvent.change(screen.getByLabelText("Service"), { target: { value: "10" } }); fireEvent.change(screen.getByLabelText("Staff"), { target: { value: "99" } }); fireEvent.change(screen.getByLabelText("Date"), { target: { value: "2026-03-14" } }); fireEvent.change(screen.getByLabelText("Time"), { target: { value: "10:30" } }); fireEvent.click(screen.getByRole("button", { name: "Confirm booking" })); await waitFor(() => { expect(screen.getByText("Booking failed")).toBeInTheDocument(); }); }); });