pyPhotoAlbum/tests/test_page_ops_mixin.py
Duncan Tourolle 5de3384c35
Some checks failed
Python CI / test (push) Successful in 1m19s
Lint / lint (push) Successful in 1m21s
Tests / test (3.10) (push) Failing after 1m2s
Tests / test (3.11) (push) Failing after 57s
Tests / test (3.9) (push) Failing after 59s
Many improvements and fixes
2025-11-21 22:35:47 +01:00

362 lines
13 KiB
Python

"""
Tests for PageOperationsMixin
"""
import pytest
from unittest.mock import Mock, MagicMock, patch
from PyQt6.QtWidgets import QMainWindow
from pyPhotoAlbum.mixins.operations.page_ops import PageOperationsMixin
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
class TestPageOpsWindow(PageOperationsMixin, QMainWindow):
"""Test window with page operations mixin"""
def __init__(self):
super().__init__()
self.gl_widget = Mock()
self.gl_widget.current_page_index = 0
self.gl_widget.zoom_level = 1.0
self.gl_widget.pan_offset = [0, 0]
self.gl_widget._page_renderers = []
self.gl_widget.width = Mock(return_value=800)
self.gl_widget.height = Mock(return_value=600)
self.project = Project(name="Test")
self.project.working_dpi = 96
self.project.page_size_mm = (210, 297)
self._update_view_called = False
self._status_message = None
def update_view(self):
self._update_view_called = True
def show_status(self, message, timeout=0):
self._status_message = message
def show_warning(self, title, message):
pass
class TestGetMostVisiblePageIndex:
"""Test _get_most_visible_page_index method"""
def test_no_renderers_returns_current_index(self, qtbot):
"""Test returns current_page_index when no renderers"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
window.gl_widget.current_page_index = 3
window.gl_widget._page_renderers = []
result = window._get_most_visible_page_index()
assert result == 3
def test_single_page_returns_zero(self, qtbot):
"""Test with single page returns index 0"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
# Create a single page
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
window.project.pages = [page]
# Create mock renderer
mock_renderer = Mock()
mock_renderer.screen_y = 100
window.gl_widget._page_renderers = [(mock_renderer, page)]
result = window._get_most_visible_page_index()
assert result == 0
def test_multiple_pages_finds_closest_to_center(self, qtbot):
"""Test finds page closest to viewport center"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
window.gl_widget.height = Mock(return_value=600) # Viewport center at y=300
# Create three pages
page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
page2 = Page(layout=PageLayout(width=210, height=297), page_number=2)
page3 = Page(layout=PageLayout(width=210, height=297), page_number=3)
window.project.pages = [page1, page2, page3]
# Calculate page height in pixels: 297mm * 96dpi / 25.4 = ~1122px
# At zoom 1.0, half page height = ~561px
# Viewport center is at y=300
# Create renderers with different screen_y positions
# Page 1: screen_y = 50, center at 50 + 561 = 611, distance = |611 - 300| = 311
# Page 2: screen_y = -300, center at -300 + 561 = 261, distance = |261 - 300| = 39 <- closest!
# Page 3: screen_y = 800, center at 800 + 561 = 1361, distance = |1361 - 300| = 1061
renderer1 = Mock()
renderer1.screen_y = 50
renderer2 = Mock()
renderer2.screen_y = -300 # This will put page center near viewport center
renderer3 = Mock()
renderer3.screen_y = 800
window.gl_widget._page_renderers = [
(renderer1, page1),
(renderer2, page2),
(renderer3, page3)
]
result = window._get_most_visible_page_index()
# Page 2 (index 1) should be closest to viewport center
assert result == 1
def test_handles_page_not_in_project_list(self, qtbot):
"""Test handles case where page is not in project.pages"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
orphan_page = Page(layout=PageLayout(width=210, height=297), page_number=99)
window.project.pages = [page1]
renderer1 = Mock()
renderer1.screen_y = 100
renderer_orphan = Mock()
renderer_orphan.screen_y = 50 # Closer to center
window.gl_widget._page_renderers = [
(renderer1, page1),
(renderer_orphan, orphan_page) # Not in project.pages
]
window.gl_widget.current_page_index = 0
result = window._get_most_visible_page_index()
# Should fallback to valid page (page1) or current_page_index
assert result == 0
class TestToggleDoubleSpread:
"""Test toggle_double_spread method"""
def test_toggle_spread_no_pages(self, qtbot):
"""Test returns early when no pages"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
window.project.pages = []
window.toggle_double_spread()
# Should return early without error
assert not window._update_view_called
def test_toggle_spread_enables_double_spread(self, qtbot):
"""Test enables double spread on single page"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
# Create single page
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
page.is_double_spread = False
window.project.pages = [page]
# Mock renderer
mock_renderer = Mock()
mock_renderer.screen_y = 100
window.gl_widget._page_renderers = [(mock_renderer, page)]
window.toggle_double_spread()
assert page.is_double_spread is True
assert page.manually_sized is True
assert page.layout.is_facing_page is True
assert page.layout.size[0] == 420 # 210 * 2
assert page.layout.size[1] == 297
assert window._update_view_called
assert "enabled" in window._status_message
def test_toggle_spread_disables_double_spread(self, qtbot):
"""Test disables double spread on double page"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
# Create double spread page
page = Page(layout=PageLayout(width=420, height=297), page_number=1)
page.is_double_spread = True
page.layout.base_width = 210
page.layout.is_facing_page = True
window.project.pages = [page]
mock_renderer = Mock()
mock_renderer.screen_y = 100
window.gl_widget._page_renderers = [(mock_renderer, page)]
window.toggle_double_spread()
assert page.is_double_spread is False
assert page.layout.is_facing_page is False
assert page.layout.size[0] == 210 # Back to single width
assert page.layout.size[1] == 297
assert window._update_view_called
assert "disabled" in window._status_message
def test_toggle_spread_uses_most_visible_page(self, qtbot):
"""Test toggles the most visible page, not always first page"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
window.gl_widget.height = Mock(return_value=600) # Viewport center at y=300
# Create three pages
page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
page1.is_double_spread = False
page2 = Page(layout=PageLayout(width=210, height=297), page_number=2)
page2.is_double_spread = False
page3 = Page(layout=PageLayout(width=210, height=297), page_number=3)
page3.is_double_spread = False
window.project.pages = [page1, page2, page3]
# Set up renderers so page 2 is most visible (see calculation above)
# Page 2 center should be closest to viewport center at y=300
renderer1 = Mock()
renderer1.screen_y = 50
renderer2 = Mock()
renderer2.screen_y = -300 # This will put page 2 center near viewport center
renderer3 = Mock()
renderer3.screen_y = 800
window.gl_widget._page_renderers = [
(renderer1, page1),
(renderer2, page2),
(renderer3, page3)
]
window.toggle_double_spread()
# Only page 2 should be toggled
assert page1.is_double_spread is False
assert page2.is_double_spread is True # Toggled
assert page3.is_double_spread is False
assert window._update_view_called
def test_toggle_spread_invalid_index_uses_zero(self, qtbot):
"""Test uses index 0 when calculated index is invalid"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
page.is_double_spread = False
window.project.pages = [page]
# Mock _get_most_visible_page_index to return invalid index
window._get_most_visible_page_index = Mock(return_value=999)
window.toggle_double_spread()
# Should fallback to first page (index 0)
assert page.is_double_spread is True
assert window._update_view_called
def test_toggle_spread_calculates_base_width(self, qtbot):
"""Test correctly calculates base_width from facing page"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
# Create page with is_facing_page=True (which doubles the width automatically)
# PageLayout(width=210, is_facing_page=True) creates size=(420, 297) and base_width=210
page = Page(layout=PageLayout(width=210, height=297, is_facing_page=True), page_number=1)
page.is_double_spread = False # Not marked as double spread yet
window.project.pages = [page]
mock_renderer = Mock()
mock_renderer.screen_y = 100
window.gl_widget._page_renderers = [(mock_renderer, page)]
# Now toggle it on
window.toggle_double_spread()
# Should enable double spread
assert page.is_double_spread is True
# base_width should remain 210 (was already set correctly)
assert page.layout.base_width == 210
# Width should still be doubled
assert page.layout.size[0] == 420 # base_width * 2
assert page.layout.is_facing_page is True
class TestAddPage:
"""Test add_page method"""
def test_add_page_to_empty_project(self, qtbot):
"""Test adds first page to empty project"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
window.project.pages = []
window.add_page()
assert len(window.project.pages) == 1
assert window.project.pages[0].page_number == 1
assert window.project.pages[0].layout.size == (210, 297)
assert window.project.pages[0].manually_sized is False
assert window._update_view_called
def test_add_page_to_existing_pages(self, qtbot):
"""Test adds page to project with existing pages"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
window.project.pages = [page1]
window.add_page()
assert len(window.project.pages) == 2
assert window.project.pages[1].page_number == 2
assert window._update_view_called
class TestRemovePage:
"""Test remove_page method"""
def test_remove_last_page(self, qtbot):
"""Test removes last page"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
page2 = Page(layout=PageLayout(width=210, height=297), page_number=2)
window.project.pages = [page1, page2]
window.remove_page()
assert len(window.project.pages) == 1
assert window.project.pages[0].page_number == 1
assert window._update_view_called
def test_cannot_remove_only_page(self, qtbot):
"""Test cannot remove when only one page exists"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
window.project.pages = [page1]
window.remove_page()
# Should still have one page
assert len(window.project.pages) == 1
assert not window._update_view_called
def test_remove_page_renumbers_remaining(self, qtbot):
"""Test remaining pages are renumbered after removal"""
window = TestPageOpsWindow()
qtbot.addWidget(window)
page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
page2 = Page(layout=PageLayout(width=210, height=297), page_number=2)
page3 = Page(layout=PageLayout(width=210, height=297), page_number=3)
window.project.pages = [page1, page2, page3]
window.remove_page()
assert len(window.project.pages) == 2
assert window.project.pages[0].page_number == 1
assert window.project.pages[1].page_number == 2