101 lines
3.3 KiB
React
101 lines
3.3 KiB
React
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(
|
|
<AuthProvider>
|
|
<MemoryRouter initialEntries={initialEntries}>
|
|
<Routes>
|
|
<Route path="/book" element={<BookPage />} />
|
|
<Route path="/pay" element={<div>Pay Page</div>} />
|
|
</Routes>
|
|
</MemoryRouter>
|
|
</AuthProvider>
|
|
);
|
|
}
|
|
|
|
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();
|
|
});
|
|
});
|
|
});
|