# UI Testing ## Visual Testing Page Visit: [http://127.0.0.1:8411/testing/ui](http://127.0.0.1:8411/testing/ui) These test/ui views are only available in development when `DEBUG=True`. They are conditionally loaded in the root `urls.py`. ## UI Testing Framework Guidelines The visual testing framework is designed for viewing UI styling and layout during development. These are **read-only** views that should never modify system state. ### Critical Principle: System State Isolation **NEVER modify real system state in UI test views:** - Do not add data to real managers (AlertManager, WeatherManager, etc.) - Do not modify database records - Do not modify in-memory caches or singletons - Do not persist test data that appears in production views ### Correct Approach: Render Templates Directly ```python class TestUiAlertDetailsView(View): def get(self, request, *args, **kwargs): # Create synthetic data alert = AlertSyntheticData.create_single_alarm_alert( alarm_level=AlarmLevel.WARNING, has_image=True ) # Prepare context data using domain object methods visual_content = alert.get_first_visual_content() # Render template directly with synthetic data context = { 'alert': alert, 'alert_visual_content': visual_content, } return render(request, 'alert/modals/alert_details.html', context) ``` ### Architecture Patterns **Render Templates Directly (Preferred):** - For testing UI styling and layout - When you need specific synthetic data scenarios - When testing requires system state isolation - Follows pattern used by weather, notify modules **Code Duplication Prevention:** - Move shared logic to domain object methods (e.g., `Alert.get_first_visual_content()`) - Use centralized synthetic data classes - Create utility functions for common data preparation patterns ## Setting Up Visual Testing ### Auto-Discovery Structure The `hi.tests.ui` module uses auto-discovery by looking in app directories. In the app directory you want to have a visual testing page: ```bash mkdir -p tests/ui touch tests/__init__.py touch tests/ui/__init__.py ``` ### Required Files Create these files: - `tests/ui/views.py` - `tests/ui/urls.py` (gets auto-discovered) ### Template Structure Templates go in `templates/${APPNAME}/testing/ui/`. Create a home page: ```html {% extends "pages/base.html" %} {% load icons %} {% block head_title %}HI: {{ app_name|title }} UI Tests{% endblock %} {% block content %}

{{ app_name|title }} UI Tests

Component Tests

{% endblock %} ``` ### View Implementation ```python # tests/ui/views.py from django.shortcuts import render from django.views import View from hi.apps.entity.models import Entity class TestUiEntityHomeView(View): def get(self, request, *args, **kwargs): context = { 'app_name': 'entity', } return render(request, 'entity/testing/ui/home.html', context) class TestUiEntityCardView(View): def get(self, request, *args, **kwargs): # Create synthetic test data entities = [ self.create_synthetic_entity('Living Room Light', 'active'), self.create_synthetic_entity('Kitchen Sensor', 'recent'), self.create_synthetic_entity('Garage Door', 'idle'), self.create_synthetic_entity('Weather Station', 'unknown'), ] context = { 'entities': entities, 'page_title': 'Entity Card Variations', } return render(request, 'entity/testing/ui/card_variations.html', context) def create_synthetic_entity(self, name, status): """Create synthetic entity for testing - does not save to database""" entity = Entity( name=name, integration_id=f'test.{name.lower().replace(" ", "_")}', integration_name='test_integration' ) # Add synthetic status for display entity._test_status = status return entity ``` ### URL Configuration ```python # tests/ui/urls.py from django.urls import re_path from . import views urlpatterns = [ re_path(r'^$', views.TestUiEntityHomeView.as_view(), name='entity_tests_ui'), re_path(r'^cards/$', views.TestUiEntityCardView.as_view(), name='test_entity_cards'), ] ``` ## Component Testing Patterns ### Testing Responsive Design Create viewport testing utilities: ```html

Viewport Testing

{% include "entity/panes/entity_card.html" %}
``` ### Testing State Variations Create comprehensive state testing: ```python class TestUiStatusStatesView(View): def get(self, request, *args, **kwargs): # Test all possible status states status_variations = [ ('active', 'Red - Currently active'), ('recent', 'Orange - Recently active'), ('past', 'Yellow - Past activity'), ('idle', 'Green - Idle state'), ('unknown', 'Gray - Unknown/offline'), ] entities = [] for status, description in status_variations: entity = self.create_entity_with_status(status) entity._description = description entities.append(entity) context = { 'entities': entities, 'page_title': 'Status State Variations', } return render(request, 'entity/testing/ui/status_states.html', context) ``` ### Testing Icon Usage ```html

Icon Usage Tests

Actions

Icon Sizes

{% for size in "xs,sm,md,lg,xl"|split:"," %}
{% icon "home" size=size %} {{ size }} ({{ size }})
{% endfor %}

Status Icons

{% icon "check-circle" size="sm" css_class="hi-icon-left" %} Success message
{% icon "exclamation-triangle" size="sm" css_class="hi-icon-left" %} Warning message
``` ## Modal Testing ### Modal Component Testing ```python class TestUiModalView(View): def get(self, request, *args, **kwargs): # Test modal with different content types modal_variations = [ { 'size': 'sm', 'title': 'Small Modal', 'content': 'Simple confirmation modal', 'actions': ['Cancel', 'Confirm'] }, { 'size': 'lg', 'title': 'Large Modal', 'content': 'Complex form modal with multiple fields', 'actions': ['Cancel', 'Save', 'Save & Continue'] } ] context = { 'modal_variations': modal_variations, } return render(request, 'entity/testing/ui/modal_tests.html', context) ``` ## Form Testing ### Form State Testing ```html

Form State Testing

Normal State

Error State

This field is required.
``` ## Email Template Testing Use helper base classes for email testing: ```python # From hi.tests.ui.email_test_views.py class EmailTestView(View): def test_alert_notification_email(self): # Test email templates with synthetic data context = { 'alert': self.create_synthetic_alert(), 'user': self.create_synthetic_user(), } return self.render_email_template('alert/email/notification.html', context) ``` ## Performance Testing ### Template Rendering Performance ```python import time from django.test.utils import override_settings class TestUiPerformanceView(View): def get(self, request, *args, **kwargs): # Test template rendering with large datasets start_time = time.time() # Create large synthetic dataset entities = [self.create_synthetic_entity(f'Entity {i}') for i in range(100)] render_time = time.time() - start_time context = { 'entities': entities, 'render_time': render_time, } return render(request, 'entity/testing/ui/performance_test.html', context) ``` ## Related Documentation - Frontend guidelines: [Frontend Guidelines](frontend-guidelines.md) - Template conventions: [Template Conventions](template-conventions.md) - Testing patterns: [Testing Patterns](../testing/testing-patterns.md) - Test data management: [Test Data Management](../testing/test-data-management.md)