FN-6442: rescue dashboard hook tests from skiplist
Rescues three dashboard hook test files by replacing brittle coverage with stable assertions and removing their skiplist entries. - Stub global localStorage failures directly for quick chat persistence coverage. - Remove the placeholder skipped useChatRooms pagination test. - Rework useTaskDiffStats stepVersion tests around active worktree cache behavior. - Drop the rescued dashboard hook tests from the curated skiplist. Files changed: .../__tests__/quickChatLastSessionStorage.test.ts | 23 ++++-- .../app/hooks/__tests__/useChatRooms.test.ts | 6 -- .../app/hooks/__tests__/useTaskDiffStats.test.ts | 94 ++++++++++++---------- scripts/lib/dashboard-curated-skiplist.json | 12 --- 4 files changed, 67 insertions(+), 68 deletions(-) Fusion-Task-Id: FN-6442 Fusion-Task-Lineage: f2fe4791-1216-420d-9734-3938287779da
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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] });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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)"
|
||||
|
||||
Reference in New Issue
Block a user