// 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<string, string>)["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();
});
});