pyPhotoAlbum/tests/test_viewport_mixin.py
Duncan Tourolle b18a780a33
All checks were successful
Python CI / test (push) Successful in 1m28s
Lint / lint (push) Successful in 1m4s
Tests / test (3.11) (push) Successful in 1m41s
Tests / test (3.12) (push) Successful in 1m42s
Tests / test (3.13) (push) Successful in 1m35s
Tests / test (3.14) (push) Successful in 1m15s
increase test coverage
2025-11-28 19:54:41 +01:00

975 lines
34 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)
class TestContentBounds:
"""Test get_content_bounds method"""
def test_get_content_bounds_no_project(self, qtbot):
"""Test content bounds with no project returns defaults"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(800, 600)
# Mock window without project
mock_window = Mock()
mock_window.project = None
widget.window = Mock(return_value=mock_window)
bounds = widget.get_content_bounds()
assert bounds["min_x"] == 0
assert bounds["max_x"] == 800
assert bounds["min_y"] == 0
assert bounds["max_y"] == 600
assert bounds["width"] == 800
assert bounds["height"] == 600
def test_get_content_bounds_empty_project(self, qtbot):
"""Test content bounds with empty project returns defaults"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(800, 600)
# Mock window with empty project
mock_window = Mock()
mock_window.project = Project(name="Empty")
mock_window.project.pages = []
widget.window = Mock(return_value=mock_window)
bounds = widget.get_content_bounds()
assert bounds["min_x"] == 0
assert bounds["max_x"] == 800
assert bounds["min_y"] == 0
assert bounds["max_y"] == 600
def test_get_content_bounds_single_page(self, qtbot):
"""Test content bounds with single 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)
widget.zoom_level = 1.0
bounds = widget.get_content_bounds()
# Should include page dimensions plus margins and spacing
# A4 at 96 DPI: width=794px, height=1123px
# Total width = 794 + (2 * 50 margin) = 894
# Total height = 1123 + (50 margin top) + (50 margin bottom) + (50 spacing) = 1273
assert bounds["min_x"] == 0
assert bounds["min_y"] == 0
assert 890 < bounds["width"] < 900 # ~894
assert 1260 < bounds["height"] < 1280 # ~1273
def test_get_content_bounds_multiple_pages(self, qtbot):
"""Test content bounds with multiple pages"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 800)
# Mock window with project and 3 pages
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
# Create 3 A4 pages
pages = [
Page(layout=PageLayout(width=210, height=297), page_number=i)
for i in range(1, 4)
]
mock_window.project.pages = pages
widget.window = Mock(return_value=mock_window)
widget.zoom_level = 1.0
bounds = widget.get_content_bounds()
# 3 pages vertically: 3 * 1123 + margins + spacings
# Height = 3369 + 50 (top margin) + 50 (bottom margin) + 3*50 (spacings) = 3619
assert bounds["min_x"] == 0
assert bounds["min_y"] == 0
assert 890 < bounds["width"] < 900 # Same width as single page
assert 3600 < bounds["height"] < 3640 # ~3619
def test_get_content_bounds_with_zoom(self, qtbot):
"""Test content bounds respects 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)
# Test at 50% zoom
widget.zoom_level = 0.5
bounds_50 = widget.get_content_bounds()
# Test at 100% zoom
widget.zoom_level = 1.0
bounds_100 = widget.get_content_bounds()
# Test at 200% zoom
widget.zoom_level = 2.0
bounds_200 = widget.get_content_bounds()
# Bounds should scale with zoom
assert bounds_50["width"] < bounds_100["width"] < bounds_200["width"]
assert bounds_50["height"] < bounds_100["height"] < bounds_200["height"]
def test_get_content_bounds_different_page_sizes(self, qtbot):
"""Test content bounds with pages of different sizes"""
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
# Create pages of different sizes
pages = [
Page(layout=PageLayout(width=210, height=297), page_number=1), # A4
Page(layout=PageLayout(width=152.4, height=101.6), page_number=2), # 6x4 photo
Page(layout=PageLayout(width=210, height=297), page_number=3), # A4
]
mock_window.project.pages = pages
widget.window = Mock(return_value=mock_window)
widget.zoom_level = 1.0
bounds = widget.get_content_bounds()
# Width should be based on the widest page (A4)
# Height should be sum of all pages
assert bounds["min_x"] == 0
assert bounds["min_y"] == 0
assert 890 < bounds["width"] < 900 # Based on A4 width
assert bounds["height"] > 2000 # Sum of all pages
class TestPanClamping:
"""Test clamp_pan_offset method"""
def test_clamp_pan_offset_no_project(self, qtbot):
"""Test clamping with no project does nothing"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(800, 600)
# Mock window without project
mock_window = Mock()
mock_window.project = None
widget.window = Mock(return_value=mock_window)
widget.pan_offset = [100, 50]
widget.clamp_pan_offset()
# Should remain unchanged
assert widget.pan_offset == [100, 50]
def test_clamp_pan_offset_empty_project(self, qtbot):
"""Test clamping with empty project does nothing"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(800, 600)
# Mock window with empty project
mock_window = Mock()
mock_window.project = Project(name="Empty")
mock_window.project.pages = []
widget.window = Mock(return_value=mock_window)
widget.pan_offset = [100, 50]
widget.clamp_pan_offset()
# Should remain unchanged
assert widget.pan_offset == [100, 50]
def test_clamp_pan_offset_vertical_limits_tall_content(self, qtbot):
"""Test vertical clamping when content is taller than viewport"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 600) # Short viewport
# Mock window with project and tall page
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
# A4 page at 100% zoom is 1123px tall
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
mock_window.project.pages = [page]
widget.window = Mock(return_value=mock_window)
widget.zoom_level = 1.0
# Try to pan way beyond top
widget.pan_offset = [0, 1000]
widget.clamp_pan_offset()
# Should be clamped to top (max_pan_up = 0)
assert widget.pan_offset[1] == 0
# Try to pan way beyond bottom
widget.pan_offset = [0, -2000]
widget.clamp_pan_offset()
# Should be clamped to bottom
# min_pan_up = -(content_height - viewport_height)
assert widget.pan_offset[1] > -2000
assert widget.pan_offset[1] < 0
def test_clamp_pan_offset_vertical_no_clamp_when_fits(self, qtbot):
"""Test vertical clamping preserves position when content fits"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 2000) # Tall viewport
# Mock window with project and small page
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
# Small page that fits in viewport
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)
widget.zoom_level = 1.0
# Set a specific pan offset
widget.pan_offset = [0, 200]
widget.clamp_pan_offset()
# When content fits, offset should be preserved
assert widget.pan_offset[1] == 200
def test_clamp_pan_offset_horizontal_single_page(self, qtbot):
"""Test horizontal clamping for single page"""
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
# A4 page
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
mock_window.project.pages = [page]
widget.window = Mock(return_value=mock_window)
widget.zoom_level = 1.0
# Try to pan too far left
widget.pan_offset = [1000, 0]
original_x = widget.pan_offset[0]
widget.clamp_pan_offset()
# Should be clamped
assert widget.pan_offset[0] < original_x
# Try to pan too far right
widget.pan_offset = [-1000, 0]
original_x = widget.pan_offset[0]
widget.clamp_pan_offset()
# Should be clamped
assert widget.pan_offset[0] > original_x
def test_clamp_pan_offset_horizontal_multiple_pages(self, qtbot):
"""Test horizontal clamping with multiple pages"""
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
# Create multiple pages
pages = [
Page(layout=PageLayout(width=210, height=297), page_number=i)
for i in range(1, 4)
]
mock_window.project.pages = pages
widget.window = Mock(return_value=mock_window)
widget.zoom_level = 1.0
# Set pan offset and clamp
widget.pan_offset = [500, -200]
widget.clamp_pan_offset()
# Should be clamped within reasonable bounds
assert -1000 < widget.pan_offset[0] < 1000
def test_clamp_pan_offset_with_zoom_changes(self, qtbot):
"""Test clamping behavior at different zoom levels"""
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)
# Test at different zoom levels
for zoom in [0.5, 1.0, 2.0]:
widget.zoom_level = zoom
widget.pan_offset = [500, -500]
widget.clamp_pan_offset()
# Should clamp appropriately for each zoom
assert isinstance(widget.pan_offset[0], (int, float))
assert isinstance(widget.pan_offset[1], (int, float))
def test_clamp_pan_offset_preserves_original_pan_y(self, qtbot):
"""Test that clamping uses original pan_y for page selection"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(1000, 600)
# Mock window with project
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
# Multiple pages
pages = [
Page(layout=PageLayout(width=210, height=297), page_number=i)
for i in range(1, 3)
]
mock_window.project.pages = pages
widget.window = Mock(return_value=mock_window)
widget.zoom_level = 1.0
# Set pan offset that will be clamped vertically
widget.pan_offset = [0, -800]
original_y = widget.pan_offset[1]
widget.clamp_pan_offset()
# Horizontal clamping should have used original Y for page selection
# even if vertical was clamped
assert isinstance(widget.pan_offset[0], (int, float))
assert isinstance(widget.pan_offset[1], (int, float))
def test_clamp_pan_offset_different_page_widths(self, qtbot):
"""Test horizontal clamping with pages of different widths"""
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
# Pages of different widths
pages = [
Page(layout=PageLayout(width=210, height=297), page_number=1), # A4
Page(layout=PageLayout(width=152.4, height=101.6), page_number=2), # 6x4
Page(layout=PageLayout(width=210, height=297), page_number=3), # A4
]
mock_window.project.pages = pages
widget.window = Mock(return_value=mock_window)
widget.zoom_level = 1.0
# Test clamping while viewing different pages
widget.pan_offset = [200, 0] # Viewing first page
widget.clamp_pan_offset()
first_page_x = widget.pan_offset[0]
widget.pan_offset = [200, -800] # Viewing second page
widget.clamp_pan_offset()
second_page_x = widget.pan_offset[0]
# Both should be clamped appropriately
assert isinstance(first_page_x, (int, float))
assert isinstance(second_page_x, (int, float))
def test_clamp_pan_offset_page_wider_than_viewport(self, qtbot):
"""Test horizontal clamping when page is wider than viewport"""
widget = TestViewportWidget()
qtbot.addWidget(widget)
widget.resize(400, 600) # Narrow viewport
# Mock window with project
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
# A4 page is 794px wide at 96 DPI, wider than 400px viewport
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
mock_window.project.pages = [page]
widget.window = Mock(return_value=mock_window)
widget.zoom_level = 1.0
# Should allow panning to see different parts of the page
widget.pan_offset = [100, 0]
widget.clamp_pan_offset()
# Should allow reasonable panning range
assert -500 < widget.pan_offset[0] < 500
def test_clamp_pan_offset_interpolation_between_pages(self, qtbot):
"""Test that horizontal clamping interpolates between page centers"""
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
# Two pages of different widths
pages = [
Page(layout=PageLayout(width=210, height=297), page_number=1), # Wide
Page(layout=PageLayout(width=100, height=150), page_number=2), # Narrow
]
mock_window.project.pages = pages
widget.window = Mock(return_value=mock_window)
widget.zoom_level = 1.0
# Test clamping at position between pages
# Position viewport center between the two pages
widget.pan_offset = [0, -700] # Between first and second page
widget.clamp_pan_offset()
# Should interpolate horizontal clamping between the two page widths
assert isinstance(widget.pan_offset[0], (int, float))