Files
MajoorWaldi-ComfyUI-Majoor-…/ui/tests/stack_group_cards.vitest.ts
T
Ewald ALOEBOETOE d0ab7d594e Resolve remaining ComfyUI integration conflicts
- Move the read-only OpenAPI compat surface from /api/v2/assets to
  /mjr/am/compat/assets so it never squats ComfyUI core's /api namespace;
  the legacy alias is opt-in via MJR_AM_LEGACY_API_V2_ASSETS=1.
- Allow Python 3.14 (requires-python <3.15) and add 3.14 to CI and tox.
- Stop wrapping api.queuePrompt: progress tracking now resolves the
  running prompt from the core Jobs API (getJobDetail), then /queue and
  /history, driven by websocket events. Remove wrapHostQueuePrompt.
- Add opt-in Majoor.Integration.HideNativeAssetsTab setting that hides
  ComfyUI's native Assets sidebar tab through the official
  getSidebarTabs/unregisterSidebarTab API and restores it when disabled.
- Fix all 913 type errors in tsconfig.test.json and type-check tests in
  CI; drop a duplicated `stubs` key in asset_card_inner test; make
  addToastHistory's type argument optional for the object form.
- Update audit report, changelog, API reference and env var docs.
2026-11-03 21:16:34 +00:00

191 lines
6.7 KiB
TypeScript

// @vitest-environment happy-dom
import { beforeEach, describe, expect, it, vi } from "vitest";
const getMock = vi.hoisted(() => vi.fn());
vi.mock("../api/client.js", () => ({
get: getMock,
}));
vi.mock("../api/endpoints.js", () => ({
buildStackMembersURL: vi.fn((stackId, options = {}) => {
const limit = Number(options?.limit || 0);
return `/mjr/am/stacks/${stackId}${limit > 0 ? `?limit=${limit}` : ""}`;
}),
}));
describe("StackGroupCards overlay buttons", () => {
beforeEach(() => {
document.body.innerHTML = "";
getMock.mockReset();
});
it("keeps stack-group button clickable without bubbling card press/drag events", async () => {
const { ensureStackGroupCard } = await import("../features/grid/StackGroupCards.js");
const { EVENTS } = await import("../app/events.js");
const grid = document.createElement("div");
grid.dataset.mjrGroupStacks = "1";
const card = document.createElement("div");
card.className = "mjr-asset-card";
grid.appendChild(card);
const asset = {
id: "asset-1",
filename: "image.png",
stack_id: "stack-1",
mtime: 100,
};
const members = [
asset,
{
id: "asset-2",
filename: "image-2.png",
stack_id: "stack-1",
mtime: 200,
},
];
getMock.mockResolvedValue({ ok: true, data: members });
ensureStackGroupCard(grid, card, asset);
const button = card.querySelector(".mjr-stack-group-button");
expect(button).toBeTruthy();
expect((button as any).draggable).toBe(false);
const cardMouseDown = vi.fn();
card.addEventListener("mousedown", cardMouseDown);
const down = new MouseEvent("mousedown", { bubbles: true, cancelable: true });
button.dispatchEvent(down);
expect(cardMouseDown).not.toHaveBeenCalled();
expect(down.defaultPrevented).toBe(true);
const cardDragStart = vi.fn();
card.addEventListener("dragstart", cardDragStart);
const dragStart = new Event("dragstart", { bubbles: true, cancelable: true });
button.dispatchEvent(dragStart);
expect(cardDragStart).not.toHaveBeenCalled();
expect(dragStart.defaultPrevented).toBe(true);
const openStackGroup = vi.fn();
grid.addEventListener(EVENTS.OPEN_STACK_GROUP, openStackGroup);
button.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
await new Promise((resolve) => setTimeout(resolve, 0));
expect(getMock).toHaveBeenCalledWith("/mjr/am/stacks/stack-1?limit=501", { timeoutMs: 30_000 });
expect(openStackGroup).toHaveBeenCalledTimes(1);
expect(card.dataset.mjrStacked).toBe("true");
expect(card.dataset.mjrStackCount).toBe("2");
expect(button.textContent).toContain("2");
const [{ detail }] = openStackGroup.mock.calls[0];
expect(detail.stackId).toBe("stack-1");
expect(detail.members.map((entry) => entry.id)).toEqual(["asset-2", "asset-1"]);
});
it("ignores repeated stack clicks while members are loading", async () => {
const { ensureStackGroupCard } = await import("../features/grid/StackGroupCards.js");
const { EVENTS } = await import("../app/events.js");
let resolveFetch;
getMock.mockReturnValue(
new Promise((resolve) => {
resolveFetch = resolve;
}),
);
const grid = document.createElement("div");
grid.dataset.mjrGroupStacks = "1";
const card = document.createElement("div");
card.className = "mjr-asset-card";
grid.appendChild(card);
const asset = {
id: "asset-1",
filename: "image.png",
stack_id: "stack-1",
stack_asset_count: 2,
mtime: 100,
};
const openStackGroup = vi.fn();
grid.addEventListener(EVENTS.OPEN_STACK_GROUP, openStackGroup);
ensureStackGroupCard(grid, card, asset);
const button = card.querySelector(".mjr-stack-group-button");
button.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
button.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
expect(getMock).toHaveBeenCalledTimes(1);
expect((button as any).disabled).toBe(true);
resolveFetch({
ok: true,
data: [
asset,
{ id: "asset-2", filename: "image-2.png", stack_id: "stack-1", mtime: 200 },
],
});
await Promise.resolve();
await Promise.resolve();
expect(openStackGroup).toHaveBeenCalledTimes(1);
expect((button as any).disabled).toBe(false);
});
it("skips imperative button creation for Vue cards (_mjrIsVue guard)", async () => {
const { ensureStackGroupCard, ensureDupStackCard } =
await import("../features/grid/StackGroupCards.js");
const grid = document.createElement("div");
grid.dataset.mjrGroupStacks = "1";
const card = document.createElement("div");
card.className = "mjr-asset-card";
(card as any)._mjrIsVue = true;
grid.appendChild(card);
const asset = {
id: "asset-1",
filename: "image.png",
stack_id: "stack-1",
stack_asset_count: 3,
_mjrDupStack: true,
_mjrDupCount: 2,
};
ensureStackGroupCard(grid, card, asset);
expect(card.querySelector(".mjr-stack-group-button")).toBeNull();
ensureDupStackCard(grid, card, asset);
expect(card.querySelector(".mjr-dup-stack-button")).toBeNull();
});
it("still creates buttons for non-Vue cards", async () => {
const { ensureStackGroupCard, ensureDupStackCard } =
await import("../features/grid/StackGroupCards.js");
const grid = document.createElement("div");
grid.dataset.mjrGroupStacks = "1";
const card = document.createElement("div");
card.className = "mjr-asset-card";
// _mjrIsVue not set
grid.appendChild(card);
const asset = {
id: "asset-1",
filename: "image.png",
stack_id: "stack-1",
stack_asset_count: 3,
_mjrDupStack: true,
_mjrDupCount: 2,
_mjrDupMembers: [{ id: "a1" }, { id: "a2" }],
};
ensureStackGroupCard(grid, card, asset);
expect(card.querySelector(".mjr-stack-group-button")).toBeTruthy();
ensureDupStackCard(grid, card, asset);
expect(card.querySelector(".mjr-dup-stack-button")).toBeTruthy();
});
});