Files
bindery/tests/js/jobs.test.mjs
T
tim 2f4271314b Add the web interface
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.
2026-09-27 23:38:28 +02:00

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;
});