""" 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))