1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
import { describe, expect, it } from "vitest";
import { SnapshotSequencer } from "../src/lib/snapshot-sequencer.js";
describe("SnapshotSequencer", () => {
it("accepts the first response unconditionally", () => {
const s = new SnapshotSequencer();
const seq = s.begin();
expect(s.accept(seq)).toBe(true);
});
it("accepts responses in send order", () => {
const s = new SnapshotSequencer();
const a = s.begin();
const b = s.begin();
const c = s.begin();
expect(s.accept(a)).toBe(true);
expect(s.accept(b)).toBe(true);
expect(s.accept(c)).toBe(true);
});
it("rejects an older response that arrives AFTER a newer one (the core race)", () => {
// Sequence: user clicks hour 9 (A), then hour 10 (B). B arrives first.
const s = new SnapshotSequencer();
const a = s.begin(); // toggle hour 9
const b = s.begin(); // toggle hour 10
// B arrives first — applied.
expect(s.accept(b)).toBe(true);
// A arrives later — must be dropped, else the snapshot from B (which
// knows about both 9 and 10) gets overwritten by A's stale snapshot
// (which only knows about 9), and hour 10 vanishes from the UI.
expect(s.accept(a)).toBe(false);
});
it("rejects ALL straggler responses once a newer one wins", () => {
const s = new SnapshotSequencer();
const a = s.begin();
const b = s.begin();
const c = s.begin();
expect(s.accept(c)).toBe(true);
expect(s.accept(b)).toBe(false);
expect(s.accept(a)).toBe(false);
});
it("handles the initial-load vs first-click race", () => {
// On mount: $effect fires loadFromServer (seq=1).
// Before it lands, user clicks a hour (seq=2).
const s = new SnapshotSequencer();
const initial = s.begin();
const click = s.begin();
// Click response arrives first — applied.
expect(s.accept(click)).toBe(true);
// Initial load straggles in — must be dropped (it pre-dates the click).
expect(s.accept(initial)).toBe(false);
});
it("treats an equal seq as accept (idempotent re-arrival of the winner)", () => {
const s = new SnapshotSequencer();
const a = s.begin();
expect(s.accept(a)).toBe(true);
// Defensive: same seq accepted again (shouldn't happen in practice
// but the semantics must be 'no-op accept', not 'reject').
expect(s.accept(a)).toBe(true);
});
it("seq numbers are monotonic and unique across begin() calls", () => {
const s = new SnapshotSequencer();
const seen = new Set<number>();
let prev = 0;
for (let i = 0; i < 100; i++) {
const seq = s.begin();
expect(seq).toBeGreaterThan(prev);
expect(seen.has(seq)).toBe(false);
seen.add(seq);
prev = seq;
}
});
it("state inspector reflects last-applied watermark", () => {
const s = new SnapshotSequencer();
expect(s.state).toEqual({ nextSeq: 0, latestApplied: 0 });
const a = s.begin();
const b = s.begin();
s.accept(b);
expect(s.state).toEqual({ nextSeq: 2, latestApplied: b });
// A is too old now — accept() returns false and watermark doesn't move back.
expect(s.accept(a)).toBe(false);
expect(s.state.latestApplied).toBe(b);
});
});
|