Files
gods-eye-view/src/scenes/cameraMotion.test.mjs
T

86 lines
2.5 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { createCameraMotion } from './cameraMotion.js';
const move = {
durationSec: 2,
easing: 'linear',
from: { lat: 0, lon: 0, alt: 100, heading: 0, pitch: 0, roll: 0 },
to: { lat: 2, lon: 2, alt: 200, heading: 0, pitch: 0, roll: 0 },
};
function harness(apply) {
let time = 0;
let next = 0;
const frames = new Map();
const poses = [];
const owner = createCameraMotion({
now: () => time,
requestFrame: (cb) => {
const id = ++next;
frames.set(id, cb);
return id;
},
cancelFrame: (id) => frames.delete(id),
applyPose: (pose) => {
poses.push(pose);
return apply?.(pose);
},
});
return {
owner,
frames,
poses,
tick(ms) {
time = ms;
const callbacks = [...frames.values()];
frames.clear();
callbacks.forEach((cb) => cb());
},
};
}
test('motion uses the authored curve and releases the frame on completion', async () => {
const h = harness();
const done = h.owner.play(move, { cancelled: false });
assert.deepEqual(h.poses[0], move.from);
h.tick(1000);
assert.equal(h.poses.at(-1).alt, 150);
h.tick(2000);
assert.equal(await done, true);
assert.deepEqual(h.poses.at(-1), move.to);
assert.equal(h.frames.size, 0);
assert.equal(h.owner.active, false);
});
test('abort and replacement settle immediately and revoke already queued callbacks', async () => {
const h = harness();
const abort = new AbortController();
const first = h.owner.play(move, { signal: abort.signal });
const stale = [...h.frames.values()][0];
abort.abort();
assert.equal(await first, false);
const second = h.owner.play(move, {});
const count = h.poses.length;
stale();
assert.equal(h.poses.length, count);
h.owner.destroy();
assert.equal(await second, false);
assert.equal(h.frames.size, 0);
assert.equal(await h.owner.play(move, {}), false);
});
test('synchronous cancellation from pose application schedules no abandoned frame', async () => {
let owner;
const h = harness(() => owner.cancel());
owner = h.owner;
assert.equal(await owner.play(move, {}), false);
assert.equal(h.frames.size, 0);
});
test('failed pose application settles and invalid durations cannot schedule a loop', async () => {
const h = harness(() => {
throw new Error('camera unavailable');
});
await assert.rejects(h.owner.play(move, {}), /camera unavailable/);
assert.equal(h.frames.size, 0);
assert.throws(
() => h.owner.play({ ...move, durationSec: 0 }, {}),
/duration/,
);
});