306 lines
11 KiB
Python
306 lines
11 KiB
Python
"""
|
|
Tests for the EbookReader application interface.
|
|
|
|
Tests the high-level EbookReader API including bidirectional navigation,
|
|
image rendering consistency, and position management.
|
|
"""
|
|
|
|
import unittest
|
|
import tempfile
|
|
import shutil
|
|
from pathlib import Path
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
from pyWebLayout.layout.ereader_application import EbookReader
|
|
|
|
|
|
class TestEbookReaderNavigation(unittest.TestCase):
|
|
"""Test EbookReader navigation functionality"""
|
|
|
|
def setUp(self):
|
|
"""Set up test environment"""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.epub_path = "tests/data/test.epub"
|
|
|
|
# Verify test EPUB exists
|
|
if not Path(self.epub_path).exists():
|
|
self.skipTest(f"Test EPUB not found at {self.epub_path}")
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def compare_images(self, img1: Image.Image, img2: Image.Image) -> bool:
|
|
"""
|
|
Check if two PIL Images are pixel-perfect identical.
|
|
|
|
Args:
|
|
img1: First image
|
|
img2: Second image
|
|
|
|
Returns:
|
|
True if images are identical, False otherwise
|
|
"""
|
|
if img1 is None or img2 is None:
|
|
return False
|
|
|
|
if img1.size != img2.size:
|
|
return False
|
|
|
|
arr1 = np.array(img1)
|
|
arr2 = np.array(img2)
|
|
|
|
return np.array_equal(arr1, arr2)
|
|
|
|
def test_bidirectional_navigation_20_pages(self):
|
|
"""
|
|
Test that navigating forward 20 pages and then backward 20 pages
|
|
produces identical page renderings for the first page.
|
|
|
|
This validates that the bidirectional layout system maintains
|
|
perfect consistency during extended navigation.
|
|
|
|
Note: This test uses position save/load as a workaround for incomplete
|
|
backward rendering implementation.
|
|
"""
|
|
# Create reader with standard page size and disable buffer to avoid pickle issues
|
|
reader = EbookReader(
|
|
page_size=(800, 1000),
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0 # Disable multiprocess buffering
|
|
)
|
|
|
|
# Load the test EPUB
|
|
success = reader.load_epub(self.epub_path)
|
|
self.assertTrue(success, "Failed to load test EPUB")
|
|
self.assertTrue(reader.is_loaded(), "Reader should be loaded")
|
|
|
|
# Capture initial page (page 0) and save its position
|
|
initial_page = reader.get_current_page()
|
|
self.assertIsNotNone(initial_page, "Initial page should not be None")
|
|
|
|
# Save the initial position for later comparison
|
|
initial_position = reader.manager.current_position.copy()
|
|
|
|
# Store forward navigation pages and positions
|
|
forward_pages = [initial_page]
|
|
forward_positions = [initial_position]
|
|
pages_to_navigate = 20
|
|
|
|
# Navigate forward, capturing each page and position
|
|
for i in range(pages_to_navigate):
|
|
page = reader.next_page()
|
|
if page is None:
|
|
# Reached end of document
|
|
print(f"Reached end of document at page {i + 1}")
|
|
break
|
|
forward_pages.append(page)
|
|
forward_positions.append(reader.manager.current_position.copy())
|
|
|
|
actual_pages_navigated = len(forward_pages) - 1
|
|
print(f"Navigated forward through {actual_pages_navigated} pages")
|
|
|
|
# Now navigate backward using position jumps (more reliable than previous_page)
|
|
backward_pages = []
|
|
|
|
# Traverse backwards through our saved positions
|
|
for i in range(len(forward_positions) - 1, -1, -1):
|
|
position = forward_positions[i]
|
|
page_obj = reader.manager.jump_to_position(position)
|
|
# Render the Page object to get PIL Image
|
|
page_img = page_obj.render()
|
|
backward_pages.append(page_img)
|
|
|
|
# The last page from backward navigation should be page 0
|
|
final_page = backward_pages[-1]
|
|
|
|
# Debug: Save images to inspect differences
|
|
initial_page.save("/tmp/initial_page.png")
|
|
final_page.save("/tmp/final_page.png")
|
|
|
|
# Check image sizes first
|
|
print(f"Initial page size: {initial_page.size}")
|
|
print(f"Final page size: {final_page.size}")
|
|
print(f"Initial position: {initial_position}")
|
|
print(f"Final position: {forward_positions[0]}")
|
|
|
|
# Compare arrays to see differences
|
|
arr1 = np.array(initial_page)
|
|
arr2 = np.array(final_page)
|
|
if arr1.shape == arr2.shape:
|
|
diff = np.abs(arr1.astype(int) - arr2.astype(int))
|
|
diff_pixels = np.count_nonzero(diff)
|
|
total_pixels = arr1.shape[0] * arr1.shape[1] * arr1.shape[2]
|
|
print(f"Different pixels: {diff_pixels} out of {total_pixels} ({100*diff_pixels/total_pixels:.2f}%)")
|
|
if diff_pixels > 0:
|
|
# Save difference map
|
|
diff_img = Image.fromarray(np.clip(diff.sum(axis=2) * 10, 0, 255).astype(np.uint8))
|
|
diff_img.save("/tmp/diff_page.png")
|
|
|
|
# Critical assertion: first page should be identical after round trip
|
|
self.assertTrue(
|
|
self.compare_images(initial_page, final_page),
|
|
"First page should be identical after forward/backward navigation"
|
|
)
|
|
|
|
# Extended validation: compare all pages
|
|
# forward_pages[i] should match backward_pages[-(i+1)]
|
|
mismatches = []
|
|
for i in range(len(forward_pages)):
|
|
forward_page = forward_pages[i]
|
|
backward_page = backward_pages[-(i + 1)]
|
|
|
|
if not self.compare_images(forward_page, backward_page):
|
|
mismatches.append(i)
|
|
# Save mismatched pages for debugging
|
|
if i < 3: # Only save first few mismatches
|
|
forward_page.save(f"/tmp/forward_page_{i}.png")
|
|
backward_page.save(f"/tmp/backward_page_{i}.png")
|
|
|
|
if mismatches:
|
|
self.fail(f"Page mismatches detected at indices: {mismatches[:10]}... (showing first 10)")
|
|
|
|
print(f"Successfully validated bidirectional navigation consistency")
|
|
print(f"Tested {actual_pages_navigated} pages forward and backward")
|
|
print(f"All {len(forward_pages)} pages matched perfectly")
|
|
|
|
# Clean up
|
|
reader.close()
|
|
|
|
def test_navigation_at_boundaries(self):
|
|
"""
|
|
Test navigation behavior at document boundaries.
|
|
"""
|
|
reader = EbookReader(
|
|
page_size=(800, 1000),
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
success = reader.load_epub(self.epub_path)
|
|
self.assertTrue(success, "Failed to load test EPUB")
|
|
|
|
# Try to go backward from first page
|
|
# Should stay on first page or return None gracefully
|
|
page = reader.previous_page()
|
|
# Behavior depends on implementation - could be None or same page
|
|
|
|
# Navigate forward until end
|
|
pages_forward = 0
|
|
max_pages = 100 # Safety limit
|
|
while pages_forward < max_pages:
|
|
page = reader.next_page()
|
|
if page is None:
|
|
break
|
|
pages_forward += 1
|
|
|
|
print(f"Document has approximately {pages_forward} pages")
|
|
|
|
# Try to go forward from last page
|
|
page = reader.next_page()
|
|
self.assertIsNone(page, "Should return None at end of document")
|
|
|
|
reader.close()
|
|
|
|
|
|
class TestEbookReaderFeatures(unittest.TestCase):
|
|
"""Test other EbookReader features"""
|
|
|
|
def setUp(self):
|
|
"""Set up test environment"""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.epub_path = "tests/data/test.epub"
|
|
|
|
if not Path(self.epub_path).exists():
|
|
self.skipTest(f"Test EPUB not found at {self.epub_path}")
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_epub_loading(self):
|
|
"""Test EPUB loading functionality"""
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
|
|
# Test with valid EPUB
|
|
success = reader.load_epub(self.epub_path)
|
|
self.assertTrue(success)
|
|
self.assertTrue(reader.is_loaded())
|
|
|
|
# Get book info
|
|
info = reader.get_book_info()
|
|
self.assertIsNotNone(info)
|
|
self.assertIn('title', info)
|
|
self.assertIn('author', info)
|
|
self.assertGreater(info['total_blocks'], 0)
|
|
|
|
reader.close()
|
|
|
|
def test_page_rendering(self):
|
|
"""Test that pages render correctly as PIL Images"""
|
|
reader = EbookReader(
|
|
page_size=(400, 600),
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
reader.load_epub(self.epub_path)
|
|
|
|
page = reader.get_current_page()
|
|
self.assertIsNotNone(page)
|
|
self.assertIsInstance(page, Image.Image)
|
|
self.assertEqual(page.size, (400, 600))
|
|
|
|
reader.close()
|
|
|
|
def test_position_tracking(self):
|
|
"""Test position save/load functionality"""
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
reader.load_epub(self.epub_path)
|
|
|
|
# Navigate to a specific position
|
|
for _ in range(3):
|
|
reader.next_page()
|
|
|
|
# Save position
|
|
success = reader.save_position("test_position")
|
|
self.assertTrue(success)
|
|
|
|
# Navigate away
|
|
for _ in range(5):
|
|
reader.next_page()
|
|
|
|
# Load saved position
|
|
page = reader.load_position("test_position")
|
|
self.assertIsNotNone(page)
|
|
|
|
# List positions
|
|
positions = reader.list_saved_positions()
|
|
self.assertIn("test_position", positions)
|
|
|
|
# Delete position
|
|
success = reader.delete_position("test_position")
|
|
self.assertTrue(success)
|
|
|
|
reader.close()
|
|
|
|
def test_chapter_navigation(self):
|
|
"""Test chapter listing and navigation"""
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
reader.load_epub(self.epub_path)
|
|
|
|
# Get chapters
|
|
chapters = reader.get_chapters()
|
|
self.assertIsInstance(chapters, list)
|
|
|
|
# If book has chapters, test navigation
|
|
if len(chapters) > 0:
|
|
# Jump to first chapter
|
|
page = reader.jump_to_chapter(0)
|
|
self.assertIsNotNone(page)
|
|
|
|
reader.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|