544 lines
19 KiB
Python
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'
|
|
}
|