Files
MajoorWaldi-ComfyUI-Majoor-…/ui/tests/stack_group_cards.vitest.ts
T

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.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.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.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._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();
});
});