""" UI Test Suite Plugin - Main Plugin Class Tests and validates all EU-Utility UI components and user flows. """ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTabWidget, QScrollArea, QFrame, QTextEdit, QProgressBar, QCheckBox, QGridLayout, QSplitter, QListWidget, QListWidgetItem, QGroupBox, QMessageBox ) from PyQt6.QtCore import Qt, QTimer, pyqtSignal from PyQt6.QtGui import QColor from core.base_plugin import BasePlugin from core.eu_styles import ( get_color, get_all_colors, get_button_style, get_global_stylesheet, EU_TYPOGRAPHY, EU_SIZES ) from core.widget_registry import get_widget_registry class UITestSuitePlugin(BasePlugin): """ UI/UX Validation Specialist Test Suite Tests covered: - Overlay window (tabs, navigation, plugin display) - Activity Bar (layouts, dragging, drawer, pinned plugins) - Widget system (creation, positioning, resizing, opacity) - Settings UI (all settings categories) - Plugin Store UI (install, uninstall, dependencies) - Theme/styling consistency """ name = "UI Test Suite" description = "Comprehensive UI/UX validation and testing framework" version = "1.0.0" author = "UI/UX Validation Specialist" icon = "test_tube" def __init__(self): super().__init__() self.test_results = [] self.current_test = None def get_ui(self) -> QWidget: """Return the test suite UI.""" return TestSuiteUI(self) def register_widgets(self): """Register test widgets to widget registry.""" registry = get_widget_registry() registry.register_widget( widget_id="ui_test_overlay_validator", name="Overlay Validator", description="Real-time overlay window validation widget", icon="๐Ÿ”", creator=lambda: OverlayValidatorWidget(), plugin_id="ui_test_suite" ) registry.register_widget( widget_id="ui_test_theme_checker", name="Theme Consistency Checker", description="Checks theme consistency across components", icon="๐ŸŽจ", creator=lambda: ThemeCheckerWidget(), plugin_id="ui_test_suite" ) registry.register_widget( widget_id="ui_test_accessibility_auditor", name="Accessibility Auditor", description="Validates accessibility features", icon="โ™ฟ", creator=lambda: AccessibilityAuditorWidget(), plugin_id="ui_test_suite" ) def on_enable(self): """Called when plugin is enabled.""" self.register_widgets() print("[UI Test Suite] Enabled - Test widgets registered") def on_disable(self): """Called when plugin is disabled.""" registry = get_widget_registry() registry.unregister_widget("ui_test_overlay_validator") registry.unregister_widget("ui_test_theme_checker") registry.unregister_widget("ui_test_accessibility_auditor") class TestSuiteUI(QWidget): """Main test suite UI.""" test_completed = pyqtSignal(str, bool, str) # test_name, passed, message def __init__(self, plugin, parent=None): super().__init__(parent) self.plugin = plugin self.c = get_all_colors() self._setup_ui() self._setup_test_modules() def _setup_ui(self): """Setup the test suite UI.""" layout = QVBoxLayout(self) layout.setSpacing(16) layout.setContentsMargins(20, 20, 20, 20) # Header header = self._create_header() layout.addWidget(header) # Main content splitter splitter = QSplitter(Qt.Orientation.Horizontal) # Left: Test modules list left_panel = self._create_test_modules_panel() splitter.addWidget(left_panel) # Right: Test execution and results right_panel = self._create_results_panel() splitter.addWidget(right_panel) splitter.setSizes([300, 600]) layout.addWidget(splitter, 1) # Apply styles self.setStyleSheet(get_global_stylesheet()) def _create_header(self) -> QFrame: """Create header section.""" header = QFrame() header.setStyleSheet(f""" QFrame {{ background-color: {self.c['bg_secondary']}; border-radius: {EU_SIZES['radius_lg']}; border: 1px solid {self.c['border_default']}; }} """) layout = QHBoxLayout(header) layout.setSpacing(16) layout.setContentsMargins(16, 12, 16, 12) # Title title = QLabel("๐Ÿงช UI/UX Test Suite") title.setStyleSheet(f""" color: {self.c['text_primary']}; font-size: {EU_TYPOGRAPHY['size_2xl']}; font-weight: {EU_TYPOGRAPHY['weight_bold']}; """) layout.addWidget(title) # Stats self.stats_label = QLabel("Ready to run tests") self.stats_label.setStyleSheet(f"color: {self.c['text_secondary']};") layout.addWidget(self.stats_label) layout.addStretch() # Global actions run_all_btn = QPushButton("โ–ถ Run All Tests") run_all_btn.setStyleSheet(get_button_style('primary')) run_all_btn.clicked.connect(self._run_all_tests) layout.addWidget(run_all_btn) clear_btn = QPushButton("๐Ÿ—‘ Clear Results") clear_btn.setStyleSheet(get_button_style('ghost')) clear_btn.clicked.connect(self._clear_results) layout.addWidget(clear_btn) return header def _create_test_modules_panel(self) -> QWidget: """Create left panel with test modules.""" panel = QWidget() layout = QVBoxLayout(panel) layout.setSpacing(12) layout.setContentsMargins(0, 0, 0, 0) # Label label = QLabel("Test Categories") label.setStyleSheet(f""" color: {self.c['text_muted']}; font-size: {EU_TYPOGRAPHY['size_xs']}; font-weight: {EU_TYPOGRAPHY['weight_bold']}; text-transform: uppercase; """) layout.addWidget(label) # Test modules list self.test_modules_list = QListWidget() self.test_modules_list.setStyleSheet(f""" QListWidget {{ background-color: {self.c['bg_secondary']}; border: 1px solid {self.c['border_default']}; border-radius: {EU_SIZES['radius_md']}; padding: 8px; }} QListWidget::item {{ padding: 10px; border-radius: {EU_SIZES['radius_sm']}; margin: 2px 0; }} QListWidget::item:selected {{ background-color: {self.c['bg_selected']}; border-left: 3px solid {self.c['accent_orange']}; }} QListWidget::item:hover {{ background-color: {self.c['bg_hover']}; }} """) self.test_modules_list.itemClicked.connect(self._on_module_selected) layout.addWidget(self.test_modules_list) return panel def _create_results_panel(self) -> QWidget: """Create right panel with test execution and results.""" panel = QWidget() layout = QVBoxLayout(panel) layout.setSpacing(12) layout.setContentsMargins(0, 0, 0, 0) # Tab widget for different views self.tabs = QTabWidget() self.tabs.setStyleSheet(f""" QTabBar::tab {{ padding: 10px 20px; background-color: {self.c['bg_tertiary']}; color: {self.c['text_secondary']}; }} QTabBar::tab:selected {{ background-color: {self.c['accent_orange']}; color: white; }} """) # Console output self.console = QTextEdit() self.console.setReadOnly(True) self.console.setStyleSheet(f""" QTextEdit {{ background-color: {self.c['bg_primary']}; color: {self.c['text_primary']}; border: 1px solid {self.c['border_default']}; border-radius: {EU_SIZES['radius_md']}; font-family: {EU_TYPOGRAPHY['font_mono']}; font-size: 12px; padding: 12px; }} """) self.tabs.addTab(self.console, "๐Ÿ“ Console") # Results table self.results_list = QListWidget() self.results_list.setStyleSheet(f""" QListWidget {{ background-color: {self.c['bg_primary']}; border: 1px solid {self.c['border_default']}; border-radius: {EU_SIZES['radius_md']}; }} QListWidget::item {{ padding: 12px; border-bottom: 1px solid {self.c['border_default']}; }} """) self.tabs.addTab(self.results_list, "๐Ÿ“Š Results") # Issues/Bugs tab self.issues_text = QTextEdit() self.issues_text.setReadOnly(True) self.issues_text.setStyleSheet(f""" QTextEdit {{ background-color: {self.c['bg_primary']}; color: {self.c['text_primary']}; border: 1px solid {self.c['border_default']}; border-radius: {EU_SIZES['radius_md']}; padding: 12px; }} """) self.tabs.addTab(self.issues_text, "๐Ÿ› Issues Found") layout.addWidget(self.tabs) # Current test progress self.progress_bar = QProgressBar() self.progress_bar.setStyleSheet(f""" QProgressBar {{ background-color: {self.c['bg_tertiary']}; border-radius: 4px; height: 8px; }} QProgressBar::chunk {{ background-color: {self.c['accent_orange']}; border-radius: 4px; }} """) self.progress_bar.setVisible(False) layout.addWidget(self.progress_bar) return panel def _setup_test_modules(self): """Setup available test modules.""" from .test_modules import ( OverlayWindowTests, ActivityBarTests, WidgetSystemTests, SettingsUITests, PluginStoreTests, ThemeStylingTests, UserFlowTests, AccessibilityTests, PerformanceTests ) self.test_modules = { 'overlay': OverlayWindowTests(), 'activity_bar': ActivityBarTests(), 'widgets': WidgetSystemTests(), 'settings': SettingsUITests(), 'plugin_store': PluginStoreTests(), 'theme': ThemeStylingTests(), 'user_flows': UserFlowTests(), 'accessibility': AccessibilityTests(), 'performance': PerformanceTests(), } # Add to list for key, module in self.test_modules.items(): item = QListWidgetItem(f"{module.icon} {module.name}") item.setData(Qt.ItemDataRole.UserRole, key) self.test_modules_list.addItem(item) def _on_module_selected(self, item: QListWidgetItem): """Handle module selection.""" module_key = item.data(Qt.ItemDataRole.UserRole) module = self.test_modules.get(module_key) if module: self._log(f"Selected test module: {module.name}") self._log(f"Description: {module.description}") self._log(f"Tests available: {len(module.tests)}") def _run_all_tests(self): """Run all test modules.""" self._clear_results() self._log("=" * 60) self._log("STARTING FULL UI TEST SUITE") self._log("=" * 60) total_tests = sum(len(m.tests) for m in self.test_modules.values()) self.progress_bar.setMaximum(total_tests) self.progress_bar.setValue(0) self.progress_bar.setVisible(True) current = 0 for key, module in self.test_modules.items(): self._log(f"\n๐Ÿ“ฆ Running: {module.name}") self._log("-" * 40) for test_name, test_func in module.tests.items(): current += 1 self.progress_bar.setValue(current) self._stats_label.setText(f"Running... {current}/{total_tests}") try: result = test_func() self._add_result(module.name, test_name, result) except Exception as e: self._add_result(module.name, test_name, { 'passed': False, 'message': f"Exception: {str(e)}" }) self.progress_bar.setVisible(False) self._log("\n" + "=" * 60) self._log("TEST SUITE COMPLETE") self._log("=" * 60) self._update_stats() def _add_result(self, module_name: str, test_name: str, result: dict): """Add a test result.""" passed = result.get('passed', False) message = result.get('message', '') severity = result.get('severity', 'error' if not passed else 'info') # Log to console status = "โœ… PASS" if passed else "โŒ FAIL" self._log(f"{status} | {module_name}.{test_name}: {message}") # Add to results list icon = "โœ…" if passed else "โš ๏ธ" if severity == 'warning' else "โŒ" item_text = f"{icon} {module_name} โ€บ {test_name}\n {message}" item = QListWidgetItem(item_text) if passed: item.setForeground(QColor(self.c['accent_green'])) elif severity == 'warning': item.setForeground(QColor(self.c['accent_gold'])) else: item.setForeground(QColor(self.c['accent_red'])) self.results_list.addItem(item) # Track issues if not passed: self.plugin.test_results.append({ 'module': module_name, 'test': test_name, 'message': message, 'severity': severity, 'recommendation': result.get('recommendation', '') }) # Add to issues tab issue_text = f""" ๐Ÿ› Issue Found โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Module: {module_name} Test: {test_name} Severity: {severity.upper()} Message: {message} """ if result.get('recommendation'): issue_text += f"Recommendation: {result['recommendation']}\n" self.issues_text.append(issue_text) def _log(self, message: str): """Log message to console.""" self.console.append(message) def _clear_results(self): """Clear all results.""" self.console.clear() self.results_list.clear() self.issues_text.clear() self.plugin.test_results.clear() self.stats_label.setText("Ready to run tests") def _update_stats(self): """Update statistics display.""" total = len(self.plugin.test_results) passed = sum(1 for r in self.plugin.test_results if r.get('passed')) failed = total - passed self.stats_label.setText(f"Results: {passed} passed, {failed} failed") # Test Widget Classes class OverlayValidatorWidget(QFrame): """Widget for real-time overlay validation.""" def __init__(self, parent=None): super().__init__(parent) self.c = get_all_colors() self._setup_ui() def _setup_ui(self): layout = QVBoxLayout(self) title = QLabel("๐Ÿ” Overlay Validator") title.setStyleSheet(f"font-weight: bold; color: {self.c['accent_orange']};") layout.addWidget(title) # Validation checks checks = [ "Window positioning", "Theme consistency", "Animation smoothness", "Keyboard navigation", "Z-order (always on top)", ] for check in checks: lbl = QLabel(f"โ—‹ {check}") lbl.setStyleSheet(f"color: {self.c['text_secondary']};") layout.addWidget(lbl) validate_btn = QPushButton("Validate Now") validate_btn.setStyleSheet(get_button_style('primary', 'sm')) layout.addWidget(validate_btn) class ThemeCheckerWidget(QFrame): """Widget for theme consistency checking.""" def __init__(self, parent=None): super().__init__(parent) self.c = get_all_colors() self._setup_ui() def _setup_ui(self): layout = QVBoxLayout(self) title = QLabel("๐ŸŽจ Theme Checker") title.setStyleSheet(f"font-weight: bold; color: {self.c['accent_teal']};") layout.addWidget(title) # Color samples colors_frame = QFrame() colors_layout = QGridLayout(colors_frame) color_samples = [ ('Primary', self.c['accent_orange']), ('Secondary', self.c['accent_teal']), ('Background', self.c['bg_secondary']), ('Text', self.c['text_primary']), ] for i, (name, color) in enumerate(color_samples): sample = QFrame() sample.setFixedSize(30, 30) sample.setStyleSheet(f"background-color: {color}; border-radius: 4px;") colors_layout.addWidget(sample, 0, i) lbl = QLabel(name) lbl.setStyleSheet(f"color: {self.c['text_secondary']}; font-size: 10px;") lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) colors_layout.addWidget(lbl, 1, i) layout.addWidget(colors_frame) check_btn = QPushButton("Check Consistency") check_btn.setStyleSheet(get_button_style('secondary', 'sm')) layout.addWidget(check_btn) class AccessibilityAuditorWidget(QFrame): """Widget for accessibility validation.""" def __init__(self, parent=None): super().__init__(parent) self.c = get_all_colors() self._setup_ui() def _setup_ui(self): layout = QVBoxLayout(self) title = QLabel("โ™ฟ Accessibility Auditor") title.setStyleSheet(f"font-weight: bold; color: {self.c['accent_blue']};") layout.addWidget(title) # Accessibility checks checks_frame = QFrame() checks_layout = QVBoxLayout(checks_frame) checks = [ ("Keyboard navigation", True), ("Screen reader labels", True), ("Color contrast", True), ("Focus indicators", False), ] for check, status in checks: row = QHBoxLayout() lbl = QLabel(check) lbl.setStyleSheet(f"color: {self.c['text_secondary']};") row.addWidget(lbl) status_lbl = QLabel("โœ“" if status else "โœ—") status_lbl.setStyleSheet( f"color: {self.c['accent_green'] if status else self.c['accent_red']};" ) row.addWidget(status_lbl) checks_layout.addLayout(row) layout.addWidget(checks_frame) audit_btn = QPushButton("Run Audit") audit_btn.setStyleSheet(get_button_style('primary', 'sm')) layout.addWidget(audit_btn)