95bd703b26
Docs Check / Markdown lint (push) Successful in 1m14s
Build and Push Docker Image / build (push) Successful in 1m52s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m55s
CI / Security audit (push) Successful in 2m25s
Docs Check / Mermaid diagram parse check (push) Successful in 3m6s
CI / Swagger Validation & Coverage (push) Successful in 3m22s
CI / Tests & coverage (push) Successful in 3m45s
95 lines
3.3 KiB
JavaScript
95 lines
3.3 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
/**
|
|
* @vitest-environment jsdom
|
|
* Tests for client/src/ui/theme.js
|
|
*
|
|
* Verifies DOM actions for theme switcher button clicks, attributes, and storage calls.
|
|
*/
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { initThemeSwitcher, setTheme } from '../../../client/src/ui/theme.js';
|
|
import * as storage from '../../../client/src/utils/storage.js';
|
|
|
|
vi.mock('../../../client/src/utils/storage.js', () => {
|
|
let store = {};
|
|
return {
|
|
getTheme: vi.fn(() => store.theme || 'light'),
|
|
saveTheme: vi.fn((theme) => { store.theme = theme; })
|
|
};
|
|
});
|
|
|
|
describe('theme switcher', () => {
|
|
let lightBtn, darkBtn, monoBtn;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
document.documentElement.removeAttribute('data-theme');
|
|
|
|
// Create mock theme buttons
|
|
document.body.innerHTML = `
|
|
<div class="theme-switcher">
|
|
<button class="theme-btn" data-theme="light">Light</button>
|
|
<button class="theme-btn" data-theme="dark">Dark</button>
|
|
<button class="theme-btn" data-theme="mono">Mono</button>
|
|
</div>
|
|
`;
|
|
|
|
lightBtn = document.querySelector('[data-theme="light"]');
|
|
darkBtn = document.querySelector('[data-theme="dark"]');
|
|
monoBtn = document.querySelector('[data-theme="mono"]');
|
|
});
|
|
|
|
it('initThemeSwitcher sets active class based on saved theme on load', () => {
|
|
vi.spyOn(storage, 'getTheme').mockReturnValue('dark');
|
|
|
|
initThemeSwitcher();
|
|
|
|
expect(storage.getTheme).toHaveBeenCalled();
|
|
expect(darkBtn.classList.contains('active')).toBe(true);
|
|
expect(lightBtn.classList.contains('active')).toBe(false);
|
|
expect(monoBtn.classList.contains('active')).toBe(false);
|
|
});
|
|
|
|
it('initThemeSwitcher defaults to light theme if no theme is saved', () => {
|
|
vi.spyOn(storage, 'getTheme').mockReturnValue(null);
|
|
|
|
initThemeSwitcher();
|
|
|
|
expect(lightBtn.classList.contains('active')).toBe(true);
|
|
expect(darkBtn.classList.contains('active')).toBe(false);
|
|
});
|
|
|
|
it('clicking theme button switches the document theme and persists choice', () => {
|
|
initThemeSwitcher();
|
|
|
|
// Initial active button should be light
|
|
expect(lightBtn.classList.contains('active')).toBe(true);
|
|
|
|
// Click Dark
|
|
darkBtn.click();
|
|
|
|
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
|
|
expect(storage.saveTheme).toHaveBeenCalledWith('dark');
|
|
expect(darkBtn.classList.contains('active')).toBe(true);
|
|
expect(lightBtn.classList.contains('active')).toBe(false);
|
|
|
|
// Click Mono
|
|
monoBtn.click();
|
|
|
|
expect(document.documentElement.getAttribute('data-theme')).toBe('mono');
|
|
expect(storage.saveTheme).toHaveBeenCalledWith('mono');
|
|
expect(monoBtn.classList.contains('active')).toBe(true);
|
|
expect(darkBtn.classList.contains('active')).toBe(false);
|
|
});
|
|
|
|
it('setTheme directly sets document attribute and updates button classes if present', () => {
|
|
initThemeSwitcher(); // binds buttons
|
|
|
|
setTheme('mono');
|
|
|
|
expect(document.documentElement.getAttribute('data-theme')).toBe('mono');
|
|
expect(storage.saveTheme).toHaveBeenCalledWith('mono');
|
|
expect(monoBtn.classList.contains('active')).toBe(true);
|
|
expect(lightBtn.classList.contains('active')).toBe(false);
|
|
});
|
|
});
|