Files
olivv-cs-ComfyUI-FunPack/app/tests-layout/bin.spec.js
T
digital-garbage 9a4fb4eca0 app, core: what the project is generated at
Width, height and length belong to the PROJECT, not to a scene: a scene
cropped on the timeline and regenerated comes back at the project's size,
because the crop was a timeline decision and a regenerate is a new scene.
They sit in the Constructor beside the prompt -- the two things settled in
one sitting -- and travel to the graph on the path the prompt already uses.

No new mechanism: the pipeline declares roles at `project.video` the same
way it declares the prompt, so core still names no node and no setting. A
role whose value is a number gets a number control instead of a box; that
is the whole of the UI change. What the pipeline does not offer is not
shown, so a graph with no length input simply has no Length.

Bounds are the NODE's and stay there -- it already refuses a value outside
them with a reason, beside Generate. A project file is the one thing here
that outlives the code that wrote it, so everything read back out of one is
checked: a whole positive number, or gone.

Also, four faults the browser suite was already red with before this:

* Six tests still clicked a rail toggle that moved into the timeline head.
* At 420px the panel OVERLAYS the centre -- and the named toggles are in
  the centre, so the panel covered the control that closes it. That is the
  v4 trap this project forbids. They are not offered at that width now; the
  View menu, which nothing can cover, is the way in and out.
* A 768px window laid out 860px wide and scrolled sideways. Root cause, and
  it was never about one component: a flex item's automatic minimum is its
  CONTENT, so every stack, split, panel body and frame region in the app
  handed its content's width upward. A scroller that cannot shrink below
  what it holds is not a scroller -- the strip of scenes made the WINDOW as
  wide as the strip. Floored at the five places every layout routes through.
* A bin test counted `.cx-cell-glyph` across the whole page, so the timeline
  drawing cells with the same glyph in them broke a test about the bin.
2026-09-05 22:16:01 +03:00

97 lines
4.3 KiB
JavaScript

// The bin, where it has a box model and a real image loader.
//
// jsdom covers what the bin DOES. Two of its claims can only be checked in a
// browser: that a lazily-loaded thumbnail inside a size container actually
// loads, and that three views of the same results all fit the panel they are
// in.
import { test, expect } from "@playwright/test";
// A 2x2 PNG, so the loader has something real to fetch without a server behind
// it. Every other route on the dev server answers 404, and an image that never
// arrives would make this pass whether the loader ran or not.
const PIXEL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAEElEQVR4nGP4z8AARAwQCgAf7gP9i18U1AAAAABJRU5ErkJggg==";
async function app(page) {
await page.goto("/funpack/");
await page.waitForFunction(() => window.FunPack !== undefined);
}
const seed = (page, names) => page.evaluate((list) => {
window.FunPack.bin.absorb(list.map((filename) => ({ filename, subfolder: "", type: "output" })));
}, names);
test("a lazily-loaded thumbnail inside the bin's grid actually loads", async ({ page }) => {
// `loading="lazy"` inside a `container-type: inline-size` wrapper is the one
// combination worth proving: a lazy image that never enters the loader leaves
// an empty box forever, and nothing in the app would report it. Under
// containment that is a real possibility, and jsdom -- which applies no CSS
// and loads no images -- cannot tell either way.
//
// The image is built here rather than taken from a bin entry because the dev
// server has no /view: a real entry's thumbnail 404s and the kit replaces it
// with its glyph before this could look at it. What is under test is the
// ENVIRONMENT the kit puts an image in, so the grid is the real one.
await app(page);
await seed(page, ["pixel.png"]);
await page.evaluate((src) => {
const img = document.createElement("img");
img.className = "cx-cell-img";
img.loading = "lazy";
img.alt = "";
img.src = src;
document.querySelector(".cx-gallery-wrap .cx-gallery").append(img);
}, PIXEL);
const img = page.locator(".cx-gallery > img");
await expect.poll(() => img.evaluate((i) => i.naturalWidth), { timeout: 5000 }).toBe(2);
});
test("a result whose file cannot be fetched shows the glyph, not a broken picture", async ({ page }) => {
// The dev server answers 404 for every /view, so this is the real path.
await app(page);
await seed(page, ["gone.png"]);
// Scoped to the bin: the timeline draws cells with the same glyph in them, so
// an unscoped count here stopped being about the bin the day it arrived.
const bin = page.locator('[aria-label="Media bin"]');
await expect(bin.locator(".cx-cell-glyph")).toHaveCount(1);
await expect(bin.locator("img")).toHaveCount(0);
await expect(bin.locator(".cx-cell-name")).toHaveText(["gone.png"]);
});
test("every view of the bin fits the panel", async ({ page }) => {
await app(page);
await seed(page, ["rooftop_dusk_00001_.png", "clip_take3.mp4",
"anchor_frame_reference_that_is_far_too_long_00042_.png"]);
for (const view of ["grid", "list", "icons"]) {
await page.evaluate((v) => window.FunPack.bin.setView(v), view);
const panel = await page.locator('[aria-label="Media bin"]').boundingBox();
const cells = page.locator(".cx-cell, .cx-media-row");
await expect(cells).toHaveCount(3);
for (let i = 0; i < 3; i++) {
const box = await cells.nth(i).boundingBox();
expect(box.width, `${view}: an entry is wider than the bin`)
.toBeLessThanOrEqual(panel.width + 1);
expect(box.height, `${view}: an entry has no height`).toBeGreaterThan(8);
}
}
});
test("a long filename does not widen the bin", async ({ page }) => {
// The bin is in a docked panel with a fixed width. A name that pushes it wider
// pushes the preview over, which is v4's media bin exactly.
await app(page);
const before = await page.locator('[aria-label="Media bin"]').boundingBox();
await seed(page, ["a_name_far_longer_than_any_panel_could_ever_be_00000000042_.png"]);
for (const view of ["grid", "list", "icons"]) {
await page.evaluate((v) => window.FunPack.bin.setView(v), view);
const after = await page.locator('[aria-label="Media bin"]').boundingBox();
expect(after.width, `${view} widened the bin`).toBeLessThanOrEqual(before.width + 1);
}
});