test(FN-4530): fix remaining dashboard deep-lane one-off regressions

Fusion-Task-Id: FN-4530
Fusion-Task-Lineage: f98e68fc-de86-473b-b3a2-bcec95dd74c5
This commit is contained in:
Fusion
2026-05-14 15:01:02 -07:00
committed by gsxdsm
parent 7e3a209f66
commit 990fea9f02
5 changed files with 60 additions and 17 deletions

View File

@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type { ComponentProps } from "react";
import { render, screen, fireEvent, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { EditorView } from "@codemirror/view";
import { SettingsModal } from "../SettingsModal";
import { __test_clearCache as clearPluginUiSlotsCache } from "../../hooks/usePluginUiSlots";
import type { PluginUiContributionEntry, SettingsExportData, UpdateCheckResponse } from "../../api";
@@ -2176,6 +2177,15 @@ describe("SettingsModal", () => {
expect(addToast).toHaveBeenCalledWith("qmd installed successfully", "success");
});
const getMemoryEditorView = (path: string) => {
const host = screen.getByLabelText(`Editor for ${path}`);
const root = host.querySelector(".cm-editor") as HTMLElement | null;
if (!root) throw new Error(`Expected CodeMirror root for ${path}`);
const view = EditorView.findFromDOM(root);
if (!view) throw new Error(`Expected EditorView for ${path}`);
return view;
};
it("loads and shows memory editor content when navigating to Memory", async () => {
renderModal();
@@ -2192,8 +2202,8 @@ describe("SettingsModal", () => {
expect(mockFetchMemoryFile).toHaveBeenCalledWith(".fusion/memory/DREAMS.md", undefined);
});
const editor = await screen.findByLabelText("Editor for .fusion/memory/DREAMS.md") as HTMLTextAreaElement;
expect(editor.value).toContain("Existing dreams");
await screen.findByLabelText("Editor for .fusion/memory/DREAMS.md");
expect(getMemoryEditorView(".fusion/memory/DREAMS.md").state.doc.toString()).toContain("Existing dreams");
});
it("shows loading state while memory is being fetched", async () => {
@@ -2238,8 +2248,9 @@ describe("SettingsModal", () => {
const select = await screen.findByLabelText("Memory File");
await userEvent.selectOptions(select, ".fusion/memory/MEMORY.md");
const editor = await screen.findByLabelText("Editor for .fusion/memory/MEMORY.md");
fireEvent.change(editor, { target: { value: "# Updated memory\n- Reusable learning" } });
await screen.findByLabelText("Editor for .fusion/memory/MEMORY.md");
const view = getMemoryEditorView(".fusion/memory/MEMORY.md");
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: "# Updated memory\n- Reusable learning" } });
const saveButton = await screen.findByRole("button", { name: "Save Memory" });
await userEvent.click(saveButton);
@@ -2271,8 +2282,8 @@ describe("SettingsModal", () => {
expect(mockCompactMemory).toHaveBeenCalledWith(".fusion/memory/DREAMS.md", undefined);
});
const editor = await screen.findByLabelText("Editor for .fusion/memory/DREAMS.md") as HTMLTextAreaElement;
expect(editor.value).toContain("Compacted Memory");
await screen.findByLabelText("Editor for .fusion/memory/DREAMS.md");
expect(getMemoryEditorView(".fusion/memory/DREAMS.md").state.doc.toString()).toContain("Compacted Memory");
expect(addToast).toHaveBeenCalledWith("Memory file compacted", "success");
});
@@ -2286,8 +2297,8 @@ describe("SettingsModal", () => {
await userEvent.click(await screen.findByText("Memory"));
const editor = await screen.findByLabelText("Editor for .fusion/memory/DREAMS.md") as HTMLTextAreaElement;
expect(editor.value).toBe("");
await screen.findByLabelText("Editor for .fusion/memory/DREAMS.md");
expect(getMemoryEditorView(".fusion/memory/DREAMS.md").state.doc.toString()).toBe("");
});
it("switches between memory files in the editor", async () => {
@@ -2313,8 +2324,8 @@ describe("SettingsModal", () => {
expect(mockFetchMemoryFile).toHaveBeenCalledWith(".fusion/memory/DREAMS.md", undefined);
});
const editor = await screen.findByLabelText("Editor for .fusion/memory/DREAMS.md") as HTMLTextAreaElement;
expect(editor.value).toContain("Dreams");
await screen.findByLabelText("Editor for .fusion/memory/DREAMS.md");
expect(getMemoryEditorView(".fusion/memory/DREAMS.md").state.doc.toString()).toContain("Dreams");
});
});

View File

