// The bin: what a session has produced, and what is on screen. import test from "node:test"; import assert from "node:assert/strict"; import { setupDom, teardownDom, fire } from "../../composer/tests/_dom.js"; let createBin, keyOf, kindOf, recallView; test.before(async () => { setupDom(); await import("../../composer/composer.js"); ({ createBin, keyOf, kindOf, recallView } = await import("../bin.js")); }); test.after(() => teardownDom()); const file = (filename, extra = {}) => ({ filename, subfolder: "", type: "output", ...extra }); function bin(opts = {}) { const opened = []; const handle = createBin({ persist: false, onOpen: (item) => opened.push(item), ...opts }); document.body.replaceChildren(handle.host.node); // Not a spread of the handle: `items`, `selected` and `view` are getters, and // spreading one reads it ONCE -- every assertion would then be about the bin // as it was before the test did anything to it. return { opened, control: handle.control, node: handle.host.node, absorb: (...a) => handle.absorb(...a), open: (...a) => handle.open(...a), setView: (...a) => handle.setView(...a), get items() { return handle.items; }, get selected() { return handle.selected; }, get view() { return handle.view; }, cells: () => [...handle.host.node.querySelectorAll(".cx-cell, .cx-media-row")], }; } test("a result arrives, is shown, and is the one selected", () => { const b = bin(); b.absorb([file("a.png")]); assert.equal(b.items.length, 1); assert.equal(b.opened.length, 1); assert.equal(b.opened[0].url, "/view?filename=a.png&subfolder=&type=output"); assert.equal(b.opened[0].kind, "image"); assert.equal(b.selected, keyOf(file("a.png"))); assert.equal(b.cells().length, 1); assert.equal(b.cells()[0].getAttribute("aria-selected"), "true"); }); test("the same file arriving twice is one entry", () => { // A reload adopts a finished run and replays its outputs, so the same three // fields arrive again. Keyed by arrival, the bin would fill with copies of // whatever was generated before the last reload. const b = bin(); b.absorb([file("a.png")]); b.absorb([file("a.png")]); b.absorb([file("a.png"), file("b.png")]); assert.deepEqual(b.items.map((i) => i.label), ["b.png", "a.png"]); }); test("a state carrying nothing new leaves the user's choice alone", () => { // absorb() runs on EVERY state change -- a progress message is a state // change. Redrawing unconditionally walked a click on an older result back to // the newest one within milliseconds. const b = bin(); b.absorb([file("first.png"), file("second.png")]); assert.equal(b.items[0].label, "second.png"); b.open(keyOf(file("first.png"))); const chosen = b.selected; b.opened.length = 0; b.absorb([file("first.png"), file("second.png")]); // the same run, again b.absorb([]); b.absorb(); assert.equal(b.selected, chosen, "an unchanged state moved the selection"); assert.deepEqual(b.opened, [], "an unchanged state re-showed a result"); }); test("a result from a NEW run takes the view back", () => { const b = bin(); b.absorb([file("old.png")]); b.open(keyOf(file("old.png"))); b.opened.length = 0; b.absorb([file("new.png")]); assert.equal(b.selected, keyOf(file("new.png"))); assert.equal(b.opened.length, 1); assert.equal(b.opened[0].label, "new.png"); }); test("the newest of a batch is the one shown", () => { const b = bin(); b.absorb([file("one.png"), file("two.png"), file("three.png")]); assert.equal(b.opened.length, 1, "every file in a batch was shown in turn"); assert.equal(b.opened[0].label, "three.png"); assert.deepEqual(b.items.map((i) => i.label), ["three.png", "two.png", "one.png"]); }); test("open: false adds a file to the bin without moving what the viewer shows", () => { // A save action's own round trip (an upload) can finish well after the user // has moved on to looking at something else -- absorb()'s normal auto-open // exists for a RUN's own result appearing, not for that. `open: false` is // what a save uses instead. const b = bin(); b.absorb([file("already-open.png")]); // the normal path: this IS opened assert.equal(b.opened.length, 1); b.absorb([file("saved-later.png")], { open: false }); assert.equal(b.items.length, 2, "the file was not added to the bin at all"); assert.equal(b.opened.length, 1, "opened a second time despite open: false"); }); test("open: false still dedupes -- a file already in the bin is not added twice", () => { const b = bin(); b.absorb([file("a.png")]); const added = b.absorb([file("a.png")], { open: false }); assert.equal(added.length, 0); assert.equal(b.items.length, 1); }); test("a video is known as one, and never becomes a