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

327 lines
11 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../app/i18n.js", () => ({
t: (_key, fallback) => fallback,
}));
vi.mock("../features/panel/messages/messageCenter.js", () => ({
PANEL_MESSAGES_EVENT: "mjr:test:messages",
ensurePanelMessagesReady: vi.fn(),
getPanelLastReadAt: vi.fn(() => 0),
getPanelUnreadCount: vi.fn(() => 0),
listPanelMessages: vi.fn(() => []),
markPanelMessagesRead: vi.fn(),
}));
vi.mock("../features/panel/messages/shortcutGuide.js", () => ({
createShortcutGuidePanel: vi.fn(() => ({ nodeType: "shortcut-guide" })),
}));
function createClassList() {
const names = new Set();
return {
toggle(name, active) {
if (active) names.add(name);
else names.delete(name);
},
contains(name) {
return names.has(name);
},
};
}
function createNode() {
return {
className: "",
id: "",
hidden: false,
style: {},
textContent: "",
classList: createClassList(),
attributes: new Map(),
childNodes: [],
setAttribute(name, value) {
this.attributes.set(name, String(value));
},
replaceChildren(...children) {
this.childNodes = children;
},
appendChild(child) {
this.childNodes.push(child);
return child;
},
querySelector(selector) {
if (!String(selector || "").startsWith(".")) return null;
const cls = selector.slice(1);
const stack = [...this.childNodes];
while (stack.length) {
const node = stack.shift();
if (
String(node?.className || "")
.split(/\s+/)
.includes(cls)
)
return node;
if (Array.isArray(node?.childNodes)) stack.push(...node.childNodes);
}
return null;
},
addEventListener() {},
};
}
describe("messagePopoverController", () => {
beforeEach(() => {
vi.useRealTimers();
const eventTarget = new EventTarget();
eventTarget.location = { href: "http://127.0.0.1:8188/" };
globalThis.window = eventTarget;
globalThis.localStorage = {
_data: new Map(),
getItem(key) {
return this._data.has(key) ? this._data.get(key) : null;
},
setItem(key, value) {
this._data.set(key, String(value));
},
clear() {
this._data.clear();
},
};
globalThis.CustomEvent = class CustomEvent extends Event {
constructor(type, init = {}) {
super(type);
this.detail = init.detail;
}
};
globalThis.document = {
createElement: vi.fn(() => createNode()),
};
});
it("shows only the shortcut panel in shortcut guide tab", async () => {
const { bindMessagePopoverController } =
await import("../features/panel/messages/messagePopoverController.js");
const badge = { style: {}, textContent: "" };
const messageBtn = {
classList: createClassList(),
setAttribute() {},
querySelector(selector) {
return selector === ".mjr-message-badge" ? badge : null;
},
addEventListener() {},
};
const title = createNode();
const messagePopover = createNode();
const messageList = createNode();
const shortcutsPanel = createNode();
const messageTabBtn = createNode();
const shortcutsTabBtn = createNode();
const markReadBtn = createNode();
const popovers = {
toggle() {},
close() {},
};
const controller = bindMessagePopoverController({
messageBtn,
messagePopover,
title,
messageList,
shortcutsPanel,
messageTabBtn,
shortcutsTabBtn,
markReadBtn,
popovers,
});
controller.showShortcutsTab();
expect(title.textContent).toBe("Shortcut Guide");
expect(messageList.hidden).toBe(true);
expect(messageList.style.display).toBe("none");
expect(messageList.attributes.get("aria-hidden")).toBe("true");
expect(shortcutsPanel.hidden).toBe(false);
expect(shortcutsPanel.style.display).toBe("");
expect(shortcutsPanel.attributes.get("aria-hidden")).toBe("false");
expect(markReadBtn.hidden).toBe(true);
controller.showMessagesTab();
expect(title.textContent).toBe("Messages");
expect(messageList.hidden).toBe(false);
expect(messageList.style.display).toBe("");
expect(shortcutsPanel.hidden).toBe(true);
expect(shortcutsPanel.style.display).toBe("none");
expect(markReadBtn.hidden).toBe(false);
});
it("renders rich toast history entries with detail and metadata", async () => {
const historyModule = await import("../features/panel/messages/toastHistory.js");
historyModule.addToastHistory({
message: "Indexed 3 assets",
title: "Vector Backfill",
detail: "Indexed 3 assets across 2 folders",
type: "success",
source: "ai-index",
status: "running",
durationMs: 3600,
progress: {
current: 3,
total: 120,
percent: 3,
eligible_total: 120,
candidate_total: 10,
indexed: 3,
skipped: 1,
errors: 0,
label: "running",
},
actionLabel: "Open docs",
actionUrl: "https://example.com/docs",
});
const { bindMessagePopoverController } =
await import("../features/panel/messages/messagePopoverController.js");
const badge = { style: {}, textContent: "" };
const messageBtn = {
classList: createClassList(),
setAttribute() {},
querySelector(selector) {
return selector === ".mjr-message-badge" ? badge : null;
},
addEventListener() {},
};
const title = createNode();
const messagePopover = createNode();
const messageList = createNode();
const historyPanel = createNode();
const messageTabBtn = createNode();
const historyTabBtn = createNode();
const shortcutsTabBtn = createNode();
const markReadBtn = createNode();
const popovers = {
toggle() {},
close() {},
};
const controller = bindMessagePopoverController({
messageBtn,
messagePopover,
title,
messageList,
historyPanel,
messageTabBtn,
historyTabBtn,
shortcutsTabBtn,
markReadBtn,
popovers,
});
controller.showHistoryTab();
expect(title.textContent).toBe("History");
const historyList = historyPanel.querySelector(".mjr-history-list");
expect(historyList).not.toBeNull();
const item = historyList.childNodes[0];
expect(item.className).toContain("mjr-history-item--success");
const content = item.childNodes[1];
expect(content.childNodes[0].textContent).toBe("Vector Backfill");
expect(content.childNodes[1].textContent).toBe("Indexed 3 assets across 2 folders");
expect(content.childNodes[2].childNodes[1].textContent).toBe("3.6s");
expect(content.childNodes[2].childNodes[2].textContent).toBe("running");
expect(content.childNodes[2].childNodes[3].textContent).toBe("ai-index");
expect(content.childNodes[3].childNodes[0].textContent).toContain("running");
expect(content.childNodes[3].childNodes[0].textContent).toContain("3/120");
expect(content.childNodes[3].childNodes[1].childNodes[0].style.width).toBe("3%");
expect(content.childNodes[4].textContent).toBe("Open docs");
});
it("auto-closes history when an automatically opened tracked job completes", async () => {
vi.useFakeTimers();
const historyModule = await import("../features/panel/messages/toastHistory.js");
historyModule.clearToastHistory();
historyModule.addToastHistory({
message: "Vector backfill running",
title: "Vector Backfill",
detail: "Running",
type: "info",
trackId: "vector-backfill:test",
operation: "vector_backfill",
status: "running",
persistent: true,
forceStore: true,
});
const { EVENTS } = await import("../app/events.js");
const { bindMessagePopoverController } =
await import("../features/panel/messages/messagePopoverController.js");
const badge = { style: {}, textContent: "" };
const messageBtn = {
classList: createClassList(),
setAttribute() {},
querySelector(selector) {
return selector === ".mjr-message-badge" ? badge : null;
},
addEventListener() {},
};
const title = createNode();
const messagePopover = createNode();
const messageList = createNode();
const historyPanel = createNode();
const messageTabBtn = createNode();
const historyTabBtn = createNode();
const shortcutsTabBtn = createNode();
const markReadBtn = createNode();
const popovers = {
toggle: vi.fn((popover) => {
popover.style.display = "block";
}),
close: vi.fn((popover) => {
popover.style.display = "none";
}),
};
bindMessagePopoverController({
messageBtn,
messagePopover,
title,
messageList,
historyPanel,
messageTabBtn,
historyTabBtn,
shortcutsTabBtn,
markReadBtn,
popovers,
});
window.dispatchEvent(new CustomEvent(EVENTS.OPEN_MESSAGE_HISTORY));
expect(messagePopover.style.display).toBe("block");
expect(title.textContent).toBe("History");
historyModule.addToastHistory({
message: "Vector backfill complete",
title: "Vector Backfill",
detail: "Done",
type: "success",
trackId: "vector-backfill:test",
operation: "vector_backfill",
status: "succeeded",
durationMs: 3000,
forceStore: true,
});
expect(messagePopover.style.display).toBe("block");
await vi.advanceTimersByTimeAsync(2200);
expect(popovers.close).toHaveBeenCalledWith(messagePopover);
expect(messagePopover.style.display).toBe("none");
vi.useRealTimers();
});
});