import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { vi } from "vitest"; import PaymentForm from "./PaymentForm"; import i18n from "../i18n"; vi.mock("../api/client", () => ({ apiPost: vi.fn(), })); const { apiPost } = await import("../api/client"); describe("PaymentForm", () => { beforeEach(async () => { vi.clearAllMocks(); await i18n.changeLanguage("en"); Object.defineProperty(globalThis, "crypto", { value: { randomUUID: () => "uuid-1" }, configurable: true, }); }); it("validates source details for stc pay", async () => { render(); fireEvent.click(screen.getByRole("button", { name: "Pay now" })); expect( await screen.findByText("Mobile number is required for stc pay.") ).toBeInTheDocument(); }); it("reuses idempotency key on retry", async () => { apiPost.mockRejectedValueOnce(new Error("fail")); apiPost.mockResolvedValueOnce({ id: "ok" }); render(); fireEvent.change(screen.getByLabelText("Source value"), { target: { value: "+966500000000" }, }); fireEvent.click(screen.getByRole("button", { name: "Pay now" })); await screen.findByText("fail"); fireEvent.click(screen.getByRole("button", { name: "Pay now" })); await waitFor(() => { expect(apiPost).toHaveBeenCalledTimes(2); }); const firstPayload = apiPost.mock.calls[0][1]; const secondPayload = apiPost.mock.calls[1][1]; expect(firstPayload.idempotency_key).toBe("uuid-1"); expect(secondPayload.idempotency_key).toBe("uuid-1"); }); it("redirects when response includes redirect_url", async () => { const assign = vi.fn(); Object.defineProperty(window.location, "assign", { value: assign, configurable: true, }); apiPost.mockResolvedValueOnce({ redirect_url: "https://pay.test/redirect" }); render(); fireEvent.change(screen.getByLabelText("Source value"), { target: { value: "+966500000000" }, }); fireEvent.click(screen.getByRole("button", { name: "Pay now" })); await waitFor(() => { expect(assign).toHaveBeenCalledWith("https://pay.test/redirect"); }); }); it("disables submit while loading", async () => { let resolveRequest; apiPost.mockImplementationOnce( () => new Promise((resolve) => { resolveRequest = resolve; }) ); render(); fireEvent.change(screen.getByLabelText("Source value"), { target: { value: "+966500000000" }, }); const button = screen.getByRole("button", { name: "Pay now" }); fireEvent.click(button); expect(button).toBeDisabled(); resolveRequest({ id: "done" }); await waitFor(() => { expect(button).not.toBeDisabled(); }); }); });