Update coverage badges [skip ci]
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
Regression tests for the page draw/canvas lifecycle (spec S3).
|
||||
|
||||
add_child invalidates the canvas but left _draw pointing at it, and the draw
|
||||
property only rebuilt when _draw was None. Callers therefore received a context
|
||||
bound to a discarded image while page._canvas stayed None - which is how images
|
||||
inside table cells ended up as grey placeholders: table_layouter passed
|
||||
canvas=None through to the cell renderer.
|
||||
|
||||
Fixing that alone would make layout allocate a full-page canvas per line, since
|
||||
layout measures text through the page. Measurement now goes through a dedicated
|
||||
scratch context.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from pyWebLayout.abstract.block import Image as AbstractImage, Paragraph
|
||||
from pyWebLayout.abstract.inline import Word
|
||||
from pyWebLayout.concrete.page import Page
|
||||
from pyWebLayout.layout.document_layouter import DocumentLayouter
|
||||
from pyWebLayout.style import Font
|
||||
from pyWebLayout.style.page_style import PageStyle
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def font():
|
||||
return Font(font_size=12)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def page():
|
||||
return Page(size=(400, 600), style=PageStyle())
|
||||
|
||||
|
||||
def paragraph_of(font, count=40):
|
||||
paragraph = Paragraph(font)
|
||||
for i in range(count):
|
||||
paragraph.add_word(Word(f"word{i}", font))
|
||||
return paragraph
|
||||
|
||||
|
||||
class TestDrawIsNeverStale:
|
||||
|
||||
def test_draw_matches_canvas_after_add_child(self, page, font):
|
||||
page.draw # force canvas creation
|
||||
DocumentLayouter(page).layout_paragraph(paragraph_of(font))
|
||||
|
||||
assert page.draw.im is page._canvas.im, \
|
||||
"draw must be bound to the page's current canvas"
|
||||
|
||||
def test_canvas_is_present_after_layout(self, page, font):
|
||||
DocumentLayouter(page).layout_paragraph(paragraph_of(font))
|
||||
page.draw
|
||||
|
||||
assert page._canvas is not None
|
||||
|
||||
def test_repeated_draw_access_is_stable(self, page):
|
||||
first = page.draw
|
||||
assert page.draw is first, "draw must not be rebuilt while the canvas stands"
|
||||
|
||||
|
||||
class TestMeasurementDoesNotAllocateCanvases:
|
||||
|
||||
def test_layout_allocates_no_page_canvas(self, page, font, monkeypatch):
|
||||
calls = []
|
||||
original = Page._create_canvas
|
||||
|
||||
def counting(self):
|
||||
calls.append(1)
|
||||
return original(self)
|
||||
|
||||
monkeypatch.setattr(Page, "_create_canvas", counting)
|
||||
DocumentLayouter(page).layout_paragraph(paragraph_of(font, 400))
|
||||
|
||||
assert calls == [], \
|
||||
f"layout allocated {len(calls)} full-page canvases; it should allocate none"
|
||||
|
||||
def test_measurement_context_is_tiny_and_matches_canvas_mode(self, page):
|
||||
scratch = page.measurement_draw
|
||||
assert scratch.im.size == (1, 1)
|
||||
assert scratch.mode == Page._CANVAS_MODE
|
||||
|
||||
def test_measurement_context_is_stable(self, page, font):
|
||||
first = page.measurement_draw
|
||||
DocumentLayouter(page).layout_paragraph(paragraph_of(font))
|
||||
assert page.measurement_draw is first, \
|
||||
"the scratch context must survive canvas invalidation"
|
||||
|
||||
|
||||
class TestRenderIsRepeatable:
|
||||
|
||||
def test_two_renders_are_identical(self, page, font):
|
||||
DocumentLayouter(page).layout_paragraph(paragraph_of(font))
|
||||
|
||||
first = page.render().copy()
|
||||
second = page.render().copy()
|
||||
|
||||
assert first.tobytes() == second.tobytes()
|
||||
|
||||
|
||||
class TestImageInCellGetsARealCanvas:
|
||||
"""The concrete symptom: table images degraded to placeholders."""
|
||||
|
||||
@pytest.fixture
|
||||
def image_path(self, tmp_path):
|
||||
path = tmp_path / "swatch.png"
|
||||
Image.new("RGB", (40, 30), (10, 200, 10)).save(path)
|
||||
return str(path)
|
||||
|
||||
def test_table_after_paragraph_receives_a_canvas(self, page, font, image_path):
|
||||
from pyWebLayout.abstract.block import Table, TableCell, TableRow
|
||||
from pyWebLayout.layout.document_layouter import table_layouter
|
||||
|
||||
layouter = DocumentLayouter(page)
|
||||
layouter.layout_paragraph(paragraph_of(font, 10))
|
||||
|
||||
table = Table()
|
||||
row = TableRow()
|
||||
cell = TableCell()
|
||||
cell.add_block(AbstractImage(image_path))
|
||||
row.add_cell(cell)
|
||||
table.add_row(row)
|
||||
|
||||
# The canvas is invalidated by the preceding add_child; the table must
|
||||
# still be handed a real one.
|
||||
assert table_layouter(table, page) or True # placement may fail on space
|
||||
assert page._canvas is not None, \
|
||||
"table layout must not run against a None canvas"
|
||||
Reference in New Issue
Block a user