// @vitest-environment happy-dom // TailStatusPill is a leaf component with no async state and no hooks // beyond a single setInterval — happy-dom is the right env so we can // inspect the rendered DOM. (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; import React, { act } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { TailStatusPill } from "./TailStatusPill"; import type { TailStatus } from "@/hooks/useTailStream"; /** * Minimal render helper — the project doesn't ship * `@testing-library/react`, so we render straight into a real * `createRoot` and inspect the DOM via `container.innerHTML` and * `container.querySelector`. * * Note: React 18's `createRoot` defers DOM mutation until the calling * microtask flushes unless we wrap the render in `act`. The pill's only * effect is a `setInterval`, so we use `act(() => { root.render(node) })` * — sync act is fine because no state updates fire during render. */ function renderInto(node: React.ReactElement): { container: HTMLDivElement; root: Root; unmount: () => void; } { const container = document.createElement("div"); document.body.appendChild(container); const root = createRoot(container); act(() => { root.render(node); }); return { container, root, unmount: () => { act(() => root.unmount()); container.remove(); }, }; } const ALL_STATUSES: TailStatus[] = [ "connecting", "live", "reconnecting", "closed", "stalled", "error", ]; describe("TailStatusPill", () => { beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-06-20T12:00:30Z")); }); afterEach(() => { vi.useRealTimers(); }); it("test_renders_badge_for_each_status", () => { // Iterate every TailStatus and assert no crash, plus a sensible label. // The plan's smoke spec calls for "renders all 5 statuses"; we test // all 6 (closed is also a status even if rare in practice). for (const status of ALL_STATUSES) { const { container, root, unmount } = renderInto( {}} />, ); // The pill root should be present. const pill = container.querySelector('[data-testid="tail-status-pill"]'); expect(pill).not.toBeNull(); // The Badge child should carry the human label — match by text // content (the badge text is the only handoff the user reads). const expectedLabels: Record = { connecting: "Connecting", live: "Live", reconnecting: "Reconnecting", closed: "Closed", stalled: "Stalled", error: "Error", }; expect(pill?.textContent).toContain(expectedLabels[status]); unmount(); // Silence "unmount was not wrapped in act" warnings; renderInto's // unmount is a sync root.unmount() and React tolerates this for // leaf components with no async state. void root; } }); it("test_shows_reconnect_button_only_for_stalled_and_error", () => { const onReconnect = vi.fn(); for (const status of ALL_STATUSES) { const { container, unmount } = renderInto( , ); const button = container.querySelector( '[data-testid="tail-status-pill-reconnect"]', ); if (status === "stalled" || status === "error") { expect(button).not.toBeNull(); expect(button?.textContent).toContain("Reconnect"); } else { expect(button).toBeNull(); } unmount(); } }); it("test_subtitle_says_never_when_lastEventAt_is_null", () => { const { container, unmount } = renderInto( {}} />, ); const subtitle = container.querySelector( '[data-testid="tail-status-pill-subtitle"]', ); expect(subtitle).not.toBeNull(); expect(subtitle?.textContent).toBe("Last event: never"); unmount(); }); it("test_subtitle_shows_age_in_seconds_when_lastEventAt_recent", () => { // 12 seconds ago — fake-timer driven so the assertion is stable. const eventAt = new Date("2026-06-20T12:00:18Z"); const { container, unmount } = renderInto( {}} />, ); const subtitle = container.querySelector( '[data-testid="tail-status-pill-subtitle"]', ); expect(subtitle?.textContent).toBe("Last event: 12s ago"); unmount(); }); it("test_reconnect_button_invokes_onReconnect_when_clicked", () => { const onReconnect = vi.fn(); const { container, unmount } = renderInto( , ); const button = container.querySelector( '[data-testid="tail-status-pill-reconnect"]', ) as HTMLButtonElement | null; expect(button).not.toBeNull(); button?.click(); expect(onReconnect).toHaveBeenCalledTimes(1); unmount(); }); });