pyPhotoAlbum/tests/test_page_navigation_mixin.py
2025-11-11 16:02:02 +00:00

346 lines
12 KiB
Python

"""
Tests for PageNavigationMixin
"""
import pytest
from unittest.mock import Mock, MagicMock, patch
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from pyPhotoAlbum.mixins.page_navigation import PageNavigationMixin
from pyPhotoAlbum.mixins.viewport import ViewportMixin
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import GhostPageData
# Create test widget combining necessary mixins
class TestPageNavWidget(PageNavigationMixin, ViewportMixin, QOpenGLWidget):
"""Test widget combining page navigation and viewport mixins"""
pass
class TestPageNavigationInitialization:
"""Test PageNavigationMixin initialization"""
def test_initialization_sets_defaults(self, qtbot):
"""Test that mixin initializes with correct defaults"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
assert widget.current_page_index == 0
assert widget._page_renderers == []
def test_current_page_index_is_mutable(self, qtbot):
"""Test that current page index can be changed"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
widget.current_page_index = 5
assert widget.current_page_index == 5
class TestGetPageAt:
"""Test _get_page_at method"""
def test_get_page_at_no_renderers(self, qtbot):
"""Test returns None when no renderers"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
result = widget._get_page_at(100, 100)
assert result == (None, -1, None)
def test_get_page_at_no_project(self, qtbot):
"""Test returns None when no project"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
# Set up renderers but no project
mock_renderer = Mock()
widget._page_renderers = [(mock_renderer, Mock())]
mock_window = Mock()
mock_window.project = None
widget.window = Mock(return_value=mock_window)
result = widget._get_page_at(100, 100)
assert result == (None, -1, None)
def test_get_page_at_finds_page(self, qtbot):
"""Test finds page at coordinates"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
# Create project with page
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]
# Create renderer that returns True for is_point_in_page
mock_renderer = Mock()
mock_renderer.is_point_in_page = Mock(return_value=True)
widget._page_renderers = [(mock_renderer, page)]
widget.window = Mock(return_value=mock_window)
result_page, result_index, result_renderer = widget._get_page_at(100, 100)
assert result_page is page
assert result_index == 0
assert result_renderer is mock_renderer
def test_get_page_at_multiple_pages(self, qtbot):
"""Test finds correct page when multiple pages exist"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
mock_window = Mock()
mock_window.project = Project(name="Test")
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)
mock_window.project.pages = [page1, page2, page3]
# First renderer returns False, second returns True
renderer1 = Mock()
renderer1.is_point_in_page = Mock(return_value=False)
renderer2 = Mock()
renderer2.is_point_in_page = Mock(return_value=True)
renderer3 = Mock()
renderer3.is_point_in_page = Mock(return_value=False)
widget._page_renderers = [(renderer1, page1), (renderer2, page2), (renderer3, page3)]
widget.window = Mock(return_value=mock_window)
result_page, result_index, result_renderer = widget._get_page_at(100, 100)
assert result_page is page2
assert result_index == 1
assert result_renderer is renderer2
class TestGetPagePositions:
"""Test _get_page_positions method"""
def test_get_page_positions_no_project(self, qtbot):
"""Test returns empty list when no project"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
mock_window = Mock()
del mock_window.project # No project attribute
widget.window = Mock(return_value=mock_window)
result = widget._get_page_positions()
assert result == []
def test_get_page_positions_single_page(self, qtbot):
"""Test calculates positions for single page"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
mock_window.project.page_spacing_mm = 10
mock_window.project.page_size_mm = (210, 297)
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
mock_window.project.pages = [page]
# Mock calculate_page_layout_with_ghosts
mock_window.project.calculate_page_layout_with_ghosts = Mock(return_value=[
('page', page, 0)
])
widget.window = Mock(return_value=mock_window)
result = widget._get_page_positions()
# Should have one page entry
assert len(result) >= 1
assert result[0][0] == 'page'
assert result[0][1] is page
def test_get_page_positions_includes_ghosts(self, qtbot):
"""Test includes ghost pages in result"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
mock_window.project.page_spacing_mm = 10
mock_window.project.page_size_mm = (210, 297)
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
mock_window.project.pages = [page]
# Mock with ghost page
mock_window.project.calculate_page_layout_with_ghosts = Mock(return_value=[
('page', page, 0),
('ghost', None, 1)
])
widget.window = Mock(return_value=mock_window)
result = widget._get_page_positions()
# Should have page + ghost
assert len(result) >= 2
page_types = [r[0] for r in result]
assert 'page' in page_types
assert 'ghost' in page_types
class TestCheckGhostPageClick:
"""Test _check_ghost_page_click method"""
def test_check_ghost_page_no_renderers(self, qtbot):
"""Test returns False when no renderers"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
result = widget._check_ghost_page_click(100, 100)
assert result is False
def test_check_ghost_page_no_project(self, qtbot):
"""Test returns False when no project"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
widget._page_renderers = []
mock_window = Mock()
del mock_window.project
widget.window = Mock(return_value=mock_window)
result = widget._check_ghost_page_click(100, 100)
assert result is False
@patch('pyPhotoAlbum.page_renderer.PageRenderer')
def test_check_ghost_page_click_on_ghost(self, mock_page_renderer_class, qtbot):
"""Test clicking on ghost page creates new page"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
widget.zoom_level = 1.0
widget.pan_offset = [0, 0]
# Mock the update method
widget.update = Mock()
# Setup project
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
mock_window.project.page_spacing_mm = 10
mock_window.project.page_size_mm = (210, 297)
mock_window.project.pages = []
# Mock _get_page_positions to return a ghost
ghost = GhostPageData(page_size=(210, 297))
widget._get_page_positions = Mock(return_value=[
('ghost', ghost, 100)
])
# Mock PageRenderer to say click is in page
mock_renderer_instance = Mock()
mock_renderer_instance.is_point_in_page = Mock(return_value=True)
mock_page_renderer_class.return_value = mock_renderer_instance
widget.window = Mock(return_value=mock_window)
# Click on ghost page
result = widget._check_ghost_page_click(150, 150)
# Should return True and create page
assert result is True
assert len(mock_window.project.pages) == 1
assert widget.update.called
@patch('pyPhotoAlbum.page_renderer.PageRenderer')
def test_check_ghost_page_click_outside_ghost(self, mock_page_renderer_class, qtbot):
"""Test clicking outside ghost page returns False"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
widget.zoom_level = 1.0
widget.pan_offset = [0, 0]
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.working_dpi = 96
mock_window.project.page_spacing_mm = 10
mock_window.project.page_size_mm = (210, 297)
mock_window.project.pages = []
ghost = GhostPageData(page_size=(210, 297))
widget._get_page_positions = Mock(return_value=[
('ghost', ghost, 100)
])
# Mock renderer to say click is NOT in page
mock_renderer_instance = Mock()
mock_renderer_instance.is_point_in_page = Mock(return_value=False)
mock_page_renderer_class.return_value = mock_renderer_instance
widget.window = Mock(return_value=mock_window)
result = widget._check_ghost_page_click(5000, 5000)
assert result is False
assert len(mock_window.project.pages) == 0
class TestUpdatePageStatus:
"""Test _update_page_status method"""
def test_update_page_status_no_project(self, qtbot):
"""Test does nothing when no project"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
mock_window = Mock()
mock_window.project = None
widget.window = Mock(return_value=mock_window)
# Should not raise exception
widget._update_page_status(100, 100)
def test_update_page_status_no_renderers(self, qtbot):
"""Test does nothing when no renderers"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
mock_window = Mock()
mock_window.project = Project(name="Test")
mock_window.project.pages = []
widget.window = Mock(return_value=mock_window)
widget._update_page_status(100, 100)
def test_update_page_status_on_page(self, qtbot):
"""Test updates status bar when on a page"""
widget = TestPageNavWidget()
qtbot.addWidget(widget)
widget.zoom_level = 1.0
mock_window = Mock()
mock_window.project = Project(name="Test")
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
page.get_page_count = Mock(return_value=1)
page.is_double_spread = False
mock_window.project.pages = [page]
mock_window.status_bar = Mock()
mock_renderer = Mock()
mock_renderer.is_point_in_page = Mock(return_value=True)
widget._page_renderers = [(mock_renderer, page)]
widget.window = Mock(return_value=mock_window)
widget._update_page_status(100, 100)
# Status bar should be updated
assert mock_window.status_bar.showMessage.called
call_args = mock_window.status_bar.showMessage.call_args[0][0]
assert "Page 1" in call_args