test(dashboard): fix 30 pre-existing pnpm-test failures on main
These deterministic failures lived in the non-blocking full-suite lane (App.test.tsx + three API-backfill route tests + one modal test), so the merge gate never surfaced them. All are stale tests trailing intentional product changes — fixes are test-only; no product code and no appeasement (no widened timeouts, no skips, no weakened assertions). App.test.tsx (26): sidebar-destination tests (view switching, chat unread, GitHub import, board branch filters) never opted into leftSidebarNav:true, so with the shared defaultSettings keeping it false (to preserve legacy header-nav tests) the sidebar never rendered. Enable leftSidebarNav per sidebar describe/test. The backend-unreachable recovery test asserted a setup wizard that intentionally no longer auto-opens on zero projects (useViewState FNXC:Onboarding 2026-06-22-05:06) with modelOnboardingComplete true — assert recovery to the dashboard shell instead. board-workflows-route + promote-route: the workflowColumns flag graduated to always-on (isWorkflowColumnsEnabled returns true; stale persisted false is treated as enabled), retiring the flag-OFF 400/empty-shape branches. Update both "flag OFF" tests to assert the graduation invariant. register-command-center-routes.auth: the /command-center/tokens handler now reads modelPricingOverrides via getGlobalSettingsStore(); MockStore lacked it, yielding a 500. Add the stub. PlanningModeModal.initial: the shared .spin loader keyframe was renamed spin -> fusion-spinner-spin for collision-proofing; update the CSS regex. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1010,7 +1010,7 @@ describe("App backend-unreachable first-run flow", () => {
|
||||
expect(screen.queryByText("Welcome to Fusion")).toBeNull();
|
||||
});
|
||||
|
||||
it("retries project loading and resumes setup wizard flow after connectivity recovers", async () => {
|
||||
it("retries project loading and recovers from the backend error page after connectivity recovers", async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
try {
|
||||
@@ -1043,7 +1043,17 @@ describe("App backend-unreachable first-run flow", () => {
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
});
|
||||
|
||||
expect(screen.getByText("Set Up AI")).toBeTruthy();
|
||||
/*
|
||||
FNXC:Onboarding 2026-06-25-11:50: After connectivity recovers the backend-error
|
||||
page is replaced by the normal dashboard shell. The setup wizard no longer
|
||||
auto-resumes here: zero projects no longer force-open the project wizard
|
||||
(useViewState FNXC:Onboarding 2026-06-22-05:06) and modelOnboardingComplete is
|
||||
true in this fixture, so the AI-setup wizard ("Set Up AI") does not auto-open.
|
||||
Assert recovery (error page gone, shell rendered) instead of the retired wizard.
|
||||
*/
|
||||
expect(screen.queryByText("Can't reach the Fusion backend")).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Retry Connection" })).toBeNull();
|
||||
expect(screen.getByTestId("dashboard-project-shell")).toBeTruthy();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
@@ -1276,6 +1286,14 @@ describe("App approval notification banner", () => {
|
||||
});
|
||||
|
||||
describe("App chat unread response indicator", () => {
|
||||
/* FNXC:Navigation 2026-06-25-11:00: Sidebar-destination tests must opt into leftSidebarNav:true; the shared defaultSettings keeps it false so legacy header-nav tests stay valid. */
|
||||
beforeEach(() => {
|
||||
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...defaultSettings,
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, leftSidebarNav: true },
|
||||
});
|
||||
});
|
||||
|
||||
const getChatEvents = async () => {
|
||||
render(<App />);
|
||||
|
||||
@@ -2020,6 +2038,14 @@ describe("OnboardingResumeCard", () => {
|
||||
describe("App view switching", () => {
|
||||
// FNXC:Navigation 2026-06-22-09:30: Research/Evals/Insights/Memory are now left-sidebar
|
||||
// destinations (sidebar-nav-*), not header More-views overflow items, on desktop.
|
||||
/* FNXC:Navigation 2026-06-25-11:00: Sidebar-destination tests must opt into leftSidebarNav:true; the shared defaultSettings keeps it false so legacy header-nav tests stay valid. Tests that set their own fetchSettings mock add leftSidebarNav:true to their spread. */
|
||||
beforeEach(() => {
|
||||
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...defaultSettings,
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, leftSidebarNav: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("opens research view from the sidebar and persists view selection", async () => {
|
||||
localStorage.setItem("kb-dashboard-view-mode", "project");
|
||||
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
@@ -2027,6 +2053,7 @@ describe("App view switching", () => {
|
||||
experimentalFeatures: {
|
||||
...defaultSettings.experimentalFeatures,
|
||||
researchView: true,
|
||||
leftSidebarNav: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2327,7 +2354,7 @@ describe("App view switching", () => {
|
||||
mockUseViewportMode.mockReturnValue("desktop");
|
||||
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
...defaultSettings,
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, roadmap: true },
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, roadmap: true, leftSidebarNav: true },
|
||||
});
|
||||
(fetchPluginDashboardViews as ReturnType<typeof vi.fn>).mockResolvedValueOnce([
|
||||
{
|
||||
@@ -2494,7 +2521,7 @@ describe("App view switching", () => {
|
||||
// Override the default mock to exclude agentsView
|
||||
vi.mocked(fetchSettings).mockResolvedValue({
|
||||
...defaultSettings,
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, insights: true, skillsView: true }, // no agentsView
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, insights: true, skillsView: true, leftSidebarNav: true }, // no agentsView
|
||||
});
|
||||
|
||||
render(<App />);
|
||||
@@ -2655,7 +2682,7 @@ describe("App view switching", () => {
|
||||
it("keeps insights view button visible after graduation from experimental flags", async () => {
|
||||
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
...defaultSettings,
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, insights: false },
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, insights: false, leftSidebarNav: true },
|
||||
});
|
||||
|
||||
render(<App />);
|
||||
@@ -2701,7 +2728,7 @@ describe("App view switching", () => {
|
||||
it("keeps memory view button visible after graduation from experimental flags", async () => {
|
||||
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
...defaultSettings,
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, memoryView: false, insights: true },
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, memoryView: false, insights: true, leftSidebarNav: true },
|
||||
});
|
||||
|
||||
render(<App />);
|
||||
@@ -2773,6 +2800,14 @@ describe("App view switching", () => {
|
||||
});
|
||||
|
||||
describe("App GitHub import", () => {
|
||||
/* FNXC:Navigation 2026-06-25-11:00: Sidebar-destination tests must opt into leftSidebarNav:true; the shared defaultSettings keeps it false so legacy header-nav tests stay valid. */
|
||||
beforeEach(() => {
|
||||
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...defaultSettings,
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, leftSidebarNav: true },
|
||||
});
|
||||
});
|
||||
|
||||
// FNXC:Navigation 2026-06-22-09:30: GitHub import is now the left-sidebar "Import Tasks"
|
||||
// destination rendering the GitHubImportModal embedded in main content (presentation="embedded"),
|
||||
// not a header-button modal overlay. Navigation in/out goes through the sidebar; embedded mode
|
||||
@@ -4117,7 +4152,11 @@ describe("App board branch filters", () => {
|
||||
it("composes with search and does not affect list view tasks", async () => {
|
||||
localStorage.setItem("kb-dashboard-view-mode", "project");
|
||||
localStorage.setItem(taskViewStorageKey(), "board");
|
||||
vi.mocked(fetchSettings).mockResolvedValue({ ...defaultSettings });
|
||||
/* FNXC:Navigation 2026-06-25-11:00: This test navigates via sidebar-nav-list, so it must load leftSidebarNav:true; the shared defaultSettings keeps it false for legacy header-nav tests. */
|
||||
vi.mocked(fetchSettings).mockResolvedValue({
|
||||
...defaultSettings,
|
||||
experimentalFeatures: { ...defaultSettings.experimentalFeatures, leftSidebarNav: true },
|
||||
});
|
||||
mockUseTasks.mockImplementation(() => ({
|
||||
tasks: [
|
||||
makeTask("FN-4", "Alpha Search", "feature/a", "main"),
|
||||
|
||||
@@ -671,7 +671,10 @@ describe("PlanningModeModal", () => {
|
||||
|
||||
const { loadAllAppCss } = await import("../../test/cssFixture");
|
||||
const css = loadAllAppCss();
|
||||
expect(css).toMatch(/\.spin\s*\{[^}]*animation:\s*spin\s+1s\s+linear\s+infinite;/);
|
||||
// FNXC:PlanningModeModal 2026-06-25-11:30: The shared `.spin` loader keyframe was
|
||||
// renamed `spin` → `fusion-spinner-spin` for collision-proofing (see styles.css),
|
||||
// so assert the current keyframe name instead of the retired generic `spin`.
|
||||
expect(css).toMatch(/\.spin\s*\{[^}]*animation:\s*fusion-spinner-spin\s+1s\s+linear\s+infinite;/);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Hide thinking" })).toBeDefined();
|
||||
expect(document.querySelector(".planning-thinking-output")?.textContent).toContain("Analyzing requirements...");
|
||||
|
||||
@@ -53,6 +53,16 @@ class MockStore extends EventEmitter {
|
||||
};
|
||||
}
|
||||
|
||||
// FNXC:CommandCenter 2026-06-25-11:35: The /command-center/tokens handler now reads
|
||||
// settings.modelPricingOverrides via getGlobalSettingsStore().getSettings() for USD
|
||||
// cost derivation. Stub it so the auth check reaches a 200 instead of a 500 from a
|
||||
// missing store accessor.
|
||||
getGlobalSettingsStore() {
|
||||
return {
|
||||
getSettings: async () => ({ modelPricingOverrides: {} }),
|
||||
};
|
||||
}
|
||||
|
||||
async listTasks(): Promise<Task[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
// route registration, the flag-gated early-return shape, and the deduped
|
||||
// flag-ON payload were untested. This exercises the route end-to-end against a
|
||||
// REAL TaskStore via createApiRoutes:
|
||||
// - flag OFF → { flagEnabled: false } (the legacy single-lane shape)
|
||||
// - workflowColumns graduated → a stale persisted `false` is treated as enabled,
|
||||
// so the route returns the full payload (the legacy empty single-lane shape is retired)
|
||||
// - flag ON, mixed default + custom selections → correct taskWorkflowIds and a
|
||||
// DEDUPED workflows array (two cards on the same default lane collapse to one
|
||||
// workflow entry).
|
||||
@@ -84,17 +85,20 @@ describe("GET /tasks/board-workflows", () => {
|
||||
|
||||
const get = (path: string) => REQUEST(app, "GET", path);
|
||||
|
||||
it("flag OFF → { flagEnabled: false } legacy shape", async () => {
|
||||
// Even with tasks on the board, an explicitly-disabled flag returns the empty
|
||||
// single-lane shape regardless of the project's default feature policy.
|
||||
// FNXC:WorkflowColumns 2026-06-25-11:40: workflowColumns graduated from the
|
||||
// experimental flag (isWorkflowColumnsEnabled always returns true; stale persisted
|
||||
// `false` must resolve as enabled). The retired flag-OFF empty single-lane shape no
|
||||
// longer exists; assert the graduation invariant — a persisted `false` still yields
|
||||
// the full enabled payload with the card mapped to the default lane.
|
||||
it("persisted workflowColumns:false is treated as enabled (graduated)", async () => {
|
||||
await store.updateGlobalSettings({ experimentalFeatures: { workflowColumns: false } });
|
||||
await store.createTask({ description: "card" });
|
||||
const card = await store.createTask({ description: "card" });
|
||||
const res = await get("/api/tasks/board-workflows");
|
||||
expect(res.status).toBe(200);
|
||||
const body = res.body as { flagEnabled: boolean; workflows: unknown[]; taskWorkflowIds: Record<string, string> };
|
||||
expect(body.flagEnabled).toBe(false);
|
||||
expect(body.workflows).toEqual([]);
|
||||
expect(body.taskWorkflowIds).toEqual({});
|
||||
expect(body.flagEnabled).toBe(true);
|
||||
expect(body.taskWorkflowIds[card.id]).toBe(DEFAULT_LANE);
|
||||
expect(body.workflows.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("flag ON, mixed default + custom → correct taskWorkflowIds and deduped workflows", async () => {
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
//
|
||||
// The promote endpoint has four error branches plus a success path, none of
|
||||
// which were exercised at the HTTP layer:
|
||||
// - flag OFF → 400 (workflow columns not enabled)
|
||||
// - workflowColumns graduated → a stale persisted `false` is treated as
|
||||
// enabled, so promote proceeds (no legacy 400)
|
||||
// - promoteHeldTask success → 200 (returns the promoted task)
|
||||
// - capacity-exhausted-or-no-slot → 409 code:"capacity-exhausted"
|
||||
// - other engine rejection → 409 code:"guard-rejected"
|
||||
@@ -60,11 +61,17 @@ describe("POST /tasks/:id/promote", () => {
|
||||
promoteHeldTask.mockReset();
|
||||
});
|
||||
|
||||
it("flag OFF → 400 and never calls the engine", async () => {
|
||||
// FNXC:WorkflowColumns 2026-06-25-11:40: workflowColumns graduated from the
|
||||
// experimental flag (isWorkflowColumnsEnabled always returns true and stale
|
||||
// persisted `false` must be treated as enabled). The retired flag-OFF → 400
|
||||
// branch no longer exists; assert the graduation invariant instead — a persisted
|
||||
// `false` still lets promote proceed and reach the engine.
|
||||
it("persisted workflowColumns:false is treated as enabled (graduated) → proceeds to engine", async () => {
|
||||
const { app } = buildApp({ flagEnabled: false });
|
||||
promoteHeldTask.mockResolvedValue({ released: true, toColumn: "in-progress" });
|
||||
const res = await promote(app);
|
||||
expect(res.status).toBe(400);
|
||||
expect(promoteHeldTask).not.toHaveBeenCalled();
|
||||
expect(res.status).toBe(200);
|
||||
expect(promoteHeldTask).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("success → 200 and returns the promoted task", async () => {
|
||||
|
||||
Reference in New Issue
Block a user