import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { AddVendorButton } from "@/app/(portal)/admin/vendors/vendor-form"; vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn(), refresh: vi.fn() }), })); // The form imports server actions; stub them so the client component renders in jsdom. vi.mock("@/app/(portal)/admin/vendors/actions", () => ({ createVendor: vi.fn(), updateVendor: vi.fn(), toggleVendorActive: vi.fn(), })); const GSTIN = "27AAHCP5787B1Z6"; // 15 chars describe("VendorForm — GSTIN CAPTCHA popup (issue #114)", () => { beforeEach(() => { global.fetch = vi.fn(async () => new Response(JSON.stringify({ captchaBase64: "ABC123", sessionId: "sess-1" }), { headers: { "Content-Type": "application/json" }, }), ) as unknown as typeof fetch; }); afterEach(() => { vi.restoreAllMocks(); }); async function openFormAndLookup() { render(); fireEvent.click(screen.getByText("+ Add Vendor")); const gstinInput = screen.getByPlaceholderText(/27AAHCP5787B1Z6/); fireEvent.change(gstinInput, { target: { value: GSTIN } }); fireEvent.click(screen.getByText("Look up")); // Popup renders the CAPTCHA prompt once the fetch resolves. await screen.findByText(/Enter the code shown in the image/i); } it("opens the CAPTCHA in a popup with a Cancel/Close control, leaving the form footer reachable", async () => { await openFormAndLookup(); // The CAPTCHA lives in its own popup … expect(screen.getByRole("heading", { name: /GSTIN CAPTCHA/i })).toBeTruthy(); // Both the form's ✕ and the popup's ✕/Cancel close controls are present. expect(screen.getAllByLabelText("Close").length).toBeGreaterThanOrEqual(2); expect(screen.getAllByText("Cancel").length).toBeGreaterThanOrEqual(2); // … and the underlying vendor form's submit button is still rendered (never displaced). expect(screen.getByText("Create Vendor")).toBeTruthy(); }); it("closes the popup on Cancel without closing the vendor form", async () => { await openFormAndLookup(); // The popup's Cancel is the first one in the DOM (the CAPTCHA section precedes the footer). fireEvent.click(screen.getAllByText("Cancel")[0]); await waitFor(() => { expect(screen.queryByText(/Enter the code shown in the image/i)).toBeNull(); }); // The vendor form itself stays open. expect(screen.getByText("Create Vendor")).toBeTruthy(); expect(screen.queryByRole("heading", { name: /GSTIN CAPTCHA/i })).toBeNull(); }); it("verifies the CAPTCHA, fills the form fields, and closes the popup on success", async () => { (global.fetch as ReturnType).mockImplementation(async (url: string) => { if (String(url).includes("/api/gst/captcha")) { return new Response(JSON.stringify({ captchaBase64: "ABC123", sessionId: "sess-1" }), { headers: { "Content-Type": "application/json" }, }); } return new Response( JSON.stringify({ legalName: "Acme Pvt Ltd", tradeName: "Acme", address: "1 Dock Rd", pincode: "400001", gstin: GSTIN, status: "Active", registrationDate: "2020-01-01", }), { headers: { "Content-Type": "application/json" } }, ); }); await openFormAndLookup(); fireEvent.change(screen.getByPlaceholderText("6 digits"), { target: { value: "123456" } }); fireEvent.click(screen.getByText("Verify")); // Popup closes; success line + populated fields appear on the form. await waitFor(() => { expect(screen.queryByText(/Enter the code shown in the image/i)).toBeNull(); }); expect((screen.getByDisplayValue("Acme") as HTMLInputElement)).toBeTruthy(); expect(screen.getByText(/Acme Pvt Ltd — Active/)).toBeTruthy(); }); });