diff --git a/packages/dashboard/app/hooks/__tests__/quickChatLastSessionStorage.test.ts b/packages/dashboard/app/hooks/__tests__/quickChatLastSessionStorage.test.ts index e500e6f281..4acd5e3e57 100644 --- a/packages/dashboard/app/hooks/__tests__/quickChatLastSessionStorage.test.ts +++ b/packages/dashboard/app/hooks/__tests__/quickChatLastSessionStorage.test.ts @@ -7,6 +7,7 @@ import { describe("quickChatLastSessionStorage", () => { beforeEach(() => { + vi.unstubAllGlobals(); localStorage.clear(); vi.restoreAllMocks(); }); @@ -38,14 +39,20 @@ describe("quickChatLastSessionStorage", () => { }); it("swallows localStorage failures", () => { - vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => { - throw new Error("quota exceeded"); - }); - vi.spyOn(Storage.prototype, "getItem").mockImplementation(() => { - throw new Error("blocked"); - }); - vi.spyOn(Storage.prototype, "removeItem").mockImplementation(() => { - throw new Error("blocked"); + /* + FNXC:DashboardTesting 2026-06-14-08:46: + This rescue must prove the quick-chat persistence helpers survive an unavailable storage backend; stub the global storage object directly because jsdom's Storage prototype spy can miss the Web Storage instance and create a fake-green assertion. + */ + vi.stubGlobal("localStorage", { + setItem: vi.fn(() => { + throw new Error("quota exceeded"); + }), + getItem: vi.fn(() => { + throw new Error("blocked"); + }), + removeItem: vi.fn(() => { + throw new Error("blocked"); + }), }); expect(() => setPersistedLastQuickChatSessionId("proj-123", "session-123")).not.toThrow(); diff --git a/packages/dashboard/app/hooks/__tests__/useChatRooms.test.ts b/packages/dashboard/app/hooks/__tests__/useChatRooms.test.ts index c39ad95856..9fe07a3238 100644 --- a/packages/dashboard/app/hooks/__tests__/useChatRooms.test.ts +++ b/packages/dashboard/app/hooks/__tests__/useChatRooms.test.ts @@ -245,12 +245,6 @@ describe("useChatRooms", () => { expect(result.current.activeRoom).toBeNull(); }); - // Skipped: desc-fetch pagination test flakes under batch runs (the - // ordering of mock responses doesn't survive concurrent setup). Real - // pagination contract is still covered by useChat hook tests. - // Replaced with stub: original assertions deferred (see git history). Restore once underlying feature/bug work lands. - it("loads newest 100 room messages using desc fetch while preserving ascending transcript", async () => { expect(true).toBe(true); }); - it("sendRoomMessage inserts optimistic temp message and reconciles to server transcript", async () => { const active = room("room-1", "one", "2026-05-09T01:00:00.000Z"); mockFetchChatRooms.mockResolvedValueOnce({ rooms: [active] }); diff --git a/packages/dashboard/app/hooks/__tests__/useTaskDiffStats.test.ts b/packages/dashboard/app/hooks/__tests__/useTaskDiffStats.test.ts index 4ed092e78f..ca2afd5686 100644 --- a/packages/dashboard/app/hooks/__tests__/useTaskDiffStats.test.ts +++ b/packages/dashboard/app/hooks/__tests__/useTaskDiffStats.test.ts @@ -565,79 +565,89 @@ describe("useTaskDiffStats", () => { mockFetchTaskDiff.mockClear(); }); - it("re-fetches when stepVersion changes", async () => { - // Initial fetch - mockFetchTaskDiff.mockResolvedValueOnce({ - files: [], - stats: { filesChanged: 1, additions: 5, deletions: 1 }, - }); + it("re-fetches active worktree stats when stepVersion changes", async () => { + /* + FNXC:DashboardTesting 2026-06-14-08:49: + stepVersion is the cache key only for active worktree-backed columns; done-mode cache invalidation intentionally uses mergeSignature so this regression test must exercise in-progress behavior. + */ + mockFetchTaskDiff + .mockResolvedValueOnce({ + files: [], + stats: { filesChanged: 1, additions: 5, deletions: 1 }, + }) + .mockResolvedValueOnce({ + files: [], + stats: { filesChanged: 3, additions: 10, deletions: 2 }, + }); const { result, rerender } = renderHook( ({ stepVersion }) => useTaskDiffStats( "FN-STEP", - "done", - "abc1234", + "in-progress", undefined, - { stepVersion }, + undefined, + { worktree: "/repo/.worktrees/fn-step", stepVersion }, ), { initialProps: { stepVersion: 1 as number | string } }, ); - await waitFor(() => expect(result.current.loading).toBe(false)); - expect(result.current.stats).toEqual({ filesChanged: 1, additions: 5, deletions: 1 }); + await waitFor(() => expect(result.current.stats).toEqual({ filesChanged: 1, additions: 5, deletions: 1 })); + expect(result.current.loading).toBe(false); + expect(mockFetchTaskDiff).toHaveBeenCalledWith("FN-STEP", "/repo/.worktrees/fn-step", undefined); expect(mockFetchTaskDiff).toHaveBeenCalledTimes(1); - // Change stepVersion - should trigger re-fetch - mockFetchTaskDiff.mockResolvedValueOnce({ - files: [], - stats: { filesChanged: 3, additions: 10, deletions: 2 }, - }); - rerender({ stepVersion: 2 as number | string }); - await waitFor(() => expect(result.current.loading).toBe(false)); - expect(result.current.stats).toEqual({ filesChanged: 3, additions: 10, deletions: 2 }); + await waitFor(() => expect(result.current.stats).toEqual({ filesChanged: 3, additions: 10, deletions: 2 })); + expect(result.current.loading).toBe(false); expect(mockFetchTaskDiff).toHaveBeenCalledTimes(2); }); - it("caches stats separately per stepVersion", async () => { - // Initial fetch with stepVersion 1 - mockFetchTaskDiff.mockResolvedValueOnce({ - files: [], - stats: { filesChanged: 5, additions: 20, deletions: 3 }, - }); + it("caches active worktree stats separately per stepVersion", async () => { + mockFetchTaskDiff + .mockResolvedValueOnce({ + files: [], + stats: { filesChanged: 5, additions: 20, deletions: 3 }, + }) + .mockResolvedValueOnce({ + files: [], + stats: { filesChanged: 10, additions: 50, deletions: 8 }, + }); + + const activeOptions = { worktree: "/repo/.worktrees/fn-step-cache" }; const { result: first } = renderHook(() => - useTaskDiffStats("FN-STEP-CACHE", "done", "abc1234", undefined, { stepVersion: "v1" }), + useTaskDiffStats("FN-STEP-CACHE", "in-progress", undefined, undefined, { + ...activeOptions, + stepVersion: "v1", + }), ); - await waitFor(() => expect(first.current.loading).toBe(false)); - expect(first.current.stats).toEqual({ filesChanged: 5, additions: 20, deletions: 3 }); - - // Same task, different stepVersion - should fetch separately - mockFetchTaskDiff.mockResolvedValueOnce({ - files: [], - stats: { filesChanged: 10, additions: 50, deletions: 8 }, - }); + await waitFor(() => expect(first.current.stats).toEqual({ filesChanged: 5, additions: 20, deletions: 3 })); const { result: second } = renderHook(() => - useTaskDiffStats("FN-STEP-CACHE", "done", "abc1234", undefined, { stepVersion: "v2" }), + useTaskDiffStats("FN-STEP-CACHE", "in-progress", undefined, undefined, { + ...activeOptions, + stepVersion: "v2", + }), ); - await waitFor(() => expect(second.current.loading).toBe(false)); - expect(second.current.stats).toEqual({ filesChanged: 10, additions: 50, deletions: 8 }); - - // Both should have been fetched + await waitFor(() => expect(second.current.stats).toEqual({ filesChanged: 10, additions: 50, deletions: 8 })); expect(mockFetchTaskDiff).toHaveBeenCalledTimes(2); - // Cache should have both entries mockFetchTaskDiff.mockClear(); const { result: cached1 } = renderHook(() => - useTaskDiffStats("FN-STEP-CACHE", "done", "abc1234", undefined, { stepVersion: "v1" }), + useTaskDiffStats("FN-STEP-CACHE", "in-progress", undefined, undefined, { + ...activeOptions, + stepVersion: "v1", + }), ); const { result: cached2 } = renderHook(() => - useTaskDiffStats("FN-STEP-CACHE", "done", "abc1234", undefined, { stepVersion: "v2" }), + useTaskDiffStats("FN-STEP-CACHE", "in-progress", undefined, undefined, { + ...activeOptions, + stepVersion: "v2", + }), ); expect(cached1.current.stats).toEqual({ filesChanged: 5, additions: 20, deletions: 3 }); diff --git a/scripts/lib/dashboard-curated-skiplist.json b/scripts/lib/dashboard-curated-skiplist.json index fa441e22e2..3a2f36163b 100644 --- a/scripts/lib/dashboard-curated-skiplist.json +++ b/scripts/lib/dashboard-curated-skiplist.json @@ -5,18 +5,6 @@ "file": "packages/dashboard/app/__tests__/build-output.test.ts", "reason": "asserts the built bundle; runs standalone via `pnpm --filter @fusion/dashboard test:build` (needs a prior vite build), not in the unit gate" }, - { - "file": "packages/dashboard/app/hooks/__tests__/quickChatLastSessionStorage.test.ts", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6442)" - }, - { - "file": "packages/dashboard/app/hooks/__tests__/useChatRooms.test.ts", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6442)" - }, - { - "file": "packages/dashboard/app/hooks/__tests__/useTaskDiffStats.test.ts", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6442)" - }, { "file": "packages/dashboard/src/__tests__/evals-routes.test.ts", "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6444)"