Files
Artificial-Sweetener-Simple…/web/tests/orderedMediaPreview.test.ts
T

128 lines
4.1 KiB
TypeScript

// SimpleSyrup - workflow-focused ComfyUI extensions for image generation
// Copyright (C) 2026 Artificial Sweetener and contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
import { describe, expect, it, vi } from "vitest";
import { OrderedMediaPreviewController } from "../src/orderedMediaPreview";
import type { ComfyNodeExecutionOutput, Logger } from "../src/types";
const OUTPUT: ComfyNodeExecutionOutput = {
images: [{ filename: "mask.png", subfolder: "", type: "temp" }],
animated: [false]
};
describe("OrderedMediaPreviewController", () => {
it("renders empty selections without making a preview request", () => {
const target = fakeTarget();
const client = vi.fn();
const controller = new OrderedMediaPreviewController(target, "mask loader");
controller.refresh([], client);
expect(target.clear).toHaveBeenCalledOnce();
expect(client).not.toHaveBeenCalled();
});
it("loads and publishes exact ordered references", async () => {
const target = fakeTarget();
const controller = new OrderedMediaPreviewController(target, "mask loader");
controller.refresh(["mask.png"], () => Promise.resolve(OUTPUT));
await vi.waitFor(() => {
expect(target.publish).toHaveBeenCalledWith(OUTPUT);
});
});
it("discards stale responses after a newer channel request", async () => {
const first = deferred<ComfyNodeExecutionOutput>();
const second = deferred<ComfyNodeExecutionOutput>();
const target = fakeTarget();
const controller = new OrderedMediaPreviewController(target, "mask loader");
const latest: ComfyNodeExecutionOutput = {
images: [{ filename: "blue.png", subfolder: "", type: "temp" }]
};
controller.refresh(["mask.png"], () => first.promise);
controller.refresh(["mask.png"], () => second.promise);
second.resolve(latest);
await vi.waitFor(() => {
expect(target.publish).toHaveBeenCalledWith(latest);
});
first.resolve(OUTPUT);
await Promise.resolve();
expect(target.publish).toHaveBeenCalledTimes(1);
});
it("keeps failures visible and invalidates pending work on dispose", async () => {
const error = new Error("preview failed");
const logger: Logger = { warn: vi.fn() };
const target = fakeTarget();
const controller = new OrderedMediaPreviewController(target, "mask loader", logger);
controller.refresh(["mask.png"], () => Promise.reject(error));
await vi.waitFor(() => {
expect(target.clear).toHaveBeenCalledOnce();
});
expect(logger.warn).toHaveBeenCalledWith(
"Could not refresh mask loader preview: preview failed",
error
);
const pending = deferred<ComfyNodeExecutionOutput>();
controller.refresh(["mask.png"], () => pending.promise);
controller.dispose();
pending.resolve(OUTPUT);
await Promise.resolve();
expect(target.publish).not.toHaveBeenCalled();
});
it("preserves the last native preview when a replacement fails", async () => {
const error = new Error("replacement failed");
const logger: Logger = { warn: vi.fn() };
const target = fakeTarget();
const controller = new OrderedMediaPreviewController(
target,
"image loader",
logger
);
controller.refresh(["mask.png"], () => Promise.resolve(OUTPUT));
await vi.waitFor(() => {
expect(target.publish).toHaveBeenCalledWith(OUTPUT);
});
controller.refresh(["other.png"], () => Promise.reject(error));
await vi.waitFor(() => {
expect(logger.warn).toHaveBeenCalledWith(
"Could not refresh image loader preview: replacement failed",
error
);
});
expect(target.clear).not.toHaveBeenCalled();
});
});
function fakeTarget() {
return {
publish: vi.fn(),
clear: vi.fn()
};
}
function deferred<T>(): { promise: Promise<T>; resolve(value: T): void } {
let resolvePromise: ((value: T) => void) | undefined;
const promise = new Promise<T>((resolve) => {
resolvePromise = resolve;
});
return {
promise,
resolve(value: T) {
if (!resolvePromise) throw new Error("Deferred promise was not initialized.");
resolvePromise(value);
}
};
}