pyPhotoAlbum/tests/test_gl_widget_integration.py
Duncan Tourolle 47e5ea4b3e
All checks were successful
Python CI / test (push) Successful in 1m7s
Lint / lint (push) Successful in 1m11s
Tests / test (3.10) (push) Successful in 51s
Tests / test (3.11) (push) Successful in 52s
Tests / test (3.9) (push) Successful in 49s
additional testing
2025-11-11 11:49:16 +01:00

388 lines
12 KiB
Python

"""
Integration tests for GLWidget - verifying mixin composition
"""
import pytest
from unittest.mock import Mock, patch
from PyQt6.QtCore import Qt, QPointF
from PyQt6.QtGui import QMouseEvent
from pyPhotoAlbum.gl_widget import GLWidget
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import ImageData, TextBoxData
class TestGLWidgetInitialization:
"""Test GLWidget initialization and mixin integration"""
def test_gl_widget_initializes(self, qtbot):
"""Test GLWidget can be instantiated with all mixins"""
widget = GLWidget()
qtbot.addWidget(widget)
# Verify mixin state is initialized
assert hasattr(widget, 'zoom_level')
assert hasattr(widget, 'pan_offset')
assert hasattr(widget, 'selected_elements')
assert hasattr(widget, 'drag_start_pos')
assert hasattr(widget, 'is_dragging')
assert hasattr(widget, 'is_panning')
assert hasattr(widget, 'rotation_mode')
def test_gl_widget_accepts_drops(self, qtbot):
"""Test GLWidget is configured to accept drops"""
widget = GLWidget()
qtbot.addWidget(widget)
assert widget.acceptDrops() is True
def test_gl_widget_tracks_mouse(self, qtbot):
"""Test GLWidget has mouse tracking enabled"""
widget = GLWidget()
qtbot.addWidget(widget)
assert widget.hasMouseTracking() is True
class TestGLWidgetMixinIntegration:
"""Test that mixins work together correctly"""
def test_viewport_and_rendering_integration(self, qtbot):
"""Test viewport state affects rendering"""
widget = GLWidget()
qtbot.addWidget(widget)
# Set zoom level
initial_zoom = widget.zoom_level
widget.zoom_level = 2.0
assert widget.zoom_level == 2.0
assert widget.zoom_level != initial_zoom
# Pan offset
initial_pan = widget.pan_offset.copy()
widget.pan_offset[0] += 100
widget.pan_offset[1] += 50
assert widget.pan_offset != initial_pan
def test_selection_and_manipulation_integration(self, qtbot):
"""Test element selection works with manipulation"""
widget = GLWidget()
qtbot.addWidget(widget)
# Create an element
element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
# Select it
widget.selected_elements.add(element)
# Verify selection
assert element in widget.selected_elements
assert widget.selected_element == element
# Clear selection
widget.selected_elements.clear()
assert len(widget.selected_elements) == 0
assert widget.selected_element is None
def test_mouse_interaction_with_selection(self, qtbot):
"""Test mouse events trigger selection changes"""
widget = GLWidget()
qtbot.addWidget(widget)
widget.update = Mock()
# Mock element at position
test_element = ImageData(image_path="/test.jpg", x=50, y=50, width=100, height=100)
widget._get_element_at = Mock(return_value=test_element)
widget._get_page_at = Mock(return_value=(None, -1, None))
widget._check_ghost_page_click = Mock(return_value=False)
# Create mouse press event
event = QMouseEvent(
QMouseEvent.Type.MouseButtonPress,
QPointF(75, 75),
Qt.MouseButton.LeftButton,
Qt.MouseButton.LeftButton,
Qt.KeyboardModifier.NoModifier
)
widget.mousePressEvent(event)
# Should select the element
assert test_element in widget.selected_elements
def test_undo_integration_with_operations(self, qtbot):
"""Test undo/redo integration with element operations"""
widget = GLWidget()
qtbot.addWidget(widget)
# Create element
element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
widget.selected_elements.add(element)
# Begin operation (should be tracked for undo)
widget._begin_move(element)
assert widget._interaction_element is not None
assert widget._interaction_type == 'move'
assert widget._interaction_start_pos == (100, 100)
# End operation
widget._end_interaction()
# Interaction state should be cleared after operation
assert widget._interaction_element is None
assert widget._interaction_type is None
class TestGLWidgetKeyEvents:
"""Test keyboard event handling"""
def test_escape_clears_selection(self, qtbot):
"""Test Escape key clears selection and rotation mode"""
widget = GLWidget()
qtbot.addWidget(widget)
widget.update = Mock()
# Set up selection and rotation mode
element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
widget.selected_elements.add(element)
widget.rotation_mode = True
# Create key press event for Escape
from PyQt6.QtGui import QKeyEvent
event = QKeyEvent(
QKeyEvent.Type.KeyPress,
Qt.Key.Key_Escape,
Qt.KeyboardModifier.NoModifier
)
widget.keyPressEvent(event)
# Should clear selection and rotation mode
assert widget.selected_element is None
assert widget.rotation_mode is False
assert widget.update.called
def test_tab_toggles_rotation_mode(self, qtbot):
"""Test Tab key toggles rotation mode when element is selected"""
widget = GLWidget()
qtbot.addWidget(widget)
widget.update = Mock()
# Set up mock window for status message
mock_window = Mock()
mock_window.show_status = Mock()
widget.window = Mock(return_value=mock_window)
# Select an element
element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
widget.selected_elements.add(element)
# Initially not in rotation mode
assert widget.rotation_mode is False
# Create key press event for Tab
from PyQt6.QtGui import QKeyEvent
event = QKeyEvent(
QKeyEvent.Type.KeyPress,
Qt.Key.Key_Tab,
Qt.KeyboardModifier.NoModifier
)
widget.keyPressEvent(event)
# Should toggle rotation mode
assert widget.rotation_mode is True
assert widget.update.called
# Press Tab again
widget.keyPressEvent(event)
# Should toggle back
assert widget.rotation_mode is False
def test_delete_key_requires_main_window(self, qtbot):
"""Test Delete key calls main window's delete method"""
widget = GLWidget()
qtbot.addWidget(widget)
# Set up mock window
mock_window = Mock()
mock_window.delete_selected_element = Mock()
widget.window = Mock(return_value=mock_window)
# Select an element
element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
widget.selected_elements.add(element)
# Create key press event for Delete
from PyQt6.QtGui import QKeyEvent
event = QKeyEvent(
QKeyEvent.Type.KeyPress,
Qt.Key.Key_Delete,
Qt.KeyboardModifier.NoModifier
)
widget.keyPressEvent(event)
# Should call main window's delete method
assert mock_window.delete_selected_element.called
class TestGLWidgetWithProject:
"""Test GLWidget with a full project setup"""
def test_gl_widget_with_project(self, qtbot):
"""Test GLWidget can work with a project and pages"""
widget = GLWidget()
qtbot.addWidget(widget)
# Create a mock main window with project
mock_window = Mock()
mock_window.project = Project(name="Test Project")
mock_window.project.working_dpi = 96
# Add a page
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
mock_window.project.pages.append(page)
# Add an element to the page
element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
page.layout.add_element(element)
widget.window = Mock(return_value=mock_window)
# Verify we can access project through widget
main_window = widget.window()
assert hasattr(main_window, 'project')
assert main_window.project.name == "Test Project"
assert len(main_window.project.pages) == 1
assert len(main_window.project.pages[0].layout.elements) == 1
def test_fit_to_screen_zoom_calculation(self, qtbot):
"""Test fit-to-screen zoom calculation with project"""
widget = GLWidget()
qtbot.addWidget(widget)
# Create mock window with project
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
mock_window.project.pages.append(page)
widget.window = Mock(return_value=mock_window)
# Mock widget dimensions
widget.width = Mock(return_value=800)
widget.height = Mock(return_value=600)
# Calculate fit-to-screen zoom
zoom = widget._calculate_fit_to_screen_zoom()
# Should return a valid zoom level
assert isinstance(zoom, float)
assert zoom > 0
def test_gl_widget_without_project(self, qtbot):
"""Test GLWidget handles missing project gracefully"""
widget = GLWidget()
qtbot.addWidget(widget)
# Create mock window without project
mock_window = Mock()
del mock_window.project
widget.window = Mock(return_value=mock_window)
# Should not crash when calculating zoom
zoom = widget._calculate_fit_to_screen_zoom()
assert zoom == 1.0
class TestGLWidgetOpenGL:
"""Test OpenGL-specific functionality"""
def test_gl_widget_has_opengl_format(self, qtbot):
"""Test GLWidget has OpenGL format configured"""
widget = GLWidget()
qtbot.addWidget(widget)
# Should have a format
format = widget.format()
assert format is not None
def test_gl_widget_update_behavior(self, qtbot):
"""Test GLWidget update behavior is configured"""
widget = GLWidget()
qtbot.addWidget(widget)
# Should have NoPartialUpdate set
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
assert widget.updateBehavior() == QOpenGLWidget.UpdateBehavior.NoPartialUpdate
class TestGLWidgetStateManagement:
"""Test state management across mixins"""
def test_rotation_mode_state(self, qtbot):
"""Test rotation mode state is properly managed"""
widget = GLWidget()
qtbot.addWidget(widget)
# Initial state
assert widget.rotation_mode is False
# Toggle rotation mode
widget.rotation_mode = True
assert widget.rotation_mode is True
# Toggle back
widget.rotation_mode = False
assert widget.rotation_mode is False
def test_drag_state_management(self, qtbot):
"""Test drag state is properly managed"""
widget = GLWidget()
qtbot.addWidget(widget)
# Initial state
assert widget.is_dragging is False
assert widget.drag_start_pos is None
# Start drag
widget.is_dragging = True
widget.drag_start_pos = (100, 100)
assert widget.is_dragging is True
assert widget.drag_start_pos == (100, 100)
# End drag
widget.is_dragging = False
widget.drag_start_pos = None
assert widget.is_dragging is False
assert widget.drag_start_pos is None
def test_pan_state_management(self, qtbot):
"""Test pan state is properly managed"""
widget = GLWidget()
qtbot.addWidget(widget)
# Initial state
assert widget.is_panning is False
# Start panning
widget.is_panning = True
widget.drag_start_pos = (200, 200)
assert widget.is_panning is True
# End panning
widget.is_panning = False
widget.drag_start_pos = None
assert widget.is_panning is False