test(KB-208): fix EventSource mock cleanup in useTasks tests
- Fix EventSource mock cleanup to prevent test pollution between cases - Improve test isolation for SSE event handling - Refactor test setup and teardown for better reliability
This commit is contained in:
@@ -1,3 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* EventSource Mock Cleanup Requirements:
|
||||||
|
*
|
||||||
|
* This test file uses a MockEventSource class that tracks all instances in a static
|
||||||
|
* `instances` array. To prevent test isolation issues, we must ensure:
|
||||||
|
*
|
||||||
|
* 1. `MockEventSource.instances` is reset to empty before each test
|
||||||
|
* 2. Any lingering EventSource instances are closed and removed after each test
|
||||||
|
* 3. Fake timers are restored to real timers after each test (in case a test failed
|
||||||
|
* before it could restore them)
|
||||||
|
* 4. The reconnectTimer from useTasks hook (3000ms) is cleared by closing all
|
||||||
|
* EventSources in afterEach
|
||||||
|
*
|
||||||
|
* Without proper cleanup, fake timers from one test can leak to subsequent tests,
|
||||||
|
* causing `waitFor()` calls to hang indefinitely.
|
||||||
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
import { renderHook, act, waitFor } from "@testing-library/react";
|
import { renderHook, act, waitFor } from "@testing-library/react";
|
||||||
import { useTasks } from "../useTasks";
|
import { useTasks } from "../useTasks";
|
||||||
@@ -55,13 +72,27 @@ class MockEventSource {
|
|||||||
const originalEventSource = globalThis.EventSource;
|
const originalEventSource = globalThis.EventSource;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
// Reset all mock state
|
||||||
MockEventSource.instances = [];
|
MockEventSource.instances = [];
|
||||||
(globalThis as any).EventSource = MockEventSource;
|
(globalThis as any).EventSource = MockEventSource;
|
||||||
mockFetchTasks.mockReset().mockResolvedValue([]);
|
mockFetchTasks.mockReset().mockResolvedValue([]);
|
||||||
|
|
||||||
|
// Ensure we start with real timers for every test
|
||||||
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
// Close all lingering EventSource instances to clear reconnect timers
|
||||||
|
for (const instance of MockEventSource.instances) {
|
||||||
|
instance.close();
|
||||||
|
}
|
||||||
|
MockEventSource.instances = [];
|
||||||
|
|
||||||
|
// Restore original EventSource
|
||||||
(globalThis as any).EventSource = originalEventSource;
|
(globalThis as any).EventSource = originalEventSource;
|
||||||
|
|
||||||
|
// Safety: ensure real timers are restored even if a test failed
|
||||||
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
function createMockTask(overrides: Partial<Task> = {}): Task {
|
function createMockTask(overrides: Partial<Task> = {}): Task {
|
||||||
@@ -195,9 +226,10 @@ describe("useTasks", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("closes the broken SSE connection and reconnects after an error", async () => {
|
it("closes the broken SSE connection and reconnects after an error", async () => {
|
||||||
vi.useFakeTimers();
|
// Note: We use real timers here because React Testing Library's waitFor
|
||||||
try {
|
// doesn't play well with fake timers - it can hang waiting for promises.
|
||||||
renderHook(() => useTasks());
|
// The test relies on the actual 3-second reconnect delay being processed.
|
||||||
|
const { unmount } = renderHook(() => useTasks());
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(MockEventSource.instances).toHaveLength(1);
|
expect(MockEventSource.instances).toHaveLength(1);
|
||||||
@@ -211,16 +243,15 @@ describe("useTasks", () => {
|
|||||||
|
|
||||||
expect(first.close).toHaveBeenCalledTimes(1);
|
expect(first.close).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
act(() => {
|
// Wait for reconnect timer (3s) to create a second EventSource
|
||||||
vi.advanceTimersByTime(3000);
|
await waitFor(
|
||||||
});
|
() => {
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(MockEventSource.instances).toHaveLength(2);
|
expect(MockEventSource.instances).toHaveLength(2);
|
||||||
});
|
},
|
||||||
} finally {
|
{ timeout: 5000 }
|
||||||
vi.useRealTimers();
|
);
|
||||||
}
|
|
||||||
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("SSE event: task:updated", () => {
|
describe("SSE event: task:updated", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user