Files
ComfyAssets-ComfyUI_PromptM…/tests/js/image-helpers.test.js
T
Vito Sansevero 85d1031179 fix: thumbnails are written atomically and served under mtime-versioned urls
Cards showed "Failed to load" for thumbnails that were valid on disk. The
browser had fetched them while the thumbnail run was still writing the
file and, because image responses are cached for an hour, kept the broken
bytes. The card also had no fallback to the original.

- image and video thumbnails are written to a hidden temp sibling and
  renamed into place, so a partial file is never visible at the final path
- every thumbnail_url (gallery listing, prompt images, duplicates, autotag
  listing) carries ?v=<mtime>, so a regenerated thumbnail gets a new URL
  instead of the cached old one
- a 404 from the image serve route is marked no-store; ComfyUI's cache
  middleware would otherwise let browsers cache the miss for an hour
- image-helpers gains fallbackImageSource(): the original is tried once
  before a card gives up

Test hygiene: the autotag tests now stub ComfyUI's server module like the
other API test modules, so the module passes when run on its own.
2026-10-08 18:01:35 -07:00

111 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Run with: node --test tests/js/
const test = require("node:test");
const assert = require("node:assert/strict");
const {
formatImageCaption,
workflowDocumentHtml,
autotagSingleBody,
fallbackImageSource,
} = require("../../web/js/image-helpers.js");
const WD14 = { modelType: "wd14-vit", generalThreshold: 0.35, characterThreshold: 0.85, prompt: "ignored" };
const CAPTION = { modelType: "blip", generalThreshold: 0.35, characterThreshold: 0.85, prompt: "describe it" };
test("autotagSingleBody sends image_id for a database row", () => {
const body = autotagSingleBody({ id: 42, prompt_id: 7, image_path: "/out/a.png" }, WD14);
assert.equal(body.image_id, 42);
assert.equal("path" in body, false);
});
test("autotagSingleBody sends path for an output-folder entry whose id is a digest", () => {
const body = autotagSingleBody({ id: "3f2a9c1b0d4e5f67", path: "/out/sub/a.png", filename: "a.png" }, WD14);
assert.equal(body.path, "/out/sub/a.png");
assert.equal("image_id" in body, false);
});
test("autotagSingleBody prefers path when a numeric-looking string id has no prompt link", () => {
const body = autotagSingleBody({ id: "1234567890abcdef", path: "/out/b.png" }, WD14);
assert.deepEqual(Object.keys(body).filter((k) => k === "path" || k === "image_id"), ["path"]);
});
test("autotagSingleBody adds wd14 thresholds and no prompt", () => {
const body = autotagSingleBody({ id: 1 }, WD14);
assert.equal(body.model_type, "wd14-vit");
assert.equal(body.general_threshold, 0.35);
assert.equal(body.character_threshold, 0.85);
assert.equal("prompt" in body, false);
});
test("autotagSingleBody adds the prompt for caption models and no thresholds", () => {
const body = autotagSingleBody({ id: 1 }, CAPTION);
assert.equal(body.model_type, "blip");
assert.equal(body.prompt, "describe it");
assert.equal("general_threshold" in body, false);
});
test("autotagSingleBody does not mutate its inputs", () => {
const image = Object.freeze({ id: 1 });
const settings = Object.freeze({ ...WD14 });
assert.doesNotThrow(() => autotagSingleBody(image, settings));
});
const XSS = '"><img src=x onerror=1>';
const formatFileSize = (bytes) => `${bytes} B`;
test("formatImageCaption joins dimensions and size with the separator", () => {
const caption = formatImageCaption({ width: 640, height: 480, file_size: 2048 }, { formatFileSize });
assert.equal(caption, "640×480 | 2048 B");
});
test("formatImageCaption honours a custom separator and omits a missing size", () => {
assert.equal(formatImageCaption({ width: 1, height: 2 }, { formatFileSize, separator: " • " }), "1×2");
});
test("formatImageCaption falls back to Unknown size when a dimension is missing", () => {
assert.equal(formatImageCaption({ width: 640 }, { formatFileSize }), "Unknown size");
assert.equal(formatImageCaption({}, { formatFileSize }), "Unknown size");
assert.equal(formatImageCaption(null, { formatFileSize }), "Unknown size");
});
test("formatImageCaption escapes string dimensions so they cannot break out of an attribute", () => {
const caption = formatImageCaption({ width: XSS, height: 10 }, { formatFileSize });
assert.ok(!caption.includes("<"), caption);
assert.ok(!caption.includes('"'), caption);
assert.ok(caption.includes("&quot;&gt;&lt;img"), caption);
});
test("formatImageCaption ignores a non-numeric file_size instead of formatting it", () => {
const caption = formatImageCaption({ width: 1, height: 1, file_size: XSS }, { formatFileSize });
assert.equal(caption, "1×1");
});
test("formatImageCaption escapes whatever formatFileSize returns", () => {
const caption = formatImageCaption({ width: 1, height: 1, file_size: 5 }, { formatFileSize: () => "<b>" });
assert.equal(caption, "1×1 | &lt;b&gt;");
});
test("workflowDocumentHtml escapes the JSON so it cannot close the pre block", () => {
const html = workflowDocumentHtml({ note: "</pre><script>alert(1)</script>" });
assert.ok(html.includes("&lt;/pre&gt;&lt;script&gt;"), html);
assert.ok(!html.includes("</pre><script>"), html);
assert.ok(html.startsWith("<!DOCTYPE html>"));
assert.ok(html.includes("<title>ComfyUI Workflow Data</title>"));
});
test("workflowDocumentHtml pretty-prints the workflow with two-space indentation", () => {
const html = workflowDocumentHtml({ a: 1 });
assert.ok(html.includes('{\n &quot;a&quot;: 1\n}'), html);
});
test("fallbackImageSource offers the original once when a thumbnail fails", () => {
const dataset = { original: "/serve/a.png", thumbnail: "/serve/thumbnails/a_thumb.png?v=1" };
assert.equal(fallbackImageSource("/serve/thumbnails/a_thumb.png?v=1", dataset), "/serve/a.png");
});
test("fallbackImageSource gives up when the original itself failed or was already tried", () => {
assert.equal(fallbackImageSource("/serve/a.png", { original: "/serve/a.png" }), null);
assert.equal(fallbackImageSource("/serve/t.png", { original: "/serve/a.png", fellBack: "1" }), null);
assert.equal(fallbackImageSource("/serve/t.png", {}), null);
assert.equal(fallbackImageSource("/serve/t.png", undefined), null);
});