212 lines
7.4 KiB
TypeScript
212 lines
7.4 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 {
|
|
SIMPLE_SYRUP_SETTING_DESCRIPTION,
|
|
SIMPLE_SYRUP_SETTING_ID,
|
|
SIMPLE_SYRUP_SETTING_LABEL
|
|
} from "../../src/downloadableModelsSetting";
|
|
import {
|
|
EXTERNAL_LLM_API_KEY_SETTING_ID,
|
|
EXTERNAL_LLM_ENDPOINT_SETTING_ID
|
|
} from "../../src/externalLlmSettings";
|
|
import { QUANT_CACHE_SETTING_ID } from "../../src/quantCacheSetting";
|
|
import { registerSimpleSyrupSettings } from "../../src/settingsRegistration";
|
|
import type { SimpleSyrupSettingsApi } from "../../src/settingsRegistration";
|
|
import { createFakeComfyApp } from "../support/testUtils";
|
|
import {
|
|
defaultExternalLLMSettings,
|
|
defaultQuantCacheStatus,
|
|
fakeSettingsApi,
|
|
flushPromises,
|
|
getDefinition,
|
|
renderSetting,
|
|
requiredButton,
|
|
requiredInput
|
|
} from "./settingsTestSupport";
|
|
|
|
describe("general and quant-cache settings registration", () => {
|
|
it("registers every setting from backend state", async () => {
|
|
const app = createFakeComfyApp();
|
|
await registerSimpleSyrupSettings(app, fakeSettingsApi(false));
|
|
|
|
expect(app.ui.settings.definitions).toHaveLength(4);
|
|
expect(app.ui.settings.definitions[0]).toMatchObject({
|
|
id: SIMPLE_SYRUP_SETTING_ID,
|
|
name: SIMPLE_SYRUP_SETTING_LABEL,
|
|
type: "boolean",
|
|
defaultValue: false,
|
|
tooltip: SIMPLE_SYRUP_SETTING_DESCRIPTION
|
|
});
|
|
expect(SIMPLE_SYRUP_SETTING_DESCRIPTION).toContain("WD14 tagger");
|
|
expect(SIMPLE_SYRUP_SETTING_DESCRIPTION).toContain("Ultralytics");
|
|
expect(app.ui.settings.settings[0]?.value).toBe(false);
|
|
expect(app.ui.settings.definitions[1]).toMatchObject({
|
|
id: QUANT_CACHE_SETTING_ID,
|
|
sortOrder: 321
|
|
});
|
|
expect(app.ui.settings.definitions[2]).toMatchObject({
|
|
id: EXTERNAL_LLM_ENDPOINT_SETTING_ID,
|
|
sortOrder: 320
|
|
});
|
|
expect(app.ui.settings.definitions[3]).toMatchObject({
|
|
id: EXTERNAL_LLM_API_KEY_SETTING_ID,
|
|
sortOrder: 319
|
|
});
|
|
});
|
|
|
|
it("saves downloadable-model setting changes", async () => {
|
|
const app = createFakeComfyApp();
|
|
const refreshComboInNodes = vi.fn().mockResolvedValue(undefined);
|
|
app.refreshComboInNodes = refreshComboInNodes;
|
|
const saveSettings = vi
|
|
.fn<SimpleSyrupSettingsApi["saveSettings"]>()
|
|
.mockResolvedValue({
|
|
show_downloadable_models: true,
|
|
quant_cache_limit_gib: 20
|
|
});
|
|
const api = fakeSettingsApi(false);
|
|
api.saveSettings = saveSettings;
|
|
|
|
await registerSimpleSyrupSettings(app, api);
|
|
await app.ui.settings.definitions[0]?.onChange?.(true);
|
|
|
|
expect(saveSettings).toHaveBeenCalledWith({
|
|
show_downloadable_models: true,
|
|
quant_cache_limit_gib: 20
|
|
});
|
|
expect(app.ui.settings.settings[0]?.value).toBe(true);
|
|
expect(refreshComboInNodes).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("falls back to the default and warns when backend load fails", async () => {
|
|
const app = createFakeComfyApp();
|
|
const logger = { warn: vi.fn() };
|
|
const api = fakeSettingsApi(true);
|
|
api.getSettings = vi.fn().mockRejectedValue(new Error("offline"));
|
|
api.getExternalLLMSettings = vi
|
|
.fn()
|
|
.mockResolvedValue(defaultExternalLLMSettings());
|
|
|
|
await registerSimpleSyrupSettings(app, api, logger);
|
|
|
|
expect(app.ui.settings.settings[0]?.value).toBe(true);
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
expect.stringContaining("Could not load SimpleSyrup settings"),
|
|
expect.any(Error)
|
|
);
|
|
});
|
|
|
|
it("restores the previous downloadable-model value when save fails", async () => {
|
|
const app = createFakeComfyApp();
|
|
const logger = { warn: vi.fn() };
|
|
const api = fakeSettingsApi(false);
|
|
api.saveSettings = vi
|
|
.fn<SimpleSyrupSettingsApi["saveSettings"]>()
|
|
.mockResolvedValueOnce({
|
|
show_downloadable_models: true,
|
|
quant_cache_limit_gib: 20
|
|
})
|
|
.mockRejectedValueOnce(new Error("rejected"));
|
|
|
|
await registerSimpleSyrupSettings(app, api, logger);
|
|
await app.ui.settings.definitions[0]?.onChange?.(true);
|
|
await app.ui.settings.definitions[0]?.onChange?.(false);
|
|
|
|
expect(app.ui.settings.settings[0]?.value).toBe(true);
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
expect.stringContaining("Could not save SimpleSyrup settings"),
|
|
expect.any(Error)
|
|
);
|
|
});
|
|
|
|
it("keeps a saved setting when live model-choice refresh fails", async () => {
|
|
const app = createFakeComfyApp();
|
|
const logger = { warn: vi.fn() };
|
|
app.refreshComboInNodes = vi.fn().mockRejectedValue(new Error("offline"));
|
|
|
|
await registerSimpleSyrupSettings(app, fakeSettingsApi(false), logger);
|
|
await app.ui.settings.definitions[0]?.onChange?.(true);
|
|
|
|
expect(app.ui.settings.settings[0]?.value).toBe(true);
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
expect.stringContaining("Could not refresh Comfy loader model choices"),
|
|
expect.any(Error)
|
|
);
|
|
});
|
|
|
|
it("shows global quant cache usage and saves its GiB limit", async () => {
|
|
const app = createFakeComfyApp();
|
|
const api = fakeSettingsApi(true);
|
|
const saveSettings = vi
|
|
.fn<SimpleSyrupSettingsApi["saveSettings"]>()
|
|
.mockResolvedValue({
|
|
show_downloadable_models: true,
|
|
quant_cache_limit_gib: 30
|
|
});
|
|
api.saveSettings = saveSettings;
|
|
|
|
await registerSimpleSyrupSettings(app, api);
|
|
const control = renderSetting(getDefinition(app, 1));
|
|
const input = requiredInput(control, "input[type=number]");
|
|
expect(input.value).toBe("20");
|
|
expect(control.textContent).toContain("models/SyrupQuants");
|
|
input.value = "30";
|
|
requiredButton(control, "button").click();
|
|
await flushPromises();
|
|
|
|
expect(saveSettings).toHaveBeenCalledWith({
|
|
show_downloadable_models: true,
|
|
quant_cache_limit_gib: 30
|
|
});
|
|
expect(input.value).toBe("30");
|
|
});
|
|
|
|
it("clears inactive quant artifacts and refreshes cache status", async () => {
|
|
const app = createFakeComfyApp();
|
|
const api = fakeSettingsApi(true);
|
|
const clearQuantCache = vi.fn().mockResolvedValue({
|
|
...defaultQuantCacheStatus(),
|
|
removed_artifacts: 2,
|
|
removed_bytes: 1024
|
|
});
|
|
api.clearQuantCache = clearQuantCache;
|
|
|
|
await registerSimpleSyrupSettings(app, api);
|
|
const control = renderSetting(getDefinition(app, 1));
|
|
const clearButton = control.querySelectorAll("button")[1];
|
|
if (!(clearButton instanceof HTMLButtonElement)) {
|
|
throw new Error("Expected quant cache clear button.");
|
|
}
|
|
clearButton.click();
|
|
await flushPromises();
|
|
|
|
expect(clearQuantCache).toHaveBeenCalledOnce();
|
|
expect(control.textContent).toContain("0.00 GiB used");
|
|
});
|
|
|
|
it("restores the previous quant limit when backend saving fails", async () => {
|
|
const app = createFakeComfyApp();
|
|
const logger = { warn: vi.fn() };
|
|
const api = fakeSettingsApi(true);
|
|
api.saveSettings = vi.fn().mockRejectedValue(new Error("rejected"));
|
|
|
|
await registerSimpleSyrupSettings(app, api, logger);
|
|
const control = renderSetting(getDefinition(app, 1));
|
|
const input = requiredInput(control, "input[type=number]");
|
|
input.value = "30";
|
|
requiredButton(control, "button").click();
|
|
await flushPromises();
|
|
|
|
expect(input.value).toBe("20");
|
|
expect(control.textContent).toContain("was not saved");
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
expect.stringContaining("quant cache limit"),
|
|
expect.any(Error)
|
|
);
|
|
});
|
|
});
|