// The live check-in follower: without EventSource it polls /updates every 3 seconds, // passes each event on once, and stops when closed. import { afterEach, describe, expect, it, vi } from "vitest"; import { followCheckIn, POLL_MS } from "../src/api/events"; import type { CheckInEvent } from "../src/api/types"; const status = (id: number): CheckInEvent => ({ id, type: "status", at: `2026-10-30T10:00:0${id}Z`, status: "ringing", detail: null, later: null }); afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); }); describe("following a check-in (YCI-23)", () => { it("polls every 3 seconds from the last event id, without repeating events", async () => { vi.useFakeTimers(); const seen: string[] = []; const answers = [ { events: [status(4), status(5)], lastEventId: 5 }, { events: [status(5), status(6)], lastEventId: 6 }, { events: [], lastEventId: 6 }, ]; const fetchMock = vi.fn(async (url: string, init: RequestInit) => { seen.push(`${url} ${(init.headers as Record)["X-Context-Background"] ?? ""}`); return new Response(JSON.stringify(answers.shift() ?? { events: [], lastEventId: 6 }), { status: 200 }); }); vi.stubGlobal("fetch", fetchMock); const got: number[] = []; const modes: string[] = []; const sub = followCheckIn("c1", 3, (events) => got.push(...events.map((e) => e.id)), (m) => modes.push(m), { forcePolling: true }); await vi.advanceTimersByTimeAsync(10); expect(got).toEqual([4, 5]); await vi.advanceTimersByTimeAsync(POLL_MS); expect(got).toEqual([4, 5, 6]); expect(seen[0]).toBe("/api/clinician/check-ins/c1/updates?after=3 1"); expect(seen[1]).toContain("after=5"); sub.close(); const calls = fetchMock.mock.calls.length; await vi.advanceTimersByTimeAsync(POLL_MS * 3); expect(fetchMock.mock.calls.length).toBe(calls); expect(modes).toEqual(["polling", "closed"]); }); it("keeps polling after a failed request", async () => { vi.useFakeTimers(); let fail = true; vi.stubGlobal( "fetch", vi.fn(async () => { if (fail) { fail = false; throw new TypeError("offline"); } return new Response(JSON.stringify({ events: [status(2)], lastEventId: 2 }), { status: 200 }); }), ); const got: number[] = []; const sub = followCheckIn("c1", 1, (events) => got.push(...events.map((e) => e.id)), () => undefined, { forcePolling: true }); await vi.advanceTimersByTimeAsync(10); expect(got).toEqual([]); await vi.advanceTimersByTimeAsync(POLL_MS); expect(got).toEqual([2]); sub.close(); }); });