Plain HTML, CSS and JavaScript modules, with no build step. Hash routes and relative URLs make it work under any path. The job list is polled: every 2 seconds while a job runs, and less often otherwise.
89 lines
2.7 KiB
JavaScript
89 lines
2.7 KiB
JavaScript
// Tests for the job polling in bindery/static/js/jobs.js. Run with: node --test tests/js/
|
|
|
|
import assert from 'node:assert/strict';
|
|
import { after, beforeEach, test } from 'node:test';
|
|
|
|
// jobs.js listens for tab visibility changes when it loads.
|
|
globalThis.document = { hidden: false, addEventListener() {} };
|
|
|
|
let jobs = [];
|
|
globalThis.fetch = async () => ({ ok: true, status: 200, json: async () => jobs });
|
|
|
|
const { refreshJobs, startPolling, stopPolling, watchJobs } = await import('../../bindery/static/js/jobs.js');
|
|
|
|
const job = (id, storyId, status) => ({ id, story_id: storyId, status });
|
|
const tick = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
beforeEach(async () => {
|
|
jobs = [];
|
|
startPolling();
|
|
await tick();
|
|
});
|
|
|
|
after(() => stopPolling());
|
|
|
|
test('watchJobs renders the jobs of one story', async () => {
|
|
const rendered = [];
|
|
const stop = watchJobs({ storyId: 1, render: (list) => rendered.push(list.map((j) => j.id)) });
|
|
jobs = [job('a', 1, 'queued'), job('b', 2, 'queued')];
|
|
await refreshJobs();
|
|
assert.deepEqual(rendered.at(-1), ['a']);
|
|
stop();
|
|
jobs = [job('c', 1, 'queued')];
|
|
await refreshJobs();
|
|
assert.deepEqual(rendered.at(-1), ['a']);
|
|
});
|
|
|
|
test('watchJobs reports a finished job once', async () => {
|
|
let finished = 0;
|
|
const stop = watchJobs({ storyId: 1, onFinished: () => finished++ });
|
|
jobs = [job('a', 1, 'queued')];
|
|
await refreshJobs();
|
|
jobs = [job('a', 1, 'running')];
|
|
await refreshJobs();
|
|
assert.equal(finished, 0);
|
|
jobs = [job('a', 1, 'done')];
|
|
await refreshJobs();
|
|
assert.equal(finished, 1);
|
|
await refreshJobs();
|
|
assert.equal(finished, 1);
|
|
stop();
|
|
});
|
|
|
|
test('watchJobs ignores jobs that finished before it started, and other stories', async () => {
|
|
let finished = 0;
|
|
jobs = [job('old', 1, 'done')];
|
|
await refreshJobs();
|
|
const stop = watchJobs({ storyId: 1, onFinished: () => finished++ });
|
|
jobs = [job('old', 1, 'done'), job('other', 2, 'running')];
|
|
await refreshJobs();
|
|
jobs = [job('old', 1, 'done'), job('other', 2, 'error')];
|
|
await refreshJobs();
|
|
assert.equal(finished, 0);
|
|
stop();
|
|
});
|
|
|
|
test('watchJobs without a story watches every job', async () => {
|
|
let finished = 0;
|
|
const stop = watchJobs({ onFinished: () => finished++ });
|
|
jobs = [job('a', 1, 'running'), job('b', null, 'queued')];
|
|
await refreshJobs();
|
|
jobs = [job('a', 1, 'cancelled'), job('b', null, 'error')];
|
|
await refreshJobs();
|
|
assert.equal(finished, 1);
|
|
stop();
|
|
});
|
|
|
|
test('stopPolling stops the requests', async () => {
|
|
stopPolling();
|
|
let calls = 0;
|
|
const saved = globalThis.fetch;
|
|
globalThis.fetch = async () => {
|
|
calls++;
|
|
return { ok: true, status: 200, json: async () => [] };
|
|
};
|
|
await refreshJobs();
|
|
assert.equal(calls, 0);
|
|
globalThis.fetch = saved;
|
|
});
|