314 lines
9.0 KiB
Python
314 lines
9.0 KiB
Python
"""
|
|
Unit tests for DynamicPage class.
|
|
"""
|
|
|
|
import pytest
|
|
from PIL import Image
|
|
from pyWebLayout.concrete.dynamic_page import DynamicPage, SizeConstraints
|
|
from pyWebLayout.concrete.text import Line, Text
|
|
from pyWebLayout.style.fonts import Font
|
|
from pyWebLayout.style.page_style import PageStyle
|
|
from pyWebLayout.style import Alignment
|
|
|
|
|
|
class TestSizeConstraints:
|
|
"""Test SizeConstraints dataclass."""
|
|
|
|
def test_default_constraints(self):
|
|
"""Test default constraint values."""
|
|
constraints = SizeConstraints()
|
|
assert constraints.min_width is None
|
|
assert constraints.max_width is None
|
|
assert constraints.min_height is None
|
|
assert constraints.max_height is None
|
|
|
|
def test_custom_constraints(self):
|
|
"""Test custom constraint values."""
|
|
constraints = SizeConstraints(
|
|
min_width=100,
|
|
max_width=500,
|
|
min_height=50,
|
|
max_height=1000
|
|
)
|
|
assert constraints.min_width == 100
|
|
assert constraints.max_width == 500
|
|
assert constraints.min_height == 50
|
|
assert constraints.max_height == 1000
|
|
|
|
|
|
class TestDynamicPage:
|
|
"""Test DynamicPage class."""
|
|
|
|
def test_initialization(self):
|
|
"""Test DynamicPage initialization."""
|
|
page = DynamicPage()
|
|
|
|
assert page.size == (0, 0) # Starts with zero size
|
|
assert not page._is_measured
|
|
assert not page._is_laid_out
|
|
assert page._render_offset == 0
|
|
assert page.constraints is not None
|
|
|
|
def test_initialization_with_constraints(self):
|
|
"""Test initialization with custom constraints."""
|
|
constraints = SizeConstraints(min_width=200, max_width=800)
|
|
page = DynamicPage(constraints=constraints)
|
|
|
|
assert page.constraints.min_width == 200
|
|
assert page.constraints.max_width == 800
|
|
|
|
def test_initialization_with_style(self):
|
|
"""Test initialization with custom style."""
|
|
style = PageStyle(border_width=2, padding=(10, 20, 10, 20))
|
|
page = DynamicPage(style=style)
|
|
|
|
assert page.style.border_width == 2
|
|
assert page.style.padding_top == 10
|
|
|
|
def test_measure_empty_page(self):
|
|
"""Test measuring an empty page."""
|
|
page = DynamicPage()
|
|
width, height = page.measure()
|
|
|
|
# Empty page should have minimal size (just padding/borders)
|
|
assert width > 0 # At least padding/borders
|
|
assert height > 0
|
|
assert page._is_measured
|
|
|
|
def test_measure_with_constraints(self):
|
|
"""Test measuring respects constraints."""
|
|
constraints = SizeConstraints(min_width=300, min_height=200)
|
|
page = DynamicPage(constraints=constraints)
|
|
|
|
width, height = page.measure()
|
|
|
|
assert width >= 300
|
|
assert height >= 200
|
|
|
|
def test_measure_caching(self):
|
|
"""Test that measurement is cached."""
|
|
page = DynamicPage()
|
|
|
|
# First measurement
|
|
size1 = page.measure()
|
|
|
|
# Second measurement should return cached value
|
|
size2 = page.measure()
|
|
|
|
assert size1 == size2
|
|
assert page._is_measured
|
|
|
|
def test_get_min_width(self):
|
|
"""Test get_min_width."""
|
|
page = DynamicPage()
|
|
min_width = page.get_min_width()
|
|
|
|
assert min_width > 0
|
|
assert isinstance(min_width, int)
|
|
|
|
def test_get_preferred_width(self):
|
|
"""Test get_preferred_width."""
|
|
page = DynamicPage()
|
|
pref_width = page.get_preferred_width()
|
|
|
|
assert pref_width > 0
|
|
assert isinstance(pref_width, int)
|
|
|
|
def test_measure_content_height(self):
|
|
"""Test measure_content_height."""
|
|
page = DynamicPage()
|
|
content_height = page.measure_content_height()
|
|
|
|
assert content_height > 0
|
|
assert isinstance(content_height, int)
|
|
|
|
def test_layout(self):
|
|
"""Test layout method."""
|
|
page = DynamicPage()
|
|
target_size = (400, 600)
|
|
|
|
page.layout(target_size)
|
|
|
|
assert page.size == target_size
|
|
assert page._is_laid_out
|
|
assert page._dirty # Should be marked for re-render
|
|
|
|
def test_render_without_layout(self):
|
|
"""Test rendering without explicit layout (auto-sizing)."""
|
|
page = DynamicPage()
|
|
image = page.render()
|
|
|
|
assert isinstance(image, Image.Image)
|
|
assert image.size[0] > 0
|
|
assert image.size[1] > 0
|
|
|
|
def test_render_with_layout(self):
|
|
"""Test rendering after explicit layout."""
|
|
page = DynamicPage()
|
|
page.layout((500, 700))
|
|
|
|
image = page.render()
|
|
|
|
assert isinstance(image, Image.Image)
|
|
assert image.size == (500, 700)
|
|
|
|
def test_add_child_invalidates_cache(self):
|
|
"""Test that adding a child invalidates measurement caches."""
|
|
page = DynamicPage()
|
|
|
|
# Measure to populate cache
|
|
page.measure()
|
|
assert page._is_measured
|
|
|
|
# Add a child (mock renderable)
|
|
class MockRenderable:
|
|
def __init__(self):
|
|
self.size = (100, 50)
|
|
self._origin = (0, 0)
|
|
|
|
@property
|
|
def origin(self):
|
|
return self._origin
|
|
|
|
def render(self):
|
|
pass
|
|
|
|
page.add_child(MockRenderable())
|
|
|
|
# Caches should be invalidated
|
|
assert not page._is_measured
|
|
assert page._intrinsic_size is None
|
|
|
|
def test_clear_children_invalidates_cache(self):
|
|
"""Test that clearing children invalidates caches."""
|
|
page = DynamicPage()
|
|
|
|
# Measure to populate cache
|
|
page.measure()
|
|
assert page._is_measured
|
|
|
|
# Clear children
|
|
page.clear_children()
|
|
|
|
# Caches should be invalidated
|
|
assert not page._is_measured
|
|
|
|
def test_pagination_reset(self):
|
|
"""Test pagination reset."""
|
|
page = DynamicPage()
|
|
page._render_offset = 100
|
|
|
|
page.reset_pagination()
|
|
|
|
assert page._render_offset == 0
|
|
|
|
def test_has_more_content_false(self):
|
|
"""Test has_more_content when all content is rendered."""
|
|
page = DynamicPage()
|
|
|
|
# Set render offset to total height
|
|
total_height = page.measure_content_height()
|
|
page._render_offset = total_height
|
|
|
|
assert not page.has_more_content()
|
|
|
|
def test_has_more_content_true(self):
|
|
"""Test has_more_content when content remains."""
|
|
page = DynamicPage()
|
|
|
|
# Offset is less than total
|
|
page._render_offset = 0
|
|
|
|
assert page.has_more_content()
|
|
|
|
def test_min_width_measurement(self):
|
|
"""Test min width measures longest word."""
|
|
page = DynamicPage()
|
|
|
|
# Min width should be at least padding/borders
|
|
min_width = page.get_min_width()
|
|
assert min_width > 0
|
|
|
|
def test_invalidate_caches(self):
|
|
"""Test cache invalidation."""
|
|
page = DynamicPage()
|
|
|
|
# Populate caches
|
|
page.measure()
|
|
page.get_min_width()
|
|
page.get_preferred_width()
|
|
page.measure_content_height()
|
|
|
|
assert page._is_measured
|
|
assert page._intrinsic_size is not None
|
|
assert page._min_width_cache is not None
|
|
assert page._preferred_width_cache is not None
|
|
assert page._content_height_cache is not None
|
|
|
|
# Invalidate
|
|
page.invalidate_caches()
|
|
|
|
assert not page._is_measured
|
|
assert page._intrinsic_size is None
|
|
assert page._min_width_cache is None
|
|
assert page._preferred_width_cache is None
|
|
assert page._content_height_cache is None
|
|
assert not page._is_laid_out
|
|
|
|
def test_measure_with_available_width(self):
|
|
"""Test measurement with available_width constraint."""
|
|
page = DynamicPage()
|
|
|
|
width, height = page.measure(available_width=300)
|
|
|
|
# Width should respect available_width
|
|
assert width <= 300
|
|
|
|
def test_constraints_override_available_width(self):
|
|
"""Test that constraints override available_width."""
|
|
constraints = SizeConstraints(min_width=400)
|
|
page = DynamicPage(constraints=constraints)
|
|
|
|
width, height = page.measure(available_width=300)
|
|
|
|
# Should use min_width constraint, not available_width
|
|
assert width >= 400
|
|
|
|
def test_render_partial_empty_page(self):
|
|
"""Test partial rendering on empty page."""
|
|
page = DynamicPage()
|
|
|
|
rendered = page.render_partial(available_height=100)
|
|
|
|
assert rendered >= 0
|
|
assert isinstance(rendered, int)
|
|
|
|
def test_method_chaining_add_child(self):
|
|
"""Test that add_child returns self for chaining."""
|
|
page = DynamicPage()
|
|
|
|
class MockRenderable:
|
|
def __init__(self):
|
|
self.size = (50, 50)
|
|
self._origin = (0, 0)
|
|
|
|
@property
|
|
def origin(self):
|
|
return self._origin
|
|
|
|
result = page.add_child(MockRenderable())
|
|
|
|
assert result is page
|
|
|
|
def test_method_chaining_clear_children(self):
|
|
"""Test that clear_children returns self for chaining."""
|
|
page = DynamicPage()
|
|
|
|
result = page.clear_children()
|
|
|
|
assert result is page
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__, '-v'])
|