diff --git a/src/shared/components/RiskNoticeModal.tsx b/src/shared/components/RiskNoticeModal.tsx index 5579cf696c..d668fac36f 100644 --- a/src/shared/components/RiskNoticeModal.tsx +++ b/src/shared/components/RiskNoticeModal.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { useTranslations } from "next-intl"; -import { Button } from "@/shared/components/Button"; +import Button from "@/shared/components/Button"; export interface RiskNoticeModalProps { open: boolean; diff --git a/tests/unit/risk-notice-modal-button-import.test.ts b/tests/unit/risk-notice-modal-button-import.test.ts new file mode 100644 index 0000000000..f5556a9480 --- /dev/null +++ b/tests/unit/risk-notice-modal-button-import.test.ts @@ -0,0 +1,56 @@ +/** + * Regression guard for the RiskNoticeModal Button import bug (R4 fix #1). + * + * For 3 review rounds the agent-card.test.tsx failure ("Element type is invalid + * — Check the render method of RiskNoticeModal") was misclassified as + * "pre-existing / flaky". The actual root cause was a broken named import: + * + * import { Button } from "@/shared/components/Button"; // ← undefined + * + * `Button.tsx` exposes only a default export, so the named import resolved to + * `undefined`, causing every render of RiskNoticeModal to crash with React's + * "Element type is invalid" error. The modal opens on first DNS activation of + * every agent — so the bug effectively broke DNS interception for every agent + * in production. The R4 reviewer caught it; this test prevents recurrence. + */ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const MODAL = path.resolve(__dirname, "../../src/shared/components/RiskNoticeModal.tsx"); +const BUTTON = path.resolve(__dirname, "../../src/shared/components/Button.tsx"); + +const modalSrc = readFileSync(MODAL, "utf-8"); +const buttonSrc = readFileSync(BUTTON, "utf-8"); + +describe("RiskNoticeModal Button import (R4 fix #1, prod crash regression guard)", () => { + it("Button.tsx exposes a default export", () => { + assert.ok( + /export\s+default\s+function\s+Button/.test(buttonSrc), + "Button.tsx must keep its default export", + ); + }); + + it("Button.tsx does NOT have a named `export { Button }` or `export function Button`", () => { + assert.ok( + !/export\s+(\{[^}]*\bButton\b[^}]*\}|function\s+Button|const\s+Button)/.test( + buttonSrc.replace(/export\s+default\s+function\s+Button/g, ""), + ), + "Button.tsx is default-only — if you add a named export, also fix any default consumers", + ); + }); + + it("RiskNoticeModal imports Button as default (not named)", () => { + assert.ok( + /import\s+Button\s+from\s+["']@\/shared\/components\/Button["']/.test(modalSrc), + "RiskNoticeModal must use `import Button from ...` (default), not `import { Button }`", + ); + assert.ok( + !/import\s+\{\s*Button\s*\}\s+from\s+["']@\/shared\/components\/Button["']/.test(modalSrc), + "Named import of Button from the .tsx file is broken (Button.tsx has no named export)", + ); + }); +}); diff --git a/tests/unit/ui/agent-card-risk-modal.test.tsx b/tests/unit/ui/agent-card-risk-modal.test.tsx index f122411283..a1f1b98521 100644 --- a/tests/unit/ui/agent-card-risk-modal.test.tsx +++ b/tests/unit/ui/agent-card-risk-modal.test.tsx @@ -21,8 +21,12 @@ vi.mock("next/link", () => ({ React.createElement("a", { href }, children), })); +// Button.tsx exposes a default export — match the real module shape so +// RiskNoticeModal (which uses `import Button from ...`) resolves correctly. +// Round 3 had this as a named-export mock, which masked the production +// `import { Button }` bug fixed in R4 #1. vi.mock("@/shared/components/Button", () => ({ - Button: ({ + default: ({ children, onClick, }: { diff --git a/tests/unit/ui/agent-card.test.tsx b/tests/unit/ui/agent-card.test.tsx index 9ea5669eb6..84ae2fb5bc 100644 --- a/tests/unit/ui/agent-card.test.tsx +++ b/tests/unit/ui/agent-card.test.tsx @@ -124,6 +124,13 @@ describe("AgentCard", { timeout: 30000 }, () => { "../../../src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentCard" ); + // Simulate that the per-agent RiskNoticeModal (Fix4 M5) has already been + // accepted for this agent — otherwise the DNS click opens the modal first + // and onDnsToggle is only called after the user accepts. We test the + // "already accepted" path here; the modal flow is covered by + // tests/unit/ui/agent-card-risk-modal.test.tsx. + localStorage.setItem("omniroute-agentbridge-risk-dismissed-copilot", "true"); + const onDnsToggle = vi.fn().mockResolvedValue(undefined); const container = makeContainer();