Skip to content
Source
Sign inSign up

clock.test.ts

140 lines4.8 KB

1import { afterEach, describe, expect, it, vi } from "vite-plus/test";
2import { deadlineStore, secondTick } from "./clock.ts";
3​
4describe("deadlineStore", () => {
5 afterEach(() => {
6 vi.useRealTimers();
7 });
8​
9 it("flips once at the deadline and notifies every subscriber from one timer", () => {
10 vi.useFakeTimers({ now: 10_000 });
11 const store = deadlineStore(10_500);
12 const first = vi.fn();
13 const second = vi.fn();
14 const unsubscribeFirst = store.subscribe(first);
15 const unsubscribeSecond = deadlineStore(10_500).subscribe(second);
16 expect(vi.getTimerCount()).toBe(1);
17 expect(store.getSnapshot()).toBe(false);
18​
19 vi.advanceTimersByTime(499);
20 expect(store.getSnapshot()).toBe(false);
21 vi.advanceTimersByTime(1);
22 expect(store.getSnapshot()).toBe(true);
23 expect(first).toHaveBeenCalledTimes(1);
24 expect(second).toHaveBeenCalledTimes(1);
25 expect(vi.getTimerCount()).toBe(0);
26 unsubscribeFirst();
27 unsubscribeSecond();
28 });
29​
30 it("shares one timer between callers that obtain the deadline before subscribing", () => {
31 vi.useFakeTimers({ now: 0 });
32 const first = deadlineStore(2_000);
33 const second = deadlineStore(2_000);
34 const firstListener = vi.fn();
35 const secondListener = vi.fn();
36 const unsubscribeFirst = first.subscribe(firstListener);
37 const unsubscribeSecond = second.subscribe(secondListener);
38 expect(vi.getTimerCount()).toBe(1);
39​
40 // A click observing the passed deadline must update every subscriber.
41 vi.setSystemTime(5_000);
42 first.refresh();
43 expect(second.getSnapshot()).toBe(true);
44 expect(secondListener).toHaveBeenCalledTimes(1);
45 expect(firstListener).toHaveBeenCalledTimes(1);
46 unsubscribeFirst();
47 unsubscribeSecond();
48 });
49​
50 it("re-arms deadlines beyond the 32-bit timer range instead of firing early", () => {
51 vi.useFakeTimers({ now: 0 });
52 const deadline = 2_147_483_647 + 1_000;
53 const store = deadlineStore(deadline);
54 const listener = vi.fn();
55 const unsubscribe = store.subscribe(listener);
56 vi.advanceTimersByTime(2_147_483_647);
57 expect(store.getSnapshot()).toBe(false);
58 expect(listener).not.toHaveBeenCalled();
59 vi.advanceTimersByTime(1_000);
60 expect(store.getSnapshot()).toBe(true);
61 expect(listener).toHaveBeenCalledTimes(1);
62 unsubscribe();
63 });
64​
65 it("refresh catches up when the timer runs late", () => {
66 vi.useFakeTimers({ now: 0 });
67 const store = deadlineStore(2_000);
68 const listener = vi.fn();
69 const unsubscribe = store.subscribe(listener);
70 store.refresh();
71 expect(listener).not.toHaveBeenCalled();
72 // Wall clock jumps past the deadline without the timer firing (sleep, throttling).
73 vi.setSystemTime(5_000);
74 expect(store.getSnapshot()).toBe(false);
75 store.refresh();
76 expect(store.getSnapshot()).toBe(true);
77 expect(listener).toHaveBeenCalledTimes(1);
78 expect(vi.getTimerCount()).toBe(0);
79 unsubscribe();
80 });
81​
82 it("stops its timer when the last subscriber leaves", () => {
83 vi.useFakeTimers({ now: 0 });
84 const store = deadlineStore(1_000);
85 const unsubscribe = store.subscribe(vi.fn());
86 unsubscribe();
87 expect(vi.getTimerCount()).toBe(0);
88 vi.advanceTimersByTime(1_000);
89 expect(store.getSnapshot()).toBe(true);
90 });
91​
92 it("treats a missing or unparseable deadline as never passing", () => {
93 vi.useFakeTimers({ now: 0 });
94 expect(deadlineStore(null).getSnapshot()).toBe(false);
95 const store = deadlineStore(Number.NaN);
96 store.subscribe(vi.fn());
97 expect(vi.getTimerCount()).toBe(0);
98 expect(store.getSnapshot()).toBe(false);
99 });
100});
101​
102describe("secondTick", () => {
103 afterEach(() => {
104 vi.useRealTimers();
105 });
106​
107 it("advances on wall-clock second boundaries", () => {
108 vi.useFakeTimers({ now: 5_250 });
109 const listener = vi.fn();
110 const unsubscribe = secondTick.subscribe(listener);
111 expect(secondTick.getSnapshot()).toBe(5_000);
112 vi.advanceTimersByTime(749);
113 expect(listener).not.toHaveBeenCalled();
114 vi.advanceTimersByTime(1);
115 expect(secondTick.getSnapshot()).toBe(6_000);
116 expect(listener).toHaveBeenCalledTimes(1);
117 unsubscribe();
118 expect(vi.getTimerCount()).toBe(0);
119 });
120​
121 it("stops ticking when the last subscriber leaves during a tick", () => {
122 vi.useFakeTimers({ now: 0 });
123 const unsubscribe = secondTick.subscribe(() => unsubscribe());
124 vi.advanceTimersByTime(1_000);
125 expect(vi.getTimerCount()).toBe(0);
126 });
127​
128 it("keeps one timer when a subscriber replaces itself during a tick", () => {
129 vi.useFakeTimers({ now: 0 });
130 let unsubscribeNext = () => {};
131 const unsubscribe = secondTick.subscribe(() => {
132 unsubscribe();
133 unsubscribeNext = secondTick.subscribe(() => {});
134 });
135 vi.advanceTimersByTime(1_000);
136 expect(vi.getTimerCount()).toBe(1);
137 unsubscribeNext();
138 expect(vi.getTimerCount()).toBe(0);
139 });
140});