fix(frontend): correct AppCore example images initialization

This commit is contained in:
pixelpaws
2025-09-24 16:10:27 +08:00
parent 6d41211b07
commit 9d5ec43c4e
3 changed files with 132 additions and 8 deletions

View File

@@ -8,7 +8,7 @@ This roadmap tracks the planned rollout of automated testing for the ComfyUI LoR
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| Phase 0 | Establish baseline tooling | Add Node test runner, jsdom environment, and seed smoke tests | ✅ Complete | Vitest + jsdom configured, example state tests committed | | Phase 0 | Establish baseline tooling | Add Node test runner, jsdom environment, and seed smoke tests | ✅ Complete | Vitest + jsdom configured, example state tests committed |
| Phase 1 | Cover state management logic | Unit test selectors, derived data helpers, and storage utilities under `static/js/state` and `static/js/utils` | ✅ Complete | Storage helpers and state selectors now exercised via deterministic suites | | Phase 1 | Cover state management logic | Unit test selectors, derived data helpers, and storage utilities under `static/js/state` and `static/js/utils` | ✅ Complete | Storage helpers and state selectors now exercised via deterministic suites |
| Phase 2 | Test AppCore orchestration | Simulate page bootstrapping, infinite scroll hooks, and manager registration using JSDOM DOM fixtures | 🟡 In Progress | AppCore initialization specs landed; DOM fixtures now cover page feature wiring (context menus + infinite scroll); next focus is scroll hooks and manager wiring | | Phase 2 | Test AppCore orchestration | Simulate page bootstrapping, infinite scroll hooks, and manager registration using JSDOM DOM fixtures | ✅ Complete | AppCore initialization + page feature suites now validate manager wiring, infinite scroll hooks, and onboarding gating |
| Phase 3 | Validate page-specific managers | Add focused suites for `loras`, `checkpoints`, `embeddings`, and `recipes` managers covering filtering, sorting, and bulk actions | ⚪ Not Started | Consider shared helpers for mocking API modules and storage | | Phase 3 | Validate page-specific managers | Add focused suites for `loras`, `checkpoints`, `embeddings`, and `recipes` managers covering filtering, sorting, and bulk actions | ⚪ Not Started | Consider shared helpers for mocking API modules and storage |
| Phase 4 | Interaction-level regression tests | Exercise template fragments, modals, and menus to ensure UI wiring remains intact | ⚪ Not Started | Evaluate Playwright component testing or happy-path DOM snapshots | | Phase 4 | Interaction-level regression tests | Exercise template fragments, modals, and menus to ensure UI wiring remains intact | ⚪ Not Started | Evaluate Playwright component testing or happy-path DOM snapshots |
| Phase 5 | Continuous integration & coverage | Integrate frontend tests into CI workflow and track coverage metrics | ⚪ Not Started | Align reporting directories with backend coverage for unified reporting | | Phase 5 | Continuous integration & coverage | Integrate frontend tests into CI workflow and track coverage metrics | ⚪ Not Started | Align reporting directories with backend coverage for unified reporting |
@@ -19,7 +19,9 @@ This roadmap tracks the planned rollout of automated testing for the ComfyUI LoR
- [x] Document DOM fixture strategy for reproducing template structures in tests. - [x] Document DOM fixture strategy for reproducing template structures in tests.
- [x] Prototype AppCore initialization test that verifies manager bootstrapping with stubbed dependencies. - [x] Prototype AppCore initialization test that verifies manager bootstrapping with stubbed dependencies.
- [x] Add AppCore page feature suite exercising context menu creation and infinite scroll registration via DOM fixtures. - [x] Add AppCore page feature suite exercising context menu creation and infinite scroll registration via DOM fixtures.
- [x] Extend AppCore orchestration tests to cover manager wiring, bulk menu setup, and onboarding gating scenarios.
- [ ] Evaluate integrating coverage reporting once test surface grows (> 20 specs). - [ ] Evaluate integrating coverage reporting once test surface grows (> 20 specs).
- [ ] Create shared fixtures for the loras and checkpoints pages once dedicated manager suites are added. - [ ] Create shared fixtures for the loras and checkpoints pages once dedicated manager suites are added.
- [ ] Draft focused test matrix for loras/checkpoints manager filtering and sorting paths ahead of Phase 3.
Maintaining this roadmap alongside code changes will make it easier to append new automated test tasks and update their progress. Maintaining this roadmap alongside code changes will make it easier to append new automated test tasks and update their progress.

View File

