From f51f354e48cd3c08b8d106b4872681ba18d87094 Mon Sep 17 00:00:00 2001 From: pixelpaws Date: Wed, 24 Sep 2025 15:39:52 +0800 Subject: [PATCH] test(frontend): add dom fixture helpers --- docs/frontend-dom-fixtures.md | 51 ++++++++++++++ docs/frontend-testing-roadmap.md | 5 +- tests/frontend/setup.js | 6 +- .../js => tests/frontend}/state/index.test.js | 6 +- tests/frontend/utils/domFixtures.js | 68 +++++++++++++++++++ .../frontend}/utils/storageHelpers.test.js | 2 +- vitest.config.js | 1 - 7 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 docs/frontend-dom-fixtures.md rename {static/js => tests/frontend}/state/index.test.js (88%) create mode 100644 tests/frontend/utils/domFixtures.js rename {static/js => tests/frontend}/utils/storageHelpers.test.js (97%) diff --git a/docs/frontend-dom-fixtures.md b/docs/frontend-dom-fixtures.md new file mode 100644 index 00000000..ee7b785c --- /dev/null +++ b/docs/frontend-dom-fixtures.md @@ -0,0 +1,51 @@ +# Frontend DOM Fixture Strategy + +This guide outlines how to reproduce the markup emitted by the Django templates while running Vitest in jsdom. The aim is to make it straightforward to write integration-style unit tests for managers and UI helpers without having to duplicate template fragments inline. + +## Loading Template Markup + +Vitest executes inside Node, so we can read the same HTML templates that ship with the extension: + +1. Use the helper utilities from `tests/frontend/utils/domFixtures.js` to read files under the `templates/` directory. +2. Mount the returned markup into `document.body` (or any custom container) before importing the module under test so its query selectors resolve correctly. + +```js +import { renderTemplate } from '../utils/domFixtures.js'; // adjust the relative path to your spec + +beforeEach(() => { + renderTemplate('loras.html', { + dataset: { page: 'loras' } + }); +}); +``` + +The helper ensures the dataset is applied to the container, which mirrors how Django sets `data-page` in production. + +## Working with Partial Components + +Many features are implemented as template partials located under `templates/components/`. When a test only needs a fragment (for example, the progress panel or context menu markup), load the component file directly: + +```js +const container = renderTemplate('components/progress_panel.html'); + +const progressPanel = container.querySelector('#progress-panel'); +``` + +This pattern avoids hand-written fixture strings and keeps the tests aligned with the actual markup. + +## Resetting Between Tests + +The shared Vitest setup clears `document.body` and storage APIs before each test. If a suite adds additional DOM nodes outside of the body or needs to reset custom attributes mid-test, use `resetDom()` exported from `domFixtures.js`. + +```js +import { resetDom } from '../utils/domFixtures.js'; + +afterEach(() => { + resetDom(); +}); +``` + +## Future Enhancements + +- Provide typed helpers for injecting mock script tags (e.g., replicating ComfyUI globals). +- Compose higher-level fixtures that mimic specific pages (loras, checkpoints, recipes) once those managers receive dedicated suites. diff --git a/docs/frontend-testing-roadmap.md b/docs/frontend-testing-roadmap.md index e39e7c03..515e1baf 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; 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; documented DOM fixture workflow and plan to expand to additional page wiring and scroll hooks | | 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 | @@ -16,8 +16,9 @@ This roadmap tracks the planned rollout of automated testing for the ComfyUI LoR ## Next Steps Checklist - [x] Expand unit tests for `storageHelpers` covering migrations and namespace behavior. -- [ ] 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. - [ ] 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. 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/setup.js b/tests/frontend/setup.js index d575c568..b6862e67 100644 --- a/tests/frontend/setup.js +++ b/tests/frontend/setup.js @@ -1,4 +1,5 @@ import { afterEach, beforeEach } from 'vitest'; +import { resetDom } from './utils/domFixtures.js'; beforeEach(() => { // Ensure storage is clean before each test to avoid cross-test pollution @@ -6,11 +7,10 @@ beforeEach(() => { sessionStorage.clear(); // Reset DOM state for modules that rely on body attributes - document.body.innerHTML = ''; - document.body.dataset.page = ''; + resetDom(); }); afterEach(() => { // Clean any dynamically attached globals by tests - delete document.body.dataset.page; + resetDom(); }); diff --git a/static/js/state/index.test.js b/tests/frontend/state/index.test.js similarity index 88% rename from static/js/state/index.test.js rename to tests/frontend/state/index.test.js index da9221ea..50e5e6e8 100644 --- a/static/js/state/index.test.js +++ b/tests/frontend/state/index.test.js @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { createDefaultSettings, getCurrentPageState, initPageState, setCurrentPageType, state } from './index.js'; -import { MODEL_TYPES } from '../api/apiConfig.js'; -import { DEFAULT_PATH_TEMPLATES } from '../utils/constants.js'; +import { createDefaultSettings, getCurrentPageState, initPageState, setCurrentPageType, state } from '../../../static/js/state/index.js'; +import { MODEL_TYPES } from '../../../static/js/api/apiConfig.js'; +import { DEFAULT_PATH_TEMPLATES } from '../../../static/js/utils/constants.js'; describe('state module', () => { beforeEach(() => { diff --git a/tests/frontend/utils/domFixtures.js b/tests/frontend/utils/domFixtures.js new file mode 100644 index 00000000..ea903473 --- /dev/null +++ b/tests/frontend/utils/domFixtures.js @@ -0,0 +1,68 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +const TEMPLATE_ROOT = path.resolve(process.cwd(), 'templates'); + +/** + * Reads an HTML template from the templates directory and returns its markup. + * @param {string} relativePath - Path relative to the templates directory. + * @returns {string} + */ +export function readTemplate(relativePath) { + const filePath = path.join(TEMPLATE_ROOT, relativePath); + return fs.readFileSync(filePath, 'utf-8'); +} + +/** + * Injects the provided HTML markup into the supplied container (defaults to document.body). + * @param {string} html + * @param {Element} [container=document.body] + * @returns {Element} + */ +export function mountMarkup(html, container = document.body) { + container.innerHTML = html; + return container; +} + +/** + * Loads a template file and mounts it into the DOM, returning the container used. + * @param {string} relativePath - Template path relative to templates directory. + * @param {{ + * container?: Element, + * dataset?: Record, + * beforeMount?: (options: { container: Element }) => void, + * afterMount?: (options: { container: Element }) => void + * }} [options] + * @returns {Element} + */ +export function renderTemplate(relativePath, options = {}) { + const { container = document.body, dataset = {}, beforeMount, afterMount } = options; + if (beforeMount) { + beforeMount({ container }); + } + + const html = readTemplate(relativePath); + const target = mountMarkup(html, container); + + Object.entries(dataset).forEach(([key, value]) => { + target.dataset[key] = value; + }); + + if (afterMount) { + afterMount({ container: target }); + } + + return target; +} + +/** + * Utility to reset the DOM to a clean state. Useful when tests modify the structure + * beyond what the shared Vitest setup clears. + * @param {Element} [container=document.body] + */ +export function resetDom(container = document.body) { + container.innerHTML = ''; + if (container === document.body) { + document.body.removeAttribute('data-page'); + } +} diff --git a/static/js/utils/storageHelpers.test.js b/tests/frontend/utils/storageHelpers.test.js similarity index 97% rename from static/js/utils/storageHelpers.test.js rename to tests/frontend/utils/storageHelpers.test.js index 8603deb1..87acbcc7 100644 --- a/static/js/utils/storageHelpers.test.js +++ b/tests/frontend/utils/storageHelpers.test.js @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import * as storageHelpers from './storageHelpers.js'; +import * as storageHelpers from '../../../static/js/utils/storageHelpers.js'; const { getStorageItem, diff --git a/vitest.config.js b/vitest.config.js index f0dd3877..7f804af9 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -6,7 +6,6 @@ export default defineConfig({ globals: true, setupFiles: ['tests/frontend/setup.js'], include: [ - 'static/js/**/*.test.js', 'tests/frontend/**/*.test.js' ], coverage: {