diff --git a/docs/frontend-testing-roadmap.md b/docs/frontend-testing-roadmap.md index 515e1baf..4c64398e 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; documented DOM fixture workflow and plan to expand to additional page wiring and scroll hooks | +| 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 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 | @@ -18,6 +18,7 @@ This roadmap tracks the planned rollout of automated testing for the ComfyUI LoR - [x] Expand unit tests for `storageHelpers` covering migrations and namespace behavior. - [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. - [ ] 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. diff --git a/tests/frontend/core/appCore.test.js b/tests/frontend/core/appCore.test.js new file mode 100644 index 00000000..864cbd29 --- /dev/null +++ b/tests/frontend/core/appCore.test.js @@ -0,0 +1,153 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { renderTemplate, resetDom } from '../utils/domFixtures.js'; + +vi.mock('../../../static/js/managers/LoadingManager.js', () => ({ + LoadingManager: vi.fn(() => ({})), +})); + +vi.mock('../../../static/js/managers/ModalManager.js', () => ({ + modalManager: { initialize: vi.fn() }, +})); + +vi.mock('../../../static/js/managers/UpdateService.js', () => ({ + updateService: { initialize: vi.fn() }, +})); + +vi.mock('../../../static/js/components/Header.js', () => ({ + HeaderManager: vi.fn(() => ({})), +})); + +vi.mock('../../../static/js/managers/SettingsManager.js', () => ({ + settingsManager: { + waitForInitialization: vi.fn().mockResolvedValue(undefined), + }, +})); + +vi.mock('../../../static/js/managers/MoveManager.js', () => ({ + moveManager: { initialize: vi.fn() }, +})); + +vi.mock('../../../static/js/managers/BulkManager.js', () => ({ + bulkManager: { + initialize: vi.fn(), + setBulkContextMenu: vi.fn(), + }, +})); + +vi.mock('../../../static/js/managers/ExampleImagesManager.js', () => ({ + ExampleImagesManager: vi.fn(() => ({ + initialize: vi.fn(), + })), +})); + +vi.mock('../../../static/js/managers/HelpManager.js', () => ({ + helpManager: { + initialize: vi.fn(), + }, +})); + +vi.mock('../../../static/js/managers/BannerService.js', () => ({ + bannerService: { + initialize: vi.fn(), + isBannerVisible: vi.fn().mockReturnValue(false), + }, +})); + +vi.mock('../../../static/js/utils/uiHelpers.js', () => ({ + initTheme: vi.fn(), + initBackToTop: vi.fn(), + showToast: vi.fn(), +})); + +vi.mock('../../../static/js/i18n/index.js', () => ({ + i18n: { + waitForReady: vi.fn().mockResolvedValue(undefined), + getCurrentLocale: vi.fn().mockReturnValue('en'), + }, +})); + +vi.mock('../../../static/js/managers/OnboardingManager.js', () => ({ + onboardingManager: { + start: vi.fn(), + }, +})); + +vi.mock('../../../static/js/components/ContextMenu/BulkContextMenu.js', () => ({ + BulkContextMenu: vi.fn(), +})); + +vi.mock('../../../static/js/utils/eventManagementInit.js', () => ({ + initializeEventManagement: vi.fn(), +})); + +vi.mock('../../../static/js/utils/infiniteScroll.js', () => ({ + initializeInfiniteScroll: vi.fn(), +})); + +vi.mock('../../../static/js/components/ContextMenu/index.js', () => ({ + createPageContextMenu: vi.fn((pageType) => ({ pageType })), + createGlobalContextMenu: vi.fn(() => ({ type: 'global' })), +})); + +import { appCore } from '../../../static/js/core.js'; +import { initializeInfiniteScroll } from '../../../static/js/utils/infiniteScroll.js'; +import { createPageContextMenu, createGlobalContextMenu } from '../../../static/js/components/ContextMenu/index.js'; + +const SUPPORTED_PAGES = ['loras', 'recipes', 'checkpoints', 'embeddings']; + +describe('AppCore page orchestration', () => { + beforeEach(() => { + resetDom(); + delete window.pageContextMenu; + delete window.globalContextMenuInstance; + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it.each(SUPPORTED_PAGES)('initializes page features for %s pages', (pageType) => { + renderTemplate('loras.html', { dataset: { page: pageType } }); + const contextSpy = vi.spyOn(appCore, 'initializeContextMenus'); + + appCore.initializePageFeatures(); + + expect(contextSpy).toHaveBeenCalledWith(pageType); + expect(initializeInfiniteScroll).toHaveBeenCalledWith(pageType); + }); + + it('skips initialization when page type is unsupported', () => { + renderTemplate('statistics.html', { dataset: { page: 'statistics' } }); + const contextSpy = vi.spyOn(appCore, 'initializeContextMenus'); + + appCore.initializePageFeatures(); + + expect(contextSpy).not.toHaveBeenCalled(); + expect(initializeInfiniteScroll).not.toHaveBeenCalled(); + }); + + it('creates page and global context menus on first initialization', () => { + const pageMenu = { menu: 'page' }; + const globalMenu = { menu: 'global' }; + createPageContextMenu.mockReturnValueOnce(pageMenu); + createGlobalContextMenu.mockReturnValueOnce(globalMenu); + + appCore.initializeContextMenus('loras'); + + expect(createPageContextMenu).toHaveBeenCalledWith('loras'); + expect(window.pageContextMenu).toBe(pageMenu); + expect(createGlobalContextMenu).toHaveBeenCalledTimes(1); + expect(window.globalContextMenuInstance).toBe(globalMenu); + }); + + it('reuses the existing global context menu instance on subsequent calls', () => { + const existingGlobalMenu = { menu: 'existing' }; + window.globalContextMenuInstance = existingGlobalMenu; + + appCore.initializeContextMenus('loras'); + + expect(createGlobalContextMenu).not.toHaveBeenCalled(); + expect(window.globalContextMenuInstance).toBe(existingGlobalMenu); + }); +});