All checks were successful
Python CI / test (push) Successful in 1m32s
Lint / lint (push) Successful in 1m18s
Tests / test (3.11) (push) Successful in 1m47s
Tests / test (3.12) (push) Successful in 1m48s
Tests / test (3.13) (push) Successful in 1m42s
Tests / test (3.14) (push) Successful in 1m18s
902 lines
32 KiB
Python
902 lines
32 KiB
Python
"""
|
|
Tests for RenderingMixin
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, MagicMock, patch
|
|
from PyQt6.QtWidgets import QApplication
|
|
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
|
|
from PyQt6.QtCore import Qt
|
|
from pyPhotoAlbum.mixins.rendering import RenderingMixin
|
|
from pyPhotoAlbum.mixins.viewport import ViewportMixin
|
|
from pyPhotoAlbum.project import Project, Page
|
|
from pyPhotoAlbum.page_layout import PageLayout
|
|
from pyPhotoAlbum.models import ImageData, TextBoxData
|
|
|
|
|
|
# Create a minimal test widget class that combines necessary mixins
|
|
class TestRenderingWidget(ViewportMixin, RenderingMixin, QOpenGLWidget):
|
|
"""Test widget combining rendering and viewport mixins with QOpenGLWidget"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
# Initialize attributes needed by rendering
|
|
self.selected_elements = []
|
|
self.rotation_mode = False
|
|
|
|
def _get_page_positions(self):
|
|
"""Mock method to return page positions"""
|
|
main_window = self.window()
|
|
if not hasattr(main_window, "project") or not main_window.project or not main_window.project.pages:
|
|
return []
|
|
|
|
positions = []
|
|
y_offset = 50 # PAGE_MARGIN
|
|
for page in main_window.project.pages:
|
|
positions.append(("page", page, y_offset))
|
|
# Calculate page height
|
|
page_width_mm, page_height_mm = page.layout.size
|
|
dpi = main_window.project.working_dpi
|
|
page_height_px = page_height_mm * dpi / 25.4
|
|
y_offset += page_height_px + 50 # PAGE_SPACING
|
|
|
|
return positions
|
|
|
|
|
|
class TestRenderingInitialization:
|
|
"""Test rendering mixin setup"""
|
|
|
|
def test_rendering_mixin_exists(self, qtbot):
|
|
"""Test that rendering mixin can be instantiated"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
assert hasattr(widget, "paintGL")
|
|
assert callable(widget.paintGL)
|
|
assert hasattr(widget, "_draw_selection_handles")
|
|
assert callable(widget._draw_selection_handles)
|
|
|
|
|
|
class TestPaintGL:
|
|
"""Test paintGL method"""
|
|
|
|
def test_paintGL_no_project(self, qtbot):
|
|
"""Test paintGL with no project does nothing"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(800, 600)
|
|
|
|
# Mock window without project
|
|
mock_window = Mock()
|
|
mock_window.project = None
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
# Should not raise exception
|
|
with patch("pyPhotoAlbum.mixins.rendering.glClear"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
|
|
widget.paintGL()
|
|
|
|
def test_paintGL_empty_project(self, qtbot):
|
|
"""Test paintGL with empty project does nothing"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(800, 600)
|
|
|
|
# Mock window with empty project
|
|
mock_window = Mock()
|
|
mock_window.project = Project(name="Empty")
|
|
mock_window.project.pages = []
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
# Should not raise exception
|
|
with patch("pyPhotoAlbum.mixins.rendering.glClear"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
|
|
widget.paintGL()
|
|
|
|
def test_paintGL_sets_initial_zoom(self, qtbot):
|
|
"""Test paintGL sets initial zoom on first render"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Mock window with project
|
|
mock_window = Mock()
|
|
mock_window.project = Project(name="Test")
|
|
mock_window.project.working_dpi = 96
|
|
mock_window.update_scrollbars = Mock()
|
|
|
|
# A4 page
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
mock_window.project.pages = [page]
|
|
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
# Ensure initial_zoom_set is False
|
|
widget.initial_zoom_set = False
|
|
|
|
with patch("pyPhotoAlbum.mixins.rendering.glClear"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
|
|
with patch("pyPhotoAlbum.page_renderer.PageRenderer"):
|
|
widget.paintGL()
|
|
|
|
# Should have set initial zoom
|
|
assert widget.initial_zoom_set is True
|
|
assert widget.zoom_level > 0
|
|
assert widget.zoom_level <= 1.0
|
|
|
|
def test_paintGL_renders_page(self, qtbot):
|
|
"""Test paintGL renders a page"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Mock window with project
|
|
mock_window = Mock()
|
|
mock_window.project = Project(name="Test")
|
|
mock_window.project.working_dpi = 96
|
|
mock_window.update_scrollbars = Mock()
|
|
|
|
# A4 page
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
mock_window.project.pages = [page]
|
|
|
|
widget.window = Mock(return_value=mock_window)
|
|
widget.initial_zoom_set = True
|
|
widget.zoom_level = 1.0
|
|
widget.pan_offset = [0, 0]
|
|
|
|
# Mock PageRenderer
|
|
mock_renderer = Mock()
|
|
mock_renderer.begin_render = Mock()
|
|
mock_renderer.end_render = Mock()
|
|
|
|
with patch("pyPhotoAlbum.mixins.rendering.glClear"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
|
|
with patch("pyPhotoAlbum.page_renderer.PageRenderer", return_value=mock_renderer):
|
|
widget.paintGL()
|
|
|
|
# Should have created page renderers
|
|
assert hasattr(widget, "_page_renderers")
|
|
assert len(widget._page_renderers) == 1
|
|
|
|
# Should have called renderer methods
|
|
mock_renderer.begin_render.assert_called_once()
|
|
mock_renderer.end_render.assert_called_once()
|
|
|
|
def test_paintGL_renders_multiple_pages(self, qtbot):
|
|
"""Test paintGL renders multiple pages"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Mock window with project
|
|
mock_window = Mock()
|
|
mock_window.project = Project(name="Test")
|
|
mock_window.project.working_dpi = 96
|
|
mock_window.update_scrollbars = Mock()
|
|
|
|
# Create 3 A4 pages
|
|
pages = [
|
|
Page(layout=PageLayout(width=210, height=297), page_number=i)
|
|
for i in range(1, 4)
|
|
]
|
|
mock_window.project.pages = pages
|
|
|
|
widget.window = Mock(return_value=mock_window)
|
|
widget.initial_zoom_set = True
|
|
widget.zoom_level = 1.0
|
|
widget.pan_offset = [0, 0]
|
|
|
|
# Mock PageRenderer
|
|
mock_renderer = Mock()
|
|
mock_renderer.begin_render = Mock()
|
|
mock_renderer.end_render = Mock()
|
|
|
|
with patch("pyPhotoAlbum.mixins.rendering.glClear"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
|
|
with patch("pyPhotoAlbum.page_renderer.PageRenderer", return_value=mock_renderer):
|
|
widget.paintGL()
|
|
|
|
# Should have created page renderers for all pages
|
|
assert hasattr(widget, "_page_renderers")
|
|
assert len(widget._page_renderers) == 3
|
|
|
|
def test_paintGL_updates_selected_elements_renderer(self, qtbot):
|
|
"""Test paintGL updates page renderer references for selected elements"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Mock window with project
|
|
mock_window = Mock()
|
|
mock_window.project = Project(name="Test")
|
|
mock_window.project.working_dpi = 96
|
|
mock_window.update_scrollbars = Mock()
|
|
|
|
# A4 page
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
mock_window.project.pages = [page]
|
|
|
|
# Create a mock element
|
|
mock_element = Mock()
|
|
mock_element._parent_page = page
|
|
widget.selected_elements = [mock_element]
|
|
|
|
widget.window = Mock(return_value=mock_window)
|
|
widget.initial_zoom_set = True
|
|
widget.zoom_level = 1.0
|
|
widget.pan_offset = [0, 0]
|
|
|
|
# Mock PageRenderer
|
|
mock_renderer = Mock()
|
|
|
|
with patch("pyPhotoAlbum.mixins.rendering.glClear"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
|
|
with patch("pyPhotoAlbum.page_renderer.PageRenderer", return_value=mock_renderer):
|
|
with patch.object(widget, "_draw_selection_handles"):
|
|
widget.paintGL()
|
|
|
|
# Element should have renderer reference
|
|
assert hasattr(mock_element, "_page_renderer")
|
|
assert mock_element._page_renderer == mock_renderer
|
|
|
|
|
|
class TestDrawSelectionHandles:
|
|
"""Test _draw_selection_handles method"""
|
|
|
|
def test_draw_selection_handles_no_element(self, qtbot):
|
|
"""Test drawing handles with no element does nothing"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
# Should not raise exception
|
|
widget._draw_selection_handles(None)
|
|
|
|
def test_draw_selection_handles_no_project(self, qtbot):
|
|
"""Test drawing handles with no project does nothing"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
# Mock window without project
|
|
mock_window = Mock()
|
|
mock_window.project = None
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
mock_element = Mock()
|
|
mock_element._page_renderer = Mock()
|
|
|
|
# Should not raise exception
|
|
widget._draw_selection_handles(mock_element)
|
|
|
|
def test_draw_selection_handles_no_renderer(self, qtbot):
|
|
"""Test drawing handles without renderer does nothing"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
# Mock window with project
|
|
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]
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
# Element without renderer
|
|
mock_element = Mock(spec=[]) # No _page_renderer attribute
|
|
|
|
# Should not raise exception
|
|
widget._draw_selection_handles(mock_element)
|
|
|
|
def test_draw_selection_handles_normal_mode(self, qtbot):
|
|
"""Test drawing handles in normal (non-rotation) mode"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.rotation_mode = False
|
|
|
|
# Mock window with project
|
|
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]
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
# Create element with renderer
|
|
mock_element = Mock()
|
|
mock_element.position = (100, 100)
|
|
mock_element.size = (200, 150)
|
|
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(150, 150))
|
|
mock_renderer.zoom = 1.0
|
|
|
|
mock_element._page_renderer = mock_renderer
|
|
|
|
# Mock OpenGL calls
|
|
with patch("pyPhotoAlbum.mixins.rendering.glColor3f"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glLineWidth"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glBegin"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glVertex2f"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glEnd"):
|
|
widget._draw_selection_handles(mock_element)
|
|
|
|
# Should have converted position
|
|
mock_renderer.page_to_screen.assert_called_once_with(100, 100)
|
|
|
|
def test_draw_selection_handles_rotation_mode(self, qtbot):
|
|
"""Test drawing handles in rotation mode"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.rotation_mode = True
|
|
|
|
# Mock window with project
|
|
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]
|
|
widget.window = Mock(return_value=mock_window)
|
|
|
|
# Create element with renderer
|
|
mock_element = Mock()
|
|
mock_element.position = (100, 100)
|
|
mock_element.size = (200, 150)
|
|
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(150, 150))
|
|
mock_renderer.zoom = 1.0
|
|
|
|
mock_element._page_renderer = mock_renderer
|
|
|
|
# Mock OpenGL calls
|
|
with patch("pyPhotoAlbum.mixins.rendering.glColor3f"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glLineWidth"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glBegin"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glVertex2f"):
|
|
with patch("pyPhotoAlbum.mixins.rendering.glEnd"):
|
|
widget._draw_selection_handles(mock_element)
|
|
|
|
# Should have converted position
|
|
mock_renderer.page_to_screen.assert_called_once_with(100, 100)
|
|
|
|
|
|
class TestRenderTextOverlays:
|
|
"""Test _render_text_overlays method"""
|
|
|
|
def test_render_text_overlays_no_renderers(self, qtbot):
|
|
"""Test rendering text overlays with no page renderers"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
# Should not raise exception
|
|
widget._render_text_overlays()
|
|
|
|
def test_render_text_overlays_empty_renderers(self, qtbot):
|
|
"""Test rendering text overlays with empty renderers list"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
widget._page_renderers = []
|
|
|
|
# Should not raise exception
|
|
widget._render_text_overlays()
|
|
|
|
def test_render_text_overlays_no_text_elements(self, qtbot):
|
|
"""Test rendering text overlays with no text elements"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Mock page with no text elements
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
page.layout.elements = []
|
|
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = 1.0
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
|
|
# Mock QPainter
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_text_overlays()
|
|
|
|
# Painter should have been created and ended
|
|
mock_painter_class.assert_called_once_with(widget)
|
|
mock_painter.end.assert_called_once()
|
|
|
|
def test_render_text_overlays_with_text_element(self, qtbot):
|
|
"""Test rendering text overlays with text element"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Create text element
|
|
text_element = TextBoxData(
|
|
x=50, y=50, width=200, height=100,
|
|
text_content="Test Text",
|
|
font_settings={
|
|
"family": "Arial",
|
|
"size": 12,
|
|
"color": (0, 0, 0)
|
|
},
|
|
alignment="left"
|
|
)
|
|
|
|
# Mock page with text element
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
page.layout.elements = [text_element]
|
|
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = 1.0
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
|
|
# Mock QPainter
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_text_overlays()
|
|
|
|
# Painter should have been used
|
|
mock_painter_class.assert_called_once_with(widget)
|
|
mock_painter.setFont.assert_called()
|
|
mock_painter.setPen.assert_called()
|
|
mock_painter.drawText.assert_called()
|
|
mock_painter.end.assert_called_once()
|
|
|
|
def test_render_text_overlays_with_rotated_text(self, qtbot):
|
|
"""Test rendering text overlays with rotated text"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Create rotated text element
|
|
text_element = TextBoxData(
|
|
x=50, y=50, width=200, height=100, rotation=45,
|
|
text_content="Rotated Text",
|
|
font_settings={
|
|
"family": "Arial",
|
|
"size": 14,
|
|
"color": (0, 0, 0)
|
|
},
|
|
alignment="center"
|
|
)
|
|
|
|
# Mock page with text element
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
page.layout.elements = [text_element]
|
|
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = 1.0
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
|
|
# Mock QPainter
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_text_overlays()
|
|
|
|
# Painter should have used save/restore for rotation
|
|
mock_painter.save.assert_called()
|
|
mock_painter.rotate.assert_called_with(45)
|
|
mock_painter.restore.assert_called()
|
|
|
|
def test_render_text_overlays_different_alignments(self, qtbot):
|
|
"""Test rendering text with different alignments"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
for alignment in ["left", "center", "right", "justify"]:
|
|
# Create text element with alignment
|
|
text_element = TextBoxData(
|
|
x=50, y=50, width=200, height=100,
|
|
text_content=f"{alignment} aligned",
|
|
font_settings={
|
|
"family": "Arial",
|
|
"size": 12,
|
|
"color": (0, 0, 0)
|
|
},
|
|
alignment=alignment
|
|
)
|
|
|
|
# Mock page with text element
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
page.layout.elements = [text_element]
|
|
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = 1.0
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
|
|
# Mock QPainter
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_text_overlays()
|
|
|
|
# Should have drawn text
|
|
mock_painter.drawText.assert_called()
|
|
|
|
def test_render_text_overlays_normalized_color(self, qtbot):
|
|
"""Test rendering text with normalized color values (0-1 range)"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Create text element with normalized colors
|
|
text_element = TextBoxData(
|
|
x=50, y=50, width=200, height=100,
|
|
text_content="Normalized Color",
|
|
font_settings={
|
|
"family": "Arial",
|
|
"size": 12,
|
|
"color": (0.5, 0.5, 0.5) # Normalized (0-1 range)
|
|
},
|
|
alignment="left"
|
|
)
|
|
|
|
# Mock page with text element
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
page.layout.elements = [text_element]
|
|
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = 1.0
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
|
|
# Mock QPainter
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_text_overlays()
|
|
|
|
# Should have converted normalized colors to 0-255 range
|
|
mock_painter.setPen.assert_called()
|
|
|
|
def test_render_text_overlays_empty_text(self, qtbot):
|
|
"""Test rendering text element with no content"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Create text element with no content
|
|
text_element = TextBoxData(
|
|
x=50, y=50, width=200, height=100,
|
|
text_content="", # Empty
|
|
font_settings={"family": "Arial", "size": 12, "color": (0, 0, 0)},
|
|
alignment="left"
|
|
)
|
|
|
|
# Mock page with text element
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
page.layout.elements = [text_element]
|
|
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = 1.0
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
|
|
# Mock QPainter
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_text_overlays()
|
|
|
|
# Should not draw empty text
|
|
mock_painter.drawText.assert_not_called()
|
|
|
|
|
|
class TestRenderGhostPage:
|
|
"""Test _render_ghost_page method"""
|
|
|
|
def test_render_ghost_page(self, qtbot):
|
|
"""Test rendering a ghost page"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Create mock ghost data
|
|
ghost_data = Mock()
|
|
ghost_data.page_size = (210, 297)
|
|
ghost_data.render = Mock()
|
|
ghost_data.get_page_rect = Mock(return_value=(0, 0, 794, 1123))
|
|
|
|
# Create mock renderer
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = 1.0
|
|
mock_renderer.begin_render = Mock()
|
|
mock_renderer.end_render = Mock()
|
|
|
|
# Mock QPainter
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_ghost_page(ghost_data, mock_renderer)
|
|
|
|
# Should have called renderer methods
|
|
mock_renderer.begin_render.assert_called_once()
|
|
ghost_data.render.assert_called_once()
|
|
mock_renderer.end_render.assert_called_once()
|
|
|
|
# Should have drawn text overlay
|
|
mock_painter.drawText.assert_called()
|
|
# Check that "Click to Add Page" was drawn
|
|
call_args = mock_painter.drawText.call_args
|
|
assert "Click to Add Page" in str(call_args)
|
|
|
|
def test_render_ghost_page_painter_cleanup(self, qtbot):
|
|
"""Test that ghost page rendering cleans up QPainter"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Create mock ghost data
|
|
ghost_data = Mock()
|
|
ghost_data.page_size = (210, 297)
|
|
ghost_data.render = Mock()
|
|
ghost_data.get_page_rect = Mock(return_value=(0, 0, 794, 1123))
|
|
|
|
# Create mock renderer
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = 1.0
|
|
mock_renderer.begin_render = Mock()
|
|
mock_renderer.end_render = Mock()
|
|
|
|
# Mock QPainter
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_ghost_page(ghost_data, mock_renderer)
|
|
|
|
# Painter should be ended (cleanup)
|
|
mock_painter.end.assert_called_once()
|
|
|
|
|
|
class TestFontZoomIndependence:
|
|
"""Test that font size is independent of zoom level (bug fix verification)"""
|
|
|
|
def test_font_size_not_scaled_by_zoom(self, qtbot):
|
|
"""Test that font size remains constant regardless of zoom level.
|
|
|
|
This test verifies the fix for the bug where font size was multiplied
|
|
by zoom level, causing text to scale differently than in PDF export.
|
|
"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
base_font_size = 24
|
|
|
|
# Create text element with specific font size
|
|
text_element = TextBoxData(
|
|
x=50, y=50, width=200, height=100,
|
|
text_content="Test Text",
|
|
font_settings={
|
|
"family": "Arial",
|
|
"size": base_font_size,
|
|
"color": (0, 0, 0)
|
|
},
|
|
alignment="left"
|
|
)
|
|
|
|
# Mock page with text element
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
page.layout.elements = [text_element]
|
|
|
|
# Test with different zoom levels
|
|
for zoom_level in [0.5, 1.0, 2.0, 3.0]:
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = zoom_level
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
|
|
# Mock QPainter and QFont
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
with patch("pyPhotoAlbum.mixins.rendering.QFont") as mock_qfont_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_text_overlays()
|
|
|
|
# QFont should be created with base font size, NOT scaled by zoom
|
|
mock_qfont_class.assert_called()
|
|
call_args = mock_qfont_class.call_args
|
|
actual_font_size = call_args[0][1] # Second positional arg is size
|
|
|
|
assert actual_font_size == base_font_size, (
|
|
f"Font size should be {base_font_size} at zoom {zoom_level}, "
|
|
f"got {actual_font_size}"
|
|
)
|
|
|
|
def test_zoom_applied_via_painter_scale(self, qtbot):
|
|
"""Test that zoom is applied via painter.scale() transformation.
|
|
|
|
This ensures text scales consistently with the page content using
|
|
the painter's transformation matrix rather than font size modification.
|
|
"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Create text element
|
|
text_element = TextBoxData(
|
|
x=50, y=50, width=200, height=100,
|
|
text_content="Test Text",
|
|
font_settings={
|
|
"family": "Arial",
|
|
"size": 12,
|
|
"color": (0, 0, 0)
|
|
},
|
|
alignment="left"
|
|
)
|
|
|
|
# Mock page with text element
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
page.layout.elements = [text_element]
|
|
|
|
zoom_level = 2.5
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = zoom_level
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
|
|
# Mock QPainter
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_text_overlays()
|
|
|
|
# Painter should call scale() with zoom level
|
|
mock_painter.scale.assert_called_with(zoom_level, zoom_level)
|
|
|
|
# Painter should use save/restore for transformation
|
|
mock_painter.save.assert_called()
|
|
mock_painter.restore.assert_called()
|
|
|
|
def test_text_rect_uses_page_coordinates(self, qtbot):
|
|
"""Test that text rect uses page coordinates (not screen coordinates).
|
|
|
|
The text rect should be in page coordinates (w, h) since the painter
|
|
applies the zoom transformation via scale().
|
|
"""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
element_width = 200
|
|
element_height = 100
|
|
|
|
# Create text element
|
|
text_element = TextBoxData(
|
|
x=50, y=50, width=element_width, height=element_height,
|
|
text_content="Test Text",
|
|
font_settings={
|
|
"family": "Arial",
|
|
"size": 12,
|
|
"color": (0, 0, 0)
|
|
},
|
|
alignment="left"
|
|
)
|
|
|
|
# Mock page with text element
|
|
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
|
page.layout.elements = [text_element]
|
|
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = 2.0 # Zoom that would double screen size
|
|
|
|
widget._page_renderers = [(mock_renderer, page)]
|
|
|
|
# Mock QPainter and QRectF
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
with patch("pyPhotoAlbum.mixins.rendering.QRectF") as mock_qrectf_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_text_overlays()
|
|
|
|
# QRectF should be called with page coordinates (0, 0, w, h)
|
|
# not screen coordinates (0, 0, w*zoom, h*zoom)
|
|
mock_qrectf_class.assert_called()
|
|
call_args = mock_qrectf_class.call_args
|
|
rect_width = call_args[0][2]
|
|
rect_height = call_args[0][3]
|
|
|
|
assert rect_width == element_width, (
|
|
f"Rect width should be {element_width} (page coords), got {rect_width}"
|
|
)
|
|
assert rect_height == element_height, (
|
|
f"Rect height should be {element_height} (page coords), got {rect_height}"
|
|
)
|
|
|
|
def test_ghost_page_font_not_scaled_by_zoom(self, qtbot):
|
|
"""Test that ghost page font size is not scaled by zoom level."""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Create mock ghost data
|
|
ghost_data = Mock()
|
|
ghost_data.page_size = (210, 297)
|
|
ghost_data.render = Mock()
|
|
ghost_data.get_page_rect = Mock(return_value=(0, 0, 794, 1123))
|
|
|
|
for zoom_level in [0.5, 1.0, 2.0]:
|
|
# Create mock renderer with varying zoom
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = zoom_level
|
|
mock_renderer.begin_render = Mock()
|
|
mock_renderer.end_render = Mock()
|
|
|
|
# Mock QPainter and QFont
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
with patch("pyPhotoAlbum.mixins.rendering.QFont") as mock_qfont_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_ghost_page(ghost_data, mock_renderer)
|
|
|
|
# QFont should be created with base font size 16, NOT scaled
|
|
mock_qfont_class.assert_called()
|
|
call_args = mock_qfont_class.call_args
|
|
actual_font_size = call_args[0][1] # Second positional arg is size
|
|
|
|
assert actual_font_size == 16, (
|
|
f"Ghost page font size should be 16 at zoom {zoom_level}, "
|
|
f"got {actual_font_size}"
|
|
)
|
|
|
|
def test_ghost_page_zoom_applied_via_scale(self, qtbot):
|
|
"""Test that ghost page zoom is applied via painter.scale()."""
|
|
widget = TestRenderingWidget()
|
|
qtbot.addWidget(widget)
|
|
widget.resize(1000, 800)
|
|
|
|
# Create mock ghost data
|
|
ghost_data = Mock()
|
|
ghost_data.page_size = (210, 297)
|
|
ghost_data.render = Mock()
|
|
ghost_data.get_page_rect = Mock(return_value=(0, 0, 794, 1123))
|
|
|
|
zoom_level = 1.5
|
|
mock_renderer = Mock()
|
|
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
|
mock_renderer.zoom = zoom_level
|
|
mock_renderer.begin_render = Mock()
|
|
mock_renderer.end_render = Mock()
|
|
|
|
# Mock QPainter
|
|
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
|
mock_painter = Mock()
|
|
mock_painter_class.return_value = mock_painter
|
|
|
|
widget._render_ghost_page(ghost_data, mock_renderer)
|
|
|
|
# Painter should call scale() with zoom level
|
|
mock_painter.scale.assert_called_with(zoom_level, zoom_level)
|