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

536 lines
20 KiB
Python

"""
Accessibility Tests
Tests for accessibility features including:
- Keyboard navigation
- Screen reader support
- Focus indicators
- Color contrast
- High contrast mode
"""
class AccessibilityTests:
"""Test suite for accessibility."""
name = "Accessibility"
icon = ""
description = "Tests keyboard navigation, screen reader support, focus indicators, and color contrast"
def __init__(self):
self.tests = {
'keyboard_navigation': self.test_keyboard_navigation,
'focus_indicators': self.test_focus_indicators,
'screen_reader_support': self.test_screen_reader_support,
'color_contrast': self.test_color_contrast,
'high_contrast_mode': self.test_high_contrast_mode,
'shortcut_hints': self.test_shortcut_hints,
'accessible_names': self.test_accessible_names,
'tab_order': self.test_tab_order,
'reduced_motion': self.test_reduced_motion,
'font_scaling': self.test_font_scaling,
}
def test_keyboard_navigation(self) -> dict:
"""Test keyboard navigation support."""
try:
from core.overlay_window import OverlayWindow
from core.eu_styles import AccessibilityHelper
issues = []
recommendations = []
# Check shortcut setup
if not hasattr(OverlayWindow, '_setup_shortcuts'):
issues.append("_setup_shortcuts missing")
# Check keyboard handler
if not hasattr(OverlayWindow, 'keyPressEvent'):
issues.append("keyPressEvent missing")
# Check accessibility helper
if not hasattr(AccessibilityHelper, 'get_keyboard_shortcut_hint'):
recommendations.append("Consider using AccessibilityHelper for shortcut hints")
# Check for common keyboard shortcuts
import inspect
if hasattr(OverlayWindow, '_setup_shortcuts'):
source = inspect.getsource(OverlayWindow._setup_shortcuts)
expected = ['esc', 'ctrl']
missing = [e for e in expected if e not in source.lower()]
if missing:
recommendations.append(f"Consider adding keyboard shortcuts: {missing}")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'error',
'recommendation': recommendations[0] if recommendations else None
}
return {
'passed': True,
'message': "Keyboard navigation features present",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking keyboard navigation: {e}",
'severity': 'error'
}
def test_focus_indicators(self) -> dict:
"""Test focus indicator visibility."""
try:
from core.eu_styles import AccessibilityHelper, get_global_stylesheet
issues = []
# Check focus ring style
if not hasattr(AccessibilityHelper, 'FOCUS_RING_STYLE'):
issues.append("FOCUS_RING_STYLE not defined")
else:
style = AccessibilityHelper.FOCUS_RING_STYLE
if 'focus' not in style.lower():
issues.append("Focus ring style may not target :focus state")
if 'outline' not in style.lower():
issues.append("Focus ring should use outline for visibility")
# Check global stylesheet includes focus styles
global_style = get_global_stylesheet()
if 'focus' not in global_style.lower():
issues.append("Global stylesheet missing focus styles")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning'
}
return {
'passed': True,
'message': "Focus indicators present in styles",
'severity': 'info'
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking focus indicators: {e}",
'severity': 'error'
}
def test_screen_reader_support(self) -> dict:
"""Test screen reader support."""
try:
from core.overlay_window import OverlayWindow
from core.eu_styles import AccessibilityHelper
issues = []
recommendations = []
# Check accessibility helper methods
if not hasattr(AccessibilityHelper, 'set_accessible_name'):
recommendations.append("Consider using set_accessible_name for widgets")
if not hasattr(AccessibilityHelper, 'set_accessible_description'):
recommendations.append("Consider using set_accessible_description for complex widgets")
# Check for accessibleName usage in overlay
import inspect
if hasattr(OverlayWindow, '_setup_tray'):
source = inspect.getsource(OverlayWindow._setup_tray)
if 'accessible' not in source.lower():
recommendations.append("Consider adding accessible names to tray menu items")
# Check sidebar buttons
if hasattr(OverlayWindow, '_create_sidebar'):
source = inspect.getsource(OverlayWindow._create_sidebar)
if 'accessible' not in source.lower():
recommendations.append("Consider adding accessible names to sidebar buttons")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning',
'recommendation': recommendations[0] if recommendations else None
}
return {
'passed': True,
'message': "Screen reader support features present",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking screen reader support: {e}",
'severity': 'error'
}
def test_color_contrast(self) -> dict:
"""Test color contrast ratios."""
try:
from core.eu_styles import EU_DARK_COLORS, EU_LIGHT_COLORS
issues = []
recommendations = []
# Check for sufficient contrast (simplified check)
# WCAG AA requires 4.5:1 for normal text
dark_pairs = [
('bg_primary', 'text_primary'),
('bg_secondary', 'text_primary'),
('bg_tertiary', 'text_secondary'),
]
for bg, fg in dark_pairs:
if bg not in EU_DARK_COLORS or fg not in EU_DARK_COLORS:
issues.append(f"Missing colors for contrast check: {bg}, {fg}")
light_pairs = [
('bg_primary', 'text_primary'),
('bg_secondary', 'text_primary'),
('bg_tertiary', 'text_secondary'),
]
for bg, fg in light_pairs:
if bg not in EU_LIGHT_COLORS or fg not in EU_LIGHT_COLORS:
issues.append(f"Missing light theme colors: {bg}, {fg}")
# Check for status colors that might be problematic
status_colors = ['status_success', 'status_warning', 'status_error']
for color in status_colors:
if color not in EU_DARK_COLORS:
recommendations.append(f"Consider adding {color} for status indication")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning'
}
return {
'passed': True,
'message': "Color contrast structure checked",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking color contrast: {e}",
'severity': 'error'
}
def test_high_contrast_mode(self) -> dict:
"""Test high contrast mode support."""
try:
from core.eu_styles import AccessibilityHelper
issues = []
recommendations = []
# Check for high contrast method
if not hasattr(AccessibilityHelper, 'make_high_contrast'):
recommendations.append("Consider implementing make_high_contrast method")
else:
# Check if method actually modifies colors
import inspect
source = inspect.getsource(AccessibilityHelper.make_high_contrast)
if 'color' not in source.lower():
recommendations.append("make_high_contrast may not modify colors")
# Check for UI toggle
recommendations.append("Consider adding high contrast toggle in settings")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
return {
'passed': True,
'message': "High contrast mode features checked",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking high contrast: {e}",
'severity': 'error'
}
def test_shortcut_hints(self) -> dict:
"""Test keyboard shortcut hints."""
try:
from core.overlay_window import OverlayWindow
from core.eu_styles import AccessibilityHelper
issues = []
recommendations = []
# Check for shortcut hints in UI
import inspect
if hasattr(OverlayWindow, '_create_sidebar'):
source = inspect.getsource(OverlayWindow._create_sidebar)
if 'ctrl' in source.lower() or 'keyboard' in source.lower():
pass # Good, has keyboard hints
else:
recommendations.append("Consider adding keyboard shortcut hints to sidebar")
# Check about tab for shortcuts
if hasattr(OverlayWindow, '_create_about_tab'):
source = inspect.getsource(OverlayWindow._create_about_tab)
if 'ctrl' not in source.lower() and 'shortcut' not in source.lower():
recommendations.append("Consider documenting shortcuts in about tab")
# Check accessibility helper
if hasattr(AccessibilityHelper, 'get_keyboard_shortcut_hint'):
# Test the method
hint = AccessibilityHelper.get_keyboard_shortcut_hint("Ctrl+T")
if not hint or "Ctrl+T" not in hint:
recommendations.append("get_keyboard_shortcut_hint may not format correctly")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
return {
'passed': True,
'message': "Shortcut hints checked",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking shortcut hints: {e}",
'severity': 'error'
}
def test_accessible_names(self) -> dict:
"""Test accessible names on widgets."""
try:
from core.overlay_window import OverlayWindow
from core.activity_bar import ActivityBar
issues = []
recommendations = []
# Check for setAccessibleName usage
import inspect
checked_methods = ['_setup_tray', '_create_sidebar', '_create_header']
for method_name in checked_methods:
if hasattr(OverlayWindow, method_name):
source = inspect.getsource(getattr(OverlayWindow, method_name))
if 'accessible' not in source.lower():
recommendations.append(f"Consider adding accessible names in {method_name}")
# Check close button specifically
if hasattr(OverlayWindow, '_create_header'):
source = inspect.getsource(OverlayWindow._create_header)
if 'close' in source.lower() and 'accessible' not in source.lower():
recommendations.append("Close button should have accessible name")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning',
'recommendation': recommendations[0] if recommendations else None
}
return {
'passed': True,
'message': "Accessible names checked",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking accessible names: {e}",
'severity': 'error'
}
def test_tab_order(self) -> dict:
"""Test tab order for keyboard navigation."""
try:
from core.overlay_window import OverlayWindow
issues = []
recommendations = []
# Check for setTabOrder usage
import inspect
if hasattr(OverlayWindow, '_setup_ui'):
source = inspect.getsource(OverlayWindow._setup_ui)
if 'taborder' not in source.lower() and 'tab order' not in source.lower():
recommendations.append("Consider defining explicit tab order for widgets")
# Check settings dialog
if hasattr(OverlayWindow, '_open_settings'):
source = inspect.getsource(OverlayWindow._open_settings)
if 'taborder' not in source.lower():
recommendations.append("Settings dialog should have defined tab order")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
return {
'passed': True,
'message': "Tab order checked",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking tab order: {e}",
'severity': 'error'
}
def test_reduced_motion(self) -> dict:
"""Test reduced motion support."""
try:
from core.eu_styles import AnimationHelper
from core.overlay_window import OverlayWindow
issues = []
recommendations = []
# Check for animation duration controls
if hasattr(AnimationHelper, 'fade_in'):
import inspect
source = inspect.getsource(AnimationHelper.fade_in)
# Should have duration parameter
if 'duration' not in source:
recommendations.append("AnimationHelper.fade_in should accept duration parameter")
# Check if animations can be disabled
recommendations.append("Consider adding setting to disable animations for accessibility")
# Check animation setup
if hasattr(OverlayWindow, '_setup_animations'):
import inspect
source = inspect.getsource(OverlayWindow._setup_animations)
# Check for reasonable durations
if '200' not in source and '300' not in source:
recommendations.append("Consider keeping animations under 300ms")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
return {
'passed': True,
'message': "Reduced motion support checked",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking reduced motion: {e}",
'severity': 'error'
}
def test_font_scaling(self) -> dict:
"""Test font scaling support."""
try:
from core.eu_styles import EU_TYPOGRAPHY
issues = []
recommendations = []
# Check for relative font sizes
font_sizes = [v for k, v in EU_TYPOGRAPHY.items() if 'size' in k]
if not font_sizes:
issues.append("No font sizes defined in EU_TYPOGRAPHY")
# Check if sizes use px (problematic for scaling)
px_sizes = [s for s in font_sizes if 'px' in str(s)]
if px_sizes:
recommendations.append("Consider using relative units (em, rem) for better scaling")
# Check for font scaling setting
recommendations.append("Consider adding font size scaling setting")
if issues:
return {
'passed': False,
'message': "; ".join(issues),
'severity': 'warning',
'recommendation': recommendations[0] if recommendations else None
}
return {
'passed': True,
'message': f"Font scaling checked ({len(font_sizes)} size definitions)",
'severity': 'info',
'recommendation': recommendations[0] if recommendations else None
}
except Exception as e:
return {
'passed': False,
'message': f"Error checking font scaling: {e}",
'severity': 'error'
}