test(frontend): add dom fixture helpers

This commit is contained in:
pixelpaws
2025-09-24 15:39:52 +08:00
parent 59d027181d
commit f51f354e48
7 changed files with 129 additions and 10 deletions

View File

@@ -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<string, string>,
* 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');
}
}

View File

@@ -0,0 +1,111 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import * as storageHelpers from '../../../static/js/utils/storageHelpers.js';
const {
getStorageItem,
setStorageItem,
removeStorageItem,
getSessionItem,
setSessionItem,
removeSessionItem,
} = storageHelpers;
const createFakeStorage = () => {
const store = new Map();
return {
getItem: vi.fn((key) => (store.has(key) ? store.get(key) : null)),
setItem: vi.fn((key, value) => {
store.set(key, value);
}),
removeItem: vi.fn((key) => {
store.delete(key);
}),
clear: vi.fn(() => {
store.clear();
}),
key: vi.fn((index) => Array.from(store.keys())[index] ?? null),
get length() {
return store.size;
},
_store: store
};
};
let localStorageMock;
let sessionStorageMock;
let consoleLogMock;
beforeEach(() => {
localStorageMock = createFakeStorage();
sessionStorageMock = createFakeStorage();
vi.stubGlobal('localStorage', localStorageMock);
vi.stubGlobal('sessionStorage', sessionStorageMock);
consoleLogMock = vi.spyOn(console, 'log').mockImplementation(() => {});
});
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
describe('storageHelpers namespace utilities', () => {
it('returns parsed JSON for prefixed localStorage items', () => {
localStorage.setItem('lora_manager_preferences', JSON.stringify({ theme: 'dark' }));
const result = getStorageItem('preferences');
expect(result).toEqual({ theme: 'dark' });
expect(localStorage.getItem).toHaveBeenCalledWith('lora_manager_preferences');
});
it('falls back to legacy keys and migrates them to the namespace', () => {
localStorage.setItem('legacy_key', 'value');
const value = getStorageItem('legacy_key');
expect(value).toBe('value');
expect(localStorage.getItem('lora_manager_legacy_key')).toBe('value');
});
it('serializes objects when setting prefixed localStorage values', () => {
const data = { ids: [1, 2, 3] };
setStorageItem('data', data);
expect(localStorage.setItem).toHaveBeenCalledWith('lora_manager_data', JSON.stringify(data));
expect(localStorage.getItem('lora_manager_data')).toEqual(JSON.stringify(data));
});
it('removes both prefixed and legacy localStorage entries', () => {
localStorage.setItem('lora_manager_temp', '123');
localStorage.setItem('temp', '456');
removeStorageItem('temp');
expect(localStorage.getItem('lora_manager_temp')).toBeNull();
expect(localStorage.getItem('temp')).toBeNull();
});
it('returns parsed JSON for session storage items', () => {
sessionStorage.setItem('lora_manager_session', JSON.stringify({ page: 'loras' }));
const session = getSessionItem('session');
expect(session).toEqual({ page: 'loras' });
});
it('stores primitives in session storage directly', () => {
setSessionItem('token', 'abc123');
expect(sessionStorage.setItem).toHaveBeenCalledWith('lora_manager_token', 'abc123');
expect(sessionStorage.getItem('lora_manager_token')).toBe('abc123');
});
it('removes session storage entries by namespace', () => {
sessionStorage.setItem('lora_manager_flag', '1');
removeSessionItem('flag');
expect(sessionStorage.getItem('lora_manager_flag')).toBeNull();
});
});