456 lines
15 KiB
Python
456 lines
15 KiB
Python
"""
|
|
Comprehensive tests for the ereader layout system.
|
|
|
|
Tests the complete ereader functionality including position tracking,
|
|
font scaling, chapter navigation, and page buffering.
|
|
"""
|
|
|
|
import unittest
|
|
import tempfile
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font
|
|
from pyWebLayout.style.page_style import PageStyle
|
|
from pyWebLayout.layout.ereader_layout import RenderingPosition, ChapterNavigator, FontScaler, BidirectionalLayouter
|
|
from pyWebLayout.layout.ereader_manager import EreaderLayoutManager, BookmarkManager, create_ereader_manager
|
|
|
|
|
|
class TestRenderingPosition(unittest.TestCase):
|
|
"""Test the RenderingPosition class"""
|
|
|
|
def test_position_creation(self):
|
|
"""Test creating a rendering position"""
|
|
pos = RenderingPosition(
|
|
chapter_index=1,
|
|
block_index=5,
|
|
word_index=10,
|
|
table_row=2,
|
|
table_col=3
|
|
)
|
|
|
|
self.assertEqual(pos.chapter_index, 1)
|
|
self.assertEqual(pos.block_index, 5)
|
|
self.assertEqual(pos.word_index, 10)
|
|
self.assertEqual(pos.table_row, 2)
|
|
self.assertEqual(pos.table_col, 3)
|
|
|
|
def test_position_serialization(self):
|
|
"""Test position serialization and deserialization"""
|
|
pos = RenderingPosition(
|
|
chapter_index=1,
|
|
block_index=5,
|
|
word_index=10,
|
|
remaining_pretext="test"
|
|
)
|
|
|
|
# Serialize to dict
|
|
pos_dict = pos.to_dict()
|
|
self.assertIsInstance(pos_dict, dict)
|
|
self.assertEqual(pos_dict['chapter_index'], 1)
|
|
self.assertEqual(pos_dict['remaining_pretext'], "test")
|
|
|
|
# Deserialize from dict
|
|
pos2 = RenderingPosition.from_dict(pos_dict)
|
|
self.assertEqual(pos, pos2)
|
|
|
|
def test_position_copy(self):
|
|
"""Test position copying"""
|
|
pos = RenderingPosition(chapter_index=1, block_index=5)
|
|
pos_copy = pos.copy()
|
|
|
|
self.assertEqual(pos, pos_copy)
|
|
self.assertIsNot(pos, pos_copy) # Different objects
|
|
|
|
# Modify copy
|
|
pos_copy.word_index = 10
|
|
self.assertNotEqual(pos, pos_copy)
|
|
|
|
def test_position_equality_and_hashing(self):
|
|
"""Test position equality and hashing"""
|
|
pos1 = RenderingPosition(chapter_index=1, block_index=5)
|
|
pos2 = RenderingPosition(chapter_index=1, block_index=5)
|
|
pos3 = RenderingPosition(chapter_index=1, block_index=6)
|
|
|
|
self.assertEqual(pos1, pos2)
|
|
self.assertNotEqual(pos1, pos3)
|
|
|
|
# Test hashing (for use as dict keys)
|
|
pos_dict = {pos1: "test"}
|
|
self.assertEqual(pos_dict[pos2], "test") # Should work due to equality
|
|
|
|
|
|
class TestChapterNavigator(unittest.TestCase):
|
|
"""Test the ChapterNavigator class"""
|
|
|
|
def setUp(self):
|
|
"""Set up test data"""
|
|
self.font = Font()
|
|
|
|
# Create test blocks with headings
|
|
self.blocks = [
|
|
Paragraph(self.font), # Block 0
|
|
Heading(HeadingLevel.H1, self.font), # Block 1 - Chapter 1
|
|
Paragraph(self.font), # Block 2
|
|
Heading(HeadingLevel.H2, self.font), # Block 3 - Subsection
|
|
Paragraph(self.font), # Block 4
|
|
Heading(HeadingLevel.H1, self.font), # Block 5 - Chapter 2
|
|
Paragraph(self.font), # Block 6
|
|
]
|
|
|
|
# Add text to headings
|
|
self.blocks[1].add_word(Word("Chapter", self.font))
|
|
self.blocks[1].add_word(Word("One", self.font))
|
|
|
|
self.blocks[3].add_word(Word("Subsection", self.font))
|
|
self.blocks[3].add_word(Word("A", self.font))
|
|
|
|
self.blocks[5].add_word(Word("Chapter", self.font))
|
|
self.blocks[5].add_word(Word("Two", self.font))
|
|
|
|
def test_chapter_detection(self):
|
|
"""Test that chapters are detected correctly"""
|
|
navigator = ChapterNavigator(self.blocks)
|
|
|
|
self.assertEqual(len(navigator.chapters), 3) # 2 H1s + 1 H2
|
|
|
|
# Check chapter titles
|
|
titles = [chapter.title for chapter in navigator.chapters]
|
|
self.assertIn("Chapter One", titles)
|
|
self.assertIn("Subsection A", titles)
|
|
self.assertIn("Chapter Two", titles)
|
|
|
|
def test_table_of_contents(self):
|
|
"""Test table of contents generation"""
|
|
navigator = ChapterNavigator(self.blocks)
|
|
toc = navigator.get_table_of_contents()
|
|
|
|
self.assertEqual(len(toc), 3)
|
|
|
|
# Check first entry
|
|
title, level, position = toc[0]
|
|
self.assertEqual(title, "Chapter One")
|
|
self.assertEqual(level, HeadingLevel.H1)
|
|
self.assertIsInstance(position, RenderingPosition)
|
|
|
|
def test_chapter_position_lookup(self):
|
|
"""Test looking up chapter positions"""
|
|
navigator = ChapterNavigator(self.blocks)
|
|
|
|
pos = navigator.get_chapter_position("Chapter One")
|
|
self.assertIsNotNone(pos)
|
|
self.assertEqual(pos.chapter_index, 0)
|
|
|
|
pos = navigator.get_chapter_position("Nonexistent Chapter")
|
|
self.assertIsNone(pos)
|
|
|
|
def test_current_chapter_detection(self):
|
|
"""Test detecting current chapter from position"""
|
|
navigator = ChapterNavigator(self.blocks)
|
|
|
|
# Position in first chapter
|
|
pos = RenderingPosition(chapter_index=0, block_index=2)
|
|
chapter = navigator.get_current_chapter(pos)
|
|
self.assertIsNotNone(chapter)
|
|
self.assertEqual(chapter.title, "Chapter One")
|
|
|
|
|
|
class TestFontScaler(unittest.TestCase):
|
|
"""Test the FontScaler class"""
|
|
|
|
def test_font_scaling(self):
|
|
"""Test font scaling functionality"""
|
|
original_font = Font(font_size=12)
|
|
|
|
# Test no scaling
|
|
scaled_font = FontScaler.scale_font(original_font, 1.0)
|
|
self.assertEqual(scaled_font.font_size, 12)
|
|
|
|
# Test 2x scaling
|
|
scaled_font = FontScaler.scale_font(original_font, 2.0)
|
|
self.assertEqual(scaled_font.font_size, 24)
|
|
|
|
# Test 0.5x scaling
|
|
scaled_font = FontScaler.scale_font(original_font, 0.5)
|
|
self.assertEqual(scaled_font.font_size, 6)
|
|
|
|
# Test minimum size constraint
|
|
scaled_font = FontScaler.scale_font(original_font, 0.01)
|
|
self.assertGreaterEqual(scaled_font.font_size, 1)
|
|
|
|
def test_word_spacing_scaling(self):
|
|
"""Test word spacing scaling"""
|
|
original_spacing = (5, 15)
|
|
|
|
# Test no scaling
|
|
scaled_spacing = FontScaler.scale_word_spacing(original_spacing, 1.0)
|
|
self.assertEqual(scaled_spacing, (5, 15))
|
|
|
|
# Test 2x scaling
|
|
scaled_spacing = FontScaler.scale_word_spacing(original_spacing, 2.0)
|
|
self.assertEqual(scaled_spacing, (10, 30))
|
|
|
|
# Test minimum constraints
|
|
scaled_spacing = FontScaler.scale_word_spacing(original_spacing, 0.1)
|
|
self.assertGreaterEqual(scaled_spacing[0], 1)
|
|
self.assertGreaterEqual(scaled_spacing[1], 2)
|
|
|
|
|
|
class TestBookmarkManager(unittest.TestCase):
|
|
"""Test the BookmarkManager class"""
|
|
|
|
def setUp(self):
|
|
"""Set up test environment"""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.document_id = "test_document"
|
|
self.bookmark_manager = BookmarkManager(self.document_id, self.temp_dir)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_bookmark_operations(self):
|
|
"""Test bookmark add/remove/get operations"""
|
|
pos = RenderingPosition(chapter_index=1, block_index=5)
|
|
|
|
# Add bookmark
|
|
self.bookmark_manager.add_bookmark("test_bookmark", pos)
|
|
|
|
# Get bookmark
|
|
retrieved_pos = self.bookmark_manager.get_bookmark("test_bookmark")
|
|
self.assertEqual(retrieved_pos, pos)
|
|
|
|
# List bookmarks
|
|
bookmarks = self.bookmark_manager.list_bookmarks()
|
|
self.assertEqual(len(bookmarks), 1)
|
|
self.assertEqual(bookmarks[0][0], "test_bookmark")
|
|
self.assertEqual(bookmarks[0][1], pos)
|
|
|
|
# Remove bookmark
|
|
success = self.bookmark_manager.remove_bookmark("test_bookmark")
|
|
self.assertTrue(success)
|
|
|
|
# Verify removal
|
|
retrieved_pos = self.bookmark_manager.get_bookmark("test_bookmark")
|
|
self.assertIsNone(retrieved_pos)
|
|
|
|
def test_reading_position_persistence(self):
|
|
"""Test saving and loading reading position"""
|
|
pos = RenderingPosition(chapter_index=2, block_index=10, word_index=5)
|
|
|
|
# Save position
|
|
self.bookmark_manager.save_reading_position(pos)
|
|
|
|
# Create new manager instance (simulates app restart)
|
|
new_manager = BookmarkManager(self.document_id, self.temp_dir)
|
|
|
|
# Load position
|
|
loaded_pos = new_manager.load_reading_position()
|
|
self.assertEqual(loaded_pos, pos)
|
|
|
|
def test_bookmark_persistence(self):
|
|
"""Test that bookmarks persist across manager instances"""
|
|
pos = RenderingPosition(chapter_index=1, block_index=5)
|
|
|
|
# Add bookmark
|
|
self.bookmark_manager.add_bookmark("persistent_bookmark", pos)
|
|
|
|
# Create new manager instance
|
|
new_manager = BookmarkManager(self.document_id, self.temp_dir)
|
|
|
|
# Verify bookmark exists
|
|
retrieved_pos = new_manager.get_bookmark("persistent_bookmark")
|
|
self.assertEqual(retrieved_pos, pos)
|
|
|
|
|
|
class TestEreaderLayoutManager(unittest.TestCase):
|
|
"""Test the complete EreaderLayoutManager"""
|
|
|
|
def setUp(self):
|
|
"""Set up test data"""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.font = Font()
|
|
|
|
# Create test document with multiple paragraphs and headings
|
|
self.blocks = []
|
|
|
|
# Add a heading
|
|
heading = Heading(HeadingLevel.H1, self.font)
|
|
heading.add_word(Word("Test", self.font))
|
|
heading.add_word(Word("Chapter", self.font))
|
|
self.blocks.append(heading)
|
|
|
|
# Add several paragraphs with multiple words
|
|
for i in range(3):
|
|
paragraph = Paragraph(self.font)
|
|
for j in range(20): # 20 words per paragraph
|
|
paragraph.add_word(Word(f"Word{i}_{j}", self.font))
|
|
self.blocks.append(paragraph)
|
|
|
|
self.page_size = (400, 600)
|
|
self.document_id = "test_document"
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_manager_initialization(self):
|
|
"""Test ereader manager initialization"""
|
|
# Change to temp directory for bookmarks
|
|
original_cwd = Path.cwd()
|
|
try:
|
|
import os
|
|
os.chdir(self.temp_dir)
|
|
|
|
manager = EreaderLayoutManager(
|
|
self.blocks,
|
|
self.page_size,
|
|
self.document_id,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
self.assertEqual(manager.page_size, self.page_size)
|
|
self.assertEqual(manager.document_id, self.document_id)
|
|
self.assertEqual(manager.font_scale, 1.0)
|
|
self.assertIsInstance(manager.current_position, RenderingPosition)
|
|
|
|
manager.shutdown()
|
|
finally:
|
|
os.chdir(original_cwd)
|
|
|
|
def test_font_scaling(self):
|
|
"""Test font scaling functionality"""
|
|
original_cwd = Path.cwd()
|
|
try:
|
|
import os
|
|
os.chdir(self.temp_dir)
|
|
|
|
manager = EreaderLayoutManager(
|
|
self.blocks,
|
|
self.page_size,
|
|
self.document_id,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Test initial scale
|
|
self.assertEqual(manager.get_font_scale(), 1.0)
|
|
|
|
# Test scaling
|
|
page = manager.set_font_scale(1.5)
|
|
self.assertEqual(manager.get_font_scale(), 1.5)
|
|
self.assertIsNotNone(page)
|
|
|
|
manager.shutdown()
|
|
finally:
|
|
os.chdir(original_cwd)
|
|
|
|
def test_table_of_contents(self):
|
|
"""Test table of contents functionality"""
|
|
original_cwd = Path.cwd()
|
|
try:
|
|
import os
|
|
os.chdir(self.temp_dir)
|
|
|
|
manager = EreaderLayoutManager(
|
|
self.blocks,
|
|
self.page_size,
|
|
self.document_id,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
toc = manager.get_table_of_contents()
|
|
self.assertGreater(len(toc), 0)
|
|
|
|
# Check first entry
|
|
title, level, position = toc[0]
|
|
self.assertEqual(title, "Test Chapter")
|
|
self.assertEqual(level, HeadingLevel.H1)
|
|
|
|
manager.shutdown()
|
|
finally:
|
|
os.chdir(original_cwd)
|
|
|
|
def test_bookmark_functionality(self):
|
|
"""Test bookmark functionality"""
|
|
manager = EreaderLayoutManager(
|
|
self.blocks,
|
|
self.page_size,
|
|
self.document_id,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Add bookmark
|
|
success = manager.add_bookmark("test_bookmark")
|
|
self.assertTrue(success)
|
|
|
|
# List bookmarks
|
|
bookmarks = manager.list_bookmarks()
|
|
self.assertEqual(len(bookmarks), 1)
|
|
self.assertEqual(bookmarks[0][0], "test_bookmark")
|
|
|
|
# Jump to bookmark (should work even though it's the same position)
|
|
page = manager.jump_to_bookmark("test_bookmark")
|
|
self.assertIsNotNone(page)
|
|
|
|
# Remove bookmark
|
|
success = manager.remove_bookmark("test_bookmark")
|
|
self.assertTrue(success)
|
|
|
|
manager.shutdown()
|
|
|
|
def test_progress_tracking(self):
|
|
"""Test reading progress tracking"""
|
|
original_cwd = Path.cwd()
|
|
try:
|
|
import os
|
|
os.chdir(self.temp_dir)
|
|
|
|
manager = EreaderLayoutManager(
|
|
self.blocks,
|
|
self.page_size,
|
|
self.document_id,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
# Initial progress should be 0
|
|
progress = manager.get_reading_progress()
|
|
self.assertGreaterEqual(progress, 0.0)
|
|
self.assertLessEqual(progress, 1.0)
|
|
|
|
# Get position info
|
|
info = manager.get_position_info()
|
|
self.assertIn('position', info)
|
|
self.assertIn('progress', info)
|
|
self.assertIn('font_scale', info)
|
|
|
|
manager.shutdown()
|
|
finally:
|
|
os.chdir(original_cwd)
|
|
|
|
def test_convenience_function(self):
|
|
"""Test the convenience function"""
|
|
original_cwd = Path.cwd()
|
|
try:
|
|
import os
|
|
os.chdir(self.temp_dir)
|
|
|
|
manager = create_ereader_manager(
|
|
self.blocks,
|
|
self.page_size,
|
|
self.document_id,
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
self.assertIsInstance(manager, EreaderLayoutManager)
|
|
self.assertEqual(manager.page_size, self.page_size)
|
|
|
|
manager.shutdown()
|
|
finally:
|
|
os.chdir(original_cwd)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|