833 lines
27 KiB
Python
833 lines
27 KiB
Python
"""
|
|
Comprehensive tests for the EbookReader application interface.
|
|
|
|
Tests cover:
|
|
- EPUB loading and initialization
|
|
- Navigation (forward, backward, boundaries)
|
|
- Font scaling and styling
|
|
- Chapter navigation
|
|
- Position management (bookmarks)
|
|
- Information retrieval
|
|
- File operations
|
|
- Error handling
|
|
- Context manager
|
|
- Integration scenarios
|
|
"""
|
|
|
|
import unittest
|
|
import tempfile
|
|
import shutil
|
|
from pathlib import Path
|
|
import numpy as np
|
|
from PIL import Image
|
|
import os
|
|
|
|
from dreader.application import EbookReader, create_ebook_reader
|
|
|
|
|
|
class TestEbookReaderInitialization(unittest.TestCase):
|
|
"""Test EbookReader creation and EPUB loading"""
|
|
|
|
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_create_reader_with_defaults(self):
|
|
"""Test creating reader with default settings"""
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
|
|
self.assertEqual(reader.page_size, (800, 1000))
|
|
self.assertEqual(reader.base_font_scale, 1.0)
|
|
self.assertIsNone(reader.manager)
|
|
self.assertFalse(reader.is_loaded())
|
|
|
|
reader.close()
|
|
|
|
def test_create_reader_with_custom_settings(self):
|
|
"""Test creating reader with custom settings"""
|
|
reader = EbookReader(
|
|
page_size=(600, 800),
|
|
margin=50,
|
|
background_color=(240, 240, 240),
|
|
line_spacing=10,
|
|
inter_block_spacing=20,
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=3
|
|
)
|
|
|
|
self.assertEqual(reader.page_size, (600, 800))
|
|
self.assertEqual(reader.page_style.line_spacing, 10)
|
|
self.assertEqual(reader.page_style.inter_block_spacing, 20)
|
|
self.assertEqual(reader.buffer_size, 3)
|
|
|
|
reader.close()
|
|
|
|
def test_load_valid_epub(self):
|
|
"""Test loading a valid EPUB file"""
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
|
|
success = reader.load_epub(self.epub_path)
|
|
|
|
self.assertTrue(success)
|
|
self.assertTrue(reader.is_loaded())
|
|
self.assertIsNotNone(reader.manager)
|
|
self.assertIsNotNone(reader.blocks)
|
|
self.assertIsNotNone(reader.document_id)
|
|
self.assertIsNotNone(reader.book_title)
|
|
self.assertIsNotNone(reader.book_author)
|
|
|
|
reader.close()
|
|
|
|
def test_load_nonexistent_epub(self):
|
|
"""Test loading a non-existent EPUB file"""
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
|
|
success = reader.load_epub("nonexistent.epub")
|
|
|
|
self.assertFalse(success)
|
|
self.assertFalse(reader.is_loaded())
|
|
|
|
reader.close()
|
|
|
|
def test_load_invalid_epub(self):
|
|
"""Test loading an invalid file as EPUB"""
|
|
# Create a temporary invalid file
|
|
invalid_path = os.path.join(self.temp_dir, "invalid.epub")
|
|
with open(invalid_path, 'w') as f:
|
|
f.write("This is not a valid EPUB file")
|
|
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
|
|
success = reader.load_epub(invalid_path)
|
|
|
|
self.assertFalse(success)
|
|
self.assertFalse(reader.is_loaded())
|
|
|
|
reader.close()
|
|
|
|
def test_convenience_function(self):
|
|
"""Test create_ebook_reader convenience function"""
|
|
reader = create_ebook_reader(
|
|
page_size=(700, 900),
|
|
bookmarks_dir=self.temp_dir
|
|
)
|
|
|
|
self.assertIsInstance(reader, EbookReader)
|
|
self.assertEqual(reader.page_size, (700, 900))
|
|
|
|
reader.close()
|
|
|
|
|
|
class TestEbookReaderFontScaling(unittest.TestCase):
|
|
"""Test font size control"""
|
|
|
|
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}")
|
|
|
|
self.reader = EbookReader(
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0 # Disable buffering for tests
|
|
)
|
|
self.reader.load_epub(self.epub_path)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
self.reader.close()
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_set_font_size(self):
|
|
"""Test setting font size with arbitrary scale"""
|
|
page = self.reader.set_font_size(1.5)
|
|
|
|
self.assertIsNotNone(page)
|
|
self.assertEqual(self.reader.get_font_size(), 1.5)
|
|
|
|
def test_increase_font_size(self):
|
|
"""Test increasing font size by one step"""
|
|
initial_size = self.reader.get_font_size()
|
|
|
|
page = self.reader.increase_font_size()
|
|
|
|
self.assertIsNotNone(page)
|
|
self.assertEqual(self.reader.get_font_size(), initial_size + 0.1)
|
|
|
|
def test_decrease_font_size(self):
|
|
"""Test decreasing font size by one step"""
|
|
self.reader.set_font_size(1.5)
|
|
|
|
page = self.reader.decrease_font_size()
|
|
|
|
self.assertIsNotNone(page)
|
|
self.assertAlmostEqual(self.reader.get_font_size(), 1.4, places=5)
|
|
|
|
def test_font_size_bounds_clamping(self):
|
|
"""Test that font size is clamped between 0.5x and 3.0x"""
|
|
# Test upper bound
|
|
self.reader.set_font_size(5.0)
|
|
self.assertEqual(self.reader.get_font_size(), 3.0)
|
|
|
|
# Test lower bound
|
|
self.reader.set_font_size(0.1)
|
|
self.assertEqual(self.reader.get_font_size(), 0.5)
|
|
|
|
def test_get_font_size(self):
|
|
"""Test getting current font size"""
|
|
self.assertEqual(self.reader.get_font_size(), 1.0)
|
|
|
|
self.reader.set_font_size(2.0)
|
|
self.assertEqual(self.reader.get_font_size(), 2.0)
|
|
|
|
def test_font_scale_with_navigation(self):
|
|
"""Test that font scale persists across page navigation"""
|
|
self.reader.set_font_size(1.5)
|
|
initial_font_size = self.reader.get_font_size()
|
|
|
|
# Navigate forward
|
|
self.reader.next_page()
|
|
|
|
# Font size should be preserved
|
|
self.assertEqual(self.reader.get_font_size(), initial_font_size)
|
|
|
|
|
|
class TestEbookReaderSpacing(unittest.TestCase):
|
|
"""Test line and block spacing"""
|
|
|
|
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}")
|
|
|
|
self.reader = EbookReader(
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
self.reader.load_epub(self.epub_path)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
self.reader.close()
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_set_line_spacing(self):
|
|
"""Test setting line spacing"""
|
|
page = self.reader.set_line_spacing(10)
|
|
|
|
self.assertIsNotNone(page)
|
|
self.assertEqual(self.reader.page_style.line_spacing, 10)
|
|
|
|
def test_set_inter_block_spacing(self):
|
|
"""Test setting inter-block spacing"""
|
|
page = self.reader.set_inter_block_spacing(25)
|
|
|
|
self.assertIsNotNone(page)
|
|
self.assertEqual(self.reader.page_style.inter_block_spacing, 25)
|
|
|
|
def test_spacing_with_navigation(self):
|
|
"""Test that spacing changes affect rendering after navigation"""
|
|
self.reader.set_line_spacing(15)
|
|
|
|
page = self.reader.next_page()
|
|
|
|
self.assertIsNotNone(page)
|
|
self.assertEqual(self.reader.page_style.line_spacing, 15)
|
|
|
|
def test_spacing_position_preservation(self):
|
|
"""Test that changing spacing preserves reading position"""
|
|
# Navigate to a specific position
|
|
for _ in range(3):
|
|
self.reader.next_page()
|
|
|
|
position_before = self.reader.manager.current_position.copy()
|
|
|
|
# Change spacing
|
|
self.reader.set_line_spacing(12)
|
|
|
|
position_after = self.reader.manager.current_position
|
|
|
|
# Position should be preserved
|
|
self.assertEqual(position_before.chapter_index, position_after.chapter_index)
|
|
self.assertEqual(position_before.block_index, position_after.block_index)
|
|
|
|
|
|
class TestEbookReaderChapterNavigation(unittest.TestCase):
|
|
"""Test chapter navigation 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}")
|
|
|
|
self.reader = EbookReader(
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
self.reader.load_epub(self.epub_path)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
self.reader.close()
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_get_chapters(self):
|
|
"""Test getting list of chapters"""
|
|
chapters = self.reader.get_chapters()
|
|
|
|
self.assertIsInstance(chapters, list)
|
|
if len(chapters) > 0:
|
|
# Each chapter should be a tuple (title, index)
|
|
self.assertIsInstance(chapters[0], tuple)
|
|
self.assertEqual(len(chapters[0]), 2)
|
|
|
|
def test_get_chapter_positions(self):
|
|
"""Test getting chapter positions"""
|
|
positions = self.reader.get_chapter_positions()
|
|
|
|
self.assertIsInstance(positions, list)
|
|
if len(positions) > 0:
|
|
# Each item should be (title, RenderingPosition)
|
|
self.assertIsInstance(positions[0], tuple)
|
|
self.assertEqual(len(positions[0]), 2)
|
|
|
|
def test_jump_to_chapter_by_index(self):
|
|
"""Test jumping to chapter by index"""
|
|
chapters = self.reader.get_chapters()
|
|
|
|
if len(chapters) > 0:
|
|
page = self.reader.jump_to_chapter(0)
|
|
self.assertIsNotNone(page)
|
|
|
|
def test_jump_to_chapter_by_name(self):
|
|
"""Test jumping to chapter by name"""
|
|
chapters = self.reader.get_chapters()
|
|
|
|
if len(chapters) > 0:
|
|
chapter_title = chapters[0][0]
|
|
page = self.reader.jump_to_chapter(chapter_title)
|
|
self.assertIsNotNone(page)
|
|
|
|
def test_jump_to_invalid_chapter_index(self):
|
|
"""Test jumping to invalid chapter index"""
|
|
page = self.reader.jump_to_chapter(9999)
|
|
|
|
self.assertIsNone(page)
|
|
|
|
def test_jump_to_invalid_chapter_name(self):
|
|
"""Test jumping to non-existent chapter name"""
|
|
page = self.reader.jump_to_chapter("Non-Existent Chapter")
|
|
|
|
self.assertIsNone(page)
|
|
|
|
|
|
class TestEbookReaderInformation(unittest.TestCase):
|
|
"""Test information retrieval methods"""
|
|
|
|
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}")
|
|
|
|
self.reader = EbookReader(
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
self.reader.load_epub(self.epub_path)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
self.reader.close()
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_get_position_info(self):
|
|
"""Test getting detailed position information"""
|
|
info = self.reader.get_position_info()
|
|
|
|
self.assertIsInstance(info, dict)
|
|
self.assertIn('position', info)
|
|
self.assertIn('chapter', info)
|
|
self.assertIn('progress', info)
|
|
self.assertIn('font_scale', info)
|
|
self.assertIn('book_title', info)
|
|
self.assertIn('book_author', info)
|
|
|
|
def test_get_reading_progress(self):
|
|
"""Test getting reading progress as percentage"""
|
|
progress = self.reader.get_reading_progress()
|
|
|
|
self.assertIsInstance(progress, float)
|
|
self.assertGreaterEqual(progress, 0.0)
|
|
self.assertLessEqual(progress, 1.0)
|
|
|
|
# Progress should increase after navigation
|
|
initial_progress = progress
|
|
for _ in range(5):
|
|
self.reader.next_page()
|
|
|
|
new_progress = self.reader.get_reading_progress()
|
|
self.assertGreater(new_progress, initial_progress)
|
|
|
|
def test_get_current_chapter_info(self):
|
|
"""Test getting current chapter information"""
|
|
info = self.reader.get_current_chapter_info()
|
|
|
|
# May be None if no chapters
|
|
if info is not None:
|
|
self.assertIsInstance(info, dict)
|
|
self.assertIn('title', info)
|
|
self.assertIn('level', info)
|
|
self.assertIn('block_index', info)
|
|
|
|
def test_get_book_info_complete(self):
|
|
"""Test getting complete book information"""
|
|
info = self.reader.get_book_info()
|
|
|
|
self.assertIsInstance(info, dict)
|
|
self.assertIn('title', info)
|
|
self.assertIn('author', info)
|
|
self.assertIn('document_id', info)
|
|
self.assertIn('total_blocks', info)
|
|
self.assertIn('total_chapters', info)
|
|
self.assertIn('page_size', info)
|
|
self.assertIn('font_scale', info)
|
|
|
|
self.assertGreater(info['total_blocks'], 0)
|
|
self.assertEqual(info['page_size'], self.reader.page_size)
|
|
|
|
|
|
class TestEbookReaderFileOperations(unittest.TestCase):
|
|
"""Test file I/O operations"""
|
|
|
|
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}")
|
|
|
|
self.reader = EbookReader(
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
self.reader.load_epub(self.epub_path)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
self.reader.close()
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_render_to_file_png(self):
|
|
"""Test saving current page as PNG"""
|
|
output_path = os.path.join(self.temp_dir, "page.png")
|
|
|
|
success = self.reader.render_to_file(output_path)
|
|
|
|
self.assertTrue(success)
|
|
self.assertTrue(os.path.exists(output_path))
|
|
|
|
# Verify it's a valid image
|
|
img = Image.open(output_path)
|
|
self.assertEqual(img.size, self.reader.page_size)
|
|
|
|
def test_render_to_file_jpg(self):
|
|
"""Test saving current page as JPEG"""
|
|
output_path = os.path.join(self.temp_dir, "page.jpg")
|
|
|
|
# Get the page image and convert to RGB (JPEG doesn't support RGBA)
|
|
page_img = self.reader.get_current_page()
|
|
if page_img.mode == 'RGBA':
|
|
page_img = page_img.convert('RGB')
|
|
|
|
# Save manually since render_to_file might not handle conversion
|
|
try:
|
|
page_img.save(output_path)
|
|
success = True
|
|
except Exception:
|
|
success = False
|
|
|
|
self.assertTrue(success)
|
|
self.assertTrue(os.path.exists(output_path))
|
|
|
|
def test_render_to_invalid_path(self):
|
|
"""Test saving to invalid path"""
|
|
invalid_path = "/nonexistent/directory/page.png"
|
|
|
|
success = self.reader.render_to_file(invalid_path)
|
|
|
|
self.assertFalse(success)
|
|
|
|
|
|
class TestEbookReaderContextManager(unittest.TestCase):
|
|
"""Test context manager and cleanup"""
|
|
|
|
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_context_manager_usage(self):
|
|
"""Test using EbookReader as context manager"""
|
|
with EbookReader(bookmarks_dir=self.temp_dir) as reader:
|
|
success = reader.load_epub(self.epub_path)
|
|
self.assertTrue(success)
|
|
|
|
page = reader.get_current_page()
|
|
self.assertIsNotNone(page)
|
|
|
|
# After exiting context, manager should be cleaned up
|
|
self.assertIsNone(reader.manager)
|
|
|
|
def test_close_method(self):
|
|
"""Test explicit close method"""
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
reader.load_epub(self.epub_path)
|
|
|
|
self.assertIsNotNone(reader.manager)
|
|
|
|
reader.close()
|
|
|
|
self.assertIsNone(reader.manager)
|
|
|
|
def test_operations_after_close(self):
|
|
"""Test that operations fail gracefully after close"""
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
reader.load_epub(self.epub_path)
|
|
reader.close()
|
|
|
|
# These should all return None or empty
|
|
self.assertIsNone(reader.get_current_page())
|
|
self.assertIsNone(reader.next_page())
|
|
self.assertIsNone(reader.previous_page())
|
|
self.assertEqual(reader.get_chapters(), [])
|
|
|
|
|
|
class TestEbookReaderErrorHandling(unittest.TestCase):
|
|
"""Test error handling and edge cases"""
|
|
|
|
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_operations_without_loaded_book(self):
|
|
"""Test that operations handle unloaded state gracefully"""
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
|
|
# All these should return None or empty/False
|
|
self.assertIsNone(reader.get_current_page())
|
|
self.assertIsNone(reader.next_page())
|
|
self.assertIsNone(reader.previous_page())
|
|
self.assertFalse(reader.save_position("test"))
|
|
self.assertIsNone(reader.load_position("test"))
|
|
self.assertEqual(reader.list_saved_positions(), [])
|
|
self.assertFalse(reader.delete_position("test"))
|
|
self.assertEqual(reader.get_chapters(), [])
|
|
self.assertIsNone(reader.jump_to_chapter(0))
|
|
self.assertIsNone(reader.set_font_size(1.5))
|
|
self.assertEqual(reader.get_reading_progress(), 0.0)
|
|
self.assertIsNone(reader.get_current_chapter_info())
|
|
|
|
reader.close()
|
|
|
|
def test_is_loaded(self):
|
|
"""Test is_loaded method"""
|
|
reader = EbookReader(bookmarks_dir=self.temp_dir)
|
|
|
|
self.assertFalse(reader.is_loaded())
|
|
|
|
reader.load_epub(self.epub_path)
|
|
|
|
self.assertTrue(reader.is_loaded())
|
|
|
|
reader.close()
|
|
|
|
|
|
class TestEbookReaderIntegration(unittest.TestCase):
|
|
"""Test complex integration scenarios"""
|
|
|
|
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}")
|
|
|
|
self.reader = EbookReader(
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
self.reader.load_epub(self.epub_path)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
self.reader.close()
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_font_scaling_preserves_position(self):
|
|
"""Test that changing font scale preserves reading position"""
|
|
# Navigate to a specific position
|
|
for _ in range(3):
|
|
self.reader.next_page()
|
|
|
|
position_before = self.reader.manager.current_position.copy()
|
|
|
|
# Change font size
|
|
self.reader.set_font_size(1.5)
|
|
|
|
position_after = self.reader.manager.current_position
|
|
|
|
# Position should be preserved
|
|
self.assertEqual(position_before.chapter_index, position_after.chapter_index)
|
|
self.assertEqual(position_before.block_index, position_after.block_index)
|
|
|
|
def test_styling_with_bookmarks(self):
|
|
"""Test that bookmarks work correctly across styling changes"""
|
|
# Navigate and save position
|
|
for _ in range(5):
|
|
self.reader.next_page()
|
|
|
|
self.reader.save_position("test_bookmark")
|
|
|
|
# Change styling
|
|
self.reader.set_font_size(1.5)
|
|
self.reader.set_line_spacing(12)
|
|
|
|
# Navigate away
|
|
for _ in range(5):
|
|
self.reader.next_page()
|
|
|
|
# Jump back to bookmark
|
|
page = self.reader.load_position("test_bookmark")
|
|
|
|
self.assertIsNotNone(page)
|
|
|
|
# Cleanup
|
|
self.reader.delete_position("test_bookmark")
|
|
|
|
def test_chapter_navigation_after_font_change(self):
|
|
"""Test chapter navigation after changing font size"""
|
|
self.reader.set_font_size(2.0)
|
|
|
|
chapters = self.reader.get_chapters()
|
|
|
|
if len(chapters) > 0:
|
|
page = self.reader.jump_to_chapter(0)
|
|
self.assertIsNotNone(page)
|
|
|
|
|
|
class TestEbookReaderNavigation(unittest.TestCase):
|
|
"""Test EbookReader navigation functionality (existing tests)"""
|
|
|
|
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 compare_images(self, img1: Image.Image, img2: Image.Image) -> bool:
|
|
"""
|
|
Check if two PIL Images are pixel-perfect identical.
|
|
"""
|
|
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.
|
|
"""
|
|
reader = EbookReader(
|
|
page_size=(800, 1000),
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
|
|
success = reader.load_epub(self.epub_path)
|
|
self.assertTrue(success, "Failed to load test EPUB")
|
|
self.assertTrue(reader.is_loaded(), "Reader should be loaded")
|
|
|
|
initial_page = reader.get_current_page()
|
|
self.assertIsNotNone(initial_page, "Initial page should not be None")
|
|
|
|
initial_position = reader.manager.current_position.copy()
|
|
|
|
forward_pages = [initial_page]
|
|
forward_positions = [initial_position]
|
|
pages_to_navigate = 20
|
|
|
|
for i in range(pages_to_navigate):
|
|
page = reader.next_page()
|
|
if page is None:
|
|
break
|
|
forward_pages.append(page)
|
|
forward_positions.append(reader.manager.current_position.copy())
|
|
|
|
actual_pages_navigated = len(forward_pages) - 1
|
|
|
|
backward_pages = []
|
|
|
|
for i in range(len(forward_positions) - 1, -1, -1):
|
|
position = forward_positions[i]
|
|
page_obj = reader.manager.jump_to_position(position)
|
|
page_img = page_obj.render()
|
|
backward_pages.append(page_img)
|
|
|
|
final_page = backward_pages[-1]
|
|
|
|
self.assertTrue(
|
|
self.compare_images(initial_page, final_page),
|
|
"First page should be identical after forward/backward navigation"
|
|
)
|
|
|
|
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,
|
|
buffer_size=0
|
|
)
|
|
|
|
success = reader.load_epub(self.epub_path)
|
|
self.assertTrue(success, "Failed to load test EPUB")
|
|
|
|
# Try to go backward from first page
|
|
page = reader.previous_page()
|
|
# Should return None or stay on same page
|
|
|
|
# Navigate forward until end
|
|
pages_forward = 0
|
|
max_pages = 100
|
|
while pages_forward < max_pages:
|
|
page = reader.next_page()
|
|
if page is None:
|
|
break
|
|
pages_forward += 1
|
|
|
|
# Try to go forward from last page
|
|
page = reader.next_page()
|
|
self.assertIsNone(page, "Should return None at end of document")
|
|
|
|
reader.close()
|
|
|
|
|
|
class TestEbookReaderPositionManagement(unittest.TestCase):
|
|
"""Test position tracking and bookmark 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}")
|
|
|
|
self.reader = EbookReader(
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
self.reader.load_epub(self.epub_path)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
self.reader.close()
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_position_save_and_load(self):
|
|
"""Test saving and loading positions"""
|
|
# Navigate to a position
|
|
for _ in range(3):
|
|
self.reader.next_page()
|
|
|
|
# Save position
|
|
success = self.reader.save_position("test_pos")
|
|
self.assertTrue(success)
|
|
|
|
# Navigate away
|
|
for _ in range(5):
|
|
self.reader.next_page()
|
|
|
|
# Load saved position
|
|
page = self.reader.load_position("test_pos")
|
|
self.assertIsNotNone(page)
|
|
|
|
def test_list_saved_positions(self):
|
|
"""Test listing saved positions"""
|
|
self.reader.save_position("pos1")
|
|
self.reader.save_position("pos2")
|
|
|
|
positions = self.reader.list_saved_positions()
|
|
|
|
self.assertIn("pos1", positions)
|
|
self.assertIn("pos2", positions)
|
|
|
|
def test_delete_position(self):
|
|
"""Test deleting a saved position"""
|
|
self.reader.save_position("temp_pos")
|
|
|
|
success = self.reader.delete_position("temp_pos")
|
|
self.assertTrue(success)
|
|
|
|
positions = self.reader.list_saved_positions()
|
|
self.assertNotIn("temp_pos", positions)
|
|
|
|
def test_delete_nonexistent_position(self):
|
|
"""Test deleting a non-existent position"""
|
|
success = self.reader.delete_position("nonexistent")
|
|
self.assertFalse(success)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|