Browser tests for the page's live updates (#404)

events.ts's handlers for a download in progress, finished and failed,
and for Signals' new hits, ran in no test. The fixture server has a
show to download from by hand: one episode sent over two seconds and
one that always fails, added with a word nothing has so that nothing
downloads on its own. Downloading the slow one fills its bar, the count
line says how far, the toast names the file and the row shows it
downloaded; another person with a page open, not subscribed, hears of
none of it (#294). The broken one says which item failed and what to
do (#304), and its bar stops. A Signal open on its page shows its new
hits and its count as soon as a pass picks them out, without a reload;
its folder is opened from the keyboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 04:37:45 +00:00
parent 8e8307b569
commit ee5610ba4e
2 changed files with 94 additions and 0 deletions

View File

@@ -43,6 +43,30 @@ http.createServer((req, res) => {
<title>Signal Show</title>${item('sig-1', 'Monaco qualifying')}${item('sig-2', 'Baking bread')}</channel></rss>`);
return;
}
// A show to download from by hand (#404): one episode sent slowly enough for its progress to be
// seen, and one that always fails. Their files are theirs alone, as enclosure URLs must be.
if (req.url === '/live.xml') {
const item = (guid: string, title: string, file: string) =>
`<item><guid>${guid}</guid><title>${title}</title><enclosure url="http://127.0.0.1:${port}/${file}" type="audio/mpeg" length="40000"/></item>`;
res.writeHead(200, { 'content-type': 'application/xml' }).end(`<?xml version="1.0"?><rss version="2.0"><channel>
<title>Live Show</title>${item('live-1', 'Slow episode', 'live-slow.mp3')}${item('live-2', 'Broken episode', 'live-broken.mp3')}</channel></rss>`);
return;
}
if (req.url === '/live-broken.mp3') { res.writeHead(500).end('broken'); return; }
if (req.url === '/live-slow.mp3') {
const body = fs.readFileSync(path.join(dir, 'ep1.mp3'));
res.writeHead(200, { 'content-type': 'audio/mpeg', 'content-length': body.length });
// Ten parts, a fifth of a second apart: two seconds of progress.
let part = 0;
const step = Math.ceil(body.length / 10);
const next = () => {
if (part * step >= body.length) { res.end(); return; }
res.write(body.subarray(part * step, ++part * step));
setTimeout(next, 200);
};
next();
return;
}
const name = decodeURIComponent((req.url ?? '/').split('?')[0]!.replace(/^\//, '')) || 'index';
const file = path.join(dir, path.basename(name));
fs.readFile(file, (err, body) => {