EU-Utility/plugins/ui_test_suite/test_modules/theme_tests.py

544 lines
19 KiB
Python

"""
Theme & Styling Tests
Tests for theme consistency and styling including:
- Color system
- Typography
- Component styles
- Dark/light theme switching
- Style consistency
"""
class ThemeStylingTests:
"""Test suite for theme and styling."""
name = "Theme & Styling"
icon = "🎨"
description = "Tests theme consistency, styling, and dark/light mode switching"
def __init__(self):
self.tests = {
'color_system': self.test_color_system,
'typography_system': self.test_typography_system,
'theme_switching': self.test_theme_switching,
'button_styles': self.test_button_styles,
'input_styles': self.test_input_styles,
'table_styles': self.test_table_styles,
'scrollbar_styles': self.test_scrollbar_styles,
'global_stylesheet': self.test_global_stylesheet,
'component_consistency': self.test_component_consistency,
'accessibility_colors': self.test_accessibility_colors,
}
def test_color_system(self) -> dict:
"""Test color system completeness."""
try:
from core.eu_styles import (
EU_DARK_COLORS, EU_LIGHT_COLORS,
get_color, get_all_colors, EUTheme
)
issues = []
# Check required color categories
required_colors = [
'bg_primary', 'bg_secondary', 'bg_tertiary',
'text_primary', 'text_secondary', 'text_muted',
'accent_orange', 'accent_teal', 'accent_blue',
'border_default', 'border_hover', 'border_focus',
'status_success', 'status_warning', 'status_error'
]
# Check dark theme
dark_missing = [c for c in required_colors if c not in EU_DARK_COLORS]
if dark_missing:
issues.append(f"Dark theme missing: {dark_missing}")
# Check light theme
light_missing = [c for c in required_colors if c not in EU_LIGHT_COLORS]
if light_missing:
issues.append(f"Light theme missing: {light_missing}")
# Check color getter
if not callable(get_color):
issues.append("get_color is not callable")
if not callable(get_all_colors):
issues.append("get_all_colors is not callable")
# Test get_color
EUTheme.set_theme('dark')
color = get_color('bg_primary')
if not color or not isinstance(color, str):
issues.append("get_color returned invalid value")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'error'
}
return {
'passed': True,
'message': f"Color system complete ({len(EU_DARK_COLORS)} dark, {len(EU_LIGHT_COLORS)} light colors)",
'severity': 'info'
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking color system: {e}",
'severity': 'error'
}
def test_typography_system(self) -> dict:
"""Test typography system."""
try:
from core.eu_styles import EU_TYPOGRAPHY
issues = []
# Check required typography fields
required = [
'font_family', 'font_mono',
'size_xs', 'size_sm', 'size_base', 'size_md', 'size_lg', 'size_xl',
'weight_normal', 'weight_medium', 'weight_semibold', 'weight_bold',
'line_tight', 'line_normal', 'line_relaxed'
]
missing = [r for r in required if r not in EU_TYPOGRAPHY]
if missing:
issues.append(f"Missing typography fields: {missing}")
# Check font family
if 'font_family' in EU_TYPOGRAPHY:
if 'Segoe UI' not in EU_TYPOGRAPHY['font_family']:
issues.append("Primary font family may not be optimal for Windows")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning'
}
return {
'passed': True,
'message': f"Typography system complete ({len(EU_TYPOGRAPHY)} definitions)",
'severity': 'info'
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking typography: {e}",
'severity': 'error'
}
def test_theme_switching(self) -> dict:
"""Test theme switching functionality."""
try:
from core.eu_styles import EUTheme, get_all_colors, EU_DARK_COLORS, EU_LIGHT_COLORS
issues = []
# Check EUTheme class
if not hasattr(EUTheme, 'set_theme'):
issues.append("EUTheme.set_theme missing")
if not hasattr(EUTheme, 'get_theme'):
issues.append("EUTheme.get_theme missing")
if not hasattr(EUTheme, 'is_dark'):
issues.append("EUTheme.is_dark missing")
# Test theme switching
original = EUTheme.get_theme()
EUTheme.set_theme('dark')
if EUTheme.get_theme() != 'dark':
issues.append("Failed to set dark theme")
dark_colors = get_all_colors()
if dark_colors != EU_DARK_COLORS:
issues.append("Dark theme colors not returned correctly")
EUTheme.set_theme('light')
if EUTheme.get_theme() != 'light':
issues.append("Failed to set light theme")
light_colors = get_all_colors()
if light_colors != EU_LIGHT_COLORS:
issues.append("Light theme colors not returned correctly")
# Restore
EUTheme.set_theme(original)
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'error'
}
return {
'passed': True,
'message': "Theme switching working correctly",
'severity': 'info'
}
except Exception as e:
return {
'passed': False,
'message': f"Error testing theme switching: {e}",
'severity': 'error'
}
def test_button_styles(self) -> dict:
"""Test button style generation."""
try:
from core.eu_styles import get_button_style
issues = []
# Check function exists
if not callable(get_button_style):
return {
'passed': False,
'message': "get_button_style not callable",
'severity': 'error'
}
# Test variants
variants = ['primary', 'secondary', 'ghost', 'danger', 'success']
sizes = ['sm', 'md', 'lg']
for variant in variants:
try:
style = get_button_style(variant, 'md')
if not style or not isinstance(style, str):
issues.append(f"get_button_style('{variant}') returned invalid style")
except Exception as e:
issues.append(f"get_button_style('{variant}') failed: {e}")
# Test sizes
for size in sizes:
try:
style = get_button_style('primary', size)
if not style or not isinstance(style, str):
issues.append(f"get_button_style('primary', '{size}') returned invalid style")
except Exception as e:
issues.append(f"get_button_style('primary', '{size}') failed: {e}")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning'
}
return {
'passed': True,
'message': f"Button styles working for {len(variants)} variants and {len(sizes)} sizes",
'severity': 'info'
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking button styles: {e}",
'severity': 'error'
}
def test_input_styles(self) -> dict:
"""Test input field styles."""
try:
from core.eu_styles import get_input_style, get_combo_style
issues = []
# Check input style
if not callable(get_input_style):
issues.append("get_input_style not callable")
else:
style = get_input_style()
if not style or 'QLineEdit' not in style:
issues.append("get_input_style missing QLineEdit styling")
# Check combo style
if not callable(get_combo_style):
issues.append("get_combo_style not callable")
else:
style = get_combo_style()
if not style or 'QComboBox' not in style:
issues.append("get_combo_style missing QComboBox styling")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning'
}
return {
'passed': True,
'message': "Input styles present",
'severity': 'info'
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking input styles: {e}",
'severity': 'error'
}
def test_table_styles(self) -> dict:
"""Test table widget styles."""
try:
from core.eu_styles import get_table_style
issues = []
# Check function
if not callable(get_table_style):
return {
'passed': False,
'message': "get_table_style not callable",
'severity': 'warning'
}
style = get_table_style()
# Check for required elements
required = ['QTableWidget', 'QHeaderView']
missing = [r for r in required if r not in style]
if missing:
issues.append(f"Table style missing: {missing}")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning'
}
return {
'passed': True,
'message': "Table styles present",
'severity': 'info'
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking table styles: {e}",
'severity': 'error'
}
def test_scrollbar_styles(self) -> dict:
"""Test scrollbar styles."""
try:
from core.eu_styles import get_scrollbar_style
issues = []
# Check function
if not callable(get_scrollbar_style):
return {
'passed': False,
'message': "get_scrollbar_style not callable",
'severity': 'warning'
}
style = get_scrollbar_style()
# Check for vertical and horizontal
if 'vertical' not in style:
issues.append("Missing vertical scrollbar styling")
if 'horizontal' not in style:
issues.append("Missing horizontal scrollbar styling")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning'
}
return {
'passed': True,
'message': "Scrollbar styles present",
'severity': 'info'
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking scrollbar styles: {e}",
'severity': 'error'
}
def test_global_stylesheet(self) -> dict:
"""Test global stylesheet generation."""
try:
from core.eu_styles import get_global_stylesheet
issues = []
# Check function
if not callable(get_global_stylesheet):
return {
'passed': False,
'message': "get_global_stylesheet not callable",
'severity': 'error'
}
style = get_global_stylesheet()
if not style or not isinstance(style, str):
return {
'passed': False,
'message': "get_global_stylesheet returned invalid value",
'severity': 'error'
}
# Check for base styling
required = ['QWidget', 'QMainWindow']
missing = [r for r in required if r not in style]
if missing:
issues.append(f"Global stylesheet missing: {missing}")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning'
}
return {
'passed': True,
'message': f"Global stylesheet generated ({len(style)} characters)",
'severity': 'info'
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking global stylesheet: {e}",
'severity': 'error'
}
def test_component_consistency(self) -> dict:
"""Test component style consistency."""
try:
from core.eu_styles import (
get_button_style, get_input_style, get_table_style,
get_card_style, get_panel_style, get_color
)
issues = []
recommendations = []
# Get styles
button = get_button_style('primary')
input_style = get_input_style()
table = get_table_style()
# Check for common color usage
bg_color = get_color('bg_secondary')
# All components should use theme colors
if bg_color not in input_style:
recommendations.append("Input style may not use theme background color")
# Check border radius consistency
radii = []
for style in [button, input_style]:
import re
matches = re.findall(r'border-radius:\s*(\d+)px', style)
radii.extend([int(m) for m in matches])
if radii and max(radii) - min(radii) > 8:
recommendations.append("Consider more consistent border radius values")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning'
}
return {
'passed': True,
'message': "Component consistency checked",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking consistency: {e}",
'severity': 'error'
}
def test_accessibility_colors(self) -> dict:
"""Test color accessibility."""
try:
from core.eu_styles import EU_DARK_COLORS, EU_LIGHT_COLORS
issues = []
recommendations = []
# Check contrast ratios (basic check)
# WCAG AA requires 4.5:1 for normal text, 3:1 for large text
dark_bg = EU_DARK_COLORS.get('bg_primary', '#0d1117')
dark_text = EU_DARK_COLORS.get('text_primary', '#f0f6fc')
light_bg = EU_LIGHT_COLORS.get('bg_primary', '#ffffff')
light_text = EU_LIGHT_COLORS.get('text_primary', '#24292f')
# Simple check - ensure colors are different
if dark_bg == dark_text:
issues.append("Dark theme background and text are identical")
if light_bg == light_text:
issues.append("Light theme background and text are identical")
# Check for focus indicators
if 'border_focus' not in EU_DARK_COLORS:
recommendations.append("Consider adding border_focus for accessibility")
# Check for disabled states
if 'text_disabled' not in EU_DARK_COLORS:
recommendations.append("Consider adding text_disabled state")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'error'
}
return {
'passed': True,
'message': "Color accessibility checked",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking accessibility colors: {e}",
'severity': 'error'
}