This commit is contained in:
2025-06-08 17:17:09 +02:00
parent a014de854e
commit f6ae60217f
19 changed files with 2777 additions and 944 deletions
+191
View File
@@ -0,0 +1,191 @@
#!/usr/bin/env python3
"""
Unit tests for the new external pagination system.
Tests the BlockPaginator and handler architecture to ensure it works correctly
with different block types using the unittest framework.
"""
import unittest
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel
from pyWebLayout.abstract.inline import Word
from pyWebLayout.concrete.page import Page
from pyWebLayout.style.fonts import Font
from pyWebLayout.typesetting.block_pagination import BlockPaginator, PaginationResult
class TestNewPaginationSystem(unittest.TestCase):
"""Test cases for the new pagination system."""
def setUp(self):
"""Set up test fixtures."""
self.font = Font(font_size=16)
self.heading_font = Font(font_size=20)
def create_test_paragraph(self, text: str, font: Font = None) -> Paragraph:
"""Create a test paragraph with the given text."""
if font is None:
font = self.font
paragraph = Paragraph(font)
words = text.split()
for word_text in words:
word = Word(word_text, font)
paragraph.add_word(word)
return paragraph
def create_test_heading(self, text: str, level: HeadingLevel = HeadingLevel.H1) -> Heading:
"""Create a test heading with the given text."""
heading = Heading(level, self.heading_font)
words = text.split()
for word_text in words:
word = Word(word_text, self.heading_font)
heading.add_word(word)
return heading
def test_paragraph_pagination(self):
"""Test paragraph pagination with line breaking."""
# Create a long paragraph
long_text = " ".join(["This is a very long paragraph that should be broken across multiple lines."] * 10)
paragraph = self.create_test_paragraph(long_text)
# Create a page with limited height
page = Page(size=(400, 200)) # Small page
# Test the pagination handler
paginator = BlockPaginator()
result = paginator.paginate_block(paragraph, page, available_height=100)
# Assertions
self.assertIsInstance(result, PaginationResult)
self.assertIsInstance(result.success, bool)
self.assertIsInstance(result.height_used, (int, float))
self.assertGreaterEqual(result.height_used, 0)
def test_page_filling(self):
"""Test filling a page with multiple blocks."""
# Create test blocks
blocks = [
self.create_test_heading("Chapter 1: Introduction"),
self.create_test_paragraph("This is the first paragraph of the chapter. It contains some introductory text."),
self.create_test_paragraph("This is the second paragraph. It has more content and should flow nicely."),
self.create_test_heading("Section 1.1: Overview", HeadingLevel.H2),
self.create_test_paragraph("This is a paragraph under the section. It has even more content that might not fit on the same page."),
self.create_test_paragraph("This is another long paragraph that definitely won't fit. " * 20),
]
# Create a page
page = Page(size=(600, 400))
# Fill the page
next_index, remainder_blocks = page.fill_with_blocks(blocks)
# Assertions
self.assertIsInstance(next_index, int)
self.assertGreaterEqual(next_index, 0)
self.assertLessEqual(next_index, len(blocks))
self.assertIsInstance(remainder_blocks, list)
self.assertGreaterEqual(len(page._children), 0)
# Try to render the page
try:
page_image = page.render()
self.assertIsNotNone(page_image)
self.assertEqual(len(page_image.size), 2) # Should have width and height
except Exception as e:
self.fail(f"Page rendering failed: {e}")
def test_multi_page_creation(self):
"""Test creating multiple pages from a list of blocks."""
# Create many test blocks
blocks = []
for i in range(5): # Reduced for faster testing
blocks.append(self.create_test_heading(f"Chapter {i+1}"))
for j in range(2): # Reduced for faster testing
long_text = f"This is paragraph {j+1} of chapter {i+1}. " * 10
blocks.append(self.create_test_paragraph(long_text))
self.assertGreater(len(blocks), 0)
# Create pages until all blocks are processed
pages = []
remaining_blocks = blocks
page_count = 0
while remaining_blocks and page_count < 10: # Safety limit
page = Page(size=(600, 400))
next_index, remainder_blocks = page.fill_with_blocks(remaining_blocks)
if page._children:
pages.append(page)
page_count += 1
# Update remaining blocks
if remainder_blocks:
remaining_blocks = remainder_blocks
elif next_index < len(remaining_blocks):
remaining_blocks = remaining_blocks[next_index:]
else:
remaining_blocks = []
# Safety check to prevent infinite loops
if not page._children and remaining_blocks:
break
# Assertions
self.assertGreater(len(pages), 0, "Should create at least one page")
self.assertLessEqual(page_count, 10, "Should not exceed safety limit")
# Try to render a few pages
rendered_count = 0
for page in pages[:2]: # Test first 2 pages
try:
page_image = page.render()
rendered_count += 1
self.assertIsNotNone(page_image)
except Exception as e:
self.fail(f"Page rendering failed: {e}")
self.assertGreater(rendered_count, 0, "Should render at least one page")
def test_empty_blocks_list(self):
"""Test handling of empty blocks list."""
page = Page(size=(600, 400))
next_index, remainder_blocks = page.fill_with_blocks([])
self.assertEqual(next_index, 0)
self.assertEqual(len(remainder_blocks), 0)
self.assertEqual(len(page._children), 0)
def test_single_block(self):
"""Test handling of single block."""
blocks = [self.create_test_paragraph("Single paragraph test.")]
page = Page(size=(600, 400))
next_index, remainder_blocks = page.fill_with_blocks(blocks)
self.assertEqual(next_index, 1)
self.assertEqual(len(remainder_blocks), 0)
self.assertGreater(len(page._children), 0)
def test_pagination_result_properties(self):
"""Test PaginationResult object properties."""
paragraph = self.create_test_paragraph("Test paragraph for pagination result.")
page = Page(size=(400, 200))
paginator = BlockPaginator()
result = paginator.paginate_block(paragraph, page, available_height=100)
# Test that result has expected properties
self.assertTrue(hasattr(result, 'success'))
self.assertTrue(hasattr(result, 'height_used'))
self.assertTrue(hasattr(result, 'remainder'))
self.assertTrue(hasattr(result, 'can_continue'))
if __name__ == '__main__':
unittest.main()
View File
+232
View File
@@ -0,0 +1,232 @@
#!/usr/bin/env python3
"""
Unit tests for simple pagination logic without EPUB dependencies.
Tests basic pagination functionality using the unittest framework.
"""
import unittest
from pyWebLayout.concrete.page import Page
from pyWebLayout.concrete.text import Text
from pyWebLayout.style.fonts import Font
from pyWebLayout.abstract.block import Paragraph
from pyWebLayout.abstract.inline import Word
class TestSimplePagination(unittest.TestCase):
"""Test cases for simple pagination functionality."""
def setUp(self):
"""Set up test fixtures."""
self.font = Font(font_size=16)
self.page_size = (700, 550)
self.max_page_height = 510 # Leave room for padding
def create_test_paragraph(self, text_content: str) -> Paragraph:
"""Create a test paragraph with the given text."""
paragraph = Paragraph()
words = text_content.split()
for word_text in words:
word = Word(word_text, self.font)
paragraph.add_word(word)
return paragraph
def test_single_paragraph_pagination(self):
"""Test pagination with a single paragraph."""
text = "This is a simple paragraph for testing pagination functionality."
paragraph = self.create_test_paragraph(text)
page = Page(size=self.page_size)
# Convert block to renderable
renderable = page._convert_block_to_renderable(paragraph)
self.assertIsNotNone(renderable, "Should convert paragraph to renderable")
# Add to page
page.add_child(renderable)
self.assertEqual(len(page._children), 1)
# Layout should work
try:
page.layout()
except Exception as e:
self.fail(f"Layout failed: {e}")
# Render should work
try:
rendered_image = page.render()
self.assertIsNotNone(rendered_image)
self.assertEqual(rendered_image.size, self.page_size)
except Exception as e:
self.fail(f"Render failed: {e}")
def test_multiple_paragraphs_same_page(self):
"""Test adding multiple small paragraphs to the same page."""
paragraphs = [
"First short paragraph.",
"Second short paragraph.",
"Third short paragraph."
]
page = Page(size=self.page_size)
for i, text in enumerate(paragraphs):
paragraph = self.create_test_paragraph(text)
renderable = page._convert_block_to_renderable(paragraph)
self.assertIsNotNone(renderable, f"Should convert paragraph {i+1}")
page.add_child(renderable)
self.assertEqual(len(page._children), len(paragraphs))
# Layout should work with multiple children
try:
page.layout()
except Exception as e:
self.fail(f"Layout with multiple paragraphs failed: {e}")
# Calculate page height
max_bottom = self.calculate_page_height(page)
self.assertLessEqual(max_bottom, self.max_page_height, "Page should not exceed height limit")
def test_page_overflow_detection(self):
"""Test detection of page overflow."""
# Create a very long paragraph that should cause overflow
long_text = " ".join(["This is a very long paragraph with many words."] * 20)
paragraph = self.create_test_paragraph(long_text)
page = Page(size=self.page_size)
renderable = page._convert_block_to_renderable(paragraph)
page.add_child(renderable)
try:
page.layout()
max_bottom = self.calculate_page_height(page)
# Very long content might exceed page height
# This is expected behavior for testing overflow detection
self.assertIsInstance(max_bottom, (int, float))
except Exception as e:
# Layout might fail with very long content, which is acceptable
self.assertIsInstance(e, Exception)
def test_page_height_calculation(self):
"""Test page height calculation method."""
page = Page(size=self.page_size)
# Empty page should have height 0
height = self.calculate_page_height(page)
self.assertEqual(height, 0)
# Add content and check height increases
paragraph = self.create_test_paragraph("Test content for height calculation.")
renderable = page._convert_block_to_renderable(paragraph)
page.add_child(renderable)
page.layout()
height_with_content = self.calculate_page_height(page)
self.assertGreater(height_with_content, 0)
def test_multi_page_scenario(self):
"""Test creating multiple pages from content."""
# Create test content
test_paragraphs = [
"This is the first paragraph with some content.",
"Here is a second paragraph with different content.",
"The third paragraph continues with more text.",
"Fourth paragraph here with additional content.",
"Fifth paragraph with even more content for testing."
]
pages = []
current_page = Page(size=self.page_size)
for i, text in enumerate(test_paragraphs):
paragraph = self.create_test_paragraph(text)
renderable = current_page._convert_block_to_renderable(paragraph)
if renderable:
# Store current state for potential rollback
children_backup = current_page._children.copy()
# Add to current page
current_page.add_child(renderable)
try:
current_page.layout()
max_bottom = self.calculate_page_height(current_page)
# Check if page is too full
if max_bottom > self.max_page_height and len(current_page._children) > 1:
# Rollback and start new page
current_page._children = children_backup
pages.append(current_page)
# Start new page with current content
current_page = Page(size=self.page_size)
current_page.add_child(renderable)
current_page.layout()
except Exception:
# Layout failed, rollback
current_page._children = children_backup
# Add final page if it has content
if current_page._children:
pages.append(current_page)
# Assertions
self.assertGreater(len(pages), 0, "Should create at least one page")
# Test rendering all pages
for i, page in enumerate(pages):
with self.subTest(page=i+1):
self.assertGreater(len(page._children), 0, f"Page {i+1} should have content")
try:
rendered_image = page.render()
self.assertIsNotNone(rendered_image)
self.assertEqual(rendered_image.size, self.page_size)
except Exception as e:
self.fail(f"Page {i+1} render failed: {e}")
def test_empty_paragraph_handling(self):
"""Test handling of empty paragraphs."""
empty_paragraph = self.create_test_paragraph("")
page = Page(size=self.page_size)
# Empty paragraph should still be convertible
renderable = page._convert_block_to_renderable(empty_paragraph)
if renderable: # Some implementations might return None for empty content
page.add_child(renderable)
try:
page.layout()
rendered_image = page.render()
self.assertIsNotNone(rendered_image)
except Exception as e:
self.fail(f"Empty paragraph handling failed: {e}")
def test_conversion_error_handling(self):
"""Test handling of blocks that can't be converted."""
paragraph = self.create_test_paragraph("Test content")
page = Page(size=self.page_size)
# This should normally work
renderable = page._convert_block_to_renderable(paragraph)
self.assertIsNotNone(renderable, "Normal paragraph should convert successfully")
def calculate_page_height(self, page):
"""Helper method to calculate current page height."""
max_bottom = 0
for child in page._children:
if hasattr(child, '_origin') and hasattr(child, '_size'):
child_bottom = child._origin[1] + child._size[1]
max_bottom = max(max_bottom, child_bottom)
return max_bottom
if __name__ == '__main__':
unittest.main()