Files
MajoorWaldi-ComfyUI-Majoor-…/ui/tests/message_popover_controller.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

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 as any).location = { href: "http://127.0.0.1:8188/" };
globalThis.window = eventTarget as any;
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();
},
} as any;
globalThis.CustomEvent = class CustomEvent extends Event {
constructor(type, init = {}) {
super(type);
(this as any).detail = (init as any).detail;
}
} as any;
globalThis.document = {
createElement: vi.fn(() => createNode()) as any,
} as any;
});
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 as any).display).toBe("none");
expect(messageList.attributes.get("aria-hidden")).toBe("true");
expect(shortcutsPanel.hidden).toBe(false);
expect((shortcutsPanel.style as any).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 as any).display).toBe("");
expect(shortcutsPanel.hidden).toBe(true);
expect((shortcutsPanel.style as any).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 as any).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 as any).display).toBe("block");
await vi.advanceTimersByTimeAsync(2200);
expect(popovers.close).toHaveBeenCalledWith(messagePopover);
expect((messagePopover.style as any).display).toBe("none");
vi.useRealTimers();
});
});