返回 CodeWhale
connection-state.test.ts
根目录 / web / lib / connection-state.test.ts
1 import { describe, expect, it } from "vitest";
2 import {
3 INITIAL_CONNECTION_STATE,
4 RETRY_BASE_MS,
5 RETRY_MAX_MS,
6 backoffDelayMs,
7 nextConnectionState,
8 shouldProbe,
9 type ConnectionState,
10 } from "./connection-state";
11
12 function run(events: Parameters<typeof nextConnectionState>[1][]): ConnectionState {
13 return events.reduce(nextConnectionState, INITIAL_CONNECTION_STATE);
14 }
15
16 describe("connection state", () => {
17 it("starts online and stays online on successful probes", () => {
18 const state = run([{ type: "probe-start" }, { type: "probe-ok", at: 10 }]);
19 expect(state).toEqual({ status: "online", attempt: 0, lastCheckedAt: 10, restored: false });
20 expect(shouldProbe(state)).toBe(false);
21 });
22
23 it("goes offline on the browser event and does not probe until the browser returns", () => {
24 const offline = run([{ type: "browser-offline" }]);
25 expect(offline.status).toBe("offline");
26 expect(shouldProbe(offline)).toBe(false);
27 // A probe that happens to fail while offline records the time but does
28 // not count as a reconnect attempt.
29 const still = nextConnectionState(offline, { type: "probe-failed", at: 5 });
30 expect(still.status).toBe("offline");
31 expect(still.attempt).toBe(0);
32 expect(still.lastCheckedAt).toBe(5);
33 // The same for a probe that was already in flight when the browser went
34 // offline and lands successfully afterward: the browser may emit no
35 // second event, so a late success must not hide the banner.
36 const staleOk = nextConnectionState(offline, { type: "probe-ok", at: 6 });
37 expect(staleOk.status).toBe("offline");
38 expect(staleOk.attempt).toBe(0);
39 expect(staleOk.lastCheckedAt).toBe(6);
40 });
41
42 it("reconnects through the server, not on the browser's word alone", () => {
43 const back = run([{ type: "browser-offline" }, { type: "browser-online" }]);
44 expect(back.status).toBe("reconnecting");
45 expect(shouldProbe(back)).toBe(true);
46 const failed = nextConnectionState(back, { type: "probe-failed", at: 20 });
47 expect(failed.status).toBe("degraded");
48 expect(failed.attempt).toBe(1);
49 const again = nextConnectionState(failed, { type: "probe-start" });
50 expect(again.status).toBe("reconnecting");
51 const ok = nextConnectionState(again, { type: "probe-ok", at: 30 });
52 expect(ok).toEqual({ status: "online", attempt: 0, lastCheckedAt: 30, restored: true });
53 expect(nextConnectionState(ok, { type: "restored-seen" }).restored).toBe(false);
54 });
55
56 it("marks a failed probe while apparently online as degraded", () => {
57 const state = run([{ type: "probe-failed", at: 1 }]);
58 expect(state.status).toBe("degraded");
59 expect(state.attempt).toBe(1);
60 expect(shouldProbe(state)).toBe(true);
61 });
62
63 it("schedules retries on a capped exponential backoff", () => {
64 expect(backoffDelayMs(0)).toBe(RETRY_BASE_MS);
65 expect(backoffDelayMs(1)).toBe(RETRY_BASE_MS * 2);
66 expect(backoffDelayMs(2)).toBe(RETRY_BASE_MS * 4);
67 expect(backoffDelayMs(3)).toBe(RETRY_BASE_MS * 8);
68 expect(backoffDelayMs(4)).toBe(RETRY_MAX_MS);
69 expect(backoffDelayMs(50)).toBe(RETRY_MAX_MS);
70 });
71 });
72
72 lines TYPESCRIPT