Skip to content

Commit c0bc23c

Browse files
committed
test(poll): adds hot poll resume test and fixes config change test
1 parent 206d715 commit c0bc23c

File tree

2 files changed

+49
-24
lines changed

2 files changed

+49
-24
lines changed

tests/services/hot-poll.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,36 @@ describe("createHotPollCoordinator", () => {
535535
});
536536
});
537537

538+
it("resumes fetching after hidden→visible transition", async () => {
539+
const onHotData = vi.fn();
540+
const requestFn = vi.fn(() => Promise.resolve({
541+
data: { id: 1, status: "in_progress", conclusion: null, updated_at: "2026-01-01T00:00:00Z", completed_at: null },
542+
headers: {},
543+
}));
544+
mockGetClient.mockReturnValue(makeOctokit(requestFn));
545+
546+
rebuildHotSets({
547+
...emptyData,
548+
workflowRuns: [makeWorkflowRun({ id: 1, status: "in_progress", conclusion: null, repoFullName: "o/r" })],
549+
});
550+
551+
await createRoot(async (dispose) => {
552+
createHotPollCoordinator(() => 10, onHotData);
553+
554+
// Hidden — cycle skips fetch
555+
Object.defineProperty(document, "visibilityState", { value: "hidden", writable: true, configurable: true });
556+
await vi.advanceTimersByTimeAsync(10_000);
557+
expect(onHotData).not.toHaveBeenCalled();
558+
559+
// Visible — next cycle should fetch
560+
Object.defineProperty(document, "visibilityState", { value: "visible", writable: true, configurable: true });
561+
await vi.advanceTimersByTimeAsync(10_000);
562+
expect(onHotData).toHaveBeenCalled();
563+
564+
dispose();
565+
});
566+
});
567+
538568
it("resets backoff counter on successful cycle", async () => {
539569
const onHotData = vi.fn();
540570
const requestFn = vi.fn(() => Promise.resolve({

tests/services/poll.test.ts

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import "fake-indexeddb/auto";
22
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3-
import { createRoot } from "solid-js";
3+
import { createRoot, createSignal } from "solid-js";
44
import { createPollCoordinator, disableNotifGate, resetPollState, type DashboardData } from "../../src/app/services/poll";
55

66
// Mock pushError so we can spy on it
@@ -280,40 +280,35 @@ describe("createPollCoordinator", () => {
280280
});
281281

282282
it("config change (interval change) restarts the interval", async () => {
283+
const randomSpy = vi.spyOn(Math, "random").mockReturnValue(0.5); // jitter = 0
283284
const fetchAll = makeFetchAll();
284-
let intervalSec = 300;
285285

286286
await createRoot(async (dispose) => {
287-
// Use a signal-based getter to simulate reactive config
288-
const [getInterval, setGetInterval] = (() => {
289-
let fn = () => intervalSec;
290-
return [
291-
() => fn(),
292-
(newFn: () => number) => {
293-
fn = newFn;
294-
},
295-
] as const;
296-
})();
297-
298-
createPollCoordinator(getInterval, fetchAll);
287+
const [interval, setInterval] = createSignal(300);
288+
289+
createPollCoordinator(interval, fetchAll);
299290
await Promise.resolve(); // initial fetch
300291

301-
// Simulate config change to shorter interval by providing a new accessor
302-
// In practice SolidJS createEffect re-runs when reactive dependencies change.
303-
// Here we verify that calling with interval=60 fires within 90s.
304-
intervalSec = 60;
305-
void setGetInterval; // suppress unused warning
292+
const callsAfterInit = fetchAll.mock.calls.length;
306293

294+
// At 300s interval, 90s should NOT fire
307295
vi.advanceTimersByTime(90_000);
308296
await Promise.resolve();
297+
expect(fetchAll.mock.calls.length).toBe(callsAfterInit);
298+
299+
// Change interval to 60s — createEffect re-fires, timer restarts
300+
setInterval(60);
301+
await Promise.resolve(); // let effect run
302+
303+
// Advance 61s — new 60s interval should fire
304+
vi.advanceTimersByTime(61_000);
305+
await Promise.resolve();
306+
expect(fetchAll.mock.calls.length).toBeGreaterThan(callsAfterInit);
309307

310-
// At 300s interval, 90s would not fire. But with 60s interval restart,
311-
// it should fire at least once more. Since the internal createEffect
312-
// is not re-triggered (intervalSec is not a signal), we only verify
313-
// that the original timer was set and would eventually fire.
314-
// The key test is just that manualRefresh + timer work correctly.
315308
dispose();
316309
});
310+
311+
randomSpy.mockRestore();
317312
});
318313

319314
it("interval=0 disables auto-refresh (no setInterval)", async () => {

0 commit comments

Comments
 (0)