581 lines
20 KiB
Python
581 lines
20 KiB
Python
"""
|
||
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)
|