From f0672beb467e6df69e5bd78337153bebc40c6f51 Mon Sep 17 00:00:00 2001 From: pixelpaws Date: Wed, 24 Sep 2025 16:22:17 +0800 Subject: [PATCH] test(frontend): add loras page manager suite --- docs/frontend-testing-roadmap.md | 5 +- static/js/checkpoints.js | 16 ++-- static/js/loras.js | 16 ++-- tests/frontend/pages/lorasPage.test.js | 109 +++++++++++++++++++++++++ tests/frontend/utils/pageFixtures.js | 25 ++++++ 5 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 tests/frontend/pages/lorasPage.test.js create mode 100644 tests/frontend/utils/pageFixtures.js diff --git a/docs/frontend-testing-roadmap.md b/docs/frontend-testing-roadmap.md index 03bbf026..2953c3c0 100644 --- a/docs/frontend-testing-roadmap.md +++ b/docs/frontend-testing-roadmap.md @@ -9,7 +9,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 | ✅ 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 | 🚧 In Progress | LoRA manager initialization suite landed; shared page fixtures ready for checkpoints | | 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 | @@ -21,7 +21,8 @@ This roadmap tracks the planned rollout of automated testing for the ComfyUI LoR - [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. +- [x] 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. +- [ ] Implement checkpoints page manager smoke tests covering initialization and duplicate badge wiring. 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/checkpoints.js b/static/js/checkpoints.js index a26099d3..de341008 100644 --- a/static/js/checkpoints.js +++ b/static/js/checkpoints.js @@ -5,7 +5,7 @@ import { ModelDuplicatesManager } from './components/ModelDuplicatesManager.js'; import { MODEL_TYPES } from './api/apiConfig.js'; // Initialize the Checkpoints page -class CheckpointsPageManager { +export class CheckpointsPageManager { constructor() { // Initialize page controls this.pageControls = createPageControls(MODEL_TYPES.CHECKPOINT); @@ -31,17 +31,21 @@ class CheckpointsPageManager { async initialize() { // Initialize common page features (including context menus) appCore.initializePageFeatures(); - + console.log('Checkpoints Manager initialized'); } } -// Initialize everything when DOM is ready -document.addEventListener('DOMContentLoaded', async () => { +export async function initializeCheckpointsPage() { // Initialize core application await appCore.initialize(); - + // Initialize checkpoints page const checkpointsPage = new CheckpointsPageManager(); await checkpointsPage.initialize(); -}); \ No newline at end of file + + return checkpointsPage; +} + +// Initialize everything when DOM is ready +document.addEventListener('DOMContentLoaded', initializeCheckpointsPage); \ No newline at end of file diff --git a/static/js/loras.js b/static/js/loras.js index 6566235f..3e2ccec8 100644 --- a/static/js/loras.js +++ b/static/js/loras.js @@ -6,7 +6,7 @@ import { confirmDelete, closeDeleteModal, confirmExclude, closeExcludeModal } fr import { ModelDuplicatesManager } from './components/ModelDuplicatesManager.js'; // Initialize the LoRA page -class LoraPageManager { +export class LoraPageManager { constructor() { // Add bulk mode to state state.bulkMode = false; @@ -38,18 +38,22 @@ class LoraPageManager { async initialize() { // Initialize cards for current bulk mode state (should be false initially) updateCardsForBulkMode(state.bulkMode); - + // Initialize common page features (including context menus and virtual scroll) appCore.initializePageFeatures(); } } -// Initialize everything when DOM is ready -document.addEventListener('DOMContentLoaded', async () => { +export async function initializeLoraPage() { // Initialize core application await appCore.initialize(); - + // Initialize page-specific functionality const loraPage = new LoraPageManager(); await loraPage.initialize(); -}); \ No newline at end of file + + return loraPage; +} + +// Initialize everything when DOM is ready +document.addEventListener('DOMContentLoaded', initializeLoraPage); \ No newline at end of file diff --git a/tests/frontend/pages/lorasPage.test.js b/tests/frontend/pages/lorasPage.test.js new file mode 100644 index 00000000..1768fa35 --- /dev/null +++ b/tests/frontend/pages/lorasPage.test.js @@ -0,0 +1,109 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { renderLorasPage } from '../utils/pageFixtures.js'; + +const initializeAppMock = vi.fn(); +const initializePageFeaturesMock = vi.fn(); +const updateCardsForBulkModeMock = vi.fn(); +const createPageControlsMock = vi.fn(); +const confirmDeleteMock = vi.fn(); +const closeDeleteModalMock = vi.fn(); +const confirmExcludeMock = vi.fn(); +const closeExcludeModalMock = vi.fn(); +const state = {}; +const duplicatesManagerMock = vi.fn(); + +vi.mock('../../../static/js/core.js', () => ({ + appCore: { + initialize: initializeAppMock, + initializePageFeatures: initializePageFeaturesMock, + }, +})); + +vi.mock('../../../static/js/state/index.js', () => ({ + state, +})); + +vi.mock('../../../static/js/components/shared/ModelCard.js', () => ({ + updateCardsForBulkMode: updateCardsForBulkModeMock, +})); + +vi.mock('../../../static/js/components/controls/index.js', () => ({ + createPageControls: createPageControlsMock, +})); + +vi.mock('../../../static/js/utils/modalUtils.js', () => ({ + confirmDelete: confirmDeleteMock, + closeDeleteModal: closeDeleteModalMock, + confirmExclude: confirmExcludeMock, + closeExcludeModal: closeExcludeModalMock, +})); + +vi.mock('../../../static/js/components/ModelDuplicatesManager.js', () => ({ + ModelDuplicatesManager: duplicatesManagerMock, +})); + +describe('LoraPageManager', () => { + let LoraPageManager; + let initializeLoraPage; + let duplicatesManagerInstance; + + beforeEach(async () => { + vi.clearAllMocks(); + + state.bulkMode = undefined; + state.selectedLoras = undefined; + + duplicatesManagerInstance = { + checkDuplicatesCount: vi.fn(), + }; + + duplicatesManagerMock.mockReturnValue(duplicatesManagerInstance); + createPageControlsMock.mockReturnValue({ destroy: vi.fn() }); + initializeAppMock.mockResolvedValue(undefined); + + renderLorasPage(); + + ({ LoraPageManager, initializeLoraPage } = await import('../../../static/js/loras.js')); + }); + + afterEach(() => { + delete window.confirmDelete; + delete window.closeDeleteModal; + delete window.confirmExclude; + delete window.closeExcludeModal; + delete window.modelDuplicatesManager; + }); + + it('configures state and exposes globals during construction', () => { + const manager = new LoraPageManager(); + + expect(state.bulkMode).toBe(false); + expect(state.selectedLoras).toBeInstanceOf(Set); + expect(createPageControlsMock).toHaveBeenCalledWith('loras'); + expect(duplicatesManagerMock).toHaveBeenCalledWith(manager); + + expect(window.confirmDelete).toBe(confirmDeleteMock); + expect(window.closeDeleteModal).toBe(closeDeleteModalMock); + expect(window.confirmExclude).toBe(confirmExcludeMock); + expect(window.closeExcludeModal).toBe(closeExcludeModalMock); + expect(window.modelDuplicatesManager).toBe(duplicatesManagerInstance); + }); + + it('initializes cards and page features', async () => { + const manager = new LoraPageManager(); + + await manager.initialize(); + + expect(updateCardsForBulkModeMock).toHaveBeenCalledWith(false); + expect(initializePageFeaturesMock).toHaveBeenCalledTimes(1); + }); + + it('boots the page when DOMContentLoaded handler runs', async () => { + const manager = await initializeLoraPage(); + + expect(initializeAppMock).toHaveBeenCalledTimes(1); + expect(manager).toBeInstanceOf(LoraPageManager); + expect(updateCardsForBulkModeMock).toHaveBeenCalledWith(false); + expect(window.modelDuplicatesManager).toBe(duplicatesManagerInstance); + }); +}); diff --git a/tests/frontend/utils/pageFixtures.js b/tests/frontend/utils/pageFixtures.js new file mode 100644 index 00000000..7b4ed1b4 --- /dev/null +++ b/tests/frontend/utils/pageFixtures.js @@ -0,0 +1,25 @@ +import { renderTemplate } from './domFixtures.js'; + +/** + * Renders the LoRAs page template with expected dataset attributes. + * @returns {Element} + */ +export function renderLorasPage() { + return renderTemplate('loras.html', { + dataset: { + page: 'loras', + }, + }); +} + +/** + * Renders the Checkpoints page template with expected dataset attributes. + * @returns {Element} + */ +export function renderCheckpointsPage() { + return renderTemplate('checkpoints.html', { + dataset: { + page: 'checkpoints', + }, + }); +}