Files
by-ae-ComfyUI-ByAE-Ideogram…/tests/store_client.test.js
T
by-ae 479e0bb069 fix: ask before replacing a saved preset
Saving a character, object, or scene reused an existing name and
overwrote that preset without asking. Updating the preset already
linked to the region still saves directly.
2026-11-01 07:31:19 +10:00

165 lines
5.3 KiB
JavaScript

import assert from "node:assert/strict";
import { afterEach, describe, it } from "node:test";
globalThis.window = {
comfyAPI: {
app: {},
api: { addEventListener() {} },
},
};
const {
loadStores, deleteStoreEntry, saveStoreEntry, normalizeStoreName, savedPresetCollision,
} = await import("../web/js/ideogram4_shared.js");
describe("loadStores", () => {
const originalFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = originalFetch;
});
it("returns the three buckets from a successful response", async () => {
globalThis.fetch = async () => ({
ok: true,
json: async () => ({
characters: { Ada: { desc: "a" } },
objects: {},
scenes: { Scene: { boxes: [] } },
}),
});
const stores = await loadStores();
assert.deepEqual(stores.characters.Ada, { desc: "a" });
assert.deepEqual(stores.objects, {});
assert.deepEqual(stores.scenes.Scene, { boxes: [] });
});
it("returns empty buckets when the server has no presets", async () => {
globalThis.fetch = async () => ({
ok: true,
json: async () => ({}),
});
const stores = await loadStores();
assert.deepEqual(stores, { characters: {}, objects: {}, scenes: {} });
});
it("throws when the store request fails instead of looking empty", async () => {
globalThis.fetch = async () => ({ ok: false, status: 500, json: async () => ({}) });
await assert.rejects(loadStores(), /HTTP 500/);
});
it("throws when the network request fails", async () => {
globalThis.fetch = async () => {
throw new Error("network down");
};
await assert.rejects(loadStores(), /network down/);
});
});
describe("savedPresetCollision", () => {
it("names a different preset that would be replaced", () => {
assert.equal(savedPresetCollision(["Ada", "Bea"], "Bea"), "Bea");
assert.equal(savedPresetCollision(["Ada"], "Cara"), null);
assert.equal(savedPresetCollision(["Ada"], " "), null);
assert.equal(savedPresetCollision([], "Ada"), null);
});
it("does not treat an update of the linked preset as a replacement", () => {
assert.equal(savedPresetCollision(["Ada"], "Ada", "Ada"), null);
assert.equal(savedPresetCollision(["Ada", "Bea"], "Bea", "Ada"), "Bea");
});
it("matches a composed name to the stored spelling", () => {
const nfd = "Émilie".normalize("NFD");
assert.equal(savedPresetCollision(["Émilie"], nfd), "Émilie");
assert.equal(savedPresetCollision(["Émilie"], ` ${nfd} `, "Émilie"), null);
});
});
describe("saveStoreEntry", () => {
const originalFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = originalFetch;
});
it("sends the composed name and returns the stored name", async () => {
const nfd = "Émilie".normalize("NFD");
let called;
globalThis.fetch = async (url, opts) => {
called = { url, method: opts.method, body: opts.body };
return { ok: true, json: async () => ({ ok: true, name: "Émilie" }) };
};
const saved = await saveStoreEntry("characters", nfd, { desc: "a" });
assert.equal(called.method, "PUT");
assert.equal(called.url, "/byae/ideogram/stores/characters/%C3%89milie");
assert.equal(called.body, JSON.stringify({ desc: "a" }));
assert.equal(saved, "Émilie");
});
});
describe("deleteStoreEntry", () => {
const originalFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = originalFetch;
});
it("sends DELETE to the encoded preset name", async () => {
let called;
globalThis.fetch = async (url, opts) => {
called = { url, method: opts.method };
return { ok: true, json: async () => ({ ok: true }) };
};
await deleteStoreEntry("characters", "Émilie");
assert.equal(called.method, "DELETE");
assert.equal(called.url, "/byae/ideogram/stores/characters/%C3%89milie");
});
it("sends a composed form of a unicode name", async () => {
const nfd = "Émilie".normalize("NFD");
const nfc = "Émilie".normalize("NFC");
assert.notEqual(nfd, nfc);
assert.equal(normalizeStoreName(` ${nfd} `), nfc);
let called;
globalThis.fetch = async (url, opts) => {
called = { url, method: opts.method };
return { ok: true, json: async () => ({ ok: true }) };
};
await deleteStoreEntry("characters", nfd);
assert.equal(called.method, "DELETE");
assert.equal(called.url, "/byae/ideogram/stores/characters/%C3%89milie");
});
it("encodes spaces in scene names", async () => {
let called;
globalThis.fetch = async (url, opts) => {
called = { url, method: opts.method };
return { ok: true, json: async () => ({ ok: true }) };
};
await deleteStoreEntry("scenes", "My Scene");
assert.equal(called.method, "DELETE");
assert.equal(called.url, "/byae/ideogram/stores/scenes/My%20Scene");
});
it("uses the server error message", async () => {
globalThis.fetch = async () => ({
ok: false,
status: 404,
json: async () => ({ error: "Entry not found" }),
});
await assert.rejects(deleteStoreEntry("scenes", "Gone"), /Entry not found/);
});
it("falls back to the status when the body is not JSON", async () => {
globalThis.fetch = async () => ({
ok: false,
status: 500,
json: async () => {
throw new Error("nope");
},
});
await assert.rejects(deleteStoreEntry("objects", "Box"), /HTTP 500/);
});
});