updated tests and installer
This commit is contained in:
@@ -228,6 +228,44 @@ class TestGetElementAt:
|
||||
result = widget._get_element_at(351, 301)
|
||||
assert result is None
|
||||
|
||||
def test_get_element_at_rotated_element(self, qtbot, mock_page_renderer):
|
||||
"""Test _get_element_at handles rotated elements correctly"""
|
||||
widget = TestSelectionWidget()
|
||||
qtbot.addWidget(widget)
|
||||
|
||||
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
||||
# Create element rotated 45 degrees
|
||||
elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
|
||||
elem.rotation = 45
|
||||
page.layout.add_element(elem)
|
||||
|
||||
widget._page_renderers = [(mock_page_renderer, page)]
|
||||
|
||||
# Click at center of rotated element (should still be inside)
|
||||
# Center is at (100 + 200/2, 100 + 150/2) = (200, 175) in page coords
|
||||
# Screen coords: (50 + 200, 50 + 175) = (250, 225)
|
||||
result = widget._get_element_at(250, 225)
|
||||
assert result == elem
|
||||
assert hasattr(result, '_page_renderer')
|
||||
assert hasattr(result, '_parent_page')
|
||||
|
||||
def test_get_element_at_rotated_element_outside(self, qtbot, mock_page_renderer):
|
||||
"""Test _get_element_at correctly rejects clicks outside rotated element"""
|
||||
widget = TestSelectionWidget()
|
||||
qtbot.addWidget(widget)
|
||||
|
||||
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
||||
# Create element rotated 90 degrees
|
||||
elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
|
||||
elem.rotation = 90
|
||||
page.layout.add_element(elem)
|
||||
|
||||
widget._page_renderers = [(mock_page_renderer, page)]
|
||||
|
||||
# Click far outside element
|
||||
result = widget._get_element_at(500, 500)
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestGetResizeHandleAt:
|
||||
"""Test _get_resize_handle_at method"""
|
||||
@@ -350,6 +388,55 @@ class TestGetResizeHandleAt:
|
||||
result = widget._get_resize_handle_at(250, 225)
|
||||
assert result is None
|
||||
|
||||
def test_get_resize_handle_rotated_element(self, qtbot, mock_page_renderer):
|
||||
"""Test _get_resize_handle_at handles rotated elements"""
|
||||
widget = TestSelectionWidget()
|
||||
qtbot.addWidget(widget)
|
||||
|
||||
elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
|
||||
elem.rotation = 45
|
||||
elem._page_renderer = mock_page_renderer
|
||||
widget.selected_element = elem
|
||||
|
||||
# Mock window
|
||||
from pyPhotoAlbum.project import Project, Page
|
||||
from pyPhotoAlbum.page_layout import PageLayout
|
||||
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]
|
||||
widget.window = Mock(return_value=mock_window)
|
||||
|
||||
# The rotation code should still detect handles - test NW handle
|
||||
# For rotated element, the handle positions are transformed
|
||||
result = widget._get_resize_handle_at(150, 150)
|
||||
# Should detect a handle (exact handle depends on rotation transform)
|
||||
assert result is None or result in ['nw', 'ne', 'sw', 'se']
|
||||
|
||||
def test_get_resize_handle_rotated_90_degrees(self, qtbot, mock_page_renderer):
|
||||
"""Test _get_resize_handle_at handles 90-degree rotated elements"""
|
||||
widget = TestSelectionWidget()
|
||||
qtbot.addWidget(widget)
|
||||
|
||||
elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
|
||||
elem.rotation = 90
|
||||
elem._page_renderer = mock_page_renderer
|
||||
widget.selected_element = elem
|
||||
|
||||
# Mock window
|
||||
from pyPhotoAlbum.project import Project, Page
|
||||
from pyPhotoAlbum.page_layout import PageLayout
|
||||
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]
|
||||
widget.window = Mock(return_value=mock_window)
|
||||
|
||||
# Test clicking at various positions - rotation code should handle them
|
||||
# Just verify the method runs without crashing
|
||||
result = widget._get_resize_handle_at(200, 200)
|
||||
assert result is None or result in ['nw', 'ne', 'sw', 'se']
|
||||
|
||||
|
||||
class TestMultiSelect:
|
||||
"""Test multi-selection functionality"""
|
||||
|
||||
@@ -0,0 +1,473 @@
|
||||
"""
|
||||
Tests for PageLayout and GridLayout classes
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pyPhotoAlbum.page_layout import PageLayout, GridLayout
|
||||
from pyPhotoAlbum.models import ImageData, PlaceholderData, TextBoxData
|
||||
from pyPhotoAlbum.snapping import SnappingSystem
|
||||
|
||||
|
||||
class TestPageLayoutInitialization:
|
||||
"""Test PageLayout initialization"""
|
||||
|
||||
def test_initialization_default(self):
|
||||
"""Test PageLayout with default values"""
|
||||
layout = PageLayout()
|
||||
assert layout.size == (210, 297)
|
||||
assert layout.base_width == 210
|
||||
assert layout.is_facing_page is False
|
||||
assert layout.elements == []
|
||||
assert layout.grid_layout is None
|
||||
assert layout.background_color == (1.0, 1.0, 1.0)
|
||||
assert isinstance(layout.snapping_system, SnappingSystem)
|
||||
assert layout.show_snap_lines is True
|
||||
|
||||
def test_initialization_custom_size(self):
|
||||
"""Test PageLayout with custom dimensions"""
|
||||
layout = PageLayout(width=150, height=200)
|
||||
assert layout.size == (150, 200)
|
||||
assert layout.base_width == 150
|
||||
assert layout.is_facing_page is False
|
||||
|
||||
def test_initialization_facing_page(self):
|
||||
"""Test PageLayout as facing page (double width)"""
|
||||
layout = PageLayout(width=210, height=297, is_facing_page=True)
|
||||
assert layout.size == (420, 297) # Width doubled
|
||||
assert layout.base_width == 210
|
||||
assert layout.is_facing_page is True
|
||||
|
||||
|
||||
class TestElementManagement:
|
||||
"""Test element add/remove operations"""
|
||||
|
||||
def test_add_element(self):
|
||||
"""Test adding element to layout"""
|
||||
layout = PageLayout()
|
||||
elem = ImageData(image_path="test.jpg")
|
||||
|
||||
layout.add_element(elem)
|
||||
|
||||
assert len(layout.elements) == 1
|
||||
assert layout.elements[0] == elem
|
||||
|
||||
def test_add_multiple_elements(self):
|
||||
"""Test adding multiple elements"""
|
||||
layout = PageLayout()
|
||||
elem1 = ImageData(image_path="test1.jpg")
|
||||
elem2 = PlaceholderData()
|
||||
elem3 = TextBoxData(text_content="Hello")
|
||||
|
||||
layout.add_element(elem1)
|
||||
layout.add_element(elem2)
|
||||
layout.add_element(elem3)
|
||||
|
||||
assert len(layout.elements) == 3
|
||||
assert layout.elements[0] == elem1
|
||||
assert layout.elements[1] == elem2
|
||||
assert layout.elements[2] == elem3
|
||||
|
||||
def test_remove_element(self):
|
||||
"""Test removing element from layout"""
|
||||
layout = PageLayout()
|
||||
elem1 = ImageData(image_path="test1.jpg")
|
||||
elem2 = PlaceholderData()
|
||||
|
||||
layout.add_element(elem1)
|
||||
layout.add_element(elem2)
|
||||
|
||||
layout.remove_element(elem1)
|
||||
|
||||
assert len(layout.elements) == 1
|
||||
assert layout.elements[0] == elem2
|
||||
|
||||
def test_remove_element_not_in_list_raises_error(self):
|
||||
"""Test removing non-existent element raises error"""
|
||||
layout = PageLayout()
|
||||
elem = ImageData(image_path="test.jpg")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
layout.remove_element(elem)
|
||||
|
||||
|
||||
class TestGridLayout:
|
||||
"""Test setting grid layout"""
|
||||
|
||||
def test_set_grid_layout(self):
|
||||
"""Test setting a grid layout"""
|
||||
layout = PageLayout()
|
||||
grid = GridLayout(rows=2, columns=3)
|
||||
|
||||
layout.set_grid_layout(grid)
|
||||
|
||||
assert layout.grid_layout == grid
|
||||
assert layout.grid_layout.rows == 2
|
||||
assert layout.grid_layout.columns == 3
|
||||
|
||||
|
||||
class TestPageLayoutSerialization:
|
||||
"""Test PageLayout serialization"""
|
||||
|
||||
def test_serialize_empty_layout(self):
|
||||
"""Test serializing empty layout"""
|
||||
layout = PageLayout(width=210, height=297)
|
||||
data = layout.serialize()
|
||||
|
||||
assert data["size"] == (210, 297)
|
||||
assert data["base_width"] == 210
|
||||
assert data["is_facing_page"] is False
|
||||
assert data["background_color"] == (1.0, 1.0, 1.0)
|
||||
assert data["elements"] == []
|
||||
assert data["grid_layout"] is None
|
||||
assert "snapping_system" in data
|
||||
assert data["show_snap_lines"] is True
|
||||
|
||||
def test_serialize_with_elements(self):
|
||||
"""Test serializing layout with elements"""
|
||||
layout = PageLayout()
|
||||
elem1 = ImageData(image_path="test1.jpg", x=10, y=20)
|
||||
elem2 = PlaceholderData(x=30, y=40)
|
||||
|
||||
layout.add_element(elem1)
|
||||
layout.add_element(elem2)
|
||||
|
||||
data = layout.serialize()
|
||||
|
||||
assert len(data["elements"]) == 2
|
||||
assert data["elements"][0]["type"] == "image"
|
||||
assert data["elements"][1]["type"] == "placeholder"
|
||||
|
||||
def test_serialize_with_grid_layout(self):
|
||||
"""Test serializing layout with grid"""
|
||||
layout = PageLayout()
|
||||
grid = GridLayout(rows=3, columns=2, spacing=15.0)
|
||||
layout.set_grid_layout(grid)
|
||||
|
||||
data = layout.serialize()
|
||||
|
||||
assert data["grid_layout"] is not None
|
||||
assert data["grid_layout"]["rows"] == 3
|
||||
assert data["grid_layout"]["columns"] == 2
|
||||
assert data["grid_layout"]["spacing"] == 15.0
|
||||
|
||||
def test_serialize_facing_page(self):
|
||||
"""Test serializing facing page layout"""
|
||||
layout = PageLayout(width=210, height=297, is_facing_page=True)
|
||||
data = layout.serialize()
|
||||
|
||||
assert data["size"] == (420, 297)
|
||||
assert data["base_width"] == 210
|
||||
assert data["is_facing_page"] is True
|
||||
|
||||
|
||||
class TestPageLayoutDeserialization:
|
||||
"""Test PageLayout deserialization"""
|
||||
|
||||
def test_deserialize_basic_layout(self):
|
||||
"""Test deserializing basic layout"""
|
||||
layout = PageLayout()
|
||||
data = {
|
||||
"size": (150, 200),
|
||||
"base_width": 150,
|
||||
"is_facing_page": False,
|
||||
"background_color": (0.9, 0.9, 0.9),
|
||||
"elements": [],
|
||||
"grid_layout": None,
|
||||
"snapping_system": {},
|
||||
"show_snap_lines": False
|
||||
}
|
||||
|
||||
layout.deserialize(data)
|
||||
|
||||
assert layout.size == (150, 200)
|
||||
assert layout.base_width == 150
|
||||
assert layout.is_facing_page is False
|
||||
assert layout.background_color == (0.9, 0.9, 0.9)
|
||||
assert layout.show_snap_lines is False
|
||||
|
||||
def test_deserialize_with_elements(self):
|
||||
"""Test deserializing layout with elements"""
|
||||
layout = PageLayout()
|
||||
data = {
|
||||
"size": (210, 297),
|
||||
"elements": [
|
||||
{
|
||||
"type": "image",
|
||||
"image_path": "test1.jpg",
|
||||
"position": (10, 20),
|
||||
"size": (100, 100),
|
||||
"rotation": 0,
|
||||
"z_index": 0,
|
||||
"crop_info": (0, 0, 1, 1)
|
||||
},
|
||||
{
|
||||
"type": "placeholder",
|
||||
"placeholder_type": "image",
|
||||
"default_content": "",
|
||||
"position": (30, 40),
|
||||
"size": (80, 80),
|
||||
"rotation": 0,
|
||||
"z_index": 1
|
||||
},
|
||||
{
|
||||
"type": "textbox",
|
||||
"text_content": "Hello",
|
||||
"font_settings": {"family": "Arial", "size": 12, "color": (0, 0, 0)},
|
||||
"alignment": "left",
|
||||
"position": (50, 60),
|
||||
"size": (120, 40),
|
||||
"rotation": 0,
|
||||
"z_index": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
layout.deserialize(data)
|
||||
|
||||
assert len(layout.elements) == 3
|
||||
assert isinstance(layout.elements[0], ImageData)
|
||||
assert isinstance(layout.elements[1], PlaceholderData)
|
||||
assert isinstance(layout.elements[2], TextBoxData)
|
||||
|
||||
def test_deserialize_elements_sorted_by_z_index(self):
|
||||
"""Test elements are sorted by z_index during deserialization"""
|
||||
layout = PageLayout()
|
||||
data = {
|
||||
"size": (210, 297),
|
||||
"elements": [
|
||||
{
|
||||
"type": "image",
|
||||
"image_path": "test1.jpg",
|
||||
"position": (10, 20),
|
||||
"size": (100, 100),
|
||||
"rotation": 0,
|
||||
"z_index": 5, # Higher z_index
|
||||
"crop_info": (0, 0, 1, 1)
|
||||
},
|
||||
{
|
||||
"type": "placeholder",
|
||||
"placeholder_type": "image",
|
||||
"default_content": "",
|
||||
"position": (30, 40),
|
||||
"size": (80, 80),
|
||||
"rotation": 0,
|
||||
"z_index": 1 # Lower z_index - should be first
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
layout.deserialize(data)
|
||||
|
||||
assert len(layout.elements) == 2
|
||||
# Lower z_index should come first in list
|
||||
assert layout.elements[0].z_index == 1
|
||||
assert layout.elements[1].z_index == 5
|
||||
|
||||
def test_deserialize_with_grid_layout(self):
|
||||
"""Test deserializing layout with grid"""
|
||||
layout = PageLayout()
|
||||
data = {
|
||||
"size": (210, 297),
|
||||
"elements": [],
|
||||
"grid_layout": {
|
||||
"rows": 2,
|
||||
"columns": 3,
|
||||
"spacing": 12.5,
|
||||
"merged_cells": [(0, 0), (1, 1)]
|
||||
}
|
||||
}
|
||||
|
||||
layout.deserialize(data)
|
||||
|
||||
assert layout.grid_layout is not None
|
||||
assert layout.grid_layout.rows == 2
|
||||
assert layout.grid_layout.columns == 3
|
||||
assert layout.grid_layout.spacing == 12.5
|
||||
assert layout.grid_layout.merged_cells == [(0, 0), (1, 1)]
|
||||
|
||||
def test_deserialize_unknown_element_type_skipped(self):
|
||||
"""Test that unknown element types are skipped"""
|
||||
layout = PageLayout()
|
||||
data = {
|
||||
"size": (210, 297),
|
||||
"elements": [
|
||||
{
|
||||
"type": "unknown_type",
|
||||
"position": (10, 20)
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"image_path": "test.jpg",
|
||||
"position": (30, 40),
|
||||
"size": (100, 100),
|
||||
"rotation": 0,
|
||||
"z_index": 0,
|
||||
"crop_info": (0, 0, 1, 1)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
layout.deserialize(data)
|
||||
|
||||
# Only valid element should be deserialized
|
||||
assert len(layout.elements) == 1
|
||||
assert isinstance(layout.elements[0], ImageData)
|
||||
|
||||
def test_deserialize_with_defaults(self):
|
||||
"""Test deserialization with missing fields uses defaults"""
|
||||
layout = PageLayout()
|
||||
data = {}
|
||||
|
||||
layout.deserialize(data)
|
||||
|
||||
assert layout.size == (210, 297)
|
||||
assert layout.background_color == (1.0, 1.0, 1.0)
|
||||
assert layout.show_snap_lines is True
|
||||
|
||||
|
||||
class TestSerializationRoundtrip:
|
||||
"""Test serialization/deserialization roundtrips"""
|
||||
|
||||
def test_serialize_deserialize_roundtrip(self):
|
||||
"""Test that serialize/deserialize are inverse operations"""
|
||||
original = PageLayout(width=200, height=280, is_facing_page=False)
|
||||
original.background_color = (0.95, 0.95, 0.95)
|
||||
original.show_snap_lines = False
|
||||
|
||||
elem1 = ImageData(image_path="test.jpg", x=10, y=20, width=100, height=80)
|
||||
elem2 = PlaceholderData(x=30, y=40, width=50, height=50)
|
||||
original.add_element(elem1)
|
||||
original.add_element(elem2)
|
||||
|
||||
grid = GridLayout(rows=2, columns=3, spacing=10.0)
|
||||
original.set_grid_layout(grid)
|
||||
|
||||
# Serialize and deserialize
|
||||
data = original.serialize()
|
||||
restored = PageLayout()
|
||||
restored.deserialize(data)
|
||||
|
||||
# Verify restoration
|
||||
assert restored.size == original.size
|
||||
assert restored.base_width == original.base_width
|
||||
assert restored.is_facing_page == original.is_facing_page
|
||||
assert restored.background_color == original.background_color
|
||||
assert restored.show_snap_lines == original.show_snap_lines
|
||||
assert len(restored.elements) == len(original.elements)
|
||||
assert restored.grid_layout is not None
|
||||
assert restored.grid_layout.rows == original.grid_layout.rows
|
||||
|
||||
|
||||
class TestGridLayoutClass:
|
||||
"""Test GridLayout class"""
|
||||
|
||||
def test_initialization_default(self):
|
||||
"""Test GridLayout initialization with defaults"""
|
||||
grid = GridLayout()
|
||||
assert grid.rows == 1
|
||||
assert grid.columns == 1
|
||||
assert grid.spacing == 10.0
|
||||
assert grid.merged_cells == []
|
||||
|
||||
def test_initialization_custom(self):
|
||||
"""Test GridLayout initialization with custom values"""
|
||||
grid = GridLayout(rows=3, columns=4, spacing=15.5)
|
||||
assert grid.rows == 3
|
||||
assert grid.columns == 4
|
||||
assert grid.spacing == 15.5
|
||||
|
||||
def test_merge_cells(self):
|
||||
"""Test merging cells in grid"""
|
||||
grid = GridLayout(rows=3, columns=3)
|
||||
|
||||
grid.merge_cells(0, 0)
|
||||
grid.merge_cells(1, 1)
|
||||
|
||||
assert len(grid.merged_cells) == 2
|
||||
assert (0, 0) in grid.merged_cells
|
||||
assert (1, 1) in grid.merged_cells
|
||||
|
||||
def test_get_cell_position(self):
|
||||
"""Test calculating cell position"""
|
||||
grid = GridLayout(rows=2, columns=2, spacing=10.0)
|
||||
|
||||
# Cell (0, 0) - top left
|
||||
x, y = grid.get_cell_position(0, 0, page_width=800, page_height=600)
|
||||
assert x == 10.0 # spacing
|
||||
assert y == 10.0 # spacing
|
||||
|
||||
# Cell (0, 1) - top right
|
||||
x, y = grid.get_cell_position(0, 1, page_width=800, page_height=600)
|
||||
# spacing + col * (cell_width + spacing)
|
||||
# cell_width = (800 - 30) / 2 = 385
|
||||
# x = 10 + 1 * (385 + 10) = 405
|
||||
assert x == 405.0
|
||||
|
||||
def test_get_cell_size(self):
|
||||
"""Test calculating cell size"""
|
||||
grid = GridLayout(rows=2, columns=3, spacing=10.0)
|
||||
|
||||
width, height = grid.get_cell_size(page_width=900, page_height=600)
|
||||
|
||||
# width = (900 - 10 * 4) / 3 = 860 / 3 ≈ 286.67
|
||||
# height = (600 - 10 * 3) / 2 = 570 / 2 = 285
|
||||
assert width == pytest.approx(286.666, rel=0.01)
|
||||
assert height == 285.0
|
||||
|
||||
def test_grid_serialization(self):
|
||||
"""Test GridLayout serialization"""
|
||||
grid = GridLayout(rows=3, columns=2, spacing=12.0)
|
||||
grid.merge_cells(0, 1)
|
||||
grid.merge_cells(2, 0)
|
||||
|
||||
data = grid.serialize()
|
||||
|
||||
assert data["rows"] == 3
|
||||
assert data["columns"] == 2
|
||||
assert data["spacing"] == 12.0
|
||||
assert data["merged_cells"] == [(0, 1), (2, 0)]
|
||||
|
||||
def test_grid_deserialization(self):
|
||||
"""Test GridLayout deserialization"""
|
||||
grid = GridLayout()
|
||||
data = {
|
||||
"rows": 4,
|
||||
"columns": 5,
|
||||
"spacing": 8.5,
|
||||
"merged_cells": [(1, 2), (3, 3)]
|
||||
}
|
||||
|
||||
grid.deserialize(data)
|
||||
|
||||
assert grid.rows == 4
|
||||
assert grid.columns == 5
|
||||
assert grid.spacing == 8.5
|
||||
assert grid.merged_cells == [(1, 2), (3, 3)]
|
||||
|
||||
def test_grid_deserialization_with_defaults(self):
|
||||
"""Test GridLayout deserialization with missing fields"""
|
||||
grid = GridLayout()
|
||||
data = {}
|
||||
|
||||
grid.deserialize(data)
|
||||
|
||||
assert grid.rows == 1
|
||||
assert grid.columns == 1
|
||||
assert grid.spacing == 10.0
|
||||
assert grid.merged_cells == []
|
||||
|
||||
def test_grid_serialize_deserialize_roundtrip(self):
|
||||
"""Test GridLayout serialize/deserialize roundtrip"""
|
||||
original = GridLayout(rows=3, columns=4, spacing=11.5)
|
||||
original.merge_cells(0, 0)
|
||||
original.merge_cells(1, 2)
|
||||
original.merge_cells(2, 3)
|
||||
|
||||
data = original.serialize()
|
||||
restored = GridLayout()
|
||||
restored.deserialize(data)
|
||||
|
||||
assert restored.rows == original.rows
|
||||
assert restored.columns == original.columns
|
||||
assert restored.spacing == original.spacing
|
||||
assert restored.merged_cells == original.merged_cells
|
||||
@@ -0,0 +1,474 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user