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
975 lines
34 KiB
Python
Executable File
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))
|