Files
ComfyAssets-ComfyUI_PromptM…/tests/js/list-state.test.js
T
Vito Sansevero 6e3fa65914 feat(gallery): duplicate scan runs as a background job with progress; infinite scrolling
The duplicate scan was one blocking request with a spinner: no progress,
a modal that could not be put away, and a result lost if the tab closed.

- POST /prompt_manager/scan_duplicates/stream starts the scan as a
  background job (same ScanJob fan-out as the output scan) or attaches to
  the running one; GET /prompt_manager/scan_duplicates/status reports it.
  The job holds the duplicates lock, so deletes are refused while it runs.
- hashing runs on the configured worker threads and reports progress
  about every percent
- the gallery modal shows a progress bar, closing it minimizes to a pill
  that reopens it, results wait in the pill if the modal was closed, and a
  reload re-attaches to a running scan

Infinite scrolling is a new Display Setting (off by default): the next page
is appended when the viewport nears the bottom, pagination controls hide,
and the load-more rule lives in list-state.js where it is unit tested.
Thumb-size buttons and the column setting no longer override each other.
2026-10-08 18:01:35 -07:00

66 lines
3.6 KiB
JavaScript

// Run with: node --test tests/js/
const test = require("node:test");
const assert = require("node:assert/strict");
const { nextPageState, shouldSaveEdit, hasMorePages, shouldLoadMore } = require("../../web/js/list-state.js");
test("hasMorePages follows pagination.has_more only while pages keep arriving", () => {
assert.equal(hasMorePages({ pagination: { has_more: true } }, 5000), true);
assert.equal(hasMorePages({ pagination: { has_more: false } }, 5000), false);
assert.equal(hasMorePages({ pagination: { has_more: true } }, 0), false, "an empty page ends the loop");
assert.equal(hasMorePages({}, 10), false, "no pagination block means a single page");
assert.equal(hasMorePages(null, 10), false);
});
const current = { page: 2, limit: 50, total: 120, totalPages: 3 };
test("nextPageState moves to the requested page once the fetch succeeded", () => {
assert.deepEqual(nextPageState(current, { page: 3, total: 120 }), { page: 3, limit: 50, total: 120, totalPages: 3 });
});
test("nextPageState clamps a page beyond the end when the total shrank", () => {
assert.deepEqual(nextPageState(current, { page: 3, total: 60 }), { page: 2, limit: 50, total: 60, totalPages: 2 });
});
test("nextPageState never goes below page 1 and has at least one page", () => {
assert.deepEqual(nextPageState(current, { page: 0, total: 0 }), { page: 1, limit: 50, total: 0, totalPages: 1 });
assert.equal(nextPageState(current, { page: -5, total: 10 }).page, 1);
});
test("nextPageState keeps the current page and total when the response omits them", () => {
assert.deepEqual(nextPageState(current, {}), current);
assert.deepEqual(nextPageState(current, { total: "many", page: "x" }), current);
});
test("nextPageState accepts a new limit and recomputes totalPages", () => {
assert.deepEqual(nextPageState(current, { page: 1, limit: 25, total: 120 }), { page: 1, limit: 25, total: 120, totalPages: 5 });
assert.equal(nextPageState(current, { limit: 0 }).limit, 50, "a zero limit is ignored");
});
test("nextPageState does not mutate its input", () => {
const before = { ...current };
nextPageState(current, { page: 1, total: 5 });
assert.deepEqual(current, before);
});
test("shouldSaveEdit is true only for a non-empty text that changed", () => {
assert.equal(shouldSaveEdit("old", "new"), true);
assert.equal(shouldSaveEdit("same", "same"), false);
assert.equal(shouldSaveEdit("same", " same \n"), false, "whitespace-only changes are not saved");
assert.equal(shouldSaveEdit("old", " "), false, "an emptied prompt is not saved");
assert.equal(shouldSaveEdit("old", null), false);
});
test("shouldLoadMore asks for the next page only near the bottom with pages left", () => {
const base = {
infiniteScroll: true, loading: false, page: 1, limit: 100, total: 1000,
scrollTop: 0, clientHeight: 800, scrollHeight: 4000, threshold: 600,
};
assert.equal(shouldLoadMore(base), false, "far from the bottom");
assert.equal(shouldLoadMore({ ...base, scrollTop: 2700 }), true, "within the threshold");
assert.equal(shouldLoadMore({ ...base, scrollTop: 2700, loading: true }), false, "a load is in flight");
assert.equal(shouldLoadMore({ ...base, scrollTop: 2700, infiniteScroll: false }), false, "setting off");
assert.equal(shouldLoadMore({ ...base, scrollTop: 2700, page: 10 }), false, "last page reached");
assert.equal(shouldLoadMore({ ...base, scrollHeight: 700 }), true, "content shorter than the viewport");
assert.equal(shouldLoadMore({ ...base, total: 0, scrollHeight: 700 }), false, "nothing to load");
});