345 lines
12 KiB
Python
345 lines
12 KiB
Python
"""
|
|
Tests for AssetDropMixin
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, MagicMock, patch
|
|
from PyQt6.QtCore import QMimeData, QUrl, QPoint
|
|
from PyQt6.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent
|
|
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
|
|
from pyPhotoAlbum.mixins.asset_drop import AssetDropMixin
|
|
from pyPhotoAlbum.mixins.viewport import ViewportMixin
|
|
from pyPhotoAlbum.mixins.page_navigation import PageNavigationMixin
|
|
from pyPhotoAlbum.project import Project, Page
|
|
from pyPhotoAlbum.page_layout import PageLayout
|
|
from pyPhotoAlbum.models import ImageData
|
|
|
|
|
|
# Create test widget combining necessary mixins
|
|
class TestAssetDropWidget(AssetDropMixin, PageNavigationMixin, ViewportMixin, QOpenGLWidget):
|
|
"""Test widget combining asset drop, page navigation, and viewport mixins"""
|
|
|
|
def _get_element_at(self, x, y):
|
|
"""Mock implementation for testing"""
|
|
# Will be overridden in tests that need it
|
|
return None
|
|
|
|
|
|
class TestAssetDropInitialization:
|
|
"""Test AssetDropMixin initialization"""
|
|
|
|
def test_widget_accepts_drops(self, qtbot):
|
|
"""Test that widget is configured to accept drops"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
# Should accept drops (set in GLWidget.__init__)
|
|
# This is a property of the widget, not the mixin
|
|
assert hasattr(widget, 'acceptDrops')
|
|
|
|
|
|
class TestDragEnterEvent:
|
|
"""Test dragEnterEvent method"""
|
|
|
|
def test_accepts_image_urls(self, qtbot):
|
|
"""Test accepts drag events with image file URLs"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
# Create mime data with image file
|
|
mime_data = QMimeData()
|
|
mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])
|
|
|
|
# Create drag enter event
|
|
event = Mock()
|
|
event.mimeData = Mock(return_value=mime_data)
|
|
event.acceptProposedAction = Mock()
|
|
|
|
widget.dragEnterEvent(event)
|
|
|
|
# Should accept the event
|
|
assert event.acceptProposedAction.called
|
|
|
|
def test_accepts_png_files(self, qtbot):
|
|
"""Test accepts PNG files"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
mime_data = QMimeData()
|
|
mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.png")])
|
|
|
|
event = Mock()
|
|
event.mimeData = Mock(return_value=mime_data)
|
|
event.acceptProposedAction = Mock()
|
|
|
|
widget.dragEnterEvent(event)
|
|
assert event.acceptProposedAction.called
|
|
|
|
def test_rejects_non_image_files(self, qtbot):
|
|
"""Test rejects non-image files"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
mime_data = QMimeData()
|
|
mime_data.setUrls([QUrl.fromLocalFile("/path/to/document.pdf")])
|
|
|
|
event = Mock()
|
|
event.mimeData = Mock(return_value=mime_data)
|
|
event.acceptProposedAction = Mock()
|
|
event.ignore = Mock()
|
|
|
|
widget.dragEnterEvent(event)
|
|
|
|
# Should not accept PDF files
|
|
assert not event.acceptProposedAction.called
|
|
|
|
def test_rejects_empty_mime_data(self, qtbot):
|
|
"""Test rejects events with no URLs"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
mime_data = QMimeData()
|
|
# No URLs set
|
|
|
|
event = Mock()
|
|
event.mimeData = Mock(return_value=mime_data)
|
|
event.acceptProposedAction = Mock()
|
|
|
|
widget.dragEnterEvent(event)
|
|
|
|
assert not event.acceptProposedAction.called
|
|
|
|
|
|
class TestDragMoveEvent:
|
|
"""Test dragMoveEvent method"""
|
|
|
|
def test_accepts_drag_move_with_image(self, qtbot):
|
|
"""Test accepts drag move events with image files"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
mime_data = QMimeData()
|
|
mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])
|
|
|
|
event = Mock()
|
|
event.mimeData = Mock(return_value=mime_data)
|
|
event.acceptProposedAction = Mock()
|
|
|
|
widget.dragMoveEvent(event)
|
|
|
|
assert event.acceptProposedAction.called
|
|
|
|
|
|
class TestDropEvent:
|
|
"""Test dropEvent method"""
|
|
|
|
@patch('pyPhotoAlbum.mixins.asset_drop.AddElementCommand')
|
|
def test_drop_creates_image_element(self, mock_cmd_class, qtbot):
|
|
"""Test dropping image file creates ImageData element"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.zoom_level = 1.0
|
|
widget.pan_offset = [0, 0]
|
|
|
|
# Mock update method
|
|
widget.update = Mock()
|
|
|
|
# Setup project with page
|
|
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]
|
|
|
|
# Mock asset manager
|
|
mock_window.project.asset_manager = Mock()
|
|
mock_window.project.asset_manager.import_asset = Mock(return_value="/imported/image.jpg")
|
|
|
|
# Mock history
|
|
mock_window.project.history = Mock()
|
|
|
|
# Mock page renderer
|
|
mock_renderer = Mock()
|
|
mock_renderer.is_point_in_page = Mock(return_value=True)
|
|
mock_renderer.screen_to_page = Mock(return_value=(100, 100))
|
|
|
|
# Mock _get_page_at to return tuple
|
|
widget._get_page_at = Mock(return_value=(page, 0, mock_renderer))
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
# Create drop event
|
|
mime_data = QMimeData()
|
|
mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])
|
|
|
|
event = Mock()
|
|
event.mimeData = Mock(return_value=mime_data)
|
|
event.position = Mock(return_value=QPoint(150, 150))
|
|
event.acceptProposedAction = Mock()
|
|
|
|
widget.dropEvent(event)
|
|
|
|
# Should have called asset manager
|
|
assert mock_window.project.asset_manager.import_asset.called
|
|
# Should have created command
|
|
assert mock_cmd_class.called
|
|
# Should have executed command
|
|
assert mock_window.project.history.execute.called
|
|
assert widget.update.called
|
|
|
|
def test_drop_outside_page_does_nothing(self, qtbot):
|
|
"""Test dropping outside any page does nothing"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.zoom_level = 1.0
|
|
widget.pan_offset = [0, 0]
|
|
|
|
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]
|
|
|
|
# Mock renderer that returns False (not in page)
|
|
mock_renderer = Mock()
|
|
mock_renderer.is_point_in_page = Mock(return_value=False)
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
mime_data = QMimeData()
|
|
mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])
|
|
|
|
event = Mock()
|
|
event.mimeData = Mock(return_value=mime_data)
|
|
event.position = Mock(return_value=QPoint(5000, 5000))
|
|
event.acceptProposedAction = Mock()
|
|
|
|
widget.dropEvent(event)
|
|
|
|
# Should not create any elements
|
|
assert len(page.layout.elements) == 0
|
|
|
|
def test_drop_updates_existing_placeholder(self, qtbot):
|
|
"""Test dropping on existing placeholder updates it with image"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.zoom_level = 1.0
|
|
widget.pan_offset = [0, 0]
|
|
|
|
widget.update = Mock()
|
|
|
|
# Setup project with page containing placeholder
|
|
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)
|
|
|
|
from pyPhotoAlbum.models import PlaceholderData
|
|
placeholder = PlaceholderData(x=100, y=100, width=200, height=150)
|
|
page.layout.elements.append(placeholder)
|
|
|
|
mock_window.project.pages = [page]
|
|
|
|
# Mock renderer
|
|
mock_renderer = Mock()
|
|
mock_renderer.is_point_in_page = Mock(return_value=True)
|
|
mock_renderer.screen_to_page = Mock(return_value=(150, 150))
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
# Mock element selection to return the placeholder
|
|
widget._get_element_at = Mock(return_value=placeholder)
|
|
|
|
mime_data = QMimeData()
|
|
mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])
|
|
|
|
event = Mock()
|
|
event.mimeData = Mock(return_value=mime_data)
|
|
event.position = Mock(return_value=QPoint(150, 150))
|
|
event.acceptProposedAction = Mock()
|
|
|
|
widget.dropEvent(event)
|
|
|
|
# Should replace placeholder with ImageData
|
|
assert len(page.layout.elements) == 1
|
|
assert isinstance(page.layout.elements[0], ImageData)
|
|
assert page.layout.elements[0].image_path == "/path/to/image.jpg"
|
|
|
|
@patch('pyPhotoAlbum.mixins.asset_drop.AddElementCommand')
|
|
def test_drop_multiple_files(self, mock_cmd_class, qtbot):
|
|
"""Test dropping first image from multiple files"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.zoom_level = 1.0
|
|
widget.pan_offset = [0, 0]
|
|
|
|
widget.update = Mock()
|
|
|
|
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]
|
|
|
|
mock_window.project.asset_manager = Mock()
|
|
mock_window.project.asset_manager.import_asset = Mock(return_value="/imported/image1.jpg")
|
|
|
|
mock_window.project.history = Mock()
|
|
|
|
mock_renderer = Mock()
|
|
mock_renderer.is_point_in_page = Mock(return_value=True)
|
|
mock_renderer.screen_to_page = Mock(return_value=(100, 100))
|
|
|
|
widget._get_page_at = Mock(return_value=(page, 0, mock_renderer))
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
# Create drop event with multiple files (only first is used)
|
|
mime_data = QMimeData()
|
|
mime_data.setUrls([
|
|
QUrl.fromLocalFile("/path/to/image1.jpg"),
|
|
QUrl.fromLocalFile("/path/to/image2.png"),
|
|
QUrl.fromLocalFile("/path/to/image3.jpg")
|
|
])
|
|
|
|
event = Mock()
|
|
event.mimeData = Mock(return_value=mime_data)
|
|
event.position = Mock(return_value=QPoint(150, 150))
|
|
event.acceptProposedAction = Mock()
|
|
|
|
widget.dropEvent(event)
|
|
|
|
# Only first image should be processed
|
|
assert mock_window.project.asset_manager.import_asset.call_count == 1
|
|
|
|
def test_drop_no_project_does_nothing(self, qtbot):
|
|
"""Test dropping when no project loaded does nothing"""
|
|
widget = TestAssetDropWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.update = Mock()
|
|
|
|
mock_window = Mock()
|
|
mock_window.project = None
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
# Mock _get_element_at to return None (no element hit)
|
|
widget._get_element_at = Mock(return_value=None)
|
|
|
|
mime_data = QMimeData()
|
|
mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])
|
|
|
|
event = Mock()
|
|
event.mimeData = Mock(return_value=mime_data)
|
|
event.position = Mock(return_value=QPoint(150, 150))
|
|
event.acceptProposedAction = Mock()
|
|
|
|
# Should not crash
|
|
widget.dropEvent(event)
|
|
|
|
# Should still accept event and call update
|
|
assert event.acceptProposedAction.called
|
|
assert widget.update.called
|