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:
@@ -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)",
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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}`} />,
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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),
|
||||
}));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(() => []),
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user