pyPhotoAlbum/tests/test_multiselect.py
Duncan Tourolle f6ed11b0bc
All checks were successful
Python CI / test (push) Successful in 1m20s
Lint / lint (push) Successful in 1m4s
Tests / test (3.11) (push) Successful in 1m27s
Tests / test (3.12) (push) Successful in 2m25s
Tests / test (3.13) (push) Successful in 2m52s
Tests / test (3.14) (push) Successful in 1m9s
black formatting
2025-11-27 23:07:16 +01:00

189 lines
7.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Test script to verify multiselect visual feedback functionality
"""
import pytest
from unittest.mock import Mock, patch, MagicMock
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from pyPhotoAlbum.mixins.element_selection import ElementSelectionMixin
from pyPhotoAlbum.mixins.rendering import RenderingMixin
from pyPhotoAlbum.models import ImageData
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
# Create a minimal test widget class that doesn't require full GLWidget initialization
class MultiSelectTestWidget(ElementSelectionMixin, RenderingMixin, QOpenGLWidget):
"""Widget combining necessary mixins for multiselect testing"""
def __init__(self):
super().__init__()
self._page_renderers = []
self.rotation_mode = False # Required by _draw_selection_handles
def test_multiselect_visual_feedback(qtbot):
"""Test that all selected elements get selection handles drawn"""
# Create a project with a page
project = Project("Test Project")
page_layout = PageLayout(width=200, height=200)
page = Page(layout=page_layout, page_number=1)
project.add_page(page)
# Create test widget and add to qtbot for proper lifecycle management
widget = MultiSelectTestWidget()
qtbot.addWidget(widget)
# Mock the main window to return our project
mock_window = Mock()
mock_window.project = project
widget.window = Mock(return_value=mock_window)
# Create test elements
element1 = ImageData(image_path="test1.jpg", x=10, y=10, width=50, height=50)
element2 = ImageData(image_path="test2.jpg", x=70, y=70, width=50, height=50)
element3 = ImageData(image_path="test3.jpg", x=130, y=130, width=50, height=50)
# Set up page renderer mock for each element
mock_renderer = Mock()
mock_renderer.page_to_screen = Mock(side_effect=lambda x, y: (x, y))
mock_renderer.zoom = 1.0
element1._parent_page = page
element2._parent_page = page
element3._parent_page = page
element1._page_renderer = mock_renderer
element2._page_renderer = mock_renderer
element3._page_renderer = mock_renderer
# Add elements to page
page.layout.add_element(element1)
page.layout.add_element(element2)
page.layout.add_element(element3)
print(f"Created 3 test elements")
# Test 1: Single selection
print("\nTest 1: Single selection")
widget.selected_elements = {element1}
with patch.object(widget, "_draw_selection_handles") as mock_draw:
# Simulate paintGL call (only the relevant part)
for selected_elem in widget.selected_elements:
widget._draw_selection_handles(selected_elem)
assert mock_draw.call_count == 1, f"Expected 1 call, got {mock_draw.call_count}"
assert mock_draw.call_args[0][0] == element1, "Wrong element passed"
print(f"✓ Single selection: _draw_selection_handles called 1 time with element1")
# Test 2: Multiple selection (2 elements)
print("\nTest 2: Multiple selection (2 elements)")
widget.selected_elements = {element1, element2}
with patch.object(widget, "_draw_selection_handles") as mock_draw:
for selected_elem in widget.selected_elements:
widget._draw_selection_handles(selected_elem)
assert mock_draw.call_count == 2, f"Expected 2 calls, got {mock_draw.call_count}"
called_elements = {call[0][0] for call in mock_draw.call_args_list}
assert called_elements == {element1, element2}, f"Wrong elements passed: {called_elements}"
print(f"✓ Multiple selection (2): _draw_selection_handles called 2 times with correct elements")
# Test 3: Multiple selection (3 elements)
print("\nTest 3: Multiple selection (3 elements)")
widget.selected_elements = {element1, element2, element3}
with patch.object(widget, "_draw_selection_handles") as mock_draw:
for selected_elem in widget.selected_elements:
widget._draw_selection_handles(selected_elem)
assert mock_draw.call_count == 3, f"Expected 3 calls, got {mock_draw.call_count}"
called_elements = {call[0][0] for call in mock_draw.call_args_list}
assert called_elements == {element1, element2, element3}, f"Wrong elements passed: {called_elements}"
print(f"✓ Multiple selection (3): _draw_selection_handles called 3 times with correct elements")
# Test 4: No selection
print("\nTest 4: No selection")
widget.selected_elements = set()
with patch.object(widget, "_draw_selection_handles") as mock_draw:
for selected_elem in widget.selected_elements:
widget._draw_selection_handles(selected_elem)
assert mock_draw.call_count == 0, f"Expected 0 calls, got {mock_draw.call_count}"
print(f"✓ No selection: _draw_selection_handles not called")
# Test 5: Verify _draw_selection_handles receives correct element parameter
print("\nTest 5: Verify _draw_selection_handles uses passed element")
widget.selected_elements = {element2}
# Mock OpenGL functions
with (
patch("pyPhotoAlbum.gl_widget.glColor3f"),
patch("pyPhotoAlbum.gl_widget.glLineWidth"),
patch("pyPhotoAlbum.gl_widget.glBegin"),
patch("pyPhotoAlbum.gl_widget.glEnd"),
patch("pyPhotoAlbum.gl_widget.glVertex2f"),
patch("pyPhotoAlbum.gl_widget.glPushMatrix"),
patch("pyPhotoAlbum.gl_widget.glPopMatrix"),
patch("pyPhotoAlbum.gl_widget.glTranslatef"),
patch("pyPhotoAlbum.gl_widget.glRotatef"),
):
# Call the actual method
widget._draw_selection_handles(element2)
# Verify it used element2's properties
assert element2._page_renderer.page_to_screen.called, "page_to_screen should be called"
print(f"✓ _draw_selection_handles correctly uses the passed element parameter")
print("\n✓ All multiselect visual feedback tests passed!")
def test_regression_old_code_bug(qtbot):
"""
Regression test: Verify the old bug (only first element gets handles)
would have been caught by this test
"""
print("\nRegression test: Simulating old buggy behavior...")
widget = MultiSelectTestWidget()
qtbot.addWidget(widget)
# Create mock elements
element1 = Mock()
element2 = Mock()
element3 = Mock()
# Select multiple elements
widget.selected_elements = {element1, element2, element3}
# OLD BUGGY CODE (what we fixed):
# if self.selected_element: # This only returns first element!
# self._draw_selection_handles()
# Simulate old behavior
call_count_old = 0
if widget.selected_element: # This property returns only first element
call_count_old = 1
# NEW CORRECT CODE:
# for selected_elem in self.selected_elements:
# self._draw_selection_handles(selected_elem)
# Simulate new behavior
call_count_new = 0
for selected_elem in widget.selected_elements:
call_count_new += 1
print(f"Old buggy code: would call _draw_selection_handles {call_count_old} time(s)")
print(f"New fixed code: calls _draw_selection_handles {call_count_new} time(s)")
assert call_count_old == 1, "Old code should only handle 1 element"
assert call_count_new == 3, "New code should handle all 3 elements"
print("✓ Regression test confirms the bug would have been caught!")