540 lines
18 KiB
Python
Executable File
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)
|