Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
166 lines
11 KiB
JavaScript
166 lines
11 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { setupDom, teardownDom } from "../../composer/tests/_dom.js";
|
|
import { createDom } from "../../shell/dom.js";
|
|
|
|
let hub;
|
|
test.before(async () => { setupDom(); hub = (await import("./index.js")).default; });
|
|
test.after(() => teardownDom());
|
|
|
|
test("a scene's text changed elsewhere (the Story box) shows in the Prompt box, but not under the caret", async () => {
|
|
let fire;
|
|
const doc = { id: "P", frame_rate: 25, num_frames_per_scene: 49, scenes: [{ id: "a", text: "old" }], scene_renders: {} };
|
|
const p = { project: doc, get scenes() { return doc.scenes; }, get selected() { return doc.scenes[0]; }, selectedId: "a",
|
|
setText: () => {}, setScene: () => {}, edit: (fn) => fn(doc) };
|
|
const host = document.createElement("div");
|
|
document.body.append(host);
|
|
hub.setup({ host, app: { dom: createDom(), project: p, on: (fn) => { fire = fn; return () => {}; }, api: {}, say: () => {} } });
|
|
const box = () => [...host.querySelectorAll("textarea")][0];
|
|
assert.equal(box().value, "old");
|
|
doc.scenes[0].text = "from story";
|
|
fire("change");
|
|
assert.equal(box().value, "from story");
|
|
box().focus();
|
|
doc.scenes[0].text = "while typing";
|
|
fire("change");
|
|
assert.equal(box().value, "from story"); // not rebuilt under the caret
|
|
box().blur();
|
|
await new Promise((r) => setTimeout(r, 0));
|
|
assert.equal(box().value, "while typing"); // caught up once the focus left
|
|
});
|
|
|
|
test("typing in the Prompt box, then pressing a button in the panel, does not rebuild the button out from under the click", async () => {
|
|
let fire;
|
|
const doc = { id: "P", frame_rate: 25, num_frames_per_scene: 49, scenes: [{ id: "a", text: "old" }], scene_renders: {} };
|
|
const p = { project: doc, get scenes() { return doc.scenes; }, get selected() { return doc.scenes[0]; }, selectedId: "a",
|
|
setText: () => {}, setScene: () => {}, edit: (fn) => fn(doc) };
|
|
const host = document.createElement("div");
|
|
document.body.append(host);
|
|
hub.setup({ host, app: { dom: createDom(), project: p, on: (fn) => { fire = fn; return () => {}; }, api: {}, say: () => {} } });
|
|
const box = host.querySelector("textarea");
|
|
box.focus();
|
|
doc.scenes[0].text = "typed here"; fire("change"); // the box's own edit, as setText would announce it
|
|
const button = box.closest("[aria-label=Properties]") ? [...box.closest("[aria-label=Properties]").querySelectorAll("button, input[type=checkbox]")].find((b) => b !== box) : null;
|
|
assert.ok(button, "a control inside the panel body");
|
|
button.focus(); // the mouse-down moves focus to it
|
|
await new Promise((r) => setTimeout(r, 0));
|
|
assert.ok(button.isConnected, "the pressed button is still there for its mouse-up");
|
|
host.remove();
|
|
});
|
|
|
|
test("the project's Video input card switches on and shows its controls, and the scene has none of its own", async () => {
|
|
let fire;
|
|
const calls = [];
|
|
const doc = { id: "P", frame_rate: 25, num_frames_per_scene: 49, scenes: [{ id: "a", text: "x" }], scene_renders: {} };
|
|
const p = { project: doc, video: {}, get scenes() { return doc.scenes; }, get selected() { return doc.scenes[0]; }, selectedId: "a",
|
|
setText: () => {}, setScene: () => {}, setField: (k, v) => { calls.push([k, v]); doc[k] = v; fire("change"); }, setVideo: () => {}, edit: (fn) => fn(doc) };
|
|
const host = document.createElement("div");
|
|
document.body.append(host);
|
|
hub.setup({ host, app: { dom: createDom(), project: p, on: (fn) => { fire = fn; return () => {}; }, api: {}, say: () => {} } });
|
|
assert.ok(!host.textContent.includes("Video input"), "nothing about it on the Scene tab");
|
|
[...host.querySelectorAll("[role=radio]")].find((b) => b.textContent === "Project").click();
|
|
assert.ok(host.textContent.includes("Video input"));
|
|
assert.ok(!host.textContent.includes("Every Nth frame"));
|
|
const sw = [...host.querySelectorAll("input[role=switch]")].find((i) => i.closest(".cx-toggle-row").textContent.includes("Prepare video inputs"));
|
|
sw.checked = true; sw.dispatchEvent(new window.Event("change", { bubbles: true }));
|
|
assert.equal(calls[0][0], "video_input");
|
|
assert.equal(calls[0][1].on, true);
|
|
assert.ok(host.textContent.includes("Every Nth frame") && host.textContent.includes("Skip first") && host.textContent.includes("Cut last"));
|
|
host.remove();
|
|
});
|
|
|
|
test("the Project tab flips width and height in one undo step", async () => {
|
|
let fire;
|
|
const doc = { id: "P", frame_rate: 25, num_frames_per_scene: 49, video: { width: 768, height: 512 }, scenes: [{ id: "a", text: "x" }], scene_renders: {} };
|
|
let edits = 0;
|
|
const p = { project: doc, video: doc.video, get scenes() { return doc.scenes; }, get selected() { return doc.scenes[0]; }, selectedId: "a",
|
|
setText: () => {}, setScene: () => {}, setField: () => {}, setVideo: () => {}, edit: (fn) => { edits++; const r = fn(doc); fire("change"); return r; } };
|
|
const slots = [{ roles: [{ at: "project.video", input: "width", label: "Width" }, { at: "project.video", input: "height", label: "Height" }], inputs: { width: 768, height: 512 } }];
|
|
const host = document.createElement("div");
|
|
document.body.append(host);
|
|
hub.setup({ host, app: { dom: createDom(), project: p, on: (fn) => { fire = fn; return () => {}; }, api: {}, say: () => {}, pipeline: { slots: () => slots } } });
|
|
[...host.querySelectorAll("[role=radio]")].find((b) => b.textContent === "Project").click();
|
|
const button = () => [...host.querySelectorAll("button")].find((b) => b.textContent.includes("Flip width and height"));
|
|
assert.ok(button(), "the flip button is on the Project tab");
|
|
button().click();
|
|
assert.deepEqual([doc.video.width, doc.video.height], [512, 768]);
|
|
assert.equal(edits, 1);
|
|
host.remove();
|
|
});
|
|
|
|
test("the Video input card flips its width and height", async () => {
|
|
let fire;
|
|
const doc = { id: "P", frame_rate: 25, num_frames_per_scene: 49, video_input: { on: true, width: 1280, height: 720 }, scenes: [{ id: "a", text: "x" }], scene_renders: {} };
|
|
const p = { project: doc, video: {}, get scenes() { return doc.scenes; }, get selected() { return doc.scenes[0]; }, selectedId: "a",
|
|
setText: () => {}, setScene: () => {}, setField: (k, v) => { doc[k] = v; fire("change"); }, setVideo: () => {}, edit: (fn) => fn(doc) };
|
|
const host = document.createElement("div");
|
|
document.body.append(host);
|
|
hub.setup({ host, app: { dom: createDom(), project: p, on: (fn) => { fire = fn; return () => {}; }, api: {}, say: () => {}, pipeline: { slots: () => [] } } });
|
|
[...host.querySelectorAll("[role=radio]")].find((b) => b.textContent === "Project").click();
|
|
[...host.querySelectorAll("button")].find((b) => b.textContent.includes("Flip width and height")).click();
|
|
assert.deepEqual([doc.video_input.width, doc.video_input.height], [720, 1280]);
|
|
host.remove();
|
|
});
|
|
|
|
test("the Project tab offers Short side / Long side only when the pipeline fits a picture's shape to the size, and saves the choice", async () => {
|
|
let fire;
|
|
const doc = { id: "P", frame_rate: 25, num_frames_per_scene: 49, resolution_basis: "area", video: { target_width: 1344, target_height: 768 }, scenes: [{ id: "a", text: "x" }], scene_renders: {} };
|
|
const set = [];
|
|
const p = { project: doc, video: doc.video, get scenes() { return doc.scenes; }, get selected() { return doc.scenes[0]; }, selectedId: "a",
|
|
setText: () => {}, setScene: () => {}, setField: (k, v) => { set.push([k, v]); doc[k] = v; fire("change"); }, setVideo: () => {}, edit: (fn) => fn(doc) };
|
|
const size = [{ at: "project.video", input: "width", label: "Width", was: "target_width" }, { at: "project.video", input: "height", label: "Height", was: "target_height" }];
|
|
let slots = [{ roles: size, inputs: { width: 768, height: 512 } }];
|
|
const host = document.createElement("div");
|
|
document.body.append(host);
|
|
hub.setup({ host, app: { dom: createDom(), project: p, on: (fn) => { fire = fn; return () => {}; }, api: {}, say: () => {}, pipeline: { slots: () => slots } } });
|
|
const tab = () => [...host.querySelectorAll("[role=radio]")].find((b) => b.textContent === "Project").click();
|
|
tab();
|
|
assert.ok(!host.textContent.includes("Resolution is the"), "a model that takes no picture's shape has nothing to choose");
|
|
slots = [{ roles: [...size, { at: "project.resolution_basis", input: "basis" }], inputs: { width: 768, height: 512 } }];
|
|
fire("change");
|
|
tab();
|
|
assert.ok(host.textContent.includes("Resolution is the") && host.textContent.includes("Pixel count"), "the choice is on the Project tab");
|
|
const shown = [...host.querySelectorAll("input")].map((i) => i.value);
|
|
assert.ok(shown.includes("1344") && shown.includes("768"), "the size saved under the old name shows");
|
|
host.remove();
|
|
});
|
|
|
|
test("a group opened in the panel stays open when the panel redraws", async () => {
|
|
let fire;
|
|
const doc = { id: "P", frame_rate: 25, num_frames_per_scene: 49, scenes: [{ id: "a", text: "x" }], scene_renders: {} };
|
|
const p = { project: doc, get scenes() { return doc.scenes; }, get selected() { return doc.scenes[0]; }, selectedId: "a",
|
|
setText: () => {}, setScene: () => {}, edit: (fn) => fn(doc) };
|
|
const host = document.createElement("div");
|
|
document.body.append(host);
|
|
hub.setup({ host, app: { dom: createDom(), project: p, on: (fn) => { fire = fn; return () => {}; }, api: {}, say: () => {} } });
|
|
[...host.querySelectorAll("[role=radio]")].find((b) => b.textContent === "Project").click();
|
|
const group = () => [...host.querySelectorAll("details")].find((d) => d.textContent.includes("Video input"));
|
|
assert.equal(group().open, false, "closed to begin with");
|
|
group().open = true;
|
|
group().dispatchEvent(new window.Event("toggle"));
|
|
doc.name = "renamed"; fire("change");
|
|
assert.equal(group().open, true);
|
|
host.remove();
|
|
});
|
|
|
|
test("a pipeline that saves a picture and takes no length offers no Frames or FPS, on the Project tab or the Scene tab", async () => {
|
|
let fire;
|
|
const doc = { id: "P", frame_rate: 1, num_frames_per_scene: 1, video: { width: 768, height: 512 }, scenes: [{ id: "a", text: "x" }], scene_renders: {} };
|
|
const p = { project: doc, video: doc.video, get scenes() { return doc.scenes; }, get selected() { return doc.scenes[0]; }, selectedId: "a",
|
|
setText: () => {}, setScene: () => {}, setField: () => {}, setVideo: () => {}, edit: (fn) => fn(doc) };
|
|
const size = { id: "a", roles: [{ at: "project.video", input: "width", label: "Width" }], inputs: { width: 768 } };
|
|
let slots = [size, { id: "save", node: "SaveImage" }];
|
|
const host = document.createElement("div");
|
|
document.body.append(host);
|
|
hub.setup({ host, app: { dom: createDom(), project: p, on: (fn) => { fire = fn; return () => {}; }, api: {}, say: () => {}, pipeline: { slots: () => slots } } });
|
|
const words = () => host.textContent;
|
|
assert.ok(!words().includes("Frames") && !words().includes("FPS"), "no Length on the Scene tab");
|
|
[...host.querySelectorAll("[role=radio]")].find((b) => b.textContent === "Project").click();
|
|
assert.ok(!words().includes("Frames") && !words().includes("FPS") && !words().includes("per shot"), "no Frames, FPS or seconds per shot on the Project tab");
|
|
assert.ok(words().includes("Width"), "the size is still there");
|
|
slots = [size, { id: "save", node: "FunUISaveVideo" }]; // a clip pipeline offers them
|
|
fire("change");
|
|
assert.ok(words().includes("FPS"));
|
|
host.remove();
|
|
});
|