- Rename 132 *.vitest.mjs -> *.vitest.ts - Rename vitest.setup.mjs -> vitest.setup.ts - Rename helpers/vitestEnvironment.mjs -> vitestEnvironment.ts - Delete settings_store.test.mjs (Node test runner, superseded by .vitest.ts) - Fix .mjs imports in vitest.setup.ts and grid_loader_adaptive_paging.vitest.ts Config updates: - vitest.config.mjs: setupFiles, include, coverage.include -> .ts - tsconfig.test.json: new dedicated config for test files (strict:false) - knip.json: update project pattern for tests - package.json: add typecheck:tests script (audit-only)
284 lines
9.0 KiB
TypeScript
284 lines
9.0 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const bridgeState = vi.hoisted(() => ({
|
|
values: {
|
|
scope: "output",
|
|
customRootId: "",
|
|
currentFolderRelativePath: "",
|
|
kindFilter: "",
|
|
workflowOnly: false,
|
|
minRating: 0,
|
|
minSizeMB: 0,
|
|
maxSizeMB: 0,
|
|
resolutionCompare: "gte",
|
|
minWidth: 0,
|
|
minHeight: 0,
|
|
maxWidth: 0,
|
|
maxHeight: 0,
|
|
workflowType: "",
|
|
dateRangeFilter: "",
|
|
dateExactFilter: "",
|
|
sort: "mtime_desc",
|
|
activeAssetId: "",
|
|
selectedAssetIds: [],
|
|
collectionId: "",
|
|
collectionName: "",
|
|
viewScope: "",
|
|
similarResults: [],
|
|
similarTitle: "",
|
|
similarSourceAssetId: "",
|
|
lastGridCount: 0,
|
|
lastGridTotal: 0,
|
|
},
|
|
}));
|
|
|
|
vi.mock("../stores/panelStateBridge.js", () => ({
|
|
createPanelStateBridge: () => ({
|
|
read: (key, fallback = "") =>
|
|
Object.prototype.hasOwnProperty.call(bridgeState.values, key)
|
|
? bridgeState.values[key]
|
|
: fallback,
|
|
write: vi.fn((key, value) => {
|
|
bridgeState.values[key] = value;
|
|
}),
|
|
}),
|
|
}));
|
|
|
|
vi.mock("../api/client.js", () => ({
|
|
vectorSearch: vi.fn(),
|
|
hybridSearch: vi.fn(),
|
|
vectorStats: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../app/toast.js", () => ({
|
|
comfyToast: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../app/i18n.js", () => ({
|
|
t: (_key, fallback) => fallback,
|
|
}));
|
|
|
|
vi.mock("../app/settings.js", () => ({
|
|
loadMajoorSettings: () => ({ ai: { vectorSearchEnabled: true } }),
|
|
}));
|
|
|
|
vi.mock("../app/config.js", () => ({
|
|
APP_CONFIG: { EXECUTION_GROUPING_ENABLED: true },
|
|
}));
|
|
|
|
function createGridContainer() {
|
|
return {
|
|
dataset: { mjrViewScope: "", mjrScope: "output" },
|
|
dispatchEvent: vi.fn(),
|
|
};
|
|
}
|
|
|
|
describe("gridController scope switch cache behavior", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
Object.assign(bridgeState.values, {
|
|
scope: "output",
|
|
customRootId: "",
|
|
currentFolderRelativePath: "",
|
|
kindFilter: "",
|
|
workflowOnly: false,
|
|
minRating: 0,
|
|
minSizeMB: 0,
|
|
maxSizeMB: 0,
|
|
resolutionCompare: "gte",
|
|
minWidth: 0,
|
|
minHeight: 0,
|
|
maxWidth: 0,
|
|
maxHeight: 0,
|
|
workflowType: "",
|
|
dateRangeFilter: "",
|
|
dateExactFilter: "",
|
|
sort: "mtime_desc",
|
|
activeAssetId: "",
|
|
selectedAssetIds: [],
|
|
collectionId: "",
|
|
collectionName: "",
|
|
viewScope: "",
|
|
similarResults: [],
|
|
similarTitle: "",
|
|
similarSourceAssetId: "",
|
|
lastGridCount: 0,
|
|
lastGridTotal: 0,
|
|
});
|
|
globalThis.window = {};
|
|
globalThis.CustomEvent = class {
|
|
constructor(type, init = {}) {
|
|
this.type = type;
|
|
this.detail = init.detail;
|
|
}
|
|
};
|
|
});
|
|
|
|
it("writes mjrCollectionId to the dataset during reload", async () => {
|
|
const { createGridController } =
|
|
await import("../features/panel/controllers/gridController.js");
|
|
|
|
bridgeState.values.collectionId = "col-123";
|
|
bridgeState.values.collectionName = "Test Collection";
|
|
|
|
const gridContainer = createGridContainer();
|
|
const loadAssets = vi.fn(async () => ({ ok: true, count: 5, total: 5 }));
|
|
const getCollectionAssets = vi.fn(async () => ({
|
|
ok: true,
|
|
data: { assets: [{ id: 1 }, { id: 2 }] },
|
|
}));
|
|
const loadAssetsFromList = vi.fn(async () => ({ ok: true, count: 2, total: 2 }));
|
|
|
|
const controller = createGridController({
|
|
gridContainer,
|
|
loadAssets,
|
|
loadAssetsFromList,
|
|
getCollectionAssets,
|
|
disposeGrid: vi.fn(),
|
|
getQuery: () => "*",
|
|
searchInputEl: { dataset: { mjrSemanticMode: "0" } },
|
|
state: {},
|
|
});
|
|
|
|
await controller.reloadGrid();
|
|
|
|
expect(gridContainer.dataset.mjrCollectionId).toBe("col-123");
|
|
});
|
|
|
|
it("loads collection assets when collectionId is set", async () => {
|
|
const { createGridController } =
|
|
await import("../features/panel/controllers/gridController.js");
|
|
|
|
bridgeState.values.collectionId = "col-456";
|
|
bridgeState.values.collectionName = "My Collection";
|
|
|
|
const gridContainer = createGridContainer();
|
|
const loadAssets = vi.fn(async () => ({ ok: true, count: 5, total: 5 }));
|
|
const collectionAssets = [
|
|
{ id: 10, filename: "a.png" },
|
|
{ id: 11, filename: "b.png" },
|
|
];
|
|
const getCollectionAssets = vi.fn(async () => ({
|
|
ok: true,
|
|
data: { assets: collectionAssets },
|
|
}));
|
|
const loadAssetsFromList = vi.fn(async () => ({ ok: true, count: 2, total: 2 }));
|
|
|
|
const controller = createGridController({
|
|
gridContainer,
|
|
loadAssets,
|
|
loadAssetsFromList,
|
|
getCollectionAssets,
|
|
disposeGrid: vi.fn(),
|
|
getQuery: () => "*",
|
|
searchInputEl: { dataset: { mjrSemanticMode: "0" } },
|
|
state: {},
|
|
});
|
|
|
|
await controller.reloadGrid();
|
|
|
|
expect(getCollectionAssets).toHaveBeenCalledWith("col-456");
|
|
expect(loadAssetsFromList).toHaveBeenCalledWith(
|
|
gridContainer,
|
|
collectionAssets,
|
|
expect.objectContaining({ title: "Collection: My Collection" }),
|
|
);
|
|
// Normal loadAssets should NOT be called when collection is active
|
|
expect(loadAssets).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("falls back to normal load when collection fetch fails", async () => {
|
|
const { createGridController } =
|
|
await import("../features/panel/controllers/gridController.js");
|
|
|
|
bridgeState.values.collectionId = "col-bad";
|
|
|
|
const gridContainer = createGridContainer();
|
|
const loadAssets = vi.fn(async () => ({ ok: true, count: 50, total: 50 }));
|
|
const getCollectionAssets = vi.fn(async () => ({ ok: false, error: "Not found" }));
|
|
const loadAssetsFromList = vi.fn();
|
|
|
|
const controller = createGridController({
|
|
gridContainer,
|
|
loadAssets,
|
|
loadAssetsFromList,
|
|
getCollectionAssets,
|
|
disposeGrid: vi.fn(),
|
|
getQuery: () => "*",
|
|
searchInputEl: { dataset: { mjrSemanticMode: "0" } },
|
|
state: {},
|
|
});
|
|
|
|
await controller.reloadGrid();
|
|
|
|
// Should fall back to normal loadAssets
|
|
expect(loadAssets).toHaveBeenCalled();
|
|
// collectionId should be cleared
|
|
expect(bridgeState.values.collectionId).toBe("");
|
|
});
|
|
|
|
it("switches from output to input scope and reloads normally", async () => {
|
|
const { createGridController } =
|
|
await import("../features/panel/controllers/gridController.js");
|
|
|
|
const gridContainer = createGridContainer();
|
|
const loadAssets = vi.fn(async () => ({ ok: true, count: 100, total: 100 }));
|
|
|
|
const controller = createGridController({
|
|
gridContainer,
|
|
loadAssets,
|
|
loadAssetsFromList: vi.fn(async () => ({ ok: true })),
|
|
getCollectionAssets: vi.fn(),
|
|
disposeGrid: vi.fn(),
|
|
getQuery: () => "*",
|
|
searchInputEl: { dataset: { mjrSemanticMode: "0" } },
|
|
state: {},
|
|
});
|
|
|
|
// First load in output scope
|
|
await controller.reloadGrid();
|
|
expect(gridContainer.dataset.mjrScope).toBe("output");
|
|
|
|
// Switch to input scope
|
|
bridgeState.values.scope = "input";
|
|
loadAssets.mockClear();
|
|
await controller.reloadGrid();
|
|
|
|
expect(gridContainer.dataset.mjrScope).toBe("input");
|
|
expect(loadAssets).toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not dispose the live grid when entering browser root scope", async () => {
|
|
const { createGridController } =
|
|
await import("../features/panel/controllers/gridController.js");
|
|
|
|
Object.assign(bridgeState.values, {
|
|
scope: "custom",
|
|
customRootId: "",
|
|
currentFolderRelativePath: "",
|
|
});
|
|
|
|
const gridContainer = createGridContainer();
|
|
const loadAssets = vi.fn(async () => ({ ok: true, count: 4, total: 4 }));
|
|
const disposeGrid = vi.fn();
|
|
|
|
const controller = createGridController({
|
|
gridContainer,
|
|
loadAssets,
|
|
loadAssetsFromList: vi.fn(async () => ({ ok: true })),
|
|
getCollectionAssets: vi.fn(),
|
|
disposeGrid,
|
|
getQuery: () => "*",
|
|
searchInputEl: { dataset: { mjrSemanticMode: "0" } },
|
|
state: {},
|
|
});
|
|
|
|
await controller.reloadGrid();
|
|
|
|
expect(gridContainer.dataset.mjrScope).toBe("custom");
|
|
expect(gridContainer.dataset.mjrCustomRootId).toBe("");
|
|
expect(loadAssets).toHaveBeenCalled();
|
|
expect(disposeGrid).not.toHaveBeenCalled();
|
|
});
|
|
});
|