From a03eb0b39cd5274eb1525715c0a717fe7d954ba7 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 24 Jul 2026 23:02:16 -0700 Subject: [PATCH] fix(dashboard-tests): green Full Suite after chat tags, TaskForm meta, remote lifecycle - Replace undefined --space-2xs in ChatView tag menu with calc(--space-xs / 2) - Stub fetchChatTags/create/rename/delete on streaming-thread api mock for useChat mount - Expect TaskForm model/tracking callbacks with TaskFormValueChangeMeta source - Drive remote tunnel lifecycle via one stop then error status (no double Stop race) --- .../dashboard/app/components/ChatView.css | 7 ++++++- .../ChatView.streaming-thread.test.tsx | 10 +++++++++ ...ettingsModal.remote-notifications.test.tsx | 21 +++++++++++++------ .../components/__tests__/TaskForm.test.tsx | 12 ++++++----- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/packages/dashboard/app/components/ChatView.css b/packages/dashboard/app/components/ChatView.css index b8d4c9c6e7..a236e52ec4 100644 --- a/packages/dashboard/app/components/ChatView.css +++ b/packages/dashboard/app/components/ChatView.css @@ -2788,5 +2788,10 @@ Queued-message banners stack above the composer input with a capped scroll area, .chat-tag-create-row .input { min-width: 0; } @media (max-width: 768px) { .chat-session-tags { max-height: calc(var(--space-lg) * 2); } - .chat-tag-menu-item { gap: var(--space-2xs); } + /* + FNXC:ChatTags 2026-07-24-23:05: + Compact mobile tag-menu gap must use a defined token. --space-2xs is intentionally + undefined (space-token-defined hygiene); half of --space-xs matches the prior intent. + */ + .chat-tag-menu-item { gap: calc(var(--space-xs) / 2); } } diff --git a/packages/dashboard/app/components/__tests__/ChatView.streaming-thread.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.streaming-thread.test.tsx index 52b33240c2..c10b6edce6 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.streaming-thread.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.streaming-thread.test.tsx @@ -16,6 +16,12 @@ vi.mock("../../sse-bus", () => ({ subscribeSse: vi.fn(() => () => {}), })); +/* +FNXC:ChatTags 2026-07-24-23:05: +useChat loads tags on mount via fetchChatTags/createChatTag/renameChatTag/deleteChatTag. +Partial ../../api factories must stub those exports or CI fails with +"No fetchChatTags export is defined on the mock" once tag APIs ship. +*/ vi.mock("../../api", () => ({ fetchSettings: vi.fn().mockResolvedValue({}), fetchChatSessions: vi.fn(), @@ -27,6 +33,10 @@ vi.mock("../../api", () => ({ streamChatResponse: vi.fn(), attachChatStream: vi.fn(), cancelChatResponse: vi.fn(), + fetchChatTags: vi.fn().mockResolvedValue({ tags: [] }), + createChatTag: vi.fn().mockResolvedValue({ tag: { id: "tag-1", name: "t", createdAt: "2026-04-08T00:00:00.000Z" } }), + renameChatTag: vi.fn().mockResolvedValue({ tag: { id: "tag-1", name: "t", createdAt: "2026-04-08T00:00:00.000Z" } }), + deleteChatTag: vi.fn().mockResolvedValue({ success: true }), fetchAgents: vi.fn().mockResolvedValue([ { id: "agent-001", name: "Alpha", role: "executor", state: "idle", icon: undefined, createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z", metadata: {} }, ]), diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx index 3a1b8dcdb9..88f68cc263 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx @@ -587,11 +587,17 @@ describe("SettingsModal", () => { }); it("shows lifecycle state changes for start and stop actions, including error state", async () => { + /* + FNXC:SettingsRemote 2026-07-24-23:05: + Full Suite flake: a second Stop Tunnel click races loadRemoteData after the first stop. + Product UI only shows Stop while state is running/starting; after stop refresh returns + error, the control is Start Tunnel. Drive start → running → one stop → error message + instead of double-stop, and keep running stable across start-poll ticks. + */ mockFetchRemoteStatus .mockResolvedValueOnce({ provider: null, state: "stopped", url: null, lastError: null }) .mockResolvedValueOnce({ provider: "tailscale", state: "starting", url: null, lastError: null }) - .mockResolvedValueOnce({ provider: "tailscale", state: "running", url: "https://tail.example", lastError: null }) - .mockResolvedValueOnce({ provider: "tailscale", state: "error", url: null, lastError: "Tunnel crashed" }); + .mockResolvedValue({ provider: "tailscale", state: "running", url: "https://tail.example", lastError: null }); await renderModalSection("remote", "Remote Access"); @@ -607,15 +613,18 @@ describe("SettingsModal", () => { expect(await screen.findByRole("button", { name: "Stop Tunnel" })).toBeInTheDocument(); + mockFetchRemoteStatus.mockResolvedValue({ + provider: "tailscale", + state: "error", + url: null, + lastError: "Tunnel crashed", + }); await settingsModalUser.click(screen.getByRole("button", { name: "Stop Tunnel" })); await waitFor(() => { expect(mockStopRemoteTunnel).toHaveBeenCalledTimes(1); }); - await settingsModalUser.click(screen.getByRole("button", { name: "Stop Tunnel" })); - await waitFor(() => { - expect(mockStopRemoteTunnel).toHaveBeenCalledTimes(2); - }); expect(await screen.findByText("Tunnel crashed")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Start Tunnel" })).toBeInTheDocument(); }); it("shows external tunnel panel with actions when external tunnel is detected", async () => { diff --git a/packages/dashboard/app/components/__tests__/TaskForm.test.tsx b/packages/dashboard/app/components/__tests__/TaskForm.test.tsx index d40e2156df..afe132b0f4 100644 --- a/packages/dashboard/app/components/__tests__/TaskForm.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskForm.test.tsx @@ -1038,8 +1038,9 @@ describe("TaskForm preset selection (FN-819)", () => { expect(onPresetModeChange).toHaveBeenCalledWith("preset"); expect(onSelectedPresetIdChange).toHaveBeenCalledWith("fast"); - expect(onExecutorModelChange).toHaveBeenCalledWith("anthropic/claude-sonnet-4-5"); - expect(onValidatorModelChange).toHaveBeenCalledWith("openai/gpt-4o"); + // FNXC:TaskForm 2026-07-24-23:05: model/preset handlers pass TaskFormValueChangeMeta so NewTaskModal can ignore initialization seeds vs user picks. + expect(onExecutorModelChange).toHaveBeenCalledWith("anthropic/claude-sonnet-4-5", { source: "user" }); + expect(onValidatorModelChange).toHaveBeenCalledWith("openai/gpt-4o", { source: "user" }); }); it("switching to default clears preset and model overrides", async () => { @@ -1080,8 +1081,8 @@ describe("TaskForm preset selection (FN-819)", () => { expect(onPresetModeChange).toHaveBeenCalledWith("default"); expect(onSelectedPresetIdChange).toHaveBeenCalledWith(""); - expect(onExecutorModelChange).toHaveBeenCalledWith(""); - expect(onValidatorModelChange).toHaveBeenCalledWith(""); + expect(onExecutorModelChange).toHaveBeenCalledWith("", { source: "user" }); + expect(onValidatorModelChange).toHaveBeenCalledWith("", { source: "user" }); }); it("switching to custom clears preset ID", async () => { @@ -1564,7 +1565,8 @@ describe("TaskForm focus behavior (FN-1459)", () => { }); await waitFor(() => { - expect(onGithubTrackingEnabledChange).toHaveBeenCalledWith(true); + // FNXC:TaskForm 2026-07-24-23:05: settings-default seed marks source initialization so create flow can avoid treating it as a user edit. + expect(onGithubTrackingEnabledChange).toHaveBeenCalledWith(true, { source: "initialization" }); }); });