import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { subscribeProgress, type ProgressState } from "@/lib/progress"; class MockEventSource { url: string; onmessage: ((m: { data: string }) => void) | null = null; closed = false; static last: MockEventSource; constructor(url: string) { this.url = url; MockEventSource.last = this; } emit(data: object) { this.onmessage?.({ data: JSON.stringify(data) }); } close() { this.closed = true; } } beforeEach(() => { vi.stubGlobal("EventSource", MockEventSource); }); afterEach(() => { vi.unstubAllGlobals(); }); describe("subscribeProgress", () => { it("emits running on start", () => { const states: ProgressState[] = []; subscribeProgress((s) => states.push(s)); MockEventSource.last.emit({ type: "start", elapsed_s: 0, kind: "dialog", total_turns: 3, turn: 0, }); expect(states[0]).toMatchObject({ phase: "running", kind: "dialog", total: 3 }); }); it("updates turn on turn_complete", () => { const states: ProgressState[] = []; subscribeProgress((s) => states.push(s)); MockEventSource.last.emit({ type: "start", elapsed_s: 0, kind: "dialog", total_turns: 3, turn: 0, }); MockEventSource.last.emit({ type: "turn_complete", elapsed_s: 1.2, kind: "dialog", total_turns: 3, turn: 2, }); const last = states[states.length - 1]; expect(last).toMatchObject({ phase: "running", turn: 2, total: 3 }); }); it("transitions to done then idle", async () => { vi.useFakeTimers(); const states: ProgressState[] = []; subscribeProgress((s) => states.push(s)); MockEventSource.last.emit({ type: "done", elapsed_s: 4.5 }); expect(states[states.length - 1]).toMatchObject({ phase: "done", elapsedS: 4.5 }); vi.advanceTimersByTime(1100); expect(states[states.length - 1]).toMatchObject({ phase: "idle" }); vi.useRealTimers(); }); it("emits error", () => { const states: ProgressState[] = []; subscribeProgress((s) => states.push(s)); MockEventSource.last.emit({ type: "error", elapsed_s: 2, message: "boom", }); expect(states[states.length - 1]).toMatchObject({ phase: "error", message: "boom" }); }); it("close() shuts down EventSource", () => { const close = subscribeProgress(() => {}); close(); expect(MockEventSource.last.closed).toBe(true); }); });