| 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); |
| }); |
| }); |
|
|