You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

285 lines
9.0 KiB

/**
* Theme Utility Tests
* Tests for dark/light theme switching and color management
*/
import Theme from '../../src/assets/scripts/utils/theme.js';
describe('Theme Utility', () => {
beforeEach(() => {
// Clear localStorage
localStorage.clear();
// Reset document theme
document.documentElement.removeAttribute('data-theme');
// Clear any theme-related CSS variables
document.documentElement.style.cssText = '';
});
afterEach(() => {
localStorage.clear();
});
describe('Theme Application', () => {
it('should apply light theme', () => {
Theme.apply('light');
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
});
it('should apply dark theme', () => {
Theme.apply('dark');
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
});
it('should save theme to localStorage', () => {
Theme.apply('dark');
expect(localStorage.getItem('adminator-theme')).toBe('dark');
});
it('should dispatch theme changed event', () => {
const handler = jest.fn();
window.addEventListener('adminator:themeChanged', handler);
Theme.apply('dark');
expect(handler).toHaveBeenCalled();
expect(handler.mock.calls[0][0].detail.theme).toBe('dark');
window.removeEventListener('adminator:themeChanged', handler);
});
it('should handle localStorage errors gracefully', () => {
// Mock localStorage.setItem to throw
const originalSetItem = Storage.prototype.setItem;
Storage.prototype.setItem = jest.fn(() => {
throw new Error('QuotaExceededError');
});
expect(() => Theme.apply('dark')).not.toThrow();
Storage.prototype.setItem = originalSetItem;
});
});
describe('Theme Toggle', () => {
it('should toggle from light to dark', () => {
Theme.apply('light');
Theme.toggle();
expect(Theme.current()).toBe('dark');
});
it('should toggle from dark to light', () => {
Theme.apply('dark');
Theme.toggle();
expect(Theme.current()).toBe('light');
});
it('should toggle multiple times', () => {
Theme.apply('light');
Theme.toggle(); // dark
expect(Theme.current()).toBe('dark');
Theme.toggle(); // light
expect(Theme.current()).toBe('light');
Theme.toggle(); // dark
expect(Theme.current()).toBe('dark');
});
});
describe('Current Theme', () => {
it('should return light as default', () => {
expect(Theme.current()).toBe('light');
});
it('should return saved theme', () => {
localStorage.setItem('adminator-theme', 'dark');
expect(Theme.current()).toBe('dark');
});
it('should handle localStorage errors', () => {
const originalGetItem = Storage.prototype.getItem;
Storage.prototype.getItem = jest.fn(() => {
throw new Error('SecurityError');
});
expect(Theme.current()).toBe('light');
Storage.prototype.getItem = originalGetItem;
});
});
describe('Theme Initialization', () => {
it('should initialize with saved theme', () => {
localStorage.setItem('adminator-theme', 'dark');
Theme.init();
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
});
it('should detect OS dark mode preference', () => {
// Mock matchMedia to prefer dark
window.matchMedia = jest.fn().mockImplementation(query => ({
matches: query === '(prefers-color-scheme: dark)',
media: query,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
}));
Theme.init();
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
});
it('should detect OS light mode preference', () => {
// Mock matchMedia to prefer light
window.matchMedia = jest.fn().mockImplementation(query => ({
matches: false,
media: query,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
}));
Theme.init();
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
});
it('should not override existing theme', () => {
localStorage.setItem('adminator-theme', 'light');
// Mock matchMedia to prefer dark
window.matchMedia = jest.fn().mockImplementation(query => ({
matches: query === '(prefers-color-scheme: dark)',
media: query,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
}));
Theme.init();
// Should use saved 'light', not OS preference 'dark'
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
});
});
describe('CSS Variable Access', () => {
it('should get CSS variable value', () => {
document.documentElement.style.setProperty('--test-color', '#FF0000');
const value = Theme.getCSSVar('--test-color');
expect(value).toBe('#FF0000');
});
it('should trim whitespace from CSS variable', () => {
document.documentElement.style.setProperty('--test-color', ' #FF0000 ');
const value = Theme.getCSSVar('--test-color');
expect(value).toBe('#FF0000');
});
it('should return empty string for non-existent variable', () => {
const value = Theme.getCSSVar('--non-existent');
expect(value).toBe('');
});
});
describe('Color Getters', () => {
beforeEach(() => {
// Set up CSS variables
document.documentElement.style.setProperty('--vmap-bg-color', '#FFFFFF');
document.documentElement.style.setProperty('--vmap-border-color', '#CCCCCC');
document.documentElement.style.setProperty('--sparkline-success', '#28A745');
document.documentElement.style.setProperty('--sparkline-purple', '#6F42C1');
});
describe('getVectorMapColors', () => {
it('should return vector map colors object', () => {
const colors = Theme.getVectorMapColors();
expect(colors).toHaveProperty('backgroundColor');
expect(colors).toHaveProperty('borderColor');
expect(colors).toHaveProperty('regionColor');
});
it('should return correct color values', () => {
const colors = Theme.getVectorMapColors();
expect(colors.backgroundColor).toBe('#FFFFFF');
expect(colors.borderColor).toBe('#CCCCCC');
});
});
describe('getSparklineColors', () => {
it('should return sparkline colors object', () => {
const colors = Theme.getSparklineColors();
expect(colors).toHaveProperty('success');
expect(colors).toHaveProperty('purple');
expect(colors).toHaveProperty('info');
expect(colors).toHaveProperty('danger');
expect(colors).toHaveProperty('light');
});
it('should return correct color values', () => {
const colors = Theme.getSparklineColors();
expect(colors.success).toBe('#28A745');
expect(colors.purple).toBe('#6F42C1');
});
});
describe('getChartColors', () => {
it('should return light theme chart colors', () => {
Theme.apply('light');
const colors = Theme.getChartColors();
expect(colors.textColor).toBe('#212529');
expect(colors.mutedColor).toBe('#6C757D');
});
it('should return dark theme chart colors', () => {
Theme.apply('dark');
const colors = Theme.getChartColors();
expect(colors.textColor).toBe('#FFFFFF');
expect(colors.mutedColor).toBe('#D1D5DB');
});
it('should have all required color properties', () => {
const colors = Theme.getChartColors();
expect(colors).toHaveProperty('textColor');
expect(colors).toHaveProperty('mutedColor');
expect(colors).toHaveProperty('borderColor');
expect(colors).toHaveProperty('gridColor');
expect(colors).toHaveProperty('tooltipBg');
});
});
});
describe('Theme Persistence', () => {
it('should persist theme across page reloads', () => {
Theme.apply('dark');
expect(localStorage.getItem('adminator-theme')).toBe('dark');
// Simulate page reload
const savedTheme = localStorage.getItem('adminator-theme');
expect(savedTheme).toBe('dark');
});
it('should maintain theme after toggle', () => {
Theme.apply('light');
Theme.toggle();
expect(localStorage.getItem('adminator-theme')).toBe('dark');
});
});
describe('Edge Cases', () => {
it('should handle invalid theme values', () => {
Theme.apply('invalid-theme');
expect(document.documentElement.getAttribute('data-theme')).toBe('invalid-theme');
});
it('should handle null theme', () => {
expect(() => Theme.apply(null)).not.toThrow();
});
it('should handle undefined theme', () => {
expect(() => Theme.apply(undefined)).not.toThrow();
});
it('should handle missing matchMedia', () => {
const originalMatchMedia = window.matchMedia;
delete window.matchMedia;
expect(() => Theme.init()).not.toThrow();
window.matchMedia = originalMatchMedia;
});
});
});