""" 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' }