diff --git a/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx index 9353cfad56..41bba5b7f8 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx @@ -881,7 +881,17 @@ describe("ChatView mobile behavior", () => { fireEvent.click(sendButton); expect(sendMessage).toHaveBeenCalledTimes(1); - expect(sendMessage).toHaveBeenCalledWith("Hello mobile", []); + /* + FNXC:ChatAttachments 2026-07-23-23:00: + FN-8502 made ChatView pass attachment delivery callbacks as a third + sendMessage argument ({ onDelivered, onFailed }); assert them + structurally so the iOS first-tap send contract (text + empty + attachments) stays the protected invariant. + */ + expect(sendMessage).toHaveBeenCalledWith("Hello mobile", [], expect.objectContaining({ + onDelivered: expect.any(Function), + onFailed: expect.any(Function), + })); expect(document.activeElement).toBe(input); } finally { isIOSSpy.mockRestore(); @@ -911,7 +921,11 @@ describe("ChatView mobile behavior", () => { fireEvent.click(sendButton); expect(sendMessage).toHaveBeenCalledTimes(1); - expect(sendMessage).toHaveBeenCalledWith("Hello mobile", []); + // FNXC:ChatAttachments 2026-07-23-23:00: FN-8502 delivery-callback third arg (see above). + expect(sendMessage).toHaveBeenCalledWith("Hello mobile", [], expect.objectContaining({ + onDelivered: expect.any(Function), + onFailed: expect.any(Function), + })); expect(document.activeElement).toBe(input); } finally { restoreMatchMedia.mockRestore(); diff --git a/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx b/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx index b6e8833e0f..3117f4c569 100644 --- a/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx +++ b/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx @@ -589,10 +589,19 @@ describe("PlanningModeModal sequential flow", () => { .mockResolvedValueOnce({ id: "FN-8442" }); renderSession(); - fireEvent.click(await screen.findByRole("button", { name: "Proceed with plan" })); + /* + FNXC:PlanningMode 2026-07-23-23:30: + Settle pending hydration commits and click a freshly-queried node: clicking the button + reference returned by findByRole raced late hydration re-renders on loaded CI shards + (full-suite run 30069944059), dispatching on a detached node so the create never fired and + the view stayed on plan review. Same detached-node class as the Stop/Refine race (5a5796bca). + */ + await screen.findByRole("button", { name: "Proceed with plan" }); + await act(async () => {}); + fireEvent.click(screen.getByRole("button", { name: "Proceed with plan" })); + await waitFor(() => expect(mockCreateTaskFromPlanning).toHaveBeenCalledTimes(2)); expect(await screen.findByTestId("planning-task-created")).toHaveTextContent("FN-8442"); - expect(mockCreateTaskFromPlanning).toHaveBeenCalledTimes(2); expect(screen.queryByTestId("planning-create-retry")).toBeNull(); }); @@ -610,7 +619,10 @@ describe("PlanningModeModal sequential flow", () => { .mockResolvedValueOnce({ id: "FN-8442" }); const { rerender } = renderSession(); - fireEvent.click(await screen.findByRole("button", { name: "Proceed with plan" })); + // FNXC:PlanningMode 2026-07-23-23:30: settle hydration then click a fresh node (see detached-node note above). + await screen.findByRole("button", { name: "Proceed with plan" }); + await act(async () => {}); + fireEvent.click(screen.getByRole("button", { name: "Proceed with plan" })); vi.useFakeTimers(); await act(async () => { diff --git a/packages/dashboard/src/__tests__/remote-access-routes.test.ts b/packages/dashboard/src/__tests__/remote-access-routes.test.ts index d721edd067..f1e91de270 100644 --- a/packages/dashboard/src/__tests__/remote-access-routes.test.ts +++ b/packages/dashboard/src/__tests__/remote-access-routes.test.ts @@ -87,6 +87,15 @@ function createMockStore(overrides: Partial = {}): TaskStore { moveTask: vi.fn(), logEntry: vi.fn(), getAgentLogs: vi.fn().mockResolvedValue([]), + /* + FNXC:PluginMcpServers 2026-07-23-00:00: + FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin + MCP provider on every getProjectContext call. A store that already exposes + getProjectScopedPluginMcpServers is treated as runtime-owned and skips the + binder (which would otherwise call getPluginStore()); declare it here so the + remote-access route contracts stay isolated from plugin-loader bootstrapping. + */ + getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]), on: vi.fn(), off: vi.fn(), ...overrides,