Files
gods-eye-view/src/director/document.test.mjs
T
Said Turksever 32125384fe Drop Snow from the new catalogs and migrate saved Snow shots
The it, pt and uk catalogs merged from main still carried the two Snow
button strings, which the catalog tests report as stale. They are
pruned with npm run i18n -- --prune.

A Scene Director shot saved with Snow now normalizes to Normal when the
project loads, so the shot list shows NORMAL rather than an untranslated
SNOW for a shot that plays on Normal. The README's style keys read 1-6.
2026-10-29 14:04:11 +00:00

171 lines
5.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
parseSceneDocument,
stringifySceneDocument,
validateSceneDocument,
SCENE_DOCUMENT_LIMITS,
} from './document.js';
import { createDefaultProject, normalizeProject } from '../scenes/project.js';
const fixture = () => ({
version: 3,
scenes: [
{
id: 'scene',
title: 'My scene',
shots: [
{
id: 'shot',
title: 'My shot',
durationSec: 3,
holdSec: 0,
camera: { lat: 1, lon: 2, alt: 42, pitch: 0 },
layers: {
traffic: { enabled: true, params: { nested: [1, 'two', null] } },
},
},
],
},
],
});
test('all built-in authored content exports and normalizes without edit loss', () => {
const project = createDefaultProject();
const migrated = normalizeProject(
parseSceneDocument(stringifySceneDocument(project)),
);
assert.deepEqual(
JSON.parse(stringifySceneDocument(migrated)),
JSON.parse(stringifySceneDocument(project)),
);
const edited = normalizeProject(fixture());
edited.scenes[0].appliedShotPacks = [
{ id: 'pack', version: 3, shotBindings: { Original: 'shot' } },
];
assert.deepEqual(
normalizeProject(parseSceneDocument(stringifySceneDocument(edited))),
edited,
);
assert.equal(edited.scenes[0].shots[0].camera.pitch, 0);
assert.equal(edited.scenes[0].shots[0].camera.alt, 42);
});
test('v1/v2 bloom migrates once; IDs, edits, pack bindings and zero holds survive', () => {
for (const version of [undefined, 1, 2]) {
const project = fixture();
project.version = version;
project.scenes[0].shots[0].visual = {
bloom: { enabled: true, intensity: 25 },
};
const migrated = normalizeProject(
parseSceneDocument(JSON.stringify(project)),
);
assert.equal(migrated.version, 6);
assert.equal(migrated.scenes[0].shots[0].visual.bloom.intensity, 150);
assert.equal(migrated.scenes[0].shots[0].id, 'shot');
assert.deepEqual(
normalizeProject(parseSceneDocument(stringifySceneDocument(migrated))),
migrated,
);
}
});
test('an intentionally empty project stays empty', () => {
assert.deepEqual(
normalizeProject(parseSceneDocument('{"version":3,"scenes":[]}')).scenes,
[],
);
});
test('missing legacy IDs become stable after the first saved migration', () => {
const project = { scenes: [{ shots: [{}] }] };
const migrated = normalizeProject(validateSceneDocument(project));
assert.ok(migrated.scenes[0].id);
assert.ok(migrated.scenes[0].shots[0].id);
assert.deepEqual(
normalizeProject(parseSceneDocument(stringifySceneDocument(migrated))),
migrated,
);
});
test('invalid shapes, versions, unknown fields and unsafe keys fail with field paths', () => {
for (const text of [
'null',
'[]',
'{}',
'{"version":99,"scenes":[]}',
'{"version":3,"scenes":[],"run":{}}',
'{"scenes":[],"__proto__":{}}',
]) {
assert.throws(() => parseSceneDocument(text), {
name: 'SceneDocumentError',
});
}
const project = fixture();
project.scenes[0].shots[0].camera.lat = 91;
assert.throws(
() => validateSceneDocument(project),
/\$\.scenes\[0\]\.shots\[0\]\.camera.lat/,
);
project.scenes[0].shots[0].camera.lat = 1;
project.scenes[0].shots.push(structuredClone(project.scenes[0].shots[0]));
assert.throws(() => validateSceneDocument(project), /duplicate ID/);
});
test('document byte, nesting, collection, finite-number and string bounds are enforced', () => {
assert.throws(
() => parseSceneDocument(' '.repeat(SCENE_DOCUMENT_LIMITS.bytes + 1)),
/5 MiB/,
);
assert.throws(
() =>
parseSceneDocument(
JSON.stringify({
scenes: [],
createdAt: 'é'.repeat(SCENE_DOCUMENT_LIMITS.bytes / 2),
}),
),
/5 MiB/,
);
const project = fixture();
project.scenes[0].shots[0].camera.lat = Infinity;
assert.throws(() => validateSceneDocument(project), /JSON value/);
project.scenes[0].shots[0].camera.lat = 1;
let nested = project.scenes[0].shots[0].layers.traffic.params;
for (let i = 0; i < 30; i++) nested = nested.child = {};
assert.throws(() => validateSceneDocument(project), /complexity/);
assert.throws(
() => validateSceneDocument({ scenes: Array(257).fill({ shots: [] }) }),
/256/,
);
});
test('a shot saved with the retired Snow style migrates to Normal', () => {
const project = fixture();
project.scenes[0].shots[0].visual = { style: 'snow' };
const migrated = normalizeProject(
parseSceneDocument(JSON.stringify(project)),
);
assert.equal(migrated.scenes[0].shots[0].visual.style, 'normal');
});
test('captured scope and extended detection edits survive migration', () => {
const project = fixture();
project.scenes[0].shots[0].visual = {
scope: { enabled: true, featherPct: 11 },
detection: {
mode: 'DENSE',
density: 75,
allocation: 'ELASTIC',
fadePct: 7,
outsideOpacityPct: 1,
},
};
const migrated = normalizeProject(
parseSceneDocument(JSON.stringify(project)),
);
assert.deepEqual(
migrated.scenes[0].shots[0].visual.scope,
project.scenes[0].shots[0].visual.scope,
);
assert.deepEqual(
migrated.scenes[0].shots[0].visual.detection,
project.scenes[0].shots[0].visual.detection,
);
});