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();
});
});
});