Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
145 lines
9.2 KiB
JavaScript
145 lines
9.2 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { setupDom, teardownDom } from "../../composer/tests/_dom.js";
|
|
|
|
let hub;
|
|
test.before(async () => { setupDom(); hub = (await import("./index.js")).default; });
|
|
test.after(() => teardownDom());
|
|
|
|
test("a click on a tile looks at it on the monitor; the context menu's resolution-source entry sets the scene's source, audio refused", async () => {
|
|
const set = [], said = [];
|
|
const project = { selected: { id: "s1" }, setScene: (...a) => set.push(a) };
|
|
const api = { media: async () => ({ media: [{ id: "m1", name: "cat.png", kind: "image" }, { id: "m2", name: "a.wav", kind: "audio" }] }) };
|
|
const host = document.createElement("div");
|
|
const app = { project, api, mediaPeek: {}, say: (w) => said.push(w), on: () => () => {} };
|
|
hub.setup({ host, app });
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
const cells = [...host.querySelectorAll(".cx-cell")];
|
|
assert.equal(cells.length, 2);
|
|
const named = (n) => cells.find((x) => x.textContent.includes(n));
|
|
named("cat.png").click();
|
|
assert.deepEqual([app.mediaPeek.item.id, said.filter((w) => w === "media.peek")], ["m1", ["media.peek"]]);
|
|
assert.deepEqual(set, []); // looking changes nothing
|
|
named("a.wav").click();
|
|
assert.equal(app.mediaPeek.item.id, "m2");
|
|
named("cat.png").dispatchEvent(new window.MouseEvent("contextmenu", { bubbles: true, cancelable: true }));
|
|
[...document.querySelectorAll(".cx-menu-item")].find((b) => b.textContent.includes("aspect ratio source")).click();
|
|
assert.deepEqual(set, [["s1", "source_image", "m1"]]);
|
|
});
|
|
|
|
|
|
test("the selected scene's references are marked on their tiles, and the menu takes one back off", async () => {
|
|
const set = [];
|
|
const project = { selected: { id: "s1", references: ["m2", "m1"] }, setScene: (...a) => set.push(a) };
|
|
const api = { media: async () => ({ media: [{ id: "m1", name: "cat.png", kind: "image" }, { id: "m2", name: "dog.png", kind: "image" }, { id: "m3", name: "owl.png", kind: "image" }] }) };
|
|
const host = document.createElement("div");
|
|
hub.setup({ host, app: { project, api, mediaPeek: {}, say: () => {}, on: () => () => {} } });
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
const named = (n) => [...host.querySelectorAll(".cx-cell")].find((x) => x.textContent.includes(n));
|
|
const flag = (n) => [...named(n).querySelectorAll(".cx-cell-flag")].map((x) => x.textContent).join();
|
|
assert.deepEqual([flag("dog.png"), flag("cat.png"), flag("owl.png")], ["R1", "R2", ""]);
|
|
const clear = [...host.querySelectorAll("button")].find((b) => b.textContent.startsWith("Clear references"));
|
|
assert.ok(clear && !clear.hidden && /\(2\)/.test(clear.textContent), "one click takes them all off");
|
|
document.querySelectorAll(".cx-popover").forEach((n) => n.remove());
|
|
named("dog.png").dispatchEvent(new window.MouseEvent("contextmenu", { bubbles: true, cancelable: true }));
|
|
const stop = [...document.querySelectorAll(".cx-menu-item")].find((b) => b.textContent.includes("Stop using as a reference"));
|
|
assert.ok(stop && !stop.disabled, "not greyed: it can be taken off");
|
|
stop.click();
|
|
clear.click();
|
|
assert.deepEqual(set, [["s1", "references", ["m1"]], ["s1", "references", []]]);
|
|
});
|
|
|
|
test("a changed pin repaints the slot numbers on the tiles even when the list of references is the same", async () => {
|
|
const fixed = { m1: 2 };
|
|
const project = { selected: { id: "s1", references: ["m1", "m2"] }, project: { fixed_references: fixed }, setScene: () => {} };
|
|
const api = { media: async () => ({ media: [{ id: "m1", name: "cat.png", kind: "image" }, { id: "m2", name: "dog.png", kind: "image" }] }) };
|
|
const host = document.createElement("div");
|
|
let fire;
|
|
hub.setup({ host, app: { project, api, mediaPeek: {}, say: () => {}, on: (cb) => { fire = cb; return () => {}; } } });
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
const flag = (n) => [...[...host.querySelectorAll(".cx-cell")].find((x) => x.textContent.includes(n)).querySelectorAll(".cx-cell-flag")].map((x) => x.textContent).join();
|
|
assert.deepEqual([flag("cat.png"), flag("dog.png")], ["📌R2", "R1"], "pinned at 2 (marked with a pin), the other takes the free number");
|
|
delete fixed.m1;
|
|
fire("project");
|
|
assert.deepEqual([flag("cat.png"), flag("dog.png")], ["R1", "R2"], "unpinned: back to list order");
|
|
});
|
|
|
|
test("a reference's tile menu pins it at the slot number it holds, for every scene, and takes the pin off again", async () => {
|
|
const doc = { fixed_references: {} };
|
|
const project = { selected: { id: "s1", references: ["m1", "m2"] }, project: doc, setScene: () => {}, edit: (fn) => fn(doc) };
|
|
const api = { media: async () => ({ media: [{ id: "m1", name: "cat.png", kind: "image" }, { id: "m2", name: "dog.png", kind: "image" }, { id: "m3", name: "owl.png", kind: "image" }] }) };
|
|
const host = document.createElement("div");
|
|
hub.setup({ host, app: { project, api, mediaPeek: {}, say: () => {}, on: () => () => {} } });
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
const named = (n) => [...host.querySelectorAll(".cx-cell")].find((x) => x.textContent.includes(n));
|
|
const menuFor = (n) => {
|
|
document.querySelectorAll(".cx-popover").forEach((x) => x.remove());
|
|
named(n).dispatchEvent(new window.MouseEvent("contextmenu", { bubbles: true, cancelable: true }));
|
|
return [...document.querySelectorAll(".cx-menu-item")].map((b) => b.textContent);
|
|
};
|
|
assert.ok(menuFor("dog.png").includes("Pin reference to slot R2"));
|
|
assert.ok(!menuFor("owl.png").some((t) => t.includes("slot R")), "a picture that is no reference has nothing to pin");
|
|
menuFor("dog.png");
|
|
[...document.querySelectorAll(".cx-menu-item")].find((b) => b.textContent === "Pin reference to slot R2").click();
|
|
assert.deepEqual(doc.fixed_references, { m2: 2 });
|
|
assert.ok(menuFor("dog.png").includes("Unpin reference from slot R2"));
|
|
[...document.querySelectorAll(".cx-menu-item")].find((b) => b.textContent === "Unpin reference from slot R2").click();
|
|
assert.deepEqual(doc.fixed_references, {});
|
|
});
|
|
|
|
test("toggling a reference leaves the other tiles untouched, so the bin does not jump or reload its thumbnails", async () => {
|
|
const scene = { id: "s1", references: [] };
|
|
const project = { selected: scene, project: { fixed_references: {} }, setScene: () => {} };
|
|
const api = { media: async () => ({ media: [{ id: "m1", name: "cat.png", kind: "image" }, { id: "m2", name: "dog.png", kind: "image" }, { id: "m3", name: "owl.png", kind: "image" }] }) };
|
|
const host = document.createElement("div");
|
|
let fire;
|
|
hub.setup({ host, app: { project, api, mediaPeek: {}, say: () => {}, on: (cb) => { fire = cb; return () => {}; } } });
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
const tiles = () => [...host.querySelectorAll(".cx-cell")];
|
|
const before = tiles();
|
|
scene.references = ["m2"];
|
|
fire("change");
|
|
const after = tiles();
|
|
assert.equal(after.length, 3);
|
|
assert.equal(after[0], before[0], "cat.png: same tile");
|
|
assert.equal(after[2], before[2], "owl.png: same tile");
|
|
assert.notEqual(after[1], before[1], "dog.png is the one that changed: it gained its R1 mark");
|
|
assert.equal(after[1].querySelector(".cx-cell-flag").textContent, "R1");
|
|
});
|
|
|
|
test("redrawing the bin keeps keyboard focus on the tile that has it", async () => {
|
|
const project = { selected: { id: "s1", references: [] }, project: {}, setScene: () => {} };
|
|
const api = { media: async () => ({ media: [{ id: "m1", name: "cat.png", kind: "image" }, { id: "m2", name: "dog.png", kind: "image" }] }) };
|
|
const host = document.createElement("div");
|
|
document.body.append(host);
|
|
let fire;
|
|
hub.setup({ host, app: { project, api, mediaPeek: {}, say: () => {}, on: (cb) => { fire = cb; return () => {}; } } });
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
const tile = [...host.querySelectorAll(".cx-cell")].find((x) => x.textContent.includes("dog.png"));
|
|
tile.focus(); const was = document.activeElement === tile;
|
|
project.selected.references = ["m1"];
|
|
fire("project");
|
|
const now = [...host.querySelectorAll(".cx-cell")].find((x) => x.textContent.includes("dog.png")); assert.ok(was, "focusable"); assert.equal(now, tile, "same tile node"); assert.equal(document.activeElement, tile, "the focused tile survives the redraw");
|
|
host.remove();
|
|
});
|
|
|
|
test("pinning the first reference repaints its tile even though no number moves; toggling the focused tile keeps its focus", async () => {
|
|
const fixed = {};
|
|
const project = { selected: { id: "s1", references: ["m1", "m2"] }, project: { fixed_references: fixed }, setScene: () => {} };
|
|
const api = { media: async () => ({ media: [{ id: "m1", name: "cat.png", kind: "image" }, { id: "m2", name: "dog.png", kind: "image" }] }) };
|
|
const host = document.createElement("div");
|
|
document.body.append(host);
|
|
let fire;
|
|
hub.setup({ host, app: { project, api, mediaPeek: {}, say: () => {}, on: (cb) => { fire = cb; return () => {}; } } });
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
const cell = (n) => [...host.querySelectorAll(".cx-cell")].find((x) => x.textContent.includes(n));
|
|
fixed.m1 = 1;
|
|
fire("project");
|
|
assert.match(cell("cat.png").textContent, /📌R1/, "pinned: the pin shows");
|
|
cell("dog.png").focus();
|
|
project.selected.references = ["m1"];
|
|
fire("project");
|
|
assert.equal(document.activeElement, cell("dog.png"), "the focused tile, rebuilt, keeps the focus");
|
|
host.remove();
|
|
});
|