pyPhotoAlbum/tests/test_viewport_mixin.py
Duncan Tourolle 0d698a83b4
Some checks failed
Python CI / test (push) Successful in 55s
Lint / lint (push) Successful in 1m31s
Tests / test (3.10) (push) Failing after 44s
Tests / test (3.11) (push) Failing after 42s
Tests / test (3.9) (push) Failing after 42s
large change to allow project merging
2025-11-23 00:33:42 +01:00

540 lines
18 KiB
Python
Executable File

"""
Tests for ViewportMixin
"""
import pytest
from unittest.mock import Mock, MagicMock, patch
from PyQt6.QtWidgets import QApplication
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from pyPhotoAlbum.mixins.viewport import ViewportMixin
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
# Create a minimal test widget class
class TestViewportWidget(ViewportMixin, QOpenGLWidget):
"""Test widget combining ViewportMixin with QOpenGLWidget"""
pass
class TestViewportMixinInitialization:
"""Test ViewportMixin initialization"""
def test_initialization_sets_defaults(self, qtbot):
"""Test that mixin initializes with correct defaults"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
assert widget.zoom_level == 1.0
assert widget.pan_offset == [0, 0]
assert widget.initial_zoom_set is False
def test_zoom_level_is_mutable(self, qtbot):
"""Test that zoom level can be changed"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.zoom_level = 1.5
assert widget.zoom_level == 1.5
def test_pan_offset_is_mutable(self, qtbot):
"""Test that pan offset can be changed"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.pan_offset = [100, 50]
assert widget.pan_offset == [100, 50]
class TestViewportCalculations:
"""Test viewport zoom calculations"""
def test_calculate_fit_to_screen_no_project(self, qtbot):
"""Test fit-to-screen with no project returns 1.0"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(800, 600)
# Mock window() to return a window without project
mock_window = Mock()
mock_window.project = None
widget.window = Mock(return_value=mock_window)
zoom = widget._calculate_fit_to_screen_zoom()
assert zoom == 1.0
def test_calculate_fit_to_screen_empty_project(self, qtbot):
"""Test fit-to-screen with empty project returns 1.0"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(800, 600)
# Mock window() to return a window with empty project
mock_window = Mock()
mock_window.project = Project(name="Empty")
mock_window.project.pages = []
widget.window = Mock(return_value=mock_window)
zoom = widget._calculate_fit_to_screen_zoom()
assert zoom == 1.0
def test_calculate_fit_to_screen_with_page(self, qtbot):
"""Test fit-to-screen calculates correct zoom for A4 page"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 800)
# Mock window with project and A4 page
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
# A4 page: 210mm x 297mm
page = Page(
layout=PageLayout(width=210, height=297),
page_number=1
)
mock_window.project.pages = [page]
widget.window = Mock(return_value=mock_window)
zoom = widget._calculate_fit_to_screen_zoom()
# Calculate expected zoom
# A4 at 96 DPI: width=794px, height=1123px
# Window: 1000x800, margins: 100px each side
# Available: 800x600
# zoom_w = 800/794 ≈ 1.007, zoom_h = 600/1123 ≈ 0.534
# Should use min(zoom_w, zoom_h, 1.0) = 0.534
assert 0.5 < zoom < 0.6 # Approximately 0.534
assert zoom <= 1.0 # Never zoom beyond 100%
def test_calculate_fit_to_screen_small_window(self, qtbot):
"""Test fit-to-screen with small window returns small zoom"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(400, 300) # Small window
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 = [page]
widget.window = Mock(return_value=mock_window)
zoom = widget._calculate_fit_to_screen_zoom()
# With 400x300 window and 200px margins, available space is 200x100
# This should produce a very small zoom
assert zoom < 0.3
def test_calculate_fit_to_screen_large_window(self, qtbot):
"""Test fit-to-screen with large window caps at 1.0"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(3000, 2000) # Very large window
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 = [page]
widget.window = Mock(return_value=mock_window)
zoom = widget._calculate_fit_to_screen_zoom()
# Even with huge window, zoom should not exceed 1.0
assert zoom == 1.0
def test_calculate_fit_to_screen_different_dpi(self, qtbot):
"""Test fit-to-screen respects different DPI values"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 800)
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 300 # High DPI
page = Page(
layout=PageLayout(width=210, height=297),
page_number=1
)
mock_window.project.pages = [page]
widget.window = Mock(return_value=mock_window)
zoom = widget._calculate_fit_to_screen_zoom()
# At 300 DPI, page is much larger in pixels
# So zoom should be smaller
assert zoom < 0.3
class TestViewportCentering:
"""Test viewport centering calculations"""
def test_calculate_center_pan_offset_no_project(self, qtbot):
"""Test center calculation with no project returns [0, 0]"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(800, 600)
# Mock window() to return a window without project
mock_window = Mock()
mock_window.project = None
widget.window = Mock(return_value=mock_window)
offset = widget._calculate_center_pan_offset(1.0)
assert offset == [0, 0]
def test_calculate_center_pan_offset_empty_project(self, qtbot):
"""Test center calculation with empty project returns [0, 0]"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(800, 600)
# Mock window() to return a window with empty project
mock_window = Mock()
mock_window.project = Project(name="Empty")
mock_window.project.pages = []
widget.window = Mock(return_value=mock_window)
offset = widget._calculate_center_pan_offset(1.0)
assert offset == [0, 0]
def test_calculate_center_pan_offset_with_page_at_100_percent(self, qtbot):
"""Test center calculation for A4 page at 100% zoom"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 800)
# Mock window with project and A4 page
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
# A4 page: 210mm x 297mm
page = Page(
layout=PageLayout(width=210, height=297),
page_number=1
)
mock_window.project.pages = [page]
widget.window = Mock(return_value=mock_window)
zoom_level = 1.0
offset = widget._calculate_center_pan_offset(zoom_level)
# Calculate expected offset
# A4 at 96 DPI: width=794px, height=1123px
# Window: 1000x800
# PAGE_MARGIN = 50
# x_offset = (1000 - 794) / 2 - 50 = 103 - 50 = 53
# y_offset = (800 - 1123) / 2 = -161.5
assert isinstance(offset, list)
assert len(offset) == 2
# X offset should center horizontally (accounting for PAGE_MARGIN)
assert 50 < offset[0] < 60 # Approximately 53
# Y offset should be negative (page taller than window)
assert offset[1] < 0
def test_calculate_center_pan_offset_with_page_at_50_percent(self, qtbot):
"""Test center calculation for A4 page at 50% zoom"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 800)
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
# A4 page: 210mm x 297mm
page = Page(
layout=PageLayout(width=210, height=297),
page_number=1
)
mock_window.project.pages = [page]
widget.window = Mock(return_value=mock_window)
zoom_level = 0.5
offset = widget._calculate_center_pan_offset(zoom_level)
# At 50% zoom, page dimensions are halved
# A4 at 96 DPI and 50%: width=397px, height=561.5px
# Window: 1000x800
# PAGE_MARGIN = 50
# x_offset = (1000 - 397) / 2 - 50 = 301.5 - 50 = 251.5
# y_offset = (800 - 561.5) / 2 = 119.25
assert isinstance(offset, list)
assert len(offset) == 2
# X offset should be larger (more centering space)
assert 240 < offset[0] < 260 # Approximately 251.5
# Y offset should be positive (page fits vertically)
assert offset[1] > 100
def test_calculate_center_pan_offset_small_page(self, qtbot):
"""Test center calculation for small page (6x4 photo)"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 800)
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
# 6x4 inch photo: 152.4mm x 101.6mm
page = Page(
layout=PageLayout(width=152.4, height=101.6),
page_number=1
)
mock_window.project.pages = [page]
widget.window = Mock(return_value=mock_window)
zoom_level = 1.0
offset = widget._calculate_center_pan_offset(zoom_level)
# Small page should have large positive offsets (lots of centering space)
assert offset[0] > 150 # Horizontally centered with room to spare
assert offset[1] > 200 # Vertically centered with room to spare
def test_calculate_center_pan_offset_large_window(self, qtbot):
"""Test center calculation with large window"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(3000, 2000)
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
# A4 page: 210mm x 297mm
page = Page(
layout=PageLayout(width=210, height=297),
page_number=1
)
mock_window.project.pages = [page]
widget.window = Mock(return_value=mock_window)
zoom_level = 1.0
offset = widget._calculate_center_pan_offset(zoom_level)
# Large window should have large positive offsets
assert offset[0] > 1000 # Lots of horizontal space
assert offset[1] > 400 # Lots of vertical space
def test_calculate_center_pan_offset_different_zoom_levels(self, qtbot):
"""Test that different zoom levels produce different offsets"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 800)
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 = [page]
widget.window = Mock(return_value=mock_window)
# Get offsets at different zoom levels
offset_100 = widget._calculate_center_pan_offset(1.0)
offset_50 = widget._calculate_center_pan_offset(0.5)
offset_25 = widget._calculate_center_pan_offset(0.25)
# As zoom decreases, both offsets should increase (more centering space)
assert offset_50[0] > offset_100[0]
assert offset_25[0] > offset_50[0]
# Y offset behavior depends on if page fits vertically
# At lower zoom, page is smaller, so more vertical centering space
assert offset_25[1] > offset_50[1]
def test_calculate_center_pan_offset_different_dpi(self, qtbot):
"""Test center calculation with different DPI values"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 800)
mock_window = Mock()
mock_window.project = Project(name="Test")
page = Page(
layout=PageLayout(width=210, height=297),
page_number=1
)
mock_window.project.pages = [page]
# Test at 96 DPI
mock_window.project.working_dpi = 96
widget.window = Mock(return_value=mock_window)
offset_96 = widget._calculate_center_pan_offset(1.0)
# Test at 300 DPI (page will be much larger in pixels)
mock_window.project.working_dpi = 300
offset_300 = widget._calculate_center_pan_offset(1.0)
# At higher DPI, page is larger, so centering offsets should be smaller
# (or negative if page doesn't fit)
assert offset_300[0] < offset_96[0]
assert offset_300[1] < offset_96[1]
class TestViewportResizing:
"""Test viewport behavior during window resizing"""
def test_resizeGL_recenters_when_project_loaded(self, qtbot):
"""Test that resizing window recenters the page"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
# 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 = [page]
widget.window = Mock(return_value=mock_window)
# Simulate initial load (sets initial_zoom_set to True)
widget.initial_zoom_set = True
widget.zoom_level = 0.5
# Get initial centered offset for 1000x800
with patch.object(widget, 'width', return_value=1000):
with patch.object(widget, 'height', return_value=800):
initial_offset = list(widget._calculate_center_pan_offset(0.5))
# Trigger a resize to larger window (1200x900)
# Mock the widget's dimensions during resizeGL
with patch.object(widget, 'width', return_value=1200):
with patch.object(widget, 'height', return_value=900):
widget.resizeGL(1200, 900)
new_offset = widget.pan_offset
# Offsets should be larger due to increased window size
assert new_offset[0] > initial_offset[0] # More horizontal space
assert new_offset[1] > initial_offset[1] # More vertical space
def test_resizeGL_does_not_recenter_before_project_load(self, qtbot):
"""Test that resizing before project load doesn't change pan offset"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(800, 600)
# Don't set initial_zoom_set (simulates no project loaded yet)
widget.initial_zoom_set = False
widget.pan_offset = [100, 50]
# Trigger a resize
widget.resizeGL(1000, 800)
# Pan offset should remain unchanged (no project to center)
assert widget.pan_offset == [100, 50]
def test_resizeGL_maintains_zoom_level(self, qtbot):
"""Test that resizing maintains the current zoom level"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 800)
# 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 = [page]
widget.window = Mock(return_value=mock_window)
# Set initial state
widget.initial_zoom_set = True
widget.zoom_level = 0.75
# Trigger a resize
widget.resizeGL(1200, 900)
# Zoom level should remain the same
assert widget.zoom_level == 0.75
def test_resizeGL_with_different_sizes(self, qtbot):
"""Test that resizing to different sizes produces appropriate centering"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
# 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 = [page]
widget.window = Mock(return_value=mock_window)
widget.initial_zoom_set = True
widget.zoom_level = 0.5
# Resize to small window
widget.resize(600, 400)
widget.resizeGL(600, 400)
small_offset = widget.pan_offset.copy()
# Resize to large window
widget.resize(2000, 1500)
widget.resizeGL(2000, 1500)
large_offset = widget.pan_offset.copy()
# Larger window should have larger centering offsets
assert large_offset[0] > small_offset[0]
assert large_offset[1] > small_offset[1]
class TestViewportOpenGL:
"""Test OpenGL-related viewport methods"""
def test_initializeGL_sets_clear_color(self, qtbot):
"""Test that initializeGL is callable (actual GL testing is integration)"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
# Just verify the method exists and is callable
assert hasattr(widget, 'initializeGL')
assert callable(widget.initializeGL)
def test_resizeGL_is_callable(self, qtbot):
"""Test that resizeGL is callable"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
assert hasattr(widget, 'resizeGL')
assert callable(widget.resizeGL)