Skip to main content
Glama
ThemeManager.test.ts9.16 kB
/** * Tests for ThemeManager * * Verifies theme registration, CSS generation, and preference handling. */ import { describe, it, expect, beforeEach } from '@jest/globals'; import { ThemeManager, DARK_THEME, LIGHT_THEME, type Theme } from '../../artifacts/theming/ThemeManager.js'; describe('ThemeManager', () => { let manager: ThemeManager; beforeEach(() => { manager = new ThemeManager(); }); describe('Constructor', () => { it('should initialize with built-in themes', () => { expect(manager).toBeDefined(); expect(manager.getAvailableThemes()).toContain('dark'); expect(manager.getAvailableThemes()).toContain('light'); }); }); describe('Theme Registration', () => { it('should register custom theme', () => { const customTheme: Theme = { name: 'custom', displayName: 'Custom Theme', variables: { '--bg-primary': '#000000', '--text-primary': '#ffffff' } }; manager.registerTheme(customTheme); expect(manager.getAvailableThemes()).toContain('custom'); expect(manager.getTheme('custom')).toEqual(customTheme); }); it('should throw error for theme without name', () => { const invalidTheme: Theme = { name: '', displayName: 'Invalid', variables: {} }; expect(() => manager.registerTheme(invalidTheme)).toThrow('Theme name is required'); }); it('should throw error for theme without displayName', () => { const invalidTheme: Theme = { name: 'invalid', displayName: '', variables: {} }; expect(() => manager.registerTheme(invalidTheme)).toThrow('Theme displayName is required'); }); it('should throw error for theme without variables', () => { const invalidTheme: Theme = { name: 'invalid', displayName: 'Invalid', variables: {} }; expect(() => manager.registerTheme(invalidTheme)).toThrow('Theme variables are required'); }); it('should overwrite existing theme on re-registration', () => { const theme1: Theme = { name: 'test', displayName: 'Test 1', variables: { '--color': 'red' } }; const theme2: Theme = { name: 'test', displayName: 'Test 2', variables: { '--color': 'blue' } }; manager.registerTheme(theme1); manager.registerTheme(theme2); expect(manager.getTheme('test')).toEqual(theme2); }); }); describe('Theme Retrieval', () => { it('should get dark theme by default', () => { const theme = manager.getTheme('dark'); expect(theme).toBeDefined(); expect(theme?.name).toBe('dark'); }); it('should get light theme', () => { const theme = manager.getTheme('light'); expect(theme).toBeDefined(); expect(theme?.name).toBe('light'); }); it('should return undefined for unknown theme', () => { const theme = manager.getTheme('nonexistent'); expect(theme).toBeUndefined(); }); it('should list all available themes', () => { const themes = manager.getAvailableThemes(); expect(themes).toHaveLength(2); expect(themes).toEqual(expect.arrayContaining(['dark', 'light'])); }); }); describe('CSS Generation', () => { it('should generate valid CSS with :root variables', () => { const css = manager.generateThemeCSS('dark'); expect(css).toContain('<style id="theme-css">'); expect(css).toContain(':root {'); expect(css).toContain('--bg-primary: #1e1e1e;'); expect(css).toContain('--text-primary: #d4d4d4;'); expect(css).toContain('</style>'); }); it('should include all dark theme variables', () => { const css = manager.generateThemeCSS('dark'); Object.entries(DARK_THEME.variables).forEach(([key, value]) => { expect(css).toContain(`${key}: ${value};`); }); }); it('should include all light theme variables', () => { const css = manager.generateThemeCSS('light'); Object.entries(LIGHT_THEME.variables).forEach(([key, value]) => { expect(css).toContain(`${key}: ${value};`); }); }); it('should include base styles using CSS variables', () => { const css = manager.generateThemeCSS('dark'); expect(css).toContain('background: var(--bg-primary);'); expect(css).toContain('color: var(--text-primary);'); expect(css).toContain('background: var(--button-bg);'); }); it('should include node type color classes', () => { const css = manager.generateThemeCSS('dark'); expect(css).toContain('.node-eventbus { fill: var(--node-eventbus); }'); expect(css).toContain('.node-signalbus { fill: var(--node-signalbus); }'); expect(css).toContain('.node-component { fill: var(--node-component); }'); }); it('should default to dark theme for unknown theme name', () => { const css = manager.generateThemeCSS('nonexistent'); expect(css).toContain('--bg-primary: #1e1e1e;'); // Dark theme default }); it('should default to dark theme when no theme specified', () => { const css = manager.generateThemeCSS(); expect(css).toContain('--bg-primary: #1e1e1e;'); // Dark theme default }); }); describe('Theme Switcher HTML Generation', () => { it('should generate theme switcher with buttons', () => { const html = manager.generateThemeSwitcherHTML(); expect(html).toContain('<div class="theme-switcher"'); expect(html).toContain('id="theme-dark"'); expect(html).toContain('id="theme-light"'); }); it('should include setTheme function', () => { const html = manager.generateThemeSwitcherHTML(); expect(html).toContain('function setTheme(themeName)'); expect(html).toContain('const THEMES ='); }); it('should embed theme data as JSON', () => { const html = manager.generateThemeSwitcherHTML(); expect(html).toContain('"dark"'); expect(html).toContain('"light"'); expect(html).toContain('--bg-primary'); }); it('should include localStorage persistence code', () => { const html = manager.generateThemeSwitcherHTML(); expect(html).toContain('localStorage.setItem'); expect(html).toContain('localStorage.getItem'); expect(html).toContain('cts-theme-preference'); }); it('should include DOMContentLoaded event listener', () => { const html = manager.generateThemeSwitcherHTML(); expect(html).toContain('DOMContentLoaded'); expect(html).toContain('setTheme(savedTheme)'); }); it('should have dark mode icon for dark theme button', () => { const html = manager.generateThemeSwitcherHTML(); expect(html).toContain('🌙 Dark Mode'); }); it('should have light mode icon for light theme button', () => { const html = manager.generateThemeSwitcherHTML(); expect(html).toContain('☀️ Light Mode'); }); }); describe('User Preference', () => { it('should return dark as default preference', () => { const pref = manager.getUserPreference(); expect(pref).toBe('dark'); }); }); describe('Built-in Themes', () => { describe('DARK_THEME', () => { it('should have all required variables', () => { const requiredVars = [ '--bg-primary', '--bg-secondary', '--text-primary', '--text-secondary', '--accent', '--border', '--success', '--warning', '--error', '--node-eventbus', '--node-signalbus', '--node-component', '--link-color' ]; requiredVars.forEach(varName => { expect(DARK_THEME.variables).toHaveProperty(varName); }); }); it('should have dark background colors', () => { expect(DARK_THEME.variables['--bg-primary']).toBe('#1e1e1e'); expect(DARK_THEME.variables['--bg-secondary']).toBe('#252526'); }); it('should have light text colors', () => { expect(DARK_THEME.variables['--text-primary']).toBe('#d4d4d4'); }); }); describe('LIGHT_THEME', () => { it('should have all required variables', () => { const requiredVars = [ '--bg-primary', '--bg-secondary', '--text-primary', '--text-secondary', '--accent', '--border', '--success', '--warning', '--error', '--node-eventbus', '--node-signalbus', '--node-component', '--link-color' ]; requiredVars.forEach(varName => { expect(LIGHT_THEME.variables).toHaveProperty(varName); }); }); it('should have light background colors', () => { expect(LIGHT_THEME.variables['--bg-primary']).toBe('#ffffff'); expect(LIGHT_THEME.variables['--bg-secondary']).toBe('#f5f5f5'); }); it('should have dark text colors', () => { expect(LIGHT_THEME.variables['--text-primary']).toBe('#1e1e1e'); }); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/EricA1019/CTS_MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server