assistant/scripts/check-particle-loop.mjs

91 lines
3.1 KiB
JavaScript

import assert from 'node:assert/strict';
import { mkdtemp, rm } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { build } from 'esbuild';
// Pure scheduler verification. No Electron, DOM interaction, backend, or model.
const root = fileURLToPath(new URL('../', import.meta.url));
const directory = await mkdtemp(path.join(os.tmpdir(), 'prospect-particle-loop-'));
try {
const output = path.join(directory, 'particle-loop.mjs');
await build({ entryPoints: [path.join(root, 'renderer/components/particleLoop.ts')],
platform: 'node', bundle: true, format: 'esm', outfile: output });
const { createParticleLoop } = await import(pathToFileURL(output).href);
const queued = new Map();
let handle = 0;
let render = true;
let animate = true;
const draws = [];
const loop = createParticleLoop({
draw: (elapsed, still) => draws.push({ elapsed, still }),
canRender: () => render,
canAnimate: () => animate,
requestFrame: callback => { const id = ++handle; queued.set(id, callback); return id; },
cancelFrame: id => queued.delete(id),
});
const step = time => {
const entry = queued.entries().next().value;
assert.ok(entry, 'one frame should be scheduled');
queued.delete(entry[0]);
entry[1](time);
};
const checks = [];
loop.sync();
assert.equal(queued.size, 1);
checks.push('active starts exactly one scheduled frame');
step(0);
const first = draws.length;
step(16); assert.equal(draws.length, first);
step(34); assert.equal(draws.length, first + 1);
step(50); assert.equal(draws.length, first + 1);
step(68); assert.equal(draws.length, first + 2);
checks.push('draws are capped at 30 fps');
for (let index = 0; index < 5; index += 1) loop.sync();
assert.equal(queued.size, 1);
checks.push('repeated state sync never duplicates the animation loop');
animate = false;
loop.sync();
assert.equal(queued.size, 0);
assert.equal(draws.at(-1).still, true);
checks.push('paused or reduced motion leaves a static frame and no scheduled work');
render = false;
const before = draws.length;
loop.sync();
assert.equal(queued.size, 0);
assert.equal(draws.length, before);
checks.push('hidden or offscreen canvas does not draw');
render = true;
loop.sync();
assert.equal(draws.at(-1).still, true);
assert.equal(queued.size, 0);
checks.push('visible reduced-motion frame is restored without starting work');
animate = true;
loop.sync();
const elapsed = draws.at(-1).elapsed;
step(100000); assert.equal(draws.at(-1).elapsed, elapsed);
step(100034); assert.equal(draws.at(-1).elapsed, elapsed + 34);
checks.push('resume retains elapsed motion without a hidden-time jump');
loop.dispose();
assert.equal(queued.size, 0);
const end = draws.length;
loop.sync();
assert.equal(draws.length, end);
assert.equal(queued.size, 0);
checks.push('dispose cancels work permanently');
process.stdout.write(JSON.stringify({ kind: 'pure scheduler unit check', passed: checks.length,
checks, externalActions: 0 }, null, 2) + '\n');
} finally {
await rm(directory, { recursive: true, force: true });
}