From 12fa85eefb3c44978eb0679436f4f7cc04522cba Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 24 Jul 2026 02:14:26 -0700 Subject: [PATCH] fix(dashboard-tests): finish the FN-8491 mock backlog; contain FN-8557 innerWidth leakage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine more api-lane files get the established getProjectScopedPluginMcpServers stub (includes shared-branch-group-entry-points, whose two current failures were FN-8491 500s — the historical per-task-derived pair is gone). App lane: FN-8557 made window.innerWidth<=768 a mobile signal, so leaked innerWidth=375 defineProperty stamps flipped later ListView/settings tests into mobile layout (reset in beforeEach), and useModalResizePersist's 700px "mobile" fixture became tablet-class (now a phone-class 375px viewport). Co-Authored-By: Claude Fable 5 --- .../app/components/__tests__/ListView.test.tsx | 12 ++++++++++++ .../components/__tests__/settings-mobile.test.tsx | 9 +++++++++ .../hooks/__tests__/useModalResizePersist.test.tsx | 10 +++++++++- .../dashboard/src/__tests__/debug-execfile.test.ts | 8 ++++++++ .../src/__tests__/scripts-routes.routes.test.ts | 8 ++++++++ .../shared-branch-group-entry-points.test.ts | 8 ++++++++ .../__tests__/stranded-refinements-routes.test.ts | 8 ++++++++ .../__tests__/task-effective-settings-route.test.ts | 8 ++++++++ .../src/routes/__tests__/agent-avatar-routes.test.ts | 8 ++++++++ .../routes/__tests__/agent-onboarding-routes.test.ts | 8 ++++++++ .../register-task-workflow-routes.merge.test.ts | 8 ++++++++ ...ter-task-workflow-routes.runtime-fallback.test.ts | 8 ++++++++ 12 files changed, 102 insertions(+), 1 deletion(-) diff --git a/packages/dashboard/app/components/__tests__/ListView.test.tsx b/packages/dashboard/app/components/__tests__/ListView.test.tsx index 4734775c74..6ef5944415 100644 --- a/packages/dashboard/app/components/__tests__/ListView.test.tsx +++ b/packages/dashboard/app/components/__tests__/ListView.test.tsx @@ -420,6 +420,16 @@ describe("ListView", () => { localStorage.clear(); showAllColumnsByDefault(); ensureMatchMedia(); + /* + FNXC:ViewportMode 2026-07-24-02:30: + FN-8557 (973c978f9) made isMobileViewport treat `window.innerWidth <= 768` + as a mobile signal alongside matchMedia. mockMobileViewport stamps + innerWidth=375 via defineProperty and its spy restore only resets + matchMedia, so the leaked width flipped every later test into the mobile + card layout (no rows). Reset innerWidth to a desktop width here so the + per-test viewport helpers stay authoritative. + */ + Object.defineProperty(window, "innerWidth", { value: 1280, configurable: true }); vi.spyOn(window, "matchMedia").mockImplementation((query: string) => ({ matches: false, media: query, @@ -4221,6 +4231,8 @@ describe("ListView - Bulk Selection", () => { for (const key of Object.keys(listViewSseHandlers)) delete listViewSseHandlers[key]; localStorage.clear(); ensureMatchMedia(); + // FNXC:ViewportMode 2026-07-24-02:30: FN-8557 innerWidth leak reset (see the main ListView beforeEach comment). + Object.defineProperty(window, "innerWidth", { value: 1280, configurable: true }); vi.spyOn(window, "matchMedia").mockImplementation((query: string) => ({ matches: false, media: query, diff --git a/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx b/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx index b7687621bf..de4b88544e 100644 --- a/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx +++ b/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx @@ -163,6 +163,15 @@ function setDocumentHidden(hidden: boolean): void { } function mockSettingsViewport(matches: boolean): void { + /* + FNXC:ViewportMode 2026-07-24-02:20: + FN-8557 (973c978f9) made isMobileViewport treat `window.innerWidth <= 768` as a + mobile signal alongside matchMedia. Individual mobile tests here stamp + innerWidth=375 via defineProperty without restoring it, which leaked mobile + mode into later desktop assertions. The viewport mock now owns innerWidth in + both directions so each test's declared viewport is authoritative. + */ + Object.defineProperty(window, "innerWidth", { configurable: true, value: matches ? 375 : 1280 }); Object.defineProperty(window, "matchMedia", { writable: true, value: vi.fn().mockImplementation((query: string) => ({ diff --git a/packages/dashboard/app/hooks/__tests__/useModalResizePersist.test.tsx b/packages/dashboard/app/hooks/__tests__/useModalResizePersist.test.tsx index 902b06eb28..1e79e77ea9 100644 --- a/packages/dashboard/app/hooks/__tests__/useModalResizePersist.test.tsx +++ b/packages/dashboard/app/hooks/__tests__/useModalResizePersist.test.tsx @@ -181,7 +181,15 @@ describe("useModalResizePersist", () => { }); it("clears inline size and does not inject a grip on mobile", () => { - setViewport(700); + /* + FNXC:ViewportMode 2026-07-24-02:25: + FN-8557 (973c978f9) classifies a narrow touch viewport with a tablet-class + physical screen (min edge > 480px) as tablet, which keeps the FN-6377 touch + grip. jsdom reports `"ontouchstart" in window === true`, so the old 700x800 + fixture became a tablet. Use a phone-class screen (375px min edge) so this + test still exercises the true mobile contract: no grip, inline size cleared. + */ + setViewport(375); localStorage.setItem(STORAGE_KEY, JSON.stringify({ width: 610, height: 480 })); render(); diff --git a/packages/dashboard/src/__tests__/debug-execfile.test.ts b/packages/dashboard/src/__tests__/debug-execfile.test.ts index 54e689a056..ff346e7fbc 100644 --- a/packages/dashboard/src/__tests__/debug-execfile.test.ts +++ b/packages/dashboard/src/__tests__/debug-execfile.test.ts @@ -20,6 +20,14 @@ class MockStore extends EventEmitter { addTask(task: Task): void { this.tasks.set(task.id, task); } getMissionStore() { return new EventEmitter(); } async listTasks(): Promise { return []; } + /* + FNXC:PluginMcpServers 2026-07-24-02:05: + FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin + MCP provider on every getProjectContext call; a store exposing + getProjectScopedPluginMcpServers is treated as runtime-owned and skips the + binder (which would otherwise 500 on getPluginStore()). + */ + getProjectScopedPluginMcpServers = vi.fn().mockResolvedValue([]); } function createTask(overrides: Partial = {}): Task { diff --git a/packages/dashboard/src/__tests__/scripts-routes.routes.test.ts b/packages/dashboard/src/__tests__/scripts-routes.routes.test.ts index 31ae3b3d39..841323ac2a 100644 --- a/packages/dashboard/src/__tests__/scripts-routes.routes.test.ts +++ b/packages/dashboard/src/__tests__/scripts-routes.routes.test.ts @@ -68,6 +68,14 @@ function createMockStore(overrides: Partial = {}): TaskStore { updateWorkflowStep: vi.fn(), deleteWorkflowStep: vi.fn(), getMissionStore: vi.fn().mockReturnValue(createMockMissionStore()), + /* + FNXC:PluginMcpServers 2026-07-24-02:05: + FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin + MCP provider on every getProjectContext call; a store exposing + getProjectScopedPluginMcpServers is treated as runtime-owned and skips the + binder (which would otherwise 500 on getPluginStore()). + */ + getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]), ...overrides, } as unknown as TaskStore; } diff --git a/packages/dashboard/src/__tests__/shared-branch-group-entry-points.test.ts b/packages/dashboard/src/__tests__/shared-branch-group-entry-points.test.ts index 8610052a6f..8905950237 100644 --- a/packages/dashboard/src/__tests__/shared-branch-group-entry-points.test.ts +++ b/packages/dashboard/src/__tests__/shared-branch-group-entry-points.test.ts @@ -237,6 +237,14 @@ function createMockStore(): TaskStore { getMissionTask: vi.fn(), deleteMissionTask: vi.fn(), }), + /* + FNXC:PluginMcpServers 2026-07-24-02:05: + FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin + MCP provider on every getProjectContext call; a store exposing + getProjectScopedPluginMcpServers is treated as runtime-owned and skips the + binder (which would otherwise 500 on getPluginStore()). + */ + getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]), }; return store as unknown as TaskStore; diff --git a/packages/dashboard/src/__tests__/stranded-refinements-routes.test.ts b/packages/dashboard/src/__tests__/stranded-refinements-routes.test.ts index 596271b390..0f5df22d88 100644 --- a/packages/dashboard/src/__tests__/stranded-refinements-routes.test.ts +++ b/packages/dashboard/src/__tests__/stranded-refinements-routes.test.ts @@ -20,6 +20,14 @@ function createMockStore(overrides: Partial = {}): TaskStore { logEntry: vi.fn().mockResolvedValue(undefined), on: vi.fn(), off: vi.fn(), + /* + FNXC:PluginMcpServers 2026-07-24-02:05: + FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin + MCP provider on every getProjectContext call; a store exposing + getProjectScopedPluginMcpServers is treated as runtime-owned and skips the + binder (which would otherwise 500 on getPluginStore()). + */ + getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]), ...overrides, } as unknown as TaskStore; } diff --git a/packages/dashboard/src/__tests__/task-effective-settings-route.test.ts b/packages/dashboard/src/__tests__/task-effective-settings-route.test.ts index ddf9cf421d..b5b48de6d2 100644 --- a/packages/dashboard/src/__tests__/task-effective-settings-route.test.ts +++ b/packages/dashboard/src/__tests__/task-effective-settings-route.test.ts @@ -34,6 +34,14 @@ class MockStore extends EventEmitter { getWorkflowDefinition = vi.fn(async () => undefined); getWorkflowSettingValues = vi.fn((workflowId: string, projectId: string) => this.workflowValues.get(`${workflowId}::${projectId}`) ?? {}); getWorkflowSettingsProjectId = vi.fn(() => "default"); + /* + FNXC:PluginMcpServers 2026-07-24-02:05: + FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin + MCP provider on every getProjectContext call; a store exposing + getProjectScopedPluginMcpServers is treated as runtime-owned and skips the + binder (which would otherwise 500 on getPluginStore()). + */ + getProjectScopedPluginMcpServers = vi.fn().mockResolvedValue([]); setSelection(taskId: string, workflowId: string): void { this.workflowSelections.set(taskId, { workflowId, stepIds: [] }); diff --git a/packages/dashboard/src/routes/__tests__/agent-avatar-routes.test.ts b/packages/dashboard/src/routes/__tests__/agent-avatar-routes.test.ts index 99477ebe4b..6a8d13fe13 100644 --- a/packages/dashboard/src/routes/__tests__/agent-avatar-routes.test.ts +++ b/packages/dashboard/src/routes/__tests__/agent-avatar-routes.test.ts @@ -99,6 +99,14 @@ function createMockStore(fusionDir: string) { getAllDocuments: vi.fn().mockResolvedValue([]), listWorkflowSteps: vi.fn().mockResolvedValue([]), getMissionStore: vi.fn(), + /* + FNXC:PluginMcpServers 2026-07-24-02:05: + FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin + MCP provider on every getProjectContext call; a store exposing + getProjectScopedPluginMcpServers is treated as runtime-owned and skips the + binder (which would otherwise 500 on getPluginStore()). + */ + getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]), } as any; } diff --git a/packages/dashboard/src/routes/__tests__/agent-onboarding-routes.test.ts b/packages/dashboard/src/routes/__tests__/agent-onboarding-routes.test.ts index 6db146877a..97a2a2c327 100644 --- a/packages/dashboard/src/routes/__tests__/agent-onboarding-routes.test.ts +++ b/packages/dashboard/src/routes/__tests__/agent-onboarding-routes.test.ts @@ -60,6 +60,14 @@ function createMockStore(): TaskStore { updateWorkflowStep: vi.fn(), deleteWorkflowStep: vi.fn(), getMissionStore: vi.fn(), + /* + FNXC:PluginMcpServers 2026-07-24-02:05: + FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin + MCP provider on every getProjectContext call; a store exposing + getProjectScopedPluginMcpServers is treated as runtime-owned and skips the + binder (which would otherwise 500 on getPluginStore()). + */ + getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]), } as unknown as TaskStore; } diff --git a/packages/dashboard/src/routes/__tests__/register-task-workflow-routes.merge.test.ts b/packages/dashboard/src/routes/__tests__/register-task-workflow-routes.merge.test.ts index 89eeb0071e..17b7050bef 100644 --- a/packages/dashboard/src/routes/__tests__/register-task-workflow-routes.merge.test.ts +++ b/packages/dashboard/src/routes/__tests__/register-task-workflow-routes.merge.test.ts @@ -11,6 +11,14 @@ describe("task workflow merge route", () => { const store: TaskStore = { getRootDir: vi.fn(() => process.cwd()), mergeTask: vi.fn(), + /* + FNXC:PluginMcpServers 2026-07-24-02:05: + FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin + MCP provider on every getProjectContext call; a store exposing + getProjectScopedPluginMcpServers is treated as runtime-owned and skips the + binder (which would otherwise 500 on getPluginStore()). + */ + getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]), } as unknown as TaskStore; const onMerge = vi.fn(async (id: string) => ({ diff --git a/packages/dashboard/src/routes/__tests__/register-task-workflow-routes.runtime-fallback.test.ts b/packages/dashboard/src/routes/__tests__/register-task-workflow-routes.runtime-fallback.test.ts index 9d5ff062ec..cc28c79ff6 100644 --- a/packages/dashboard/src/routes/__tests__/register-task-workflow-routes.runtime-fallback.test.ts +++ b/packages/dashboard/src/routes/__tests__/register-task-workflow-routes.runtime-fallback.test.ts @@ -58,6 +58,14 @@ const createHarness = (taskState: any, events: RunAuditEvent[]) => { } return filtered; }), + /* + FNXC:PluginMcpServers 2026-07-24-02:05: + FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin + MCP provider on every getProjectContext call; a store exposing + getProjectScopedPluginMcpServers is treated as runtime-owned and skips the + binder (which would otherwise 500 on getPluginStore()). + */ + getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]), } as unknown as TaskStore; const app = express();