@@ -7,17 +7,19 @@ vi.mock("@fusion/core", async () => {
isGhAvailable: vi.fn(),
isGhAuthenticated: vi.fn(),
runGh: vi.fn(),
runGhAsync: vi.fn(),
runGhJsonAsync: vi.fn(),
getGhErrorMessage: vi.fn((error) => error instanceof Error ? error.message : String(error)),
};
});
import { getGhErrorMessage, isGhAuthenticated, isGhAvailable, runGh, runGhJsonAsync } from "@fusion/core";
import { getGhErrorMessage, isGhAuthenticated, isGhAvailable, runGh, runGhAsync, runGhJsonAsync } from "@fusion/core";
import { GitHubClient } from "../github.js";
const mockIsGhAvailable = vi.mocked(isGhAvailable);
const mockIsGhAuthenticated = vi.mocked(isGhAuthenticated);
const mockRunGh = vi.mocked(runGh);
const mockRunGhAsync = vi.mocked(runGhAsync);
const mockRunGhJsonAsync = vi.mocked(runGhJsonAsync);
const mockGetGhErrorMessage = vi.mocked(getGhErrorMessage);
@@ -56,6 +58,7 @@ describe("GitHubClient forced mode", () => {
});
it("forced gh-cli mode uses only gh path", async () => {
mockRunGhAsync.mockResolvedValue("https://github.com/o/r/issues/2");
mockRunGhJsonAsync.mockResolvedValue({ url: "https://github.com/o/r/issues/2", number: 2, createdAt: "2026-01-02T00:00:00.000Z" } as never);
const fetchSpy = vi.spyOn(global, "fetch" as never).mockImplementation(() => {
throw new Error("fetch should not run");

View File

@@ -1,4 +1,13 @@
import { describe, expect, it, vi } from "vitest";
vi.mock("node:fs/promises", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs/promises")>();
return {
...actual,
access: vi.fn().mockResolvedValue(undefined),
readFile: vi.fn().mockResolvedValue('{"anthropic":{},"openai":{},"cursor-cli":{}}'),
};
});
import type { Router } from "express";
import { registerModelRoutes } from "../routes/register-model-routes.js";

View File

@@ -53,6 +53,24 @@ const { mockPerformUpdateCheck, mockClearUpdateCheckCache, mockExecSync, mockExe
mockExecFile: vi.fn(),
}));
vi.mock("node:fs/promises", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs/promises")>();
return {
...actual,
access: vi.fn(async (path: actual.PathLike) => {
const value = String(path);
if (value.endsWith("auth.json") || value.endsWith("models.json")) return;
return actual.access(path);
}),
readFile: vi.fn(async (path: actual.PathLike, options?: Parameters<typeof actual.readFile>[1]) => {
const value = String(path);
if (value.endsWith("auth.json")) return '{"anthropic":{},"openai":{},"cursor-cli":{}}';
if (value.endsWith("models.json")) return '{"providers":{"anthropic":{"apiKey":"x"},"openai":{"apiKey":"x"},"cursor-cli":{"apiKey":"x"}}}';
return actual.readFile(path, options as never);
}),
};
});
vi.mock("../update-check.js", async () => {
const actual = await vi.importActual<typeof import("../update-check.js")>("../update-check.js");
return {

View File

@@ -17,6 +17,7 @@ function createMockStore(): TaskStore {
dependencies: [],
steps: [],
currentStep: 0,
reviewState: { source: "reviewer-agent", items: [], addressing: [] },
log: [],
createdAt: now,
updatedAt: now,
@@ -51,16 +52,17 @@ describe("task review routes", () => {
const res = await REQUEST(buildApp(store), "GET", "/api/tasks/FN-001/review");
expect(res.status).toBe(200);
expect(res.body.reviewState.source).toBe("reviewer-agent");
expect(res.body.reviewState.items[0].reviewType).toBe("code");
expect(res.body.mode).toBe("reviewer-agent");
expect(res.body.items[0].reviewState).toBe("REVISE");
});
it("returns exact empty payload/message when no feedback exists", async () => {
const store = createMockStore();
const res = await REQUEST(buildApp(store), "GET", "/api/tasks/FN-001/review");
expect(res.status).toBe(200);
expect(res.body.reviewState.items).toEqual([]);
expect(res.body.emptyMessage).toBe("No reviewer feedback yet — this task has not produced reviewer-agent feedback in direct mode.");
expect(res.body.mode).toBe("reviewer-agent");
expect(res.body.summary).toBeNull();
expect(res.body.items).toEqual([]);
});
it("falls back to task-log summary when reviewer output is incomplete", async () => {
@@ -75,7 +77,7 @@ describe("task review routes", () => {
const res = await REQUEST(buildApp(store), "GET", "/api/tasks/FN-001/review");
expect(res.status).toBe(200);
expect(res.body.reviewState.items[0].summary).toBe("plan review Step 1: APPROVE");
expect(res.body.reviewState.items[0].step).toBe(1);
expect(res.body.items[0].title).toContain("plan review APPROVE");
expect(res.body.items[0].itemId).toContain("step-1");
});
});