diff --git a/docs/frontend-testing-roadmap.md b/docs/frontend-testing-roadmap.md index 4c64398e..03bbf026 100644 --- a/docs/frontend-testing-roadmap.md +++ b/docs/frontend-testing-roadmap.md @@ -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 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 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 | @@ -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] 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] 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). - [ ] 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. diff --git a/static/js/core.js b/static/js/core.js index 80e0ec69..9701a32f 100644 --- a/static/js/core.js +++ b/static/js/core.js @@ -49,7 +49,8 @@ export class AppCore { bannerService.initialize(); window.modalManager = modalManager; window.settingsManager = settingsManager; - window.exampleImagesManager = new ExampleImagesManager(); + const exampleImagesManager = new ExampleImagesManager(); + window.exampleImagesManager = exampleImagesManager; window.helpManager = helpManager; window.moveManager = moveManager; window.bulkManager = bulkManager; diff --git a/tests/frontend/core/appCore.test.js b/tests/frontend/core/appCore.test.js index 864cbd29..094200f1 100644 --- a/tests/frontend/core/appCore.test.js +++ b/tests/frontend/core/appCore.test.js @@ -1,8 +1,14 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; 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', () => ({ - LoadingManager: vi.fn(() => ({})), + LoadingManager: vi.fn(() => loadingManagerInstance), })); 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', () => ({ - HeaderManager: vi.fn(() => ({})), + HeaderManager: vi.fn(() => headerManagerInstance), })); 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', () => ({ - ExampleImagesManager: vi.fn(() => ({ - initialize: vi.fn(), - })), + ExampleImagesManager: vi.fn(() => exampleImagesManagerInstance), })); 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', () => ({ - BulkContextMenu: vi.fn(), + BulkContextMenu: vi.fn(() => bulkContextMenuInstance), })); 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 { 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 { createPageContextMenu, createGlobalContextMenu } from '../../../static/js/components/ContextMenu/index.js'; @@ -151,3 +170,105 @@ describe('AppCore page orchestration', () => { 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(); + }); +});