Files

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();
});