Files
fusion/packages/dashboard/app/utils/__tests__/boardWorkflowSelection.test.ts
gsxdsm 082dd55967 FN-7280: persist all-workflows board view
Persist the Board aggregate workflow view so refreshes restore the operator's last board selection.

- Store the All workflows sentinel in project-scoped board view preferences while filtering it out for real workflow consumers.
- Restore all-workflows selection on remount/project changes and clear it when workflow mode is unavailable.
- Cover refresh persistence, project scoping, hooks, utilities, and cross-surface behavior with tests.
- Document the refresh-persistent All workflows behavior and add a patch changeset.

Files changed:
 .changeset/fn-7280-all-workflows-board-view.md     |  7 +++
 docs/dashboard-guide.md                            |  7 +--
 .../workflow-selection-cross-surface.test.tsx      | 15 ++++++
 packages/dashboard/app/components/Board.tsx        | 31 +++++++++---
 .../app/components/__tests__/Board.test.tsx        | 58 ++++++++++++++++++++--
 .../app/hooks/__tests__/useBoardWorkflows.test.ts  | 11 ++++
 .../utils/__tests__/boardWorkflowSelection.test.ts | 13 +++++
 .../dashboard/app/utils/boardWorkflowSelection.ts  | 13 ++++-
 8 files changed, 137 insertions(+), 18 deletions(-)

Fusion-Task-Id: FN-7280

Fusion-Task-Lineage: 12d83b05-5b2b-4435-9383-30b35c07c087

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-06-30 09:04:03 -07:00

149 lines
5.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
ALL_WORKFLOWS_BOARD_VIEW_ID,
BOARD_WORKFLOW_SELECTION_STORAGE_KEY,
readBoardWorkflowSelection,
readBoardWorkflowViewSelection,
removeBoardWorkflowSelection,
writeBoardWorkflowSelection,
} from "../boardWorkflowSelection";
import { scopedKey } from "../projectStorage";
const projectKey = (projectId: string) => scopedKey(BOARD_WORKFLOW_SELECTION_STORAGE_KEY, projectId);
describe("boardWorkflowSelection", () => {
beforeEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
window.localStorage.clear();
});
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
window.localStorage.clear();
});
it("persists selected workflow ids per project", () => {
writeBoardWorkflowSelection("project-a", "builtin:coding");
writeBoardWorkflowSelection("project-b", "WF-123");
expect(window.localStorage.getItem(projectKey("project-a"))).toBe("builtin:coding");
expect(window.localStorage.getItem(projectKey("project-b"))).toBe("WF-123");
expect(readBoardWorkflowSelection("project-a")).toBe("builtin:coding");
expect(readBoardWorkflowSelection("project-b")).toBe("WF-123");
expect(readBoardWorkflowViewSelection("project-a")).toBe("builtin:coding");
});
it("persists the Board-only all-workflows view without exposing it as a real workflow id", () => {
writeBoardWorkflowSelection("project-a", ALL_WORKFLOWS_BOARD_VIEW_ID);
writeBoardWorkflowSelection("project-b", "WF-123");
expect(window.localStorage.getItem(projectKey("project-a"))).toBe(ALL_WORKFLOWS_BOARD_VIEW_ID);
expect(readBoardWorkflowViewSelection("project-a")).toBe(ALL_WORKFLOWS_BOARD_VIEW_ID);
expect(readBoardWorkflowSelection("project-a")).toBeNull();
expect(readBoardWorkflowSelection("project-b")).toBe("WF-123");
});
it("uses the unscoped default when no project is selected", () => {
writeBoardWorkflowSelection(undefined, "default-workflow");
writeBoardWorkflowSelection("project-a", "project-workflow");
expect(window.localStorage.getItem(BOARD_WORKFLOW_SELECTION_STORAGE_KEY)).toBe("default-workflow");
expect(readBoardWorkflowSelection()).toBe("default-workflow");
expect(readBoardWorkflowSelection("project-a")).toBe("project-workflow");
});
it("ignores malformed or empty stored values", () => {
window.localStorage.setItem(projectKey("empty"), " ");
window.localStorage.setItem(projectKey("object"), JSON.stringify({ id: "builtin:coding" }));
window.localStorage.setItem(projectKey("array"), JSON.stringify(["builtin:coding"]));
window.localStorage.setItem(projectKey("control"), "workflow\u0000id");
expect(readBoardWorkflowSelection("empty")).toBeNull();
expect(readBoardWorkflowSelection("object")).toBeNull();
expect(readBoardWorkflowSelection("array")).toBeNull();
expect(readBoardWorkflowSelection("control")).toBeNull();
});
it("trims valid workflow ids before storing and reading", () => {
writeBoardWorkflowSelection("project-a", " builtin:coding ");
expect(window.localStorage.getItem(projectKey("project-a"))).toBe("builtin:coding");
expect(readBoardWorkflowSelection("project-a")).toBe("builtin:coding");
});
it("removes the stored selection when writing an empty or malformed value", () => {
writeBoardWorkflowSelection("project-a", "builtin:coding");
writeBoardWorkflowSelection("project-a", " ");
expect(readBoardWorkflowSelection("project-a")).toBeNull();
writeBoardWorkflowSelection("project-a", "builtin:coding");
writeBoardWorkflowSelection("project-a", "{\"id\":\"builtin:coding\"}");
expect(readBoardWorkflowSelection("project-a")).toBeNull();
writeBoardWorkflowSelection("project-a", "builtin:coding");
writeBoardWorkflowSelection("project-a", "workflow\u0000id");
expect(readBoardWorkflowSelection("project-a")).toBeNull();
});
it("removes stored selections for the requested project only", () => {
writeBoardWorkflowSelection("project-a", "builtin:coding");
writeBoardWorkflowSelection("project-b", "WF-123");
removeBoardWorkflowSelection("project-a");
expect(readBoardWorkflowSelection("project-a")).toBeNull();
expect(readBoardWorkflowSelection("project-b")).toBe("WF-123");
});
it("returns null and no-ops when storage APIs are unavailable", () => {
vi.stubGlobal("window", { localStorage: {} });
expect(readBoardWorkflowSelection("project-a")).toBeNull();
expect(() => writeBoardWorkflowSelection("project-a", "builtin:coding")).not.toThrow();
expect(() => removeBoardWorkflowSelection("project-a")).not.toThrow();
});
it("returns null and no-ops without a browser window", () => {
vi.stubGlobal("window", undefined);
expect(readBoardWorkflowSelection("project-a")).toBeNull();
expect(() => writeBoardWorkflowSelection("project-a", "builtin:coding")).not.toThrow();
expect(() => removeBoardWorkflowSelection("project-a")).not.toThrow();
});
it("swallows localStorage read, write, and remove failures", () => {
vi.stubGlobal("window", {
localStorage: {
getItem: () => {
throw new Error("private mode read");
},
},
});
expect(readBoardWorkflowSelection("project-a")).toBeNull();
vi.unstubAllGlobals();
vi.stubGlobal("window", {
localStorage: {
setItem: () => {
throw new Error("quota");
},
removeItem: () => undefined,
},
});
expect(() => writeBoardWorkflowSelection("project-a", "builtin:coding")).not.toThrow();
vi.unstubAllGlobals();
vi.stubGlobal("window", {
localStorage: {
removeItem: () => {
throw new Error("private mode remove");
},
},
});
expect(() => removeBoardWorkflowSelection("project-a")).not.toThrow();
});
});