@@ -49,7 +49,8 @@ export class AppCore {
bannerService.initialize(); bannerService.initialize();
window.modalManager = modalManager; window.modalManager = modalManager;
window.settingsManager = settingsManager; window.settingsManager = settingsManager;
window.exampleImagesManager = new ExampleImagesManager(); const exampleImagesManager = new ExampleImagesManager();
window.exampleImagesManager = exampleImagesManager;
window.helpManager = helpManager; window.helpManager = helpManager;
window.moveManager = moveManager; window.moveManager = moveManager;
window.bulkManager = bulkManager; window.bulkManager = bulkManager;

View File

@@ -1,8 +1,14 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { renderTemplate, resetDom } from '../utils/domFixtures.js'; import { renderTemplate, resetDom } from '../utils/domFixtures.js';
const loadingManagerInstance = { showSimpleLoading: vi.fn(), hide: vi.fn() };
const exampleImagesManagerInitialize = vi.fn();
const exampleImagesManagerInstance = { initialize: exampleImagesManagerInitialize };
const bulkContextMenuInstance = { menu: 'bulk-context' };
const headerManagerInstance = { type: 'header-manager' };
vi.mock('../../../static/js/managers/LoadingManager.js', () => ({ vi.mock('../../../static/js/managers/LoadingManager.js', () => ({
LoadingManager: vi.fn(() => ({})), LoadingManager: vi.fn(() => loadingManagerInstance),
})); }));
vi.mock('../../../static/js/managers/ModalManager.js', () => ({ vi.mock('../../../static/js/managers/ModalManager.js', () => ({
@@ -14,7 +20,7 @@ vi.mock('../../../static/js/managers/UpdateService.js', () => ({
})); }));
vi.mock('../../../static/js/components/Header.js', () => ({ vi.mock('../../../static/js/components/Header.js', () => ({
HeaderManager: vi.fn(() => ({})), HeaderManager: vi.fn(() => headerManagerInstance),
})); }));
vi.mock('../../../static/js/managers/SettingsManager.js', () => ({ vi.mock('../../../static/js/managers/SettingsManager.js', () => ({
@@ -35,9 +41,7 @@ vi.mock('../../../static/js/managers/BulkManager.js', () => ({
})); }));
vi.mock('../../../static/js/managers/ExampleImagesManager.js', () => ({ vi.mock('../../../static/js/managers/ExampleImagesManager.js', () => ({
ExampleImagesManager: vi.fn(() => ({ ExampleImagesManager: vi.fn(() => exampleImagesManagerInstance),
initialize: vi.fn(),
})),
})); }));
vi.mock('../../../static/js/managers/HelpManager.js', () => ({ vi.mock('../../../static/js/managers/HelpManager.js', () => ({
@@ -73,7 +77,7 @@ vi.mock('../../../static/js/managers/OnboardingManager.js', () => ({
})); }));
vi.mock('../../../static/js/components/ContextMenu/BulkContextMenu.js', () => ({ vi.mock('../../../static/js/components/ContextMenu/BulkContextMenu.js', () => ({
BulkContextMenu: vi.fn(), BulkContextMenu: vi.fn(() => bulkContextMenuInstance),
})); }));
vi.mock('../../../static/js/utils/eventManagementInit.js', () => ({ vi.mock('../../../static/js/utils/eventManagementInit.js', () => ({
@@ -90,6 +94,21 @@ vi.mock('../../../static/js/components/ContextMenu/index.js', () => ({
})); }));
import { appCore } from '../../../static/js/core.js'; import { appCore } from '../../../static/js/core.js';
import { state } from '../../../static/js/state/index.js';
import { LoadingManager } from '../../../static/js/managers/LoadingManager.js';
import { modalManager } from '../../../static/js/managers/ModalManager.js';
import { updateService } from '../../../static/js/managers/UpdateService.js';
import { settingsManager } from '../../../static/js/managers/SettingsManager.js';
import { moveManager } from '../../../static/js/managers/MoveManager.js';
import { bulkManager } from '../../../static/js/managers/BulkManager.js';
import { ExampleImagesManager } from '../../../static/js/managers/ExampleImagesManager.js';
import { helpManager } from '../../../static/js/managers/HelpManager.js';
import { bannerService } from '../../../static/js/managers/BannerService.js';
import { initTheme, initBackToTop } from '../../../static/js/utils/uiHelpers.js';
import { onboardingManager } from '../../../static/js/managers/OnboardingManager.js';
import { BulkContextMenu } from '../../../static/js/components/ContextMenu/BulkContextMenu.js';
import { HeaderManager } from '../../../static/js/components/Header.js';
import { initializeEventManagement } from '../../../static/js/utils/eventManagementInit.js';
import { initializeInfiniteScroll } from '../../../static/js/utils/infiniteScroll.js'; import { initializeInfiniteScroll } from '../../../static/js/utils/infiniteScroll.js';
import { createPageContextMenu, createGlobalContextMenu } from '../../../static/js/components/ContextMenu/index.js'; import { createPageContextMenu, createGlobalContextMenu } from '../../../static/js/components/ContextMenu/index.js';
@@ -151,3 +170,105 @@ describe('AppCore page orchestration', () => {
expect(window.globalContextMenuInstance).toBe(existingGlobalMenu); expect(window.globalContextMenuInstance).toBe(existingGlobalMenu);
}); });
}); });
describe('AppCore initialization flow', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.clearAllMocks();
resetDom();
document.body.className = '';
appCore.initialized = false;
state.loadingManager = undefined;
state.currentPageType = 'loras';
state.global.settings.card_info_display = 'always';
delete window.modalManager;
delete window.settingsManager;
delete window.exampleImagesManager;
delete window.helpManager;
delete window.moveManager;
delete window.bulkManager;
delete window.headerManager;
delete window.i18n;
delete window.pageContextMenu;
delete window.globalContextMenuInstance;
});
afterEach(async () => {
await vi.runAllTimersAsync();
vi.clearAllTimers();
vi.useRealTimers();
});
it('initializes core managers and global references', async () => {
state.global.settings.card_info_display = 'hover';
const result = await appCore.initialize();
expect(result).toBe(appCore);
expect(window.i18n).toBeDefined();
expect(settingsManager.waitForInitialization).toHaveBeenCalledTimes(1);
expect(LoadingManager).toHaveBeenCalledTimes(1);
expect(state.loadingManager).toBe(loadingManagerInstance);
expect(modalManager.initialize).toHaveBeenCalledTimes(1);
expect(updateService.initialize).toHaveBeenCalledTimes(1);
expect(bannerService.initialize).toHaveBeenCalledTimes(1);
expect(window.modalManager).toBe(modalManager);
expect(window.settingsManager).toBe(settingsManager);
expect(window.moveManager).toBe(moveManager);
expect(window.bulkManager).toBe(bulkManager);
expect(HeaderManager).toHaveBeenCalledTimes(1);
expect(window.headerManager).toBe(headerManagerInstance);
expect(initTheme).toHaveBeenCalledTimes(1);
expect(initBackToTop).toHaveBeenCalledTimes(1);
expect(bulkManager.initialize).toHaveBeenCalledTimes(1);
expect(BulkContextMenu).toHaveBeenCalledTimes(1);
expect(bulkManager.setBulkContextMenu).toHaveBeenCalledWith(bulkContextMenuInstance);
expect(ExampleImagesManager).toHaveBeenCalledTimes(1);
expect(window.exampleImagesManager).toBe(exampleImagesManagerInstance);
expect(exampleImagesManagerInitialize).toHaveBeenCalledTimes(1);
expect(helpManager.initialize).toHaveBeenCalledTimes(1);
expect(document.body.classList.contains('hover-reveal')).toBe(true);
expect(initializeEventManagement).toHaveBeenCalledTimes(1);
expect(onboardingManager.start).not.toHaveBeenCalled();
await vi.runAllTimersAsync();
expect(onboardingManager.start).toHaveBeenCalledTimes(1);
expect(bannerService.isBannerVisible).toHaveBeenCalledWith('version-mismatch');
});
it('does not reinitialize once initialized', async () => {
await appCore.initialize();
await vi.runAllTimersAsync();
vi.clearAllMocks();
const result = await appCore.initialize();
expect(result).toBeUndefined();
expect(LoadingManager).not.toHaveBeenCalled();
expect(modalManager.initialize).not.toHaveBeenCalled();
expect(updateService.initialize).not.toHaveBeenCalled();
expect(ExampleImagesManager).not.toHaveBeenCalled();
expect(onboardingManager.start).not.toHaveBeenCalled();
});
it('skips bulk setup when viewing recipes', async () => {
state.currentPageType = 'recipes';
await appCore.initialize();
expect(bulkManager.initialize).not.toHaveBeenCalled();
expect(BulkContextMenu).not.toHaveBeenCalled();
expect(bulkManager.setBulkContextMenu).not.toHaveBeenCalled();
});
it('suppresses onboarding when version mismatch banner is visible', async () => {
bannerService.isBannerVisible.mockReturnValueOnce(true);
await appCore.initialize();
await vi.runAllTimersAsync();
expect(onboardingManager.start).not.toHaveBeenCalled();
});
});