import { describe, it, expect, vi, afterEach } from "vitest"; import { render, screen, fireEvent, act, waitFor, cleanup } from "@testing-library/react"; afterEach(() => { cleanup(); }); // Stub the Radix-backed Popover/Tooltip primitives so children render inline // (no portal, no measurement gymnastics) and the popover's open/close is // driven by ValidatedPopover's own onOpenChange callback that we surface via // a parent-controlled close button. vi.mock("@kandev/ui/popover", () => { return { Popover: ({ open, onOpenChange, children, }: { open: boolean; onOpenChange: (next: boolean) => void; children: React.ReactNode; }) => (
{children}
), PopoverTrigger: ({ children }: { children: React.ReactNode }) => <>{children}, PopoverContent: ({ children }: { children: React.ReactNode }) =>
{children}
, }; }); vi.mock("@kandev/ui/tooltip", () => ({ Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}, TooltipTrigger: ({ children }: { children: React.ReactNode }) => <>{children}, TooltipContent: ({ children }: { children: React.ReactNode }) => <>{children}, })); import { ValidatedPopover } from "./validated-popover"; function deferred() { let resolve!: (v: T) => void; let reject!: (e: unknown) => void; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; } function renderHarness(overrides?: { fetch?: (key: string) => Promise }) { const onSuccess = vi.fn(); const result = render( icon} triggerAriaLabel="trigger" tooltip="tip" headline="Headline" placeholder="paste here" extractKey={(raw) => (raw.trim() ? raw.trim() : null)} validationHint="hint" fetch={overrides?.fetch ?? (async (k: string) => k)} onSuccess={onSuccess} submitLabel="Submit" submittingLabel="Submitting..." />, ); return { ...result, onSuccess }; } describe("ValidatedPopover", () => { it("close-while-loading resets loading state and ignores the late resolution", async () => { const d = deferred(); const fetchSpy = vi.fn(async () => d.promise); const { onSuccess } = renderHarness({ fetch: fetchSpy }); // Type a key + submit fireEvent.change(screen.getByPlaceholderText("paste here"), { target: { value: "KEY-1" } }); fireEvent.click(screen.getByRole("button", { name: "Submit" })); // Mid-flight: submitting label visible, button disabled. await waitFor(() => { const submitting = screen.getByRole("button", { name: "Submitting..." }) as HTMLButtonElement; expect(submitting.disabled).toBe(true); }); // User dismisses the popover before fetch resolves. fireEvent.click(screen.getByTestId("popover-close")); // Loading must reset on close so the next open starts clean. await waitFor(() => { expect(screen.queryByRole("button", { name: "Submitting..." })).toBeNull(); }); // Late resolution must not call onSuccess (the submission token was // invalidated when we closed) and must not bring the popover back. await act(async () => { d.resolve("late"); await Promise.resolve(); }); expect(onSuccess).not.toHaveBeenCalled(); }); it("close-while-loading suppresses a late rejection rather than re-populating error", async () => { const d = deferred(); const fetchSpy = vi.fn(async () => d.promise); renderHarness({ fetch: fetchSpy }); fireEvent.change(screen.getByPlaceholderText("paste here"), { target: { value: "KEY-1" } }); fireEvent.click(screen.getByRole("button", { name: "Submit" })); await waitFor(() => { const submitting = screen.getByRole("button", { name: "Submitting..." }) as HTMLButtonElement; expect(submitting.disabled).toBe(true); }); fireEvent.click(screen.getByTestId("popover-close")); // Reject after close — the in-flight error must not leak into UI state. await act(async () => { d.reject(new Error("boom")); await Promise.resolve().catch(() => {}); }); // role=alert is the inline error

in ValidatedPopover; should be absent. expect(screen.queryByRole("alert")).toBeNull(); }); });