mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-07 14:30:15 -03:00
199 lines
6.5 KiB
JavaScript
199 lines
6.5 KiB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const {
|
|
VirtualScrollerMock,
|
|
MasonryScrollerMock,
|
|
RecipeCardMock,
|
|
fetchRecipesPageMock,
|
|
getModelApiClientMock,
|
|
} = vi.hoisted(() => {
|
|
const makeScrollerClass = () => vi.fn(function (options) {
|
|
this.options = options;
|
|
this.initialize = vi.fn(async () => {});
|
|
this.dispose = vi.fn();
|
|
this.reset = vi.fn();
|
|
this.handlePageUpDown = vi.fn();
|
|
});
|
|
|
|
return {
|
|
VirtualScrollerMock: makeScrollerClass(),
|
|
MasonryScrollerMock: makeScrollerClass(),
|
|
RecipeCardMock: vi.fn(function () {
|
|
this.element = document.createElement('div');
|
|
}),
|
|
fetchRecipesPageMock: vi.fn(async () => ({ items: [], totalItems: 0, hasMore: false })),
|
|
getModelApiClientMock: vi.fn(() => ({
|
|
fetchModelsPage: vi.fn(async () => ({ items: [], totalItems: 0, hasMore: false })),
|
|
})),
|
|
};
|
|
});
|
|
|
|
vi.mock('../../../static/js/utils/VirtualScroller.js', () => ({
|
|
VirtualScroller: VirtualScrollerMock,
|
|
}));
|
|
|
|
vi.mock('../../../static/js/utils/MasonryScroller.js', () => ({
|
|
MasonryScroller: MasonryScrollerMock,
|
|
}));
|
|
|
|
vi.mock('../../../static/js/components/RecipeCard.js', () => ({
|
|
RecipeCard: RecipeCardMock,
|
|
}));
|
|
|
|
vi.mock('../../../static/js/api/recipeApi.js', () => ({
|
|
fetchRecipesPage: fetchRecipesPageMock,
|
|
}));
|
|
|
|
vi.mock('../../../static/js/api/modelApiFactory.js', () => ({
|
|
getModelApiClient: getModelApiClientMock,
|
|
}));
|
|
|
|
vi.mock('../../../static/js/components/shared/ModelCard.js', () => ({
|
|
createModelCard: vi.fn(() => document.createElement('div')),
|
|
setupModelCardEventDelegation: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('../../../static/js/utils/uiHelpers.js', () => ({
|
|
showToast: vi.fn(),
|
|
}));
|
|
|
|
import {
|
|
initializeInfiniteScroll,
|
|
recreateVirtualScroll,
|
|
} from '../../../static/js/utils/infiniteScroll.js';
|
|
import { state } from '../../../static/js/state/index.js';
|
|
|
|
function setupPageDom() {
|
|
const pageContent = document.createElement('div');
|
|
pageContent.className = 'page-content';
|
|
const container = document.createElement('div');
|
|
container.className = 'container';
|
|
pageContent.appendChild(container);
|
|
document.body.appendChild(pageContent);
|
|
|
|
const grid = document.createElement('div');
|
|
vi.spyOn(document, 'getElementById').mockImplementation((id) =>
|
|
id === 'recipeGrid' || id === 'modelGrid' ? grid : null);
|
|
|
|
return { grid };
|
|
}
|
|
|
|
describe('infiniteScroll scroller class branching', () => {
|
|
let originalSettings;
|
|
|
|
beforeEach(() => {
|
|
originalSettings = state.global.settings;
|
|
state.global.settings = { ...originalSettings };
|
|
if (state.pages.recipes) {
|
|
state.pages.recipes.duplicatesMode = false;
|
|
}
|
|
state.virtualScroller = null;
|
|
state.keyboardNavHandler = null;
|
|
setupPageDom();
|
|
});
|
|
|
|
afterEach(() => {
|
|
state.global.settings = originalSettings;
|
|
state.virtualScroller = null;
|
|
state.keyboardNavHandler = null;
|
|
document.body.innerHTML = '';
|
|
vi.restoreAllMocks();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('constructs MasonryScroller for the recipes page when recipes_layout is masonry', async () => {
|
|
state.global.settings.recipes_layout = 'masonry';
|
|
|
|
await initializeInfiniteScroll('recipes');
|
|
|
|
expect(MasonryScrollerMock).toHaveBeenCalledTimes(1);
|
|
expect(VirtualScrollerMock).not.toHaveBeenCalled();
|
|
expect(state.virtualScroller).toBeInstanceOf(MasonryScrollerMock);
|
|
});
|
|
|
|
it('constructs VirtualScroller for the recipes page when recipes_layout is grid', async () => {
|
|
state.global.settings.recipes_layout = 'grid';
|
|
|
|
await initializeInfiniteScroll('recipes');
|
|
|
|
expect(VirtualScrollerMock).toHaveBeenCalledTimes(1);
|
|
expect(MasonryScrollerMock).not.toHaveBeenCalled();
|
|
expect(state.virtualScroller).toBeInstanceOf(VirtualScrollerMock);
|
|
});
|
|
|
|
it('falls back to the grid branch when recipes_layout is missing', async () => {
|
|
delete state.global.settings.recipes_layout;
|
|
|
|
await expect(initializeInfiniteScroll('recipes')).resolves.toBeUndefined();
|
|
|
|
expect(VirtualScrollerMock).toHaveBeenCalledTimes(1);
|
|
expect(MasonryScrollerMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('always constructs VirtualScroller for the loras page regardless of recipes_layout', async () => {
|
|
state.global.settings.recipes_layout = 'masonry';
|
|
|
|
await initializeInfiniteScroll('loras');
|
|
|
|
expect(VirtualScrollerMock).toHaveBeenCalledTimes(1);
|
|
expect(MasonryScrollerMock).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('recreateVirtualScroll', () => {
|
|
let originalSettings;
|
|
|
|
beforeEach(() => {
|
|
originalSettings = state.global.settings;
|
|
state.global.settings = { ...originalSettings };
|
|
if (state.pages.recipes) {
|
|
state.pages.recipes.duplicatesMode = false;
|
|
}
|
|
state.virtualScroller = null;
|
|
state.keyboardNavHandler = null;
|
|
setupPageDom();
|
|
});
|
|
|
|
afterEach(() => {
|
|
state.global.settings = originalSettings;
|
|
state.virtualScroller = null;
|
|
state.keyboardNavHandler = null;
|
|
document.body.innerHTML = '';
|
|
vi.restoreAllMocks();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('cleans up keyboard navigation, disposes the old scroller, and rebuilds with the new layout', async () => {
|
|
state.global.settings.recipes_layout = 'grid';
|
|
await initializeInfiniteScroll('recipes');
|
|
const oldScroller = state.virtualScroller;
|
|
const oldKeyboardHandler = state.keyboardNavHandler;
|
|
expect(oldKeyboardHandler).toBeTypeOf('function');
|
|
|
|
const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener');
|
|
state.global.settings.recipes_layout = 'masonry';
|
|
|
|
await recreateVirtualScroll('recipes');
|
|
|
|
// cleanupKeyboardNavigation removed the previous document keydown listener
|
|
expect(removeEventListenerSpy).toHaveBeenCalledWith('keydown', oldKeyboardHandler);
|
|
// Old scroller disposed and replaced by a new masonry instance
|
|
expect(oldScroller.dispose).toHaveBeenCalledTimes(1);
|
|
expect(MasonryScrollerMock).toHaveBeenCalledTimes(1);
|
|
expect(state.virtualScroller).toBeInstanceOf(MasonryScrollerMock);
|
|
expect(state.virtualScroller).not.toBe(oldScroller);
|
|
// A fresh keyboard navigation listener was registered for the new instance
|
|
expect(state.keyboardNavHandler).toBeTypeOf('function');
|
|
expect(state.keyboardNavHandler).not.toBe(oldKeyboardHandler);
|
|
});
|
|
|
|
it('works when there is no existing virtual scroller', async () => {
|
|
state.global.settings.recipes_layout = 'masonry';
|
|
|
|
await expect(recreateVirtualScroll('recipes')).resolves.toBeUndefined();
|
|
|
|
expect(MasonryScrollerMock).toHaveBeenCalledTimes(1);
|
|
expect(state.virtualScroller).toBeInstanceOf(MasonryScrollerMock);
|
|
});
|
|
});
|