Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tw9fjV2UVj2u3F3mbnYrQW
111 lines
5.5 KiB
JavaScript
111 lines
5.5 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { addScene, removeScene, removeFromPlan, restoreToPlan, reorder, resize, trimLeft, slip, split, nudge } from "../edits.js";
|
|
import { seconds, segments } from "../scenes.js";
|
|
|
|
const mk = (extra = {}) => ({
|
|
num_frames_per_scene: 97, frame_rate: 25, scene_renders: {}, scene_ghosts: [],
|
|
scenes: [{ id: "a", text: "A", source: { type: "empty" } }, { id: "b", text: "B", source: { type: "carry" } }],
|
|
...extra,
|
|
});
|
|
|
|
test("a new scene goes on the end, empty", () => {
|
|
const p = mk();
|
|
const sc = addScene(p, "carry");
|
|
assert.deepEqual([p.scenes.length, sc.text, sc.source.type], [3, "", "carry"]);
|
|
});
|
|
|
|
test("removing a scene with a render leaves a ghost where it was; one without just goes", () => {
|
|
const p = mk({ scene_renders: { b: { media: { filename: "x.mp4" }, inSec: 2 } } });
|
|
removeScene(p, "b");
|
|
assert.deepEqual(p.scenes.map((s) => s.id), ["a"]);
|
|
assert.deepEqual([p.scene_ghosts[0].id, p.scene_ghosts[0].afterSceneId, p.scene_ghosts[0].inSec], ["b", "a", 2]);
|
|
removeScene(p, "a");
|
|
assert.equal(p.scene_ghosts.length, 1, "a scene that never rendered leaves nothing");
|
|
});
|
|
|
|
test("removing the root of a cut hands its prompt and source to the next cut", () => {
|
|
const p = mk();
|
|
const second = split(p, "a", 49);
|
|
assert.ok(second);
|
|
removeScene(p, "a");
|
|
const left = p.scenes.find((s) => s.id === second.id);
|
|
assert.deepEqual([left.cut_offset_frames, left.text, left.source.type], [0, "A", "empty"]);
|
|
});
|
|
|
|
test("a unit with a render stays on the timeline when taken out of the plan, and can come back", () => {
|
|
const p = mk({ scene_renders: { a: { media: { filename: "x.mp4" } } } });
|
|
removeFromPlan(p, "a");
|
|
assert.deepEqual([p.scenes[0].excluded, p.scenes[0].removed_from_plan], [true, true]);
|
|
restoreToPlan(p, "a");
|
|
assert.deepEqual([p.scenes[0].excluded, p.scenes[0].removed_from_plan], [false, false]);
|
|
});
|
|
|
|
test("reordering the cut pins an order and never touches the plan", () => {
|
|
const p = mk();
|
|
assert.equal(reorder(p, "a", 1, ["a", "b"]), true);
|
|
assert.deepEqual(p.timeline_order, ["b", "a"]);
|
|
assert.equal(p.timeline_manually_ordered, true);
|
|
assert.deepEqual(p.scenes.map((s) => s.id), ["a", "b"]);
|
|
assert.equal(reorder(p, "b", 0, ["b", "a"]), false, "dropping where it already is changes nothing");
|
|
});
|
|
|
|
test("dragging an edge lands on the model's frame grid and pins the scene's own length", () => {
|
|
const p = mk();
|
|
assert.equal(resize(p, "a", 2), true);
|
|
const a = p.scenes[0];
|
|
assert.equal(a.frames_mode, "timeline");
|
|
assert.equal(a.frames % 8, 1, "8k+1");
|
|
assert.ok(Math.abs(seconds(a, p) - 2) < 0.3);
|
|
assert.equal(resize(p, "a", seconds(a, p) + 0.01), false, "a nudge under the tolerance is nothing");
|
|
});
|
|
|
|
test("trimming the start begins later in the source and runs shorter", () => {
|
|
const p = mk();
|
|
assert.equal(trimLeft(p, "a", 1), true);
|
|
const a = p.scenes[0];
|
|
assert.ok(a.source_in > 0.5 && a.frames < 97);
|
|
assert.equal(slip(p, "a", -100), true);
|
|
assert.equal(a.source_in, 0, "never before the start of the source");
|
|
});
|
|
|
|
test("a split makes two clips of one unit that keep playing the same render", () => {
|
|
const p = mk({ scene_renders: { a: { media: { filename: "x.mp4" }, inSec: 1, durationSec: 4 } } });
|
|
const second = split(p, "a", 49);
|
|
assert.equal(p.scenes.length, 3);
|
|
assert.deepEqual([p.scenes[0].frames, second.frames, second.gen_unit_id, second.cut_offset_frames], [49, 49, "a", 49]);
|
|
assert.equal(p.scene_renders[second.id].inSec, 1 + 49 / 25);
|
|
assert.equal(p.scene_renders.a.durationSec, undefined);
|
|
assert.deepEqual(segments(p).map((s) => s.id).slice(0, 2), ["a", second.id]);
|
|
assert.equal(split(p, "a", 5), false, "no cut inside the first grid step");
|
|
});
|
|
|
|
test("removing the first cut of a unit hands its takes to the clip that becomes the root", async () => {
|
|
const { removeScene } = await import("../edits.js");
|
|
const p = { scenes: [{ id: "a", text: "x", frames: 8 }, { id: "b", gen_unit_id: "a", cut_offset_frames: 8, frames: 8 }], scene_variants: { a: [{ promptId: "p" }] }, scene_renders: {} };
|
|
removeScene(p, "a");
|
|
assert.deepEqual(Object.keys(p.scene_variants), ["b"]);
|
|
});
|
|
|
|
test("clearReferences empties every scene's own list, keeps the pins, and says how many pictures were in use", async () => {
|
|
const { clearReferences, allReferences } = await import("../edits.js");
|
|
const p = { fixed_references: { a: 1 }, scenes: [{ id: "x", references: ["b", "c"] }, { id: "y", references: ["b"] }, { id: "z" }] };
|
|
assert.equal(allReferences(p).size, 2);
|
|
assert.equal(clearReferences(p), 2);
|
|
assert.deepEqual([p.scenes.map((s) => s.references || []), p.fixed_references, clearReferences(p)], [[[], [], []], { a: 1 }, 0]);
|
|
});
|
|
|
|
test("nudge: a clip dropped later leaves a pause before it, dropped back it closes, and the first clip stays at zero", () => {
|
|
const p = { frame_rate: 25, num_frames_per_scene: 50, scenes: [{ id: "a" }, { id: "b" }, { id: "c" }], scene_renders: {} };
|
|
const at = (id) => segments(p).find((s) => s.id === id).start;
|
|
assert.equal(at("b"), 2);
|
|
assert.equal(nudge(p, "b", 3.5), true);
|
|
assert.equal(p.scenes[0].gap_after_sec, 1.5);
|
|
assert.equal(at("b"), 3.5);
|
|
assert.equal(at("c"), 5.5); // what follows moves along
|
|
assert.equal(nudge(p, "b", 3.5), false); // nothing changed
|
|
assert.equal(nudge(p, "b", 1), true); // cannot start before the clip in front of it ends
|
|
assert.equal(p.scenes[0].gap_after_sec, 0);
|
|
assert.equal(nudge(p, "a", 4), false); // the first clip has nothing before it to hold a pause
|
|
});
|