475 lines
13 KiB
Python
475 lines
13 KiB
Python
"""
|
|
Tests for ViewOperationsMixin
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch
|
|
from PyQt6.QtWidgets import QMainWindow, QDialog
|
|
from pyPhotoAlbum.mixins.operations.view_ops import ViewOperationsMixin
|
|
from pyPhotoAlbum.project import Project, Page
|
|
from pyPhotoAlbum.page_layout import PageLayout
|
|
|
|
|
|
class TestViewWindow(ViewOperationsMixin, QMainWindow):
|
|
"""Test window with view operations mixin"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.gl_widget = Mock()
|
|
self.gl_widget.zoom_level = 1.0
|
|
self.gl_widget.current_page_index = 0
|
|
self.gl_widget.width = Mock(return_value=800)
|
|
self.gl_widget.height = Mock(return_value=600)
|
|
self.project = Mock(spec=Project)
|
|
self.project.working_dpi = 300
|
|
self._update_view_called = False
|
|
self._status_message = None
|
|
|
|
def get_current_page(self):
|
|
if hasattr(self, '_current_page'):
|
|
return self._current_page
|
|
return None
|
|
|
|
def update_view(self):
|
|
self._update_view_called = True
|
|
|
|
def show_status(self, message, timeout=0):
|
|
self._status_message = message
|
|
|
|
|
|
class TestZoomOperations:
|
|
"""Test zoom operations"""
|
|
|
|
def test_zoom_in_success(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
window.gl_widget.zoom_level = 1.0
|
|
|
|
window.zoom_in()
|
|
|
|
assert window.gl_widget.zoom_level == 1.2
|
|
assert window._update_view_called
|
|
assert "120%" in window._status_message
|
|
|
|
def test_zoom_in_max_limit(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
window.gl_widget.zoom_level = 4.5
|
|
|
|
window.zoom_in()
|
|
|
|
# 4.5 * 1.2 = 5.4, but clamped to 5.0
|
|
assert window.gl_widget.zoom_level == 5.0
|
|
assert window._update_view_called
|
|
|
|
def test_zoom_out_success(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
window.gl_widget.zoom_level = 1.2
|
|
|
|
window.zoom_out()
|
|
|
|
assert window.gl_widget.zoom_level == 1.0
|
|
assert window._update_view_called
|
|
assert "100%" in window._status_message
|
|
|
|
def test_zoom_out_min_limit(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
window.gl_widget.zoom_level = 0.11
|
|
|
|
window.zoom_out()
|
|
|
|
# 0.11 / 1.2 ≈ 0.092, but clamped to 0.1
|
|
assert window.gl_widget.zoom_level == 0.1
|
|
assert window._update_view_called
|
|
|
|
def test_zoom_fit_success(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
# Setup page
|
|
layout = PageLayout(width=210, height=297) # A4 in mm
|
|
page = Mock()
|
|
page.layout = layout
|
|
window.project.pages = [page]
|
|
|
|
window.zoom_fit()
|
|
|
|
# Should calculate zoom to fit page in widget
|
|
assert window._update_view_called
|
|
assert "zoom" in window._status_message.lower()
|
|
# Zoom level should be set based on widget size
|
|
assert 0.1 <= window.gl_widget.zoom_level <= 5.0
|
|
|
|
def test_zoom_fit_no_pages(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
window.project.pages = []
|
|
|
|
window.zoom_fit()
|
|
|
|
# Should return early without error
|
|
assert not window._update_view_called
|
|
|
|
|
|
class TestSnappingToggles:
|
|
"""Test snapping toggle operations"""
|
|
|
|
def test_toggle_grid_snap_enable(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_grid = False
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.toggle_grid_snap()
|
|
|
|
assert layout.snapping_system.snap_to_grid is True
|
|
assert "enabled" in window._status_message.lower()
|
|
assert window._update_view_called
|
|
|
|
def test_toggle_grid_snap_disable(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_grid = True
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.toggle_grid_snap()
|
|
|
|
assert layout.snapping_system.snap_to_grid is False
|
|
assert "disabled" in window._status_message.lower()
|
|
assert window._update_view_called
|
|
|
|
def test_toggle_grid_snap_no_page(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
window._current_page = None
|
|
|
|
window.toggle_grid_snap()
|
|
|
|
assert not window._update_view_called
|
|
|
|
def test_toggle_edge_snap_enable(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_edges = False
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.toggle_edge_snap()
|
|
|
|
assert layout.snapping_system.snap_to_edges is True
|
|
assert "enabled" in window._status_message.lower()
|
|
assert window._update_view_called
|
|
|
|
def test_toggle_edge_snap_disable(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_edges = True
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.toggle_edge_snap()
|
|
|
|
assert layout.snapping_system.snap_to_edges is False
|
|
assert "disabled" in window._status_message.lower()
|
|
|
|
def test_toggle_guide_snap_enable(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_guides = False
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.toggle_guide_snap()
|
|
|
|
assert layout.snapping_system.snap_to_guides is True
|
|
assert "enabled" in window._status_message.lower()
|
|
|
|
def test_toggle_guide_snap_disable(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_guides = True
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.toggle_guide_snap()
|
|
|
|
assert layout.snapping_system.snap_to_guides is False
|
|
assert "disabled" in window._status_message.lower()
|
|
|
|
def test_toggle_snap_lines_show(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.show_snap_lines = False
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.toggle_snap_lines()
|
|
|
|
assert layout.show_snap_lines is True
|
|
assert "visible" in window._status_message.lower()
|
|
|
|
def test_toggle_snap_lines_hide(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.show_snap_lines = True
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.toggle_snap_lines()
|
|
|
|
assert layout.show_snap_lines is False
|
|
assert "hidden" in window._status_message.lower()
|
|
|
|
|
|
class TestGuideOperations:
|
|
"""Test guide add/clear operations"""
|
|
|
|
def test_add_horizontal_guide(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout(width=210, height=297)
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.add_horizontal_guide()
|
|
|
|
# Guide should be added at vertical center (297 / 2 = 148.5mm)
|
|
assert "148.5" in window._status_message
|
|
assert window._update_view_called
|
|
|
|
def test_add_horizontal_guide_no_page(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
window._current_page = None
|
|
|
|
window.add_horizontal_guide()
|
|
|
|
assert not window._update_view_called
|
|
|
|
def test_add_vertical_guide(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout(width=210, height=297)
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.add_vertical_guide()
|
|
|
|
# Guide should be added at horizontal center (210 / 2 = 105.0mm)
|
|
assert "105.0" in window._status_message
|
|
assert window._update_view_called
|
|
|
|
def test_add_vertical_guide_no_page(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
window._current_page = None
|
|
|
|
window.add_vertical_guide()
|
|
|
|
assert not window._update_view_called
|
|
|
|
def test_clear_guides_with_guides(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
# Add some guides
|
|
layout.snapping_system.add_guide(100, 'vertical')
|
|
layout.snapping_system.add_guide(150, 'horizontal')
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.clear_guides()
|
|
|
|
assert len(layout.snapping_system.guides) == 0
|
|
assert "2" in window._status_message # Cleared 2 guides
|
|
assert window._update_view_called
|
|
|
|
def test_clear_guides_no_guides(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.clear_guides()
|
|
|
|
assert "0" in window._status_message
|
|
assert window._update_view_called
|
|
|
|
def test_clear_guides_no_page(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
window._current_page = None
|
|
|
|
window.clear_guides()
|
|
|
|
assert not window._update_view_called
|
|
|
|
|
|
class TestGridSettingsDialog:
|
|
"""Test grid settings dialog"""
|
|
|
|
def test_set_grid_size_no_page(self, qtbot):
|
|
"""Test set_grid_size with no current page"""
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
window._current_page = None
|
|
|
|
window.set_grid_size()
|
|
|
|
# Should return early without error
|
|
assert not window._update_view_called
|
|
|
|
def test_set_grid_size_with_page(self, qtbot):
|
|
"""Test set_grid_size creates dialog with current page"""
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.snapping_system.grid_size_mm = 10.0
|
|
layout.snapping_system.snap_threshold_mm = 5.0
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
# Mock the full dialog workflow
|
|
mock_dialog = Mock(spec=QDialog)
|
|
mock_dialog.exec.return_value = QDialog.DialogCode.Rejected
|
|
|
|
with patch('PyQt6.QtWidgets.QDialog', return_value=mock_dialog):
|
|
window.set_grid_size()
|
|
|
|
# Dialog should have been created and exec called
|
|
mock_dialog.exec.assert_called_once()
|
|
|
|
|
|
class TestLayoutTabDelegation:
|
|
"""Test Layout tab methods delegate to main methods"""
|
|
|
|
def test_layout_toggle_grid_snap_delegates(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_grid = False
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.layout_toggle_grid_snap()
|
|
|
|
# Should delegate to toggle_grid_snap
|
|
assert layout.snapping_system.snap_to_grid is True
|
|
assert window._update_view_called
|
|
|
|
def test_layout_toggle_edge_snap_delegates(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_edges = False
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.layout_toggle_edge_snap()
|
|
|
|
assert layout.snapping_system.snap_to_edges is True
|
|
assert window._update_view_called
|
|
|
|
def test_layout_toggle_guide_snap_delegates(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_guides = False
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.layout_toggle_guide_snap()
|
|
|
|
assert layout.snapping_system.snap_to_guides is True
|
|
assert window._update_view_called
|
|
|
|
def test_layout_toggle_snap_lines_delegates(self, qtbot):
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
layout.show_snap_lines = False
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
window.layout_toggle_snap_lines()
|
|
|
|
assert layout.show_snap_lines is True
|
|
assert window._update_view_called
|
|
|
|
def test_layout_set_grid_size_delegates(self, qtbot):
|
|
"""Test layout tab grid size delegates to main method"""
|
|
window = TestViewWindow()
|
|
qtbot.addWidget(window)
|
|
|
|
layout = PageLayout()
|
|
page = Mock()
|
|
page.layout = layout
|
|
window._current_page = page
|
|
|
|
# Mock dialog to verify delegation
|
|
mock_dialog = Mock(spec=QDialog)
|
|
mock_dialog.exec.return_value = QDialog.DialogCode.Rejected
|
|
|
|
with patch('PyQt6.QtWidgets.QDialog', return_value=mock_dialog):
|
|
window.layout_set_grid_size()
|
|
|
|
# Verify method was called (dialog creation attempted)
|
|
mock_dialog.exec.assert_called_once()
|