Files
fusion/packages/dashboard/app/components/__tests__/PreviewIframe.test.tsx
gsxdsm 3031d05a68 fix(dashboard): clear all 661 test-file type errors
Production typecheck (tsconfig.json + tsconfig.app.json) was already
clean, but a third config that includes test files surfaced 661 errors
across 60+ test files — accumulated drift between mock fixtures and
production types. Six parallel typescript-pro agents fixed every one
without touching production code.

Per-scope before/after (errors → 0):
  ChatView                                                     183
  Mailbox + Agent suite (5 files)                              156
  Task / Modal suite (6 files)                                 127
  App + small components (12 files)                             96
  Hooks + api/auth (8 files)                                    48
  Long tail (32 files)                                          51
  -----------------------------------------------------------------
  Total                                                        661

Major fix categories:
- Untyped state objects inferring `never[]` / `null` literals (root
  cause of ~120 errors in ChatView alone — added a single
  `UseChatReturn` annotation)
- Mock objects missing fields that became required: `WorkflowStep.mode`,
  `ChatMessage.thinkingOutput / metadata`, `ChatSession.projectId`,
  `Task.log`, `ProjectHealth` fields, `PtyTerminalSessionInfo.createdAt`,
  `Agent.metadata`, `InboxResponse.total`, etc.
- Mock objects with stale fields that no longer exist:
  `AgentBudgetStatus.budgetPeriod`, `truncated` on log responses,
  `OutboxResponse.unreadCount`, `MergeResult.source/target/details`
- Modal props that became required (e.g. `PlanningModeModal.onTasksCreated`)
- String literals not in narrowed unions (`Column`, `WorkflowStepPhase`,
  `InsightStatus`, `AgentLogType`, etc.)
- `querySelector` returning `Element` cast to `HTMLElement` for
  `@testing-library/react`'s `within()`
- Vitest mock typing: `.mock.calls` access needing `vi.mocked(...)`,
  zero-param tuple handling, generic `vi.fn(() => [])` inferring
  `never[]`

Helpers introduced in test files (no shared infra):
- `makeSettings(overrides)` in ModelSelectorTab.test.tsx
- `makePromptOverrides(overrides)` in AgentPromptsManager.test.tsx
- `FileBrowserTestOverrides` type alias in FileBrowser.test.tsx
- `makeInboxResponse / makeOutboxResponse` in MailboxView.test.tsx

Verification:
- tsc -p tsconfig.json:        exit 0
- tsc -p tsconfig.app.json:    exit 0
- tsc -p tsconfig.test-check.json (new — includes test files): exit 0
- vitest run:                  9639 / 9641 (2 pre-existing failures
                               in terminal-mobile-keyboard-layout.test.ts
                               unrelated to this work; verified via
                               `git stash` + run on clean HEAD)

Adds packages/dashboard/tsconfig.test-check.json to keep this regression
guard available locally — same as tsconfig.app.json minus the test
exclude.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:59:52 -07:00

