pyPhotoAlbum/tests/test_rendering_mixin.py
Duncan Tourolle cf27d9ebee
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
fixed bug in font rendering when zooming
2025-12-31 13:19:36 +01:00

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)