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:
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user