240 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { createRef } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { PreviewIframe } from "../PreviewIframe";
vi.mock("lucide-react", () => ({
AlertTriangle: () => <span data-testid="alert-icon"></span>,
Loader2: () => <span data-testid="loader-icon"></span>,
ShieldAlert: () => <span data-testid="shield-icon">🛡</span>,
}));
describe("PreviewIframe", () => {
const originalWindowOpen = window.open;
beforeEach(() => {
window.open = vi.fn();
});
afterEach(() => {
window.open = originalWindowOpen;
});
it("renders null when url is null", () => {
const { container } = render(
<PreviewIframe
url={null}
embedStatus="unknown"
onEmbedStatusChange={vi.fn()}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason={null}
embedContext={null}
/>,
);
expect(container).toBeEmptyDOMElement();
});
it("renders iframe with correct url, sandbox attributes, and title", () => {
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="embedded"
onEmbedStatusChange={vi.fn()}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason={null}
embedContext={null}
/>,
);
const iframe = screen.getByTitle("Dev server preview");
expect(iframe).toBeInTheDocument();
expect(iframe).toHaveAttribute("src", "http://localhost:3000");
expect(iframe).toHaveAttribute(
"sandbox",
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox",
);
});
it("moves unknown status into loading", async () => {
const onEmbedStatusChange = vi.fn();
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="unknown"
onEmbedStatusChange={onEmbedStatusChange}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason={null}
embedContext={null}
/>,
);
await waitFor(() => {
expect(onEmbedStatusChange).toHaveBeenCalledWith("loading");
});
});
it("iframe load marks status as embedded", () => {
const onEmbedStatusChange = vi.fn();
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="loading"
onEmbedStatusChange={onEmbedStatusChange}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason={null}
embedContext={null}
/>,
);
fireEvent.load(screen.getByTitle("Dev server preview"));
expect(onEmbedStatusChange).toHaveBeenCalledWith("embedded");
});
it("iframe error marks status as error", () => {
const onEmbedStatusChange = vi.fn();
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="loading"
onEmbedStatusChange={onEmbedStatusChange}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason={null}
embedContext={null}
/>,
);
fireEvent(screen.getByTitle("Dev server preview"), new Event("error"));
expect(onEmbedStatusChange).toHaveBeenCalledWith("error");
});
it("applies custom className", () => {
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="embedded"
onEmbedStatusChange={vi.fn()}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason={null}
embedContext={null}
className="custom-class"
/>,
);
expect(screen.getByTitle("Dev server preview")).toHaveClass("custom-class");
});
it("blocked state shows ShieldAlert icon and embed context", () => {
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="blocked"
onEmbedStatusChange={vi.fn()}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason="The server may block iframe embedding..."
embedContext="The server may block iframe embedding..."
/>,
);
expect(screen.getByTestId("shield-icon")).toBeInTheDocument();
expect(screen.getByText("Preview cannot be embedded")).toBeInTheDocument();
expect(screen.getByText("The server may block iframe embedding...")).toBeInTheDocument();
});
it("blocked state open in new tab opens external URL", () => {
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="blocked"
onEmbedStatusChange={vi.fn()}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason="The server may block iframe embedding..."
embedContext="The server may block iframe embedding..."
/>,
);
fireEvent.click(screen.getByRole("button", { name: "Open in new tab" }));
expect(window.open).toHaveBeenCalledWith("http://localhost:3000", "_blank", "noopener,noreferrer");
});
it("blocked state retry calls onRetry", () => {
const onRetry = vi.fn();
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="blocked"
onEmbedStatusChange={vi.fn()}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason="The server may block iframe embedding..."
embedContext="The server may block iframe embedding..."
onRetry={onRetry}
/>,
);
fireEvent.click(screen.getByRole("button", { name: "Retry" }));
expect(onRetry).toHaveBeenCalledTimes(1);
});
it("error state shows AlertTriangle and contextual message", () => {
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="error"
onEmbedStatusChange={vi.fn()}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason="The preview URL could not be loaded..."
embedContext="The preview URL could not be loaded..."
/>,
);
expect(screen.getByTestId("alert-icon")).toBeInTheDocument();
expect(screen.getByText("Unable to load preview")).toBeInTheDocument();
expect(screen.getByText("The preview URL could not be loaded...")).toBeInTheDocument();
});
it("error state retry calls onRetry", () => {
const onRetry = vi.fn();
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="error"
onEmbedStatusChange={vi.fn()}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason="The preview URL could not be loaded..."
embedContext="The preview URL could not be loaded..."
onRetry={onRetry}
/>,
);
fireEvent.click(screen.getByRole("button", { name: "Retry" }));
expect(onRetry).toHaveBeenCalledTimes(1);
});
it("loading state shows loading spinner without action buttons", () => {
render(
<PreviewIframe
url="http://localhost:3000"
embedStatus="loading"
onEmbedStatusChange={vi.fn()}
iframeRef={createRef<HTMLIFrameElement>()}
blockReason={null}
embedContext={null}
/>,
);
expect(screen.getByTestId("loader-icon")).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Open in new tab" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Retry" })).not.toBeInTheDocument();
});
});