// 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 = `
`; 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); }); });