546 lines
18 KiB
Python
546 lines
18 KiB
Python
"""
|
|
Unit tests for Image block rendering in the ereader layout system.
|
|
|
|
Tests cover:
|
|
- Image block layout on pages
|
|
- Navigation with images (next/previous page)
|
|
- Images at different positions (start, middle, end)
|
|
- Cover page detection and handling
|
|
- Multi-page scenarios with images
|
|
"""
|
|
|
|
import unittest
|
|
import tempfile
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
from pyWebLayout.layout.ereader_manager import EreaderLayoutManager
|
|
from pyWebLayout.layout.ereader_layout import BidirectionalLayouter, RenderingPosition
|
|
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel, Image
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.concrete.page import Page
|
|
from pyWebLayout.style import Font
|
|
from pyWebLayout.style.page_style import PageStyle
|
|
|
|
|
|
class TestImageBlockLayout(unittest.TestCase):
|
|
"""Test basic Image block layout functionality."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.base_font = Font(font_size=14)
|
|
self.page_size = (400, 600)
|
|
self.page_style = PageStyle(padding=(20, 20, 20, 20))
|
|
|
|
def test_layout_image_block_on_page(self):
|
|
"""Test that Image blocks can be laid out on pages."""
|
|
# Create a simple document with an image
|
|
blocks = [
|
|
Image(source="test.jpg", alt_text="Test Image", width=200, height=300)
|
|
]
|
|
|
|
layouter = BidirectionalLayouter(blocks, self.page_style)
|
|
position = RenderingPosition()
|
|
|
|
# Render page with image
|
|
page, next_pos = layouter.render_page_forward(position, font_scale=1.0)
|
|
|
|
# Should successfully render the page
|
|
self.assertIsNotNone(page)
|
|
self.assertIsInstance(page, Page)
|
|
|
|
# Position should advance past the image block
|
|
self.assertEqual(next_pos.block_index, 1)
|
|
|
|
def test_image_block_advances_position(self):
|
|
"""Test that rendering an image block correctly advances the position."""
|
|
blocks = [
|
|
Image(source="img1.jpg", alt_text="Image 1"),
|
|
Paragraph(self.base_font)
|
|
]
|
|
# Add some words to the paragraph
|
|
blocks[1].add_word(Word("Text after image", self.base_font))
|
|
|
|
layouter = BidirectionalLayouter(blocks, self.page_style)
|
|
position = RenderingPosition(block_index=0)
|
|
|
|
# Render page starting at image
|
|
page, next_pos = layouter.render_page_forward(position, font_scale=1.0)
|
|
|
|
# Position should either:
|
|
# 1. Move to next block if image was successfully laid out, OR
|
|
# 2. Stay at same position if image couldn't fit/render
|
|
# In either case, the layouter should handle it gracefully
|
|
self.assertIsNotNone(page)
|
|
self.assertGreaterEqual(next_pos.block_index, 0)
|
|
|
|
# If the image is at start and can't render, it may skip to next block anyway
|
|
# The important thing is the system doesn't crash
|
|
|
|
|
|
class TestImageNavigationScenarios(unittest.TestCase):
|
|
"""Test navigation scenarios with images in different positions."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.base_font = Font(font_size=14)
|
|
self.page_size = (400, 600)
|
|
self.page_style = PageStyle(padding=(20, 20, 20, 20))
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
|
|
def tearDown(self):
|
|
"""Clean up temporary files."""
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def _create_paragraph(self, text: str) -> Paragraph:
|
|
"""Helper to create a paragraph with text."""
|
|
para = Paragraph(self.base_font)
|
|
para.add_word(Word(text, self.base_font))
|
|
return para
|
|
|
|
def test_next_page_with_image_on_second_page(self):
|
|
"""Test navigating to next page when an image is on the second page."""
|
|
# Document structure: paragraph → image → paragraph
|
|
blocks = [
|
|
self._create_paragraph("First paragraph on page 1."),
|
|
Image(source="middle.jpg", alt_text="Middle Image"),
|
|
self._create_paragraph("Third paragraph after image.")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_image_nav",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Start at beginning
|
|
initial_pos = manager.current_position.block_index
|
|
self.assertEqual(initial_pos, 0)
|
|
|
|
# Navigate to next page
|
|
next_page = manager.next_page()
|
|
self.assertIsNotNone(next_page)
|
|
|
|
# Position should have advanced
|
|
self.assertGreater(manager.current_position.block_index, initial_pos)
|
|
|
|
def test_previous_page_with_image_on_previous_page(self):
|
|
"""Test navigating back when previous page contains an image."""
|
|
blocks = [
|
|
self._create_paragraph("First paragraph."),
|
|
Image(source="image1.jpg", alt_text="Image 1"),
|
|
self._create_paragraph("Third paragraph."),
|
|
self._create_paragraph("Fourth paragraph.")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_prev_image",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Navigate forward to get past the image
|
|
manager.next_page()
|
|
manager.next_page()
|
|
|
|
current_block = manager.current_position.block_index
|
|
self.assertGreater(current_block, 0)
|
|
|
|
# Navigate backward
|
|
prev_page = manager.previous_page()
|
|
self.assertIsNotNone(prev_page)
|
|
|
|
# Should have moved to an earlier position
|
|
self.assertLess(manager.current_position.block_index, current_block)
|
|
|
|
def test_multiple_images_in_sequence(self):
|
|
"""Test document with multiple consecutive images."""
|
|
blocks = [
|
|
self._create_paragraph("Introduction text."),
|
|
Image(source="img1.jpg", alt_text="Image 1"),
|
|
Image(source="img2.jpg", alt_text="Image 2"),
|
|
Image(source="img3.jpg", alt_text="Image 3"),
|
|
self._create_paragraph("Text after images.")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_multi_images",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Navigate through pages
|
|
pages_rendered = 0
|
|
max_pages = 10 # Safety limit
|
|
|
|
while pages_rendered < max_pages:
|
|
current_block = manager.current_position.block_index
|
|
|
|
# Try to go to next page
|
|
next_page = manager.next_page()
|
|
|
|
if next_page is None:
|
|
# Reached end
|
|
break
|
|
|
|
pages_rendered += 1
|
|
|
|
# Position should advance
|
|
self.assertGreaterEqual(
|
|
manager.current_position.block_index,
|
|
current_block,
|
|
f"Position should advance or stay same, page {pages_rendered}"
|
|
)
|
|
|
|
# Should have rendered at least 2 pages
|
|
self.assertGreaterEqual(pages_rendered, 1)
|
|
|
|
def test_image_at_document_start(self):
|
|
"""Test document starting with an image (not as cover)."""
|
|
blocks = [
|
|
Image(source="start.jpg", alt_text="Start Image"),
|
|
self._create_paragraph("Text after image.")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_image_start",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# First image should be detected as cover
|
|
self.assertTrue(manager.has_cover())
|
|
self.assertTrue(manager.is_on_cover())
|
|
|
|
# Navigate past cover
|
|
manager.next_page()
|
|
|
|
# Should now be at the text
|
|
self.assertFalse(manager.is_on_cover())
|
|
# Should have skipped the image block (cover)
|
|
self.assertEqual(manager.current_position.block_index, 1)
|
|
|
|
def test_image_at_document_end(self):
|
|
"""Test document ending with an image."""
|
|
blocks = [
|
|
self._create_paragraph("First paragraph."),
|
|
self._create_paragraph("Second paragraph."),
|
|
Image(source="end.jpg", alt_text="End Image")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_image_end",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Navigate to end
|
|
page_count = 0
|
|
max_pages = 10
|
|
|
|
while page_count < max_pages:
|
|
next_page = manager.next_page()
|
|
if next_page is None:
|
|
break
|
|
page_count += 1
|
|
|
|
# Should have successfully navigated through document including final image
|
|
self.assertGreater(page_count, 0)
|
|
|
|
def test_alternating_text_and_images(self):
|
|
"""Test document with alternating text and images."""
|
|
blocks = [
|
|
self._create_paragraph("Paragraph 1"),
|
|
Image(source="img1.jpg", alt_text="Image 1"),
|
|
self._create_paragraph("Paragraph 2"),
|
|
Image(source="img2.jpg", alt_text="Image 2"),
|
|
self._create_paragraph("Paragraph 3"),
|
|
Image(source="img3.jpg", alt_text="Image 3"),
|
|
self._create_paragraph("Paragraph 4")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_alternating",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Track blocks visited
|
|
blocks_visited = set()
|
|
max_pages = 15
|
|
|
|
for _ in range(max_pages):
|
|
blocks_visited.add(manager.current_position.block_index)
|
|
next_page = manager.next_page()
|
|
if next_page is None:
|
|
break
|
|
|
|
# Should have visited multiple different blocks
|
|
self.assertGreater(len(blocks_visited), 1)
|
|
|
|
|
|
class TestCoverPageWithImages(unittest.TestCase):
|
|
"""Test cover page detection and handling with images."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.base_font = Font(font_size=14)
|
|
self.page_size = (400, 600)
|
|
self.page_style = PageStyle(padding=(20, 20, 20, 20))
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
|
|
def tearDown(self):
|
|
"""Clean up temporary files."""
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def _create_paragraph(self, text: str) -> Paragraph:
|
|
"""Helper to create a paragraph with text."""
|
|
para = Paragraph(self.base_font)
|
|
para.add_word(Word(text, self.base_font))
|
|
return para
|
|
|
|
def test_cover_page_detected_from_first_image(self):
|
|
"""Test that first image is detected as cover."""
|
|
blocks = [
|
|
Image(source="cover.jpg", alt_text="Cover"),
|
|
self._create_paragraph("Chapter text.")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_cover_detection",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Should detect cover
|
|
self.assertTrue(manager.has_cover())
|
|
self.assertTrue(manager.is_on_cover())
|
|
|
|
def test_no_cover_when_first_block_is_text(self):
|
|
"""Test that cover is not detected when first block is text."""
|
|
blocks = [
|
|
self._create_paragraph("First paragraph."),
|
|
Image(source="image.jpg", alt_text="Not a cover"),
|
|
self._create_paragraph("Second paragraph.")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_no_cover",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Should NOT detect cover
|
|
self.assertFalse(manager.has_cover())
|
|
self.assertFalse(manager.is_on_cover())
|
|
|
|
def test_navigation_from_cover_skips_image_block(self):
|
|
"""Test that next_page from cover skips the cover image block."""
|
|
blocks = [
|
|
Image(source="cover.jpg", alt_text="Cover"),
|
|
self._create_paragraph("First content paragraph."),
|
|
self._create_paragraph("Second content paragraph.")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_cover_skip",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Start on cover
|
|
self.assertTrue(manager.is_on_cover())
|
|
self.assertEqual(manager.current_position.block_index, 0)
|
|
|
|
# Navigate past cover
|
|
manager.next_page()
|
|
|
|
# Should skip cover image block (index 0) and go to first content (index 1)
|
|
self.assertFalse(manager.is_on_cover())
|
|
self.assertEqual(manager.current_position.block_index, 1)
|
|
|
|
def test_previous_page_returns_to_cover(self):
|
|
"""Test that previous_page from first content returns to cover."""
|
|
blocks = [
|
|
Image(source="cover.jpg", alt_text="Cover"),
|
|
self._create_paragraph("Content text.")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_back_to_cover",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Navigate past cover
|
|
manager.next_page()
|
|
self.assertFalse(manager.is_on_cover())
|
|
|
|
# Go back
|
|
manager.previous_page()
|
|
|
|
# Should be back on cover
|
|
self.assertTrue(manager.is_on_cover())
|
|
|
|
def test_jump_to_cover_from_middle(self):
|
|
"""Test jumping to cover from middle of document."""
|
|
blocks = [
|
|
Image(source="cover.jpg", alt_text="Cover"),
|
|
self._create_paragraph("Paragraph 1"),
|
|
self._create_paragraph("Paragraph 2"),
|
|
self._create_paragraph("Paragraph 3")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_jump_cover",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Navigate to middle
|
|
manager.next_page()
|
|
manager.next_page()
|
|
self.assertFalse(manager.is_on_cover())
|
|
|
|
# Jump to cover
|
|
cover_page = manager.jump_to_cover()
|
|
|
|
self.assertIsNotNone(cover_page)
|
|
self.assertTrue(manager.is_on_cover())
|
|
|
|
|
|
class TestImageBlockPositionTracking(unittest.TestCase):
|
|
"""Test position tracking with Image blocks."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.base_font = Font(font_size=14)
|
|
self.page_size = (400, 600)
|
|
self.page_style = PageStyle(padding=(20, 20, 20, 20))
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
|
|
def tearDown(self):
|
|
"""Clean up temporary files."""
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def _create_paragraph(self, text: str) -> Paragraph:
|
|
"""Helper to create a paragraph with text."""
|
|
para = Paragraph(self.base_font)
|
|
para.add_word(Word(text, self.base_font))
|
|
return para
|
|
|
|
def test_position_info_includes_image_blocks(self):
|
|
"""Test that position info correctly handles image blocks."""
|
|
blocks = [
|
|
self._create_paragraph("Text 1"),
|
|
Image(source="img.jpg", alt_text="Image"),
|
|
self._create_paragraph("Text 2")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_pos_info",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Get initial position info
|
|
pos_info = manager.get_position_info()
|
|
|
|
self.assertIn('position', pos_info)
|
|
self.assertIn('block_index', pos_info['position'])
|
|
self.assertEqual(pos_info['position']['block_index'], 0)
|
|
|
|
def test_bookmark_image_position(self):
|
|
"""Test bookmarking at an image position."""
|
|
blocks = [
|
|
self._create_paragraph("Before image"),
|
|
Image(source="bookmarked.jpg", alt_text="Bookmarked Image"),
|
|
self._create_paragraph("After image")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_bookmark_image",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Navigate to image position
|
|
manager.next_page()
|
|
|
|
# Add bookmark
|
|
bookmark_name = "image_location"
|
|
success = manager.add_bookmark(bookmark_name)
|
|
self.assertTrue(success)
|
|
|
|
# Navigate away
|
|
manager.next_page()
|
|
|
|
# Jump back to bookmark
|
|
page = manager.jump_to_bookmark(bookmark_name)
|
|
self.assertIsNotNone(page)
|
|
|
|
# Should be at or near the image position
|
|
# (exact position depends on how much fits on page)
|
|
self.assertGreater(manager.current_position.block_index, 0)
|
|
|
|
def test_reading_progress_with_images(self):
|
|
"""Test reading progress calculation with images in document."""
|
|
blocks = [
|
|
self._create_paragraph("Text 1"),
|
|
Image(source="img1.jpg", alt_text="Image 1"),
|
|
self._create_paragraph("Text 2"),
|
|
Image(source="img2.jpg", alt_text="Image 2"),
|
|
self._create_paragraph("Text 3")
|
|
]
|
|
|
|
manager = EreaderLayoutManager(
|
|
blocks=blocks,
|
|
page_size=self.page_size,
|
|
document_id="test_progress",
|
|
page_style=self.page_style,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# At start
|
|
progress_start = manager.get_reading_progress()
|
|
self.assertEqual(progress_start, 0.0)
|
|
|
|
# Navigate through document
|
|
for _ in range(5):
|
|
if manager.next_page() is None:
|
|
break
|
|
|
|
# Progress should have increased
|
|
progress_end = manager.get_reading_progress()
|
|
self.assertGreater(progress_end, progress_start)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|