549 lines
21 KiB
Python
549 lines
21 KiB
Python
"""
|
|
User Flow Tests
|
|
|
|
Tests for user workflows including:
|
|
- First-time user experience
|
|
- Plugin installation workflow
|
|
- Widget creation and management
|
|
- Settings changes
|
|
- Hotkey functionality
|
|
- Window positioning and persistence
|
|
"""
|
|
|
|
|
|
class UserFlowTests:
|
|
"""Test suite for user flows."""
|
|
|
|
name = "User Flows"
|
|
icon = "👤"
|
|
description = "Tests first-time experience, plugin installation, widget creation, and settings workflows"
|
|
|
|
def __init__(self):
|
|
self.tests = {
|
|
'first_time_experience': self.test_first_time_experience,
|
|
'plugin_install_workflow': self.test_plugin_install_workflow,
|
|
'widget_creation_workflow': self.test_widget_creation_workflow,
|
|
'settings_change_workflow': self.test_settings_change_workflow,
|
|
'hotkey_functionality': self.test_hotkey_functionality,
|
|
'window_positioning_persistence': self.test_window_positioning_persistence,
|
|
'overlay_toggle_flow': self.test_overlay_toggle_flow,
|
|
'tab_navigation_flow': self.test_tab_navigation_flow,
|
|
'plugin_drawer_flow': self.test_plugin_drawer_flow,
|
|
'error_recovery': self.test_error_recovery,
|
|
}
|
|
|
|
def test_first_time_experience(self) -> dict:
|
|
"""Test first-time user experience."""
|
|
try:
|
|
from core.ui.dashboard_view import DashboardView
|
|
from core.overlay_window import OverlayWindow
|
|
|
|
issues = []
|
|
recommendations = []
|
|
|
|
# Check dashboard welcome widget
|
|
if not hasattr(DashboardView, '_add_builtin_widgets'):
|
|
issues.append("Dashboard missing _add_builtin_widgets")
|
|
|
|
if not hasattr(DashboardView, '_create_widget_frame'):
|
|
issues.append("Dashboard missing _create_widget_frame")
|
|
|
|
# Check for welcome content
|
|
import inspect
|
|
if hasattr(DashboardView, '_add_builtin_widgets'):
|
|
source = inspect.getsource(DashboardView._add_builtin_widgets)
|
|
|
|
welcome_terms = ['welcome', 'install', 'plugin', 'store', 'get started']
|
|
has_welcome = any(term in source.lower() for term in welcome_terms)
|
|
|
|
if not has_welcome:
|
|
recommendations.append("Consider adding more prominent welcome guidance")
|
|
|
|
# Check for placeholder when no plugins
|
|
if not hasattr(OverlayWindow, '_create_placeholder'):
|
|
recommendations.append("Consider adding placeholder for empty plugin state")
|
|
|
|
if issues:
|
|
return {
|
|
'passed': False,
|
|
'message': "; ".join(issues),
|
|
'severity': 'warning',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
return {
|
|
'passed': True,
|
|
'message': "First-time experience features present",
|
|
'severity': 'info',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'passed': False,
|
|
'message': f"Error checking first-time experience: {e}",
|
|
'severity': 'error'
|
|
}
|
|
|
|
def test_plugin_install_workflow(self) -> dict:
|
|
"""Test plugin installation workflow."""
|
|
try:
|
|
from core.overlay_window import OverlayWindow
|
|
from core.plugin_store import PluginStoreUI
|
|
|
|
issues = []
|
|
recommendations = []
|
|
|
|
# Check settings dialog opens
|
|
if not hasattr(OverlayWindow, '_open_settings'):
|
|
issues.append("_open_settings method missing")
|
|
|
|
# Check plugin store tab exists
|
|
if not hasattr(OverlayWindow, '_create_plugin_store_tab'):
|
|
issues.append("_create_plugin_store_tab missing")
|
|
|
|
# Check store UI
|
|
if not hasattr(PluginStoreUI, 'install_plugin'):
|
|
issues.append("PluginStoreUI.install_plugin missing")
|
|
|
|
# Check for feedback after install
|
|
import inspect
|
|
if hasattr(PluginStoreUI, 'install_plugin'):
|
|
source = inspect.getsource(PluginStoreUI.install_plugin)
|
|
|
|
if 'progress' not in source.lower():
|
|
recommendations.append("Consider showing progress during installation")
|
|
|
|
if 'success' not in source.lower() and 'complete' not in source.lower():
|
|
recommendations.append("Consider showing success message after install")
|
|
|
|
if issues:
|
|
return {
|
|
'passed': False,
|
|
'message': "; ".join(issues),
|
|
'severity': 'error',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
return {
|
|
'passed': True,
|
|
'message': "Plugin install workflow present",
|
|
'severity': 'info',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'passed': False,
|
|
'message': f"Error checking install workflow: {e}",
|
|
'severity': 'error'
|
|
}
|
|
|
|
def test_widget_creation_workflow(self) -> dict:
|
|
"""Test widget creation workflow."""
|
|
try:
|
|
from core.overlay_window import OverlayWindow
|
|
from core.widget_registry import get_widget_registry
|
|
|
|
issues = []
|
|
recommendations = []
|
|
|
|
# Check widgets tab exists
|
|
if not hasattr(OverlayWindow, '_create_widgets_tab'):
|
|
issues.append("_create_widgets_tab missing")
|
|
|
|
if not hasattr(OverlayWindow, '_refresh_widgets_tab'):
|
|
issues.append("_refresh_widgets_tab missing")
|
|
|
|
if not hasattr(OverlayWindow, '_create_widget_button'):
|
|
issues.append("_create_widget_button missing")
|
|
|
|
if not hasattr(OverlayWindow, '_add_registered_widget'):
|
|
issues.append("_add_registered_widget missing")
|
|
|
|
# Check widget registry
|
|
try:
|
|
registry = get_widget_registry()
|
|
if not hasattr(registry, 'get_all_widgets'):
|
|
issues.append("WidgetRegistry.get_all_widgets missing")
|
|
except Exception as e:
|
|
issues.append(f"WidgetRegistry not accessible: {e}")
|
|
|
|
# Check for widget management
|
|
if not hasattr(OverlayWindow, '_active_widgets'):
|
|
recommendations.append("Consider tracking _active_widgets to prevent GC")
|
|
|
|
if issues:
|
|
return {
|
|
'passed': False,
|
|
'message': "; ".join(issues),
|
|
'severity': 'error',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
return {
|
|
'passed': True,
|
|
'message': "Widget creation workflow present",
|
|
'severity': 'info',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'passed': False,
|
|
'message': f"Error checking widget workflow: {e}",
|
|
'severity': 'error'
|
|
}
|
|
|
|
def test_settings_change_workflow(self) -> dict:
|
|
"""Test settings change workflow."""
|
|
try:
|
|
from core.overlay_window import OverlayWindow
|
|
|
|
issues = []
|
|
recommendations = []
|
|
|
|
# Check settings dialog
|
|
if not hasattr(OverlayWindow, '_open_settings'):
|
|
issues.append("_open_settings missing")
|
|
|
|
if not hasattr(OverlayWindow, '_save_settings'):
|
|
issues.append("_save_settings missing")
|
|
|
|
# Check for apply feedback
|
|
import inspect
|
|
if hasattr(OverlayWindow, '_save_settings'):
|
|
source = inspect.getsource(OverlayWindow._save_settings)
|
|
|
|
if 'reload' not in source.lower():
|
|
recommendations.append("Consider reloading plugins after settings save")
|
|
|
|
# Check settings persistence
|
|
if not hasattr(OverlayWindow, '_refresh_ui'):
|
|
recommendations.append("Consider adding _refresh_ui for immediate theme changes")
|
|
|
|
if issues:
|
|
return {
|
|
'passed': False,
|
|
'message': "; ".join(issues),
|
|
'severity': 'error',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
return {
|
|
'passed': True,
|
|
'message': "Settings change workflow present",
|
|
'severity': 'info',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'passed': False,
|
|
'message': f"Error checking settings workflow: {e}",
|
|
'severity': 'error'
|
|
}
|
|
|
|
def test_hotkey_functionality(self) -> dict:
|
|
"""Test hotkey functionality."""
|
|
try:
|
|
from core.overlay_window import OverlayWindow
|
|
from core.hotkey_manager import HotkeyManager
|
|
|
|
issues = []
|
|
recommendations = []
|
|
|
|
# Check shortcut setup
|
|
if not hasattr(OverlayWindow, '_setup_shortcuts'):
|
|
issues.append("_setup_shortcuts missing")
|
|
|
|
# Check toggle methods
|
|
if not hasattr(OverlayWindow, 'toggle_overlay'):
|
|
issues.append("toggle_overlay missing")
|
|
|
|
if not hasattr(OverlayWindow, 'show_overlay'):
|
|
issues.append("show_overlay missing")
|
|
|
|
if not hasattr(OverlayWindow, 'hide_overlay'):
|
|
issues.append("hide_overlay missing")
|
|
|
|
# Check hotkey manager
|
|
try:
|
|
hm = HotkeyManager()
|
|
if not hasattr(hm, 'register_hotkey'):
|
|
recommendations.append("Consider implementing global hotkeys with register_hotkey")
|
|
except Exception as e:
|
|
recommendations.append(f"HotkeyManager not fully functional: {e}")
|
|
|
|
# Check for expected shortcuts
|
|
import inspect
|
|
if hasattr(OverlayWindow, '_setup_shortcuts'):
|
|
source = inspect.getsource(OverlayWindow._setup_shortcuts)
|
|
|
|
expected = ['esc', 'ctrl+t', 'ctrl+1']
|
|
missing = [e for e in expected if e not in source.lower()]
|
|
|
|
if missing:
|
|
recommendations.append(f"Consider adding shortcuts: {missing}")
|
|
|
|
if issues:
|
|
return {
|
|
'passed': False,
|
|
'message': "; ".join(issues),
|
|
'severity': 'error',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
return {
|
|
'passed': True,
|
|
'message': "Hotkey functionality present",
|
|
'severity': 'info',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'passed': False,
|
|
'message': f"Error checking hotkey functionality: {e}",
|
|
'severity': 'error'
|
|
}
|
|
|
|
def test_window_positioning_persistence(self) -> dict:
|
|
"""Test window positioning and persistence."""
|
|
try:
|
|
from core.overlay_window import OverlayWindow
|
|
from core.activity_bar import ActivityBar
|
|
|
|
issues = []
|
|
recommendations = []
|
|
|
|
# Check center window
|
|
if not hasattr(OverlayWindow, '_center_window'):
|
|
issues.append("_center_window missing")
|
|
|
|
# Check activity bar position persistence
|
|
if not hasattr(ActivityBar, '_save_position'):
|
|
recommendations.append("Consider implementing activity bar position persistence")
|
|
|
|
# Check config persistence
|
|
if not hasattr(ActivityBar, '_save_config'):
|
|
issues.append("ActivityBar._save_config missing")
|
|
|
|
if not hasattr(ActivityBar, '_load_config'):
|
|
issues.append("ActivityBar._load_config missing")
|
|
|
|
if issues:
|
|
return {
|
|
'passed': False,
|
|
'message': "; ".join(issues),
|
|
'severity': 'warning',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
return {
|
|
'passed': True,
|
|
'message': "Window positioning features present",
|
|
'severity': 'info',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'passed': False,
|
|
'message': f"Error checking window positioning: {e}",
|
|
'severity': 'error'
|
|
}
|
|
|
|
def test_overlay_toggle_flow(self) -> dict:
|
|
"""Test overlay toggle flow."""
|
|
try:
|
|
from core.overlay_window import OverlayWindow
|
|
|
|
issues = []
|
|
|
|
# Check visibility tracking
|
|
if not hasattr(OverlayWindow, 'is_visible'):
|
|
issues.append("is_visible state not tracked")
|
|
|
|
# Check visibility signal
|
|
if not hasattr(OverlayWindow, 'visibility_changed'):
|
|
issues.append("visibility_changed signal missing")
|
|
|
|
# Check methods
|
|
required = ['show_overlay', 'hide_overlay', 'toggle_overlay']
|
|
for method in required:
|
|
if not hasattr(OverlayWindow, method):
|
|
issues.append(f"{method} missing")
|
|
|
|
# Check tray integration
|
|
if not hasattr(OverlayWindow, '_tray_activated'):
|
|
issues.append("_tray_activated missing for tray toggle")
|
|
|
|
if issues:
|
|
return {
|
|
'passed': False,
|
|
'message': "; ".join(issues),
|
|
'severity': 'error'
|
|
}
|
|
|
|
return {
|
|
'passed': True,
|
|
'message': "Overlay toggle flow complete",
|
|
'severity': 'info'
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'passed': False,
|
|
'message': f"Error checking toggle flow: {e}",
|
|
'severity': 'error'
|
|
}
|
|
|
|
def test_tab_navigation_flow(self) -> dict:
|
|
"""Test tab navigation flow."""
|
|
try:
|
|
from core.overlay_window import OverlayWindow
|
|
|
|
issues = []
|
|
|
|
# Check tab switching
|
|
if not hasattr(OverlayWindow, '_switch_tab'):
|
|
issues.append("_switch_tab missing")
|
|
|
|
if not hasattr(OverlayWindow, 'tab_buttons'):
|
|
issues.append("tab_buttons not tracked")
|
|
|
|
if not hasattr(OverlayWindow, 'tab_stack'):
|
|
issues.append("tab_stack not defined")
|
|
|
|
# Check expected tabs
|
|
expected_tabs = ['plugins', 'widgets', 'settings']
|
|
|
|
import inspect
|
|
if hasattr(OverlayWindow, '_switch_tab'):
|
|
source = inspect.getsource(OverlayWindow._switch_tab)
|
|
|
|
for tab in expected_tabs:
|
|
if f"'{tab}'" not in source and f'"{tab}"' not in source:
|
|
issues.append(f"Tab '{tab}' may not be handled in _switch_tab")
|
|
|
|
if issues:
|
|
return {
|
|
'passed': False,
|
|
'message': "; ".join(issues),
|
|
'severity': 'error'
|
|
}
|
|
|
|
return {
|
|
'passed': True,
|
|
'message': f"Tab navigation flow present ({len(expected_tabs)} tabs)",
|
|
'severity': 'info'
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'passed': False,
|
|
'message': f"Error checking tab navigation: {e}",
|
|
'severity': 'error'
|
|
}
|
|
|
|
def test_plugin_drawer_flow(self) -> dict:
|
|
"""Test plugin drawer workflow."""
|
|
try:
|
|
from core.activity_bar import ActivityBar
|
|
|
|
issues = []
|
|
recommendations = []
|
|
|
|
# Check drawer toggle
|
|
if not hasattr(ActivityBar, '_toggle_drawer'):
|
|
issues.append("_toggle_drawer missing")
|
|
|
|
if not hasattr(ActivityBar, 'drawer_open'):
|
|
issues.append("drawer_open state not tracked")
|
|
|
|
# Check drawer signals
|
|
if not hasattr(ActivityBar, 'drawer_toggled'):
|
|
issues.append("drawer_toggled signal missing")
|
|
|
|
# Check drawer content
|
|
if not hasattr(ActivityBar, '_refresh_drawer'):
|
|
issues.append("_refresh_drawer missing")
|
|
|
|
if not hasattr(ActivityBar, '_create_drawer_item'):
|
|
issues.append("_create_drawer_item missing")
|
|
|
|
# Check click handling
|
|
if not hasattr(ActivityBar, '_on_drawer_item_clicked'):
|
|
issues.append("_on_drawer_item_clicked missing")
|
|
|
|
if issues:
|
|
return {
|
|
'passed': False,
|
|
'message': "; ".join(issues),
|
|
'severity': 'error'
|
|
}
|
|
|
|
return {
|
|
'passed': True,
|
|
'message': "Plugin drawer flow complete",
|
|
'severity': 'info'
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'passed': False,
|
|
'message': f"Error checking drawer flow: {e}",
|
|
'severity': 'error'
|
|
}
|
|
|
|
def test_error_recovery(self) -> dict:
|
|
"""Test error recovery flows."""
|
|
try:
|
|
from core.overlay_window import OverlayWindow
|
|
from core.activity_bar import ActivityBar
|
|
|
|
issues = []
|
|
recommendations = []
|
|
|
|
# Check error handling in plugin loading
|
|
import inspect
|
|
if hasattr(OverlayWindow, '_load_plugins'):
|
|
source = inspect.getsource(OverlayWindow._load_plugins)
|
|
|
|
if 'try' not in source or 'except' not in source:
|
|
recommendations.append("Consider adding error handling to _load_plugins")
|
|
|
|
# Check error handling in widget creation
|
|
if hasattr(OverlayWindow, '_add_registered_widget'):
|
|
source = inspect.getsource(OverlayWindow._add_registered_widget)
|
|
|
|
if 'try' not in source or 'except' not in source:
|
|
recommendations.append("Consider adding error handling to _add_registered_widget")
|
|
|
|
# Check for fallback UI
|
|
if not hasattr(OverlayWindow, '_create_placeholder'):
|
|
recommendations.append("Consider adding placeholder UI for error states")
|
|
|
|
if issues:
|
|
return {
|
|
'passed': False,
|
|
'message': "; ".join(issues),
|
|
'severity': 'error',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
return {
|
|
'passed': True,
|
|
'message': "Error recovery features checked",
|
|
'severity': 'info',
|
|
'recommendation': recommendations[0] if recommendations else None
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'passed': False,
|
|
'message': f"Error checking error recovery: {e}",
|
|
'severity': 'error'
|
|
}
|