fix: dashboard lucide/CSS/SettingsModal/mcp-helper fixes + expand mock-completeness guard to dashboard (round 11) (#2049)

## 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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## 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.


<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
gsxdsm
2026-07-13 07:10:14 -07:00
committed by GitHub
parent 502c4c132f
commit a81486559e
11 changed files with 90 additions and 10 deletions

View File

@@ -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)",

View File

@@ -112,7 +112,12 @@ const baseSettings = {
};
function renderModal() {
return render(<SettingsModal onClose={() => {}} 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(<SettingsModal onClose={() => {}} addToast={() => {}} initialSection="node-routing" />);
}
async function ready() {

View File

@@ -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 <TaskCard> 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 }) => <span data-testid={`provider-icon-${provider}`} />,

View File

@@ -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 = [

View File

@@ -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,
}));

View File

@@ -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: {

View File

@@ -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),
}));

View File

@@ -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,

View File

@@ -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(() => []),

View File

@@ -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: {

View File

@@ -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);
}
}