Files
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

164 lines
5.0 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { createPinia, setActivePinia } from "pinia";
import { bindSidebarOpen } from "../features/panel/controllers/sidebarController.js";
import { usePanelStore } from "../stores/usePanelStore.js";
class ElementStub {
constructor() {
(this as any).listeners = new Map();
(this as any).dataset = {};
(this as any).style = {};
(this as any).parentElement = null;
(this as any).children = [];
(this as any).classList = {
_set: new Set(),
add: (...names) => names.forEach((name) => (this as any).classList._set.add(name)),
remove: (...names) =>
names.forEach((name) => (this as any).classList._set.delete(name)),
contains: (name) => (this as any).classList._set.has(name),
};
}
addEventListener(type, handler) {
const list = (this as any).listeners.get(type) || [];
list.push(handler);
(this as any).listeners.set(type, list);
}
removeEventListener(type, handler) {
const list = (this as any).listeners.get(type) || [];
(this as any).listeners.set(
type,
list.filter((item) => item !== handler),
);
}
dispatchEvent(event) {
const payload = event || {};
if (!payload.target) {
try {
payload.target = this;
} catch {
/* ignore readonly event payloads */
}
}
for (const handler of (this as any).listeners.get(payload.type) || []) {
handler(payload);
}
}
contains(target) {
return target === this || (this as any).children.includes(target);
}
appendChild(child) {
(this as any).children.push(child);
child.parentElement = this;
return child;
}
querySelector(selector) {
if (selector === ".mjr-asset-card.is-selected") {
return (
(this as any).children.find((child) => child.classList?.contains("is-selected")) ||
null
);
}
if (selector.includes('[data-mjr-asset-id="')) {
const id = selector.split('[data-mjr-asset-id="')[1]?.split('"')[0] || "";
return (
(this as any).children.find(
(child) => String(child.dataset?.mjrAssetId || "") === id,
) || null
);
}
return null;
}
querySelectorAll(selector) {
if (selector === ".mjr-asset-card") return [...(this as any).children];
if (selector === ".mjr-asset-card.is-selected") {
return (this as any).children.filter((child) =>
child.classList?.contains("is-selected"),
);
}
return [];
}
closest(selector) {
if (selector === ".mjr-asset-card") {
return (this as any).classList.contains("mjr-asset-card") ? this : null;
}
if (selector === ".mjr-workflow-dot") return null;
if (selector === "a, button, input, textarea, select, label") return null;
return null;
}
setAttribute() {}
focus() {}
scrollIntoView() {}
}
describe("sidebarController", () => {
it("syncs selected asset ids into Pinia on card click", async () => {
globalThis.Element = ElementStub as any;
globalThis.ResizeObserver = class {
observe() {}
disconnect() {}
} as any;
globalThis.requestAnimationFrame = (callback) => {
(callback as any)();
return 1;
};
globalThis.cancelAnimationFrame = vi.fn();
setActivePinia(createPinia());
const panelStore = usePanelStore();
const gridContainer = new ElementStub();
const scrollRoot = new ElementStub();
(gridContainer as any).parentElement = scrollRoot;
const card = new ElementStub();
(card as any).classList.add("mjr-asset-card");
(card as any).dataset.mjrAssetId = "42";
(card as any)._mjrAsset = { id: 42, filename: "cat.png" };
gridContainer.appendChild(card);
const sidebar = new ElementStub();
const state = {
activeAssetId: "",
selectedAssetIds: [],
sidebarOpen: false,
};
const controller = bindSidebarOpen({
gridContainer,
sidebar,
createRatingBadge: vi.fn(),
createTagsBadge: vi.fn(),
showAssetInSidebar: vi.fn(),
closeSidebar: vi.fn(),
state,
});
gridContainer.dispatchEvent({
type: "click",
target: card,
defaultPrevented: false,
ctrlKey: false,
metaKey: false,
shiftKey: false,
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
});
expect(panelStore.selectedAssetIds).toEqual(["42"]);
expect(panelStore.activeAssetId).toBe("42");
controller.dispose();
});
});