import { describe, it, expect, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; import type { ReactNode } from "react"; // PairLive uses these client-only modules. Stub them so the component // can be rendered server-side for assertion. The default state on first // render is `phase: "waiting"` — exactly the case we want to verify here. vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }), })); vi.mock("next/link", () => ({ default: ({ href, children, ...rest }: { href: string; children: ReactNode } & Record) => ( {children} ), })); // useEvents subscribes to the SSE stream; on the server-render side we // just want it to no-op so the component stays in its initial state. vi.mock("@/hooks/use-events", () => ({ useEvents: () => {}, })); import { PairLive } from "./pair-live"; describe("PairLive — initial 'waiting' state shows a spinner placeholder", () => { it("renders an aria-live spinner block before any QR arrives", () => { const html = renderToStaticMarkup( , ); // The placeholder is a labelled, aria-live region so screen readers // announce it as soon as it appears. expect(html).toMatch(/data-testid="pair-loading"/); expect(html).toMatch(/role="status"/); expect(html).toMatch(/aria-live="polite"/); expect(html).toMatch(/aria-label="Generating QR code"/); }); it("uses an animated spinner icon (lucide Loader2)", () => { const html = renderToStaticMarkup( , ); // Lucide tags every icon with a stable `lucide-` class. expect(html).toMatch(/lucide-loader-?2|lucide-loader/); // The animation hook (Tailwind's animate-spin) must be applied so // the spinner actually rotates. expect(html).toContain("animate-spin"); }); it("shows the 'Generating QR…' helper text", () => { const html = renderToStaticMarkup( , ); expect(html).toContain("Generating QR…"); }); it("does NOT yet render the QR image, countdown bar, or Save QR button", () => { const html = renderToStaticMarkup( , ); // No for the QR. expect(html).not.toMatch(/]+alt="WhatsApp QR code"/); // No "QR expires" / "Pairing expires" countdown copy. expect(html).not.toContain("Pairing expires"); // No download CTA. expect(html).not.toContain("Save QR"); }); it("placeholder occupies the same 64x64 footprint as the eventual QR", () => { // Avoids a layout jump when the QR finally lands. Both the spinner // tile and the rendered QR use Tailwind's `size-64` (16rem). const html = renderToStaticMarkup( , ); expect(html).toMatch(/class="[^"]*\bsize-64\b/); }); });