From a81486559e9100050ce78c16300f0db5bc5c2dc6 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 13 Jul 2026 07:10:14 -0700 Subject: [PATCH] fix: dashboard lucide/CSS/SettingsModal/mcp-helper fixes + expand mock-completeness guard to dashboard (round 11) (#2049) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes all remaining full-suite failures from run 29231108919 (FN-7923–7931 drift) + expands the structural mock-completeness guard to cover dashboard tests. ## Fixes ### Dashboard (shard 4) — 4 files - **`TaskCard.cli-states.test.tsx`** — Proxy lucide-react mock needed `has`/`getOwnPropertyDescriptor` traps; TaskCard now imports `priorityIndicator.tsx` which reads `ArrowDown` at module-init. Vitest validates ESM named exports via `in`/descriptor, not `get`. Also added `useToast` mock. - **`SettingsModalNodeRouting.test.tsx`** — Pass `initialSection="node-routing"` (it's in `ADVANCED_SETTINGS_SECTION_IDS`, nav hidden by default). - **`styles-css-rgba-tokenization.test.ts`** — Removed stale `.settings-sidebar` color-mix expectation (FN-7825 made it structural-only). - **`mcp-helper-forwarding.test.ts`** — Added `resolvePlanningThinkingLevel` to `@fusion/engine` mock (insight extraction calls it before MCP forwarding). ### Structural guard expansion - **`.tsx` blind spot fixed** — `collectTs` and test file filter now include `.tsx` files - **Shorthand property extraction** — key extractor now matches both `key: value` and `key,` (shorthand) - **Convention mapping** — `.test.tsx → .tsx` source resolution added - **Dashboard test coverage** — `@fusion/engine` barrel check now scans `packages/dashboard/src/__tests__/` - **7 latent mock gaps completed** — `pr-conflict-resolver`, `project-pause-resume-routes`, `routes-approval-sandbox-provisioning`, `routes-approval`, `routes-worktrunk`, `session-reconnect`, `setup-routes` ### Engine (shards 1+2) — zero real failures All 3 failing files are local-only (`@agentclientprotocol/sdk` + pi-ai staleness). CI resolves them from lockfile. ## Verification - Gate (with expanded guard): exit 0 ✅ - Dashboard (5 non-local files): 36/36 passed ✅ - 3 files skipped locally (`@agentclientprotocol/sdk`) — CI will verify ## Summary by CodeRabbit * **Tests** * Improved test reliability by completing module mocks across dashboard and routing scenarios. * Updated settings and task card test coverage to reflect current UI behavior. * Enhanced mock validation to cover additional test files, TypeScript React files, and shorthand exports. * Prevented failures related to missing providers, engine helpers, and planning configuration. --- .../styles-css-rgba-tokenization.test.ts | 8 +++---- .../SettingsModalNodeRouting.test.tsx | 7 +++++- .../__tests__/TaskCard.cli-states.test.tsx | 23 +++++++++++++++++++ .../__tests__/mcp-helper-forwarding.test.ts | 6 +++++ .../__tests__/pr-conflict-resolver.test.ts | 2 ++ .../project-pause-resume-routes.test.ts | 12 ++++++++++ ...utes-approval-sandbox-provisioning.test.ts | 3 +++ .../src/__tests__/routes-approval.test.ts | 4 ++++ .../src/__tests__/session-reconnect.test.ts | 10 ++++++++ .../src/__tests__/setup-routes.test.ts | 12 ++++++++++ scripts/check-mock-completeness.mjs | 13 +++++++---- 11 files changed, 90 insertions(+), 10 deletions(-) diff --git a/packages/dashboard/app/__tests__/styles-css-rgba-tokenization.test.ts b/packages/dashboard/app/__tests__/styles-css-rgba-tokenization.test.ts index 24f25c7150..f3dff107e3 100644 --- a/packages/dashboard/app/__tests__/styles-css-rgba-tokenization.test.ts +++ b/packages/dashboard/app/__tests__/styles-css-rgba-tokenization.test.ts @@ -15,10 +15,10 @@ const convertedSelectors: SelectorExpectation[] = [ selector: ".modal-actions", expectedColorMix: "color-mix(in srgb, var(--surface) 60%, transparent)", }, - { - selector: ".settings-sidebar", - expectedColorMix: "color-mix(in srgb, var(--surface) 60%, transparent)", - }, + // FN-7825 made .settings-sidebar structural-only (display/flex/scrollbar) and removed its + // background color-mix, so it is no longer a rgba->color-mix converted selector. The remaining + // entries are the selectors that still carry a tokenized color-mix value. + // FNXC:CssTokenization 2026-07-13-14:00 (round 11): { selector: ".step-progress-segment[data-tooltip]:hover::after", expectedColorMix: "color-mix(in srgb, var(--text) 20%, transparent)", diff --git a/packages/dashboard/app/components/__tests__/SettingsModalNodeRouting.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModalNodeRouting.test.tsx index 7b075f4a22..5b7bcd2da1 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModalNodeRouting.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModalNodeRouting.test.tsx @@ -112,7 +112,12 @@ const baseSettings = { }; function renderModal() { - return render( {}} addToast={() => {}} />); + // FNXC:DashboardMocks 2026-07-13-14:00 (round 11): + // node-routing is in ADVANCED_SETTINGS_SECTION_IDS, so the nav item is hidden + // unless Advanced settings is enabled. Passing initialSection="node-routing" + // enables advanced mode (SettingsModal derives showAdvancedSettings from the + // requested section) so the "Node Routing" nav button renders and is clickable. + return render( {}} addToast={() => {}} initialSection="node-routing" />); } async function ready() { diff --git a/packages/dashboard/app/components/__tests__/TaskCard.cli-states.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.cli-states.test.tsx index b1f16d1b79..9b5d8f824d 100644 --- a/packages/dashboard/app/components/__tests__/TaskCard.cli-states.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskCard.cli-states.test.tsx @@ -6,10 +6,33 @@ import type { Task } from "@fusion/core"; vi.mock("lucide-react", () => { const Stub = () => null; + // FNXC:DashboardMocks 2026-07-13-14:00 (round 11): + // TaskCard now imports priorityIndicator.tsx, which reads ArrowDown/ArrowUp/Flag/TriangleAlert + // from lucide-react at module-init time. A get-only Proxy is not enough: Vitest validates named + // ESM exports via `in`/`getOwnPropertyDescriptor` before reading the binding, so a bare Proxy + // over {} reports no exports and throws "No ArrowDown export is defined on the lucide-react mock". + // Expose every string key as an own enumerable Stub so any icon resolves to a null stub. return new Proxy({}, { get: (_target, prop) => prop === "then" ? undefined : Stub, + has: (_target, prop) => typeof prop === "string" && prop !== "then", + getOwnPropertyDescriptor: (_target, prop) => + typeof prop === "string" && prop !== "then" + ? { configurable: true, enumerable: true, value: Stub, writable: true } + : undefined, }); }); +// FNXC:DashboardMocks 2026-07-13-14:00 (round 11): +// TaskCard embeds RuntimeFallbackBadge, which calls the shared useToast() hook +// directly (not the addToast prop). This file renders outside a +// ToastProvider, so mock the hook to avoid "useToast must be used within +// ToastProvider", matching the TaskCard.test.tsx pattern. +vi.mock("../../hooks/useToast", () => ({ + useToast: () => ({ + addToast: vi.fn(), + removeToast: vi.fn(), + toasts: [], + }), +})); vi.mock("../ProviderIcon", () => ({ ProviderIcon: ({ provider }: { provider: string }) => , diff --git a/packages/dashboard/src/__tests__/mcp-helper-forwarding.test.ts b/packages/dashboard/src/__tests__/mcp-helper-forwarding.test.ts index e2c0bad036..56b0db63b9 100644 --- a/packages/dashboard/src/__tests__/mcp-helper-forwarding.test.ts +++ b/packages/dashboard/src/__tests__/mcp-helper-forwarding.test.ts @@ -30,6 +30,12 @@ vi.mock("@fusion/engine", () => ({ createFnAgent: mockCreateFnAgent, promptWithFallback: mockPromptWithFallback, resolveMcpServersForStore: mockResolveMcpServersForStore, + // FNXC:DashboardMocks 2026-07-13-14:00 (round 11): + // Insight extraction now resolves a per-run thinking level via resolvePlanningThinkingLevel + // (imported from @fusion/engine) before reaching resolveMcpServersForStore. Without this mock + // export the call throws TypeError, the run is marked failed (still HTTP 201), and the MCP + // forwarding assertion sees 0 calls. Mirror the insights-routes.test.ts mock shape. + resolvePlanningThinkingLevel: vi.fn((_settings: unknown, thinkingLevel?: string) => thinkingLevel), })); const resolvedMcpServers = [ diff --git a/packages/dashboard/src/__tests__/pr-conflict-resolver.test.ts b/packages/dashboard/src/__tests__/pr-conflict-resolver.test.ts index 68a9effb6e..a6ec5192f5 100644 --- a/packages/dashboard/src/__tests__/pr-conflict-resolver.test.ts +++ b/packages/dashboard/src/__tests__/pr-conflict-resolver.test.ts @@ -16,6 +16,8 @@ vi.mock("../routes/resolve-diff-base.js", () => ({ })); vi.mock("@fusion/engine", () => ({ + // FNXC:TestInfrastructure 2026-07-13-11:05: Missing @fusion/engine barrel exports added for mock completeness (check-mock-completeness.mjs gate). + resolveMcpServersForStore: vi.fn(() => []), createResolvedAgentSession: mockCreateResolvedAgentSession, })); diff --git a/packages/dashboard/src/__tests__/project-pause-resume-routes.test.ts b/packages/dashboard/src/__tests__/project-pause-resume-routes.test.ts index 34bfd20531..22601639f8 100644 --- a/packages/dashboard/src/__tests__/project-pause-resume-routes.test.ts +++ b/packages/dashboard/src/__tests__/project-pause-resume-routes.test.ts @@ -33,6 +33,18 @@ vi.mock("@fusion/core", async () => { }); vi.mock("@fusion/engine", () => ({ + // FNXC:TestInfrastructure 2026-07-13-11:05: Missing @fusion/engine barrel exports added for mock completeness (check-mock-completeness.mjs gate). + getExemptToolNames: vi.fn(() => []), + promptWithFallback: vi.fn(), + reloadExemptTools: vi.fn(), + resolveIntegrationBranch: vi.fn(), + discoverMcpServers: vi.fn(() => []), + resolveMcpServersForRuntime: vi.fn(() => []), + resolveMcpServersForStore: vi.fn(() => []), + validateMcpServer: vi.fn(), + isInProcessBackupCommand: vi.fn(), + isInProcessMemoryBackupCommand: vi.fn(), + formatInProcessBackupError: vi.fn(), listCliAdapterDescriptors: () => [], createFnAgent: vi.fn(async () => ({ session: { diff --git a/packages/dashboard/src/__tests__/routes-approval-sandbox-provisioning.test.ts b/packages/dashboard/src/__tests__/routes-approval-sandbox-provisioning.test.ts index 0c2537fb45..500e6605ee 100644 --- a/packages/dashboard/src/__tests__/routes-approval-sandbox-provisioning.test.ts +++ b/packages/dashboard/src/__tests__/routes-approval-sandbox-provisioning.test.ts @@ -55,6 +55,9 @@ vi.mock("@fusion/core", async () => { }); vi.mock("@fusion/engine", () => ({ + // FNXC:TestInfrastructure 2026-07-13-11:05: Missing @fusion/engine barrel exports added for mock completeness (check-mock-completeness.mjs gate). + assertNoSecretPlaintext: vi.fn(), + executeApprovedWorktrunkInstall: vi.fn(), listCliAdapterDescriptors: () => [], executeApprovedAgentProvisioning: vi.fn(async () => undefined), })); diff --git a/packages/dashboard/src/__tests__/routes-approval.test.ts b/packages/dashboard/src/__tests__/routes-approval.test.ts index c4bd9d2aee..7aa317bab4 100644 --- a/packages/dashboard/src/__tests__/routes-approval.test.ts +++ b/packages/dashboard/src/__tests__/routes-approval.test.ts @@ -87,6 +87,10 @@ vi.mock("@fusion/core", async (importOriginal) => ({ const executeApprovedWorktrunkInstall = vi.fn(async () => ({ binaryPath: "~/.fusion/bin/wt", source: "installed-release" })); vi.mock("@fusion/engine", () => ({ + // FNXC:TestInfrastructure 2026-07-13-11:05: Missing @fusion/engine barrel exports added for mock completeness (check-mock-completeness.mjs gate). + assertNoSecretPlaintext: vi.fn(), + executeApprovedAgentProvisioning: vi.fn(), + executeApprovedWorktrunkInstall: vi.fn(), listCliAdapterDescriptors: () => [], executeApprovedAgentProvisioning, executeApprovedWorktrunkInstall, diff --git a/packages/dashboard/src/__tests__/session-reconnect.test.ts b/packages/dashboard/src/__tests__/session-reconnect.test.ts index 61b559b548..70e99d6f6e 100644 --- a/packages/dashboard/src/__tests__/session-reconnect.test.ts +++ b/packages/dashboard/src/__tests__/session-reconnect.test.ts @@ -46,6 +46,16 @@ const { mockCreateFnAgent } = vi.hoisted(() => ({ })); vi.mock("@fusion/engine", () => ({ + // FNXC:TestInfrastructure 2026-07-13-11:05: Missing @fusion/engine barrel exports added for mock completeness (check-mock-completeness.mjs gate). + getExemptToolNames: vi.fn(() => []), + reloadExemptTools: vi.fn(), + resolveIntegrationBranch: vi.fn(), + discoverMcpServers: vi.fn(() => []), + resolveMcpServersForRuntime: vi.fn(() => []), + validateMcpServer: vi.fn(), + isInProcessBackupCommand: vi.fn(), + isInProcessMemoryBackupCommand: vi.fn(), + formatInProcessBackupError: vi.fn(), listCliAdapterDescriptors: () => [], // FNXC:DashboardSessionTests 2026-06-14-09:06: planning.ts spreads createWorkflowAuthoringTools into agent customTools; this focused engine mock must export it to keep AI-session tests aligned with production planning setup. createWorkflowAuthoringTools: vi.fn(() => []), diff --git a/packages/dashboard/src/__tests__/setup-routes.test.ts b/packages/dashboard/src/__tests__/setup-routes.test.ts index 59f87247c8..13216e90dd 100644 --- a/packages/dashboard/src/__tests__/setup-routes.test.ts +++ b/packages/dashboard/src/__tests__/setup-routes.test.ts @@ -34,6 +34,18 @@ vi.mock("@fusion/core", async () => { }); vi.mock("@fusion/engine", () => ({ + // FNXC:TestInfrastructure 2026-07-13-11:05: Missing @fusion/engine barrel exports added for mock completeness (check-mock-completeness.mjs gate). + getExemptToolNames: vi.fn(() => []), + promptWithFallback: vi.fn(), + reloadExemptTools: vi.fn(), + resolveIntegrationBranch: vi.fn(), + discoverMcpServers: vi.fn(() => []), + resolveMcpServersForRuntime: vi.fn(() => []), + resolveMcpServersForStore: vi.fn(() => []), + validateMcpServer: vi.fn(), + isInProcessBackupCommand: vi.fn(), + isInProcessMemoryBackupCommand: vi.fn(), + formatInProcessBackupError: vi.fn(), listCliAdapterDescriptors: () => [], createFnAgent: vi.fn(async () => ({ session: { diff --git a/scripts/check-mock-completeness.mjs b/scripts/check-mock-completeness.mjs index 0df3703c84..7dfccd04b5 100644 --- a/scripts/check-mock-completeness.mjs +++ b/scripts/check-mock-completeness.mjs @@ -46,6 +46,8 @@ const BARRELS = [ join(root, "packages/cli/src/__tests__"), join(root, "packages/cli/src/commands/__tests__"), join(root, "packages/cli/src/plugins/__tests__"), + // FNXC:TestInfrastructure 2026-07-13-11:00: Dashboard API tests also mock @fusion/engine; include them to prevent the same barrel-export drift. + join(root, "packages/dashboard/src/__tests__"), ], cliSrc: join(root, "packages/cli/src"), }, @@ -124,9 +126,9 @@ function resolveSourceFiles(testPath, cliSrc) { if (existsSync(resolved)) sources.add(resolved); } - // Convention: __tests__/foo.test.ts → ../foo.ts + // Convention: __tests__/foo.test.ts → ../foo.ts, __tests__/foo.test.tsx → ../foo.tsx const noTests = testPath.replace(/__tests\//, ""); - const convPath = noTests.replace(/\.test\.ts$/, ".ts"); + const convPath = noTests.replace(/\.test\.ts$/, ".ts").replace(/\.test\.tsx$/, ".tsx"); if (existsSync(convPath)) sources.add(convPath); // bin.test.ts special case @@ -165,7 +167,8 @@ function extractMockKeys(testSrc, moduleName, testPath) { const body = testSrc.slice(bodyStart, i - 1); const keys = new Set(); - const keyRe = /(?:^|\n)\s*([A-Za-z_$][\w$]*)\s*(?::)/g; + // Match both regular properties (key: value) and shorthand (key, or key}). + const keyRe = /(?:^|\n)\s*([A-Za-z_$][\w$]*)\s*(?::|[,}])/g; let km; while ((km = keyRe.exec(body)) !== null) { keys.add(km[1]); @@ -216,7 +219,7 @@ function collectTs(dir) { const full = join(dir, entry); const st = statSync(full); if (st.isDirectory()) out = out.concat(collectTs(full)); - else if (entry.endsWith(".ts")) out.push(full); + else if (entry.endsWith(".ts") || entry.endsWith(".tsx")) out.push(full); } } catch { /* dir may not exist */ } return out; @@ -235,7 +238,7 @@ for (const cfg of BARRELS) { const testFiles = new Set(); for (const dir of cfg.testDirs) { for (const f of collectTs(dir)) { - if (f.endsWith(".test.ts")) testFiles.add(f); + if (f.endsWith(".test.ts") || f.endsWith(".test.tsx")) testFiles.add(f); } }