diff --git a/docs/frontend-filtering-test-matrix.md b/docs/frontend-filtering-test-matrix.md new file mode 100644 index 00000000..cd8f48d2 --- /dev/null +++ b/docs/frontend-filtering-test-matrix.md @@ -0,0 +1,44 @@ +# LoRA & Checkpoints Filtering/Sorting Test Matrix + +This matrix captures the scenarios that Phase 3 frontend tests should cover for the LoRA and Checkpoint managers. It focuses on how search, filter, sort, and duplicate badge toggles interact so future specs can share fixtures and expectations. + +## Scope + +- **Components**: `PageControls`, `FilterManager`, `SearchManager`, and `ModelDuplicatesManager` wiring invoked through `CheckpointsPageManager` and `LorasPageManager`. +- **Templates**: `templates/loras.html` and `templates/checkpoints.html` along with shared filter panel and toolbar partials. +- **APIs**: Requests issued through `baseModelApi.fetchModels` (via `resetAndReload`/`refreshModels`) and duplicates badge updates. + +## Shared Setup Considerations + +1. Render full page templates using `renderLorasPage` / `renderCheckpointsPage` helpers before importing modules so DOM queries resolve. +2. Stub storage helpers (`getStorageItem`, `setStorageItem`, `getSessionItem`, `setSessionItem`) to observe persistence behavior without mutating real storage. +3. Mock `sidebarManager` to capture refresh calls triggered after sort/filter actions. +4. Provide fake API implementations exposing `resetAndReload`, `refreshModels`, `fetchFromCivitai`, `toggleBulkMode`, and `clearCustomFilter` so control events remain asynchronous but deterministic. +5. Supply a minimal `ModelDuplicatesManager` mock exposing `toggleDuplicateMode`, `checkDuplicatesCount`, and `updateDuplicatesBadgeAfterRefresh` to validate duplicate badge wiring. + +## Scenario Matrix + +| ID | Feature | Scenario | LoRAs Expectations | Checkpoints Expectations | Notes | +| --- | --- | --- | --- | --- | --- | +| F-01 | Search filter | Typing a query updates `pageState.filters.search`, persists to session, and triggers `resetAndReload` on submit | Validate `SearchManager` writes query and reloads via API stub; confirm LoRA cards pass query downstream | Same as LoRAs | Cover `enter` press and clicking search icon | +| F-02 | Tag filter | Selecting a tag chip adds it to filters, applies active styling, and reloads results | Tag stored under `filters.tags`; `FilterManager.applyFilters` persists and triggers `resetAndReload(true)` | Same; ensure base model tag set is scoped to checkpoints dataset | Include removal path | +| F-03 | Base model filter | Toggling base model checkboxes updates `filters.baseModel`, persists, and reloads | Ensure only LoRA-supported models show; toggle multi-select | Ensure SDXL/Flux base models appear as expected | Capture UI state restored from storage on next init | +| F-04 | Favorites-only | Clicking favorites toggle updates session flag and calls `resetAndReload(true)` | Button gains `.active` class and API called | Same | Verify duplicates badge refresh when active | +| F-05 | Sort selection | Changing sort select saves preference (legacy + new format) and reloads | Confirm `PageControls.saveSortPreference` invoked with option and API called | Same with checkpoints-specific defaults | Cover `convertLegacySortFormat` branch | +| F-06 | Filter persistence | Re-initializing manager loads stored filters/sort and updates DOM | Filters pre-populate chips/checkboxes; favorites state restored | Same | Requires simulating repeated construction | +| F-07 | Combined filters | Applying search + tag + base model yields aggregated query params for fetch | Assert API receives merged filter payload | Same | Validate toast messaging for active filters | +| F-08 | Clearing filters | Using "Clear filters" resets state, storage, and reloads list | `FilterManager.clearFilters` empties `filters`, removes active class, shows toast | Same | Ensure favorites-only toggle unaffected | +| F-09 | Duplicate badge toggle | Pressing "Find duplicates" toggles duplicate mode and updates badge counts post-refresh | `ModelDuplicatesManager.toggleDuplicateMode` invoked and badge refresh called after API rebuild | Same plus checkpoint-specific duplicate badge dataset | Connects to future duplicate-specific specs | +| F-10 | Bulk actions menu | Opening bulk dropdown keeps filters intact and closes on outside click | Validate dropdown class toggling and no unintended reload | Same | Guard against regression when dropdown interacts with filters | + +## Automation Coverage Status + +- ✅ F-01 Search filter, F-02 Tag filter, F-03 Base model filter, F-04 Favorites-only toggle, F-05 Sort selection, and F-09 Duplicate badge toggle are covered by `tests/frontend/components/pageControls.filtering.test.js` for both LoRA and checkpoint pages. +- ⏳ F-06 Filter persistence, F-07 Combined filters, F-08 Clearing filters, and F-10 Bulk actions remain to be automated alongside upcoming bulk mode refinements. + +## Coverage Gaps & Follow-Ups + +- Write Vitest suites that exercise the matrix for both managers, sharing fixtures through page helpers to avoid duplication. +- Capture API parameter assertions by inspecting `baseModelApi.fetchModels` mocks rather than relying solely on state mutations. +- Add regression cases for legacy storage migrations (old filter keys) once fixtures exist for older payloads. +- Extend duplicate badge coverage with scenarios where `checkDuplicatesCount` signals zero duplicates versus pending calculations. diff --git a/docs/frontend-testing-roadmap.md b/docs/frontend-testing-roadmap.md index e4b4de39..50025cb7 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 | 🚧 In Progress | LoRA + checkpoints smoke suites landed; outlining filter/sort coverage before extending to embeddings | +| 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 + checkpoints smoke suites landed; filter/sort scenario matrix drafted to guide upcoming specs | | 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 | @@ -22,8 +22,11 @@ This roadmap tracks the planned rollout of automated testing for the ComfyUI LoR - [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). - [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. +- [x] Draft focused test matrix for loras/checkpoints manager filtering and sorting paths ahead of Phase 3. +- [x] Implement LoRAs manager filtering/sorting specs for scenarios F-01–F-05 & F-09; queue remaining edge cases after duplicate/bulk flows stabilize. +- [x] Implement checkpoints manager filtering/sorting specs for scenarios F-01–F-05 & F-09; cover remaining paths alongside bulk action work. - [x] Implement checkpoints page manager smoke tests covering initialization and duplicate badge wiring. -- [ ] Outline focused checkpoints scenarios (filtering, sorting, duplicate badge toggles) to feed into the shared test matrix. +- [x] Outline focused checkpoints scenarios (filtering, sorting, duplicate badge toggles) to feed into the shared test matrix. +- [ ] Add duplicate badge regression coverage for zero/pending states after API refreshes. Maintaining this roadmap alongside code changes will make it easier to append new automated test tasks and update their progress. diff --git a/tests/frontend/components/pageControls.filtering.test.js b/tests/frontend/components/pageControls.filtering.test.js new file mode 100644 index 00000000..bdbe4121 --- /dev/null +++ b/tests/frontend/components/pageControls.filtering.test.js @@ -0,0 +1,367 @@ +import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest'; + +const loadMoreWithVirtualScrollMock = vi.fn(); +const refreshModelsMock = vi.fn(); +const fetchCivitaiMetadataMock = vi.fn(); +const resetAndReloadMock = vi.fn(); +const getModelApiClientMock = vi.fn(); +const apiClientMock = { + loadMoreWithVirtualScroll: loadMoreWithVirtualScrollMock, + refreshModels: refreshModelsMock, + fetchCivitaiMetadata: fetchCivitaiMetadataMock, +}; + +const showToastMock = vi.fn(); +const updatePanelPositionsMock = vi.fn(); +const downloadManagerMock = { + showDownloadModal: vi.fn(), +}; + +const sidebarManagerMock = { + initialize: vi.fn(async () => { + sidebarManagerMock.isInitialized = true; + }), + refresh: vi.fn(async () => {}), + cleanup: vi.fn(), + isInitialized: false, +}; + +const createAlphabetBarMock = vi.fn(() => ({ destroy: vi.fn() })); + +getModelApiClientMock.mockReturnValue(apiClientMock); + +vi.mock('../../../static/js/api/modelApiFactory.js', () => ({ + getModelApiClient: getModelApiClientMock, + resetAndReload: resetAndReloadMock, +})); + +vi.mock('../../../static/js/utils/uiHelpers.js', () => ({ + showToast: showToastMock, + updatePanelPositions: updatePanelPositionsMock, +})); + +vi.mock('../../../static/js/managers/DownloadManager.js', () => ({ + downloadManager: downloadManagerMock, +})); + +vi.mock('../../../static/js/components/SidebarManager.js', () => ({ + sidebarManager: sidebarManagerMock, +})); + +vi.mock('../../../static/js/components/alphabet/index.js', () => ({ + createAlphabetBar: createAlphabetBarMock, +})); + +beforeEach(() => { + vi.resetModules(); + vi.clearAllMocks(); + + loadMoreWithVirtualScrollMock.mockResolvedValue(undefined); + refreshModelsMock.mockResolvedValue(undefined); + fetchCivitaiMetadataMock.mockResolvedValue(undefined); + resetAndReloadMock.mockResolvedValue(undefined); + getModelApiClientMock.mockReturnValue(apiClientMock); + + sidebarManagerMock.isInitialized = false; + sidebarManagerMock.initialize.mockImplementation(async () => { + sidebarManagerMock.isInitialized = true; + }); + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ success: true, base_models: [] }), + }); +}); + +afterEach(() => { + delete window.modelDuplicatesManager; + delete global.fetch; + vi.useRealTimers(); +}); + +function renderControlsDom(pageKey) { + document.body.dataset.page = pageKey; + document.body.innerHTML = ` +