Files
fusion/packages/dashboard/app/utils/__tests__/boardWorkflowsCache.test.ts
gsxdsm c808177979 FN-6776: prevent workflow board legacy flash
Prevent workflow-enabled board surfaces from flashing the legacy layout during first paint.

- Hydrate Board and ListView workflow metadata from a per-project session cache before async revalidation.
- Gate legacy board/list rendering behind settings and workflow-lane metadata readiness, with neutral skeleton states for cold or empty workflow loads.
- Add regression coverage for no-legacy-flash behavior and cache validation, plus docs and changeset notes.

Files changed:
 .changeset/fn-6776-board-flash.md                  |   5 +
 docs/dashboard-guide.md                            |   1 +
 packages/dashboard/app/App.tsx                     |   4 +
 packages/dashboard/app/components/Board.css        |  69 +++++
 packages/dashboard/app/components/Board.tsx        |  53 +++-
 packages/dashboard/app/components/ListView.css     |  53 ++++
 packages/dashboard/app/components/ListView.tsx     |  52 +++-
 .../__tests__/board-no-legacy-flash.test.tsx       | 326 +++++++++++++++++++++
 .../utils/__tests__/boardWorkflowsCache.test.ts    |  71 +++++
 .../dashboard/app/utils/boardWorkflowsCache.ts     |  49 ++++
 10 files changed, 676 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-6776

Fusion-Task-Lineage: caa8e53f-9b6d-49a6-94ca-b694550b43ef
2026-06-20 15:42:16 -07:00

72 lines
2.2 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import type { BoardWorkflowsPayload } from "../../api";
import { readBoardWorkflowsCache, writeBoardWorkflowsCache } from "../boardWorkflowsCache";
const payload: BoardWorkflowsPayload = {
flagEnabled: true,
defaultWorkflowId: "builtin:coding",
workflows: [
{
id: "builtin:coding",
name: "Coding",
columns: [{ id: "todo", name: "Todo", flags: {} }],
},
],
taskWorkflowIds: { "FN-1": "builtin:coding" },
};
describe("boardWorkflowsCache", () => {
afterEach(() => {
if (typeof window !== "undefined") {
window.sessionStorage.clear();
}
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
it("round-trips board-workflows payloads per project", () => {
writeBoardWorkflowsCache("project-a", payload);
expect(readBoardWorkflowsCache("project-a")).toEqual(payload);
});
it("keeps project cache keys isolated", () => {
writeBoardWorkflowsCache("project-a", payload);
expect(readBoardWorkflowsCache("project-b")).toBeNull();
});
it("returns null for missing, corrupt, or invalid entries", () => {
expect(readBoardWorkflowsCache("missing")).toBeNull();
window.sessionStorage.setItem("fusion:board-workflows:corrupt", "{");
expect(readBoardWorkflowsCache("corrupt")).toBeNull();
window.sessionStorage.setItem("fusion:board-workflows:invalid", JSON.stringify({ flagEnabled: true, workflows: {} }));
expect(readBoardWorkflowsCache("invalid")).toBeNull();
});
it("swallows sessionStorage write failures", () => {
vi.spyOn(window.sessionStorage.__proto__, "setItem").mockImplementation(() => {
throw new Error("quota");
});
expect(() => writeBoardWorkflowsCache("project-a", payload)).not.toThrow();
});
it("swallows sessionStorage read failures", () => {
vi.spyOn(window.sessionStorage.__proto__, "getItem").mockImplementation(() => {
throw new Error("private mode");
});
expect(readBoardWorkflowsCache("project-a")).toBeNull();
});
it("returns null without window for SSR callers", () => {
vi.stubGlobal("window", undefined);
expect(readBoardWorkflowsCache("project-a")).toBeNull();
expect(() => writeBoardWorkflowsCache("project-a", payload)).not.toThrow();
});
});