usePolled reports when a request is in flight (through a new onBusy of the poller) and holds it on for at least 0.4 seconds, so a quick answer still reads as a refresh. The picker swaps its clock for the spinner, in the same box so its width stays, and the spinner stands still where motion is off. Every page with a picker passes it; a page with several polled requests shows it while any of them runs. The Jobs page fetches through shared components and has none yet.
116 lines
3.5 KiB
JavaScript
116 lines
3.5 KiB
JavaScript
// The request discipline of a polled page. Run by `npm test`, which Node runs
|
|
// with its built-in test runner and type stripping, so the `.ts` module is
|
|
// imported as it is.
|
|
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { createPoller } from "../src/ui/polled/poller.ts";
|
|
|
|
// A fetch whose answers the test settles by hand, in any order.
|
|
function manualFetch() {
|
|
const calls = [];
|
|
const fetch = (signal) =>
|
|
new Promise((resolve, reject) => {
|
|
calls.push({ signal, resolve, reject });
|
|
});
|
|
return { calls, fetch };
|
|
}
|
|
|
|
// Lets the promise callbacks of a settled request run.
|
|
const flush = () => new Promise((resolve) => setImmediate(resolve));
|
|
|
|
function collect() {
|
|
const seen = { data: [], error: [] };
|
|
return {
|
|
seen,
|
|
onData: (d) => seen.data.push(d),
|
|
onError: (e) => seen.error.push(e),
|
|
};
|
|
}
|
|
|
|
test("a tick while a request is in flight asks for nothing", async () => {
|
|
const { calls, fetch } = manualFetch();
|
|
const sink = collect();
|
|
const poller = createPoller({ fetch, ...sink });
|
|
poller.tick();
|
|
poller.tick();
|
|
assert.equal(calls.length, 1);
|
|
calls[0].resolve("one");
|
|
await flush();
|
|
assert.deepEqual(sink.seen.data, ["one"]);
|
|
poller.tick();
|
|
assert.equal(calls.length, 2);
|
|
});
|
|
|
|
test("a refetch drops the request in flight, so an older answer never lands", async () => {
|
|
const { calls, fetch } = manualFetch();
|
|
const sink = collect();
|
|
const poller = createPoller({ fetch, ...sink });
|
|
poller.tick();
|
|
poller.refetch();
|
|
assert.equal(calls.length, 2);
|
|
assert.equal(calls[0].signal.aborted, true);
|
|
calls[1].resolve("new");
|
|
calls[0].resolve("old");
|
|
await flush();
|
|
assert.deepEqual(sink.seen.data, ["new"]);
|
|
});
|
|
|
|
test("a stopped poller aborts the request and ignores its answer", async () => {
|
|
const { calls, fetch } = manualFetch();
|
|
const sink = collect();
|
|
const poller = createPoller({ fetch, ...sink });
|
|
poller.tick();
|
|
poller.stop();
|
|
assert.equal(calls[0].signal.aborted, true);
|
|
calls[0].resolve("late");
|
|
await flush();
|
|
assert.deepEqual(sink.seen.data, []);
|
|
poller.tick();
|
|
assert.equal(calls.length, 2);
|
|
});
|
|
|
|
test("a failed request reports the error and frees the next tick", async () => {
|
|
const { calls, fetch } = manualFetch();
|
|
const sink = collect();
|
|
const poller = createPoller({ fetch, ...sink });
|
|
poller.tick();
|
|
const boom = new Error("boom");
|
|
calls[0].reject(boom);
|
|
await flush();
|
|
assert.deepEqual(sink.seen.error, [boom]);
|
|
poller.tick();
|
|
assert.equal(calls.length, 2);
|
|
});
|
|
|
|
test("the answer of a dropped request that fails is not reported", async () => {
|
|
const { calls, fetch } = manualFetch();
|
|
const sink = collect();
|
|
const poller = createPoller({ fetch, ...sink });
|
|
poller.tick();
|
|
poller.refetch();
|
|
calls[0].reject(new Error("aborted"));
|
|
await flush();
|
|
assert.deepEqual(sink.seen.error, []);
|
|
});
|
|
|
|
test("onBusy follows a request in flight, however it ends", async () => {
|
|
const { calls, fetch } = manualFetch();
|
|
const sink = collect();
|
|
const busy = [];
|
|
const poller = createPoller({ fetch, ...sink, onBusy: (b) => busy.push(b) });
|
|
poller.tick();
|
|
poller.tick();
|
|
assert.deepEqual(busy, [true]);
|
|
calls[0].resolve("one");
|
|
await flush();
|
|
assert.deepEqual(busy, [true, false]);
|
|
poller.tick();
|
|
calls[1].reject(new Error("boom"));
|
|
await flush();
|
|
assert.deepEqual(busy, [true, false, true, false]);
|
|
poller.tick();
|
|
poller.stop();
|
|
assert.deepEqual(busy, [true, false, true, false, true, false]);
|
|
poller.stop();
|
|
assert.equal(busy.length, 6);
|
|
});
|