This commit is contained in:
parent
5e3170497e
commit
84229ad4da
302
tests/io_tests/test_base_reader.py
Normal file
302
tests/io_tests/test_base_reader.py
Normal file
@ -0,0 +1,302 @@
|
||||
"""
|
||||
Tests for pyWebLayout.io.readers.base module.
|
||||
|
||||
Tests the base reader classes and their functionality.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pyWebLayout.io.readers.base import (
|
||||
BaseReader,
|
||||
MetadataReader,
|
||||
StructureReader,
|
||||
ContentReader,
|
||||
ResourceReader,
|
||||
CompositeReader
|
||||
)
|
||||
from pyWebLayout.abstract.document import Document
|
||||
|
||||
|
||||
# Concrete implementations for testing
|
||||
|
||||
class ConcreteBaseReader(BaseReader):
|
||||
"""Test implementation of BaseReader."""
|
||||
|
||||
def can_read(self, source):
|
||||
return isinstance(source, str) and source.endswith('.test')
|
||||
|
||||
def read(self, source, **options):
|
||||
doc = Document()
|
||||
doc.set_metadata('source', source)
|
||||
return doc
|
||||
|
||||
|
||||
class ConcreteMetadataReader(MetadataReader):
|
||||
"""Test implementation of MetadataReader."""
|
||||
|
||||
def extract_metadata(self, source, document):
|
||||
metadata = {
|
||||
'title': 'Test Title',
|
||||
'author': 'Test Author'
|
||||
}
|
||||
document.set_metadata('title', metadata['title'])
|
||||
document.set_metadata('author', metadata['author'])
|
||||
return metadata
|
||||
|
||||
|
||||
class ConcreteStructureReader(StructureReader):
|
||||
"""Test implementation of StructureReader."""
|
||||
|
||||
def extract_structure(self, source, document):
|
||||
return ['heading1', 'heading2']
|
||||
|
||||
|
||||
class ConcreteContentReader(ContentReader):
|
||||
"""Test implementation of ContentReader."""
|
||||
|
||||
def extract_content(self, source, document):
|
||||
return "Test content"
|
||||
|
||||
|
||||
class ConcreteResourceReader(ResourceReader):
|
||||
"""Test implementation of ResourceReader."""
|
||||
|
||||
def extract_resources(self, source, document):
|
||||
resources = {
|
||||
'image1.png': b'fake image data',
|
||||
'style.css': 'fake css'
|
||||
}
|
||||
for name, data in resources.items():
|
||||
document.add_resource(name, data)
|
||||
return resources
|
||||
|
||||
|
||||
class ConcreteCompositeReader(CompositeReader):
|
||||
"""Test implementation of CompositeReader."""
|
||||
|
||||
def can_read(self, source):
|
||||
return True
|
||||
|
||||
|
||||
# Test Cases
|
||||
|
||||
class TestBaseReaderOptions:
|
||||
"""Test BaseReader options functionality."""
|
||||
|
||||
def test_set_and_get_option(self):
|
||||
"""Test setting and getting options."""
|
||||
reader = ConcreteBaseReader()
|
||||
reader.set_option('font_size', 12)
|
||||
assert reader.get_option('font_size') == 12
|
||||
|
||||
def test_get_option_with_default(self):
|
||||
"""Test getting option with default value."""
|
||||
reader = ConcreteBaseReader()
|
||||
assert reader.get_option('nonexistent', 'default_value') == 'default_value'
|
||||
|
||||
def test_get_option_without_default(self):
|
||||
"""Test getting nonexistent option without default."""
|
||||
reader = ConcreteBaseReader()
|
||||
assert reader.get_option('nonexistent') is None
|
||||
|
||||
def test_multiple_options(self):
|
||||
"""Test setting multiple options."""
|
||||
reader = ConcreteBaseReader()
|
||||
reader.set_option('font_size', 12)
|
||||
reader.set_option('line_height', 1.5)
|
||||
reader.set_option('color', 'black')
|
||||
|
||||
assert reader.get_option('font_size') == 12
|
||||
assert reader.get_option('line_height') == 1.5
|
||||
assert reader.get_option('color') == 'black'
|
||||
|
||||
|
||||
class TestBaseReaderConcrete:
|
||||
"""Test concrete BaseReader implementation."""
|
||||
|
||||
def test_can_read_valid_source(self):
|
||||
"""Test can_read with valid source."""
|
||||
reader = ConcreteBaseReader()
|
||||
assert reader.can_read('document.test') is True
|
||||
|
||||
def test_can_read_invalid_source(self):
|
||||
"""Test can_read with invalid source."""
|
||||
reader = ConcreteBaseReader()
|
||||
assert reader.can_read('document.html') is False
|
||||
|
||||
def test_read_creates_document(self):
|
||||
"""Test read creates a Document."""
|
||||
reader = ConcreteBaseReader()
|
||||
doc = reader.read('test.test')
|
||||
assert isinstance(doc, Document)
|
||||
assert doc.get_metadata('source') == 'test.test'
|
||||
|
||||
|
||||
class TestMetadataReaderConcrete:
|
||||
"""Test concrete MetadataReader implementation."""
|
||||
|
||||
def test_extract_metadata(self):
|
||||
"""Test metadata extraction."""
|
||||
reader = ConcreteMetadataReader()
|
||||
doc = Document()
|
||||
metadata = reader.extract_metadata('source', doc)
|
||||
|
||||
assert metadata['title'] == 'Test Title'
|
||||
assert metadata['author'] == 'Test Author'
|
||||
assert doc.get_metadata('title') == 'Test Title'
|
||||
assert doc.get_metadata('author') == 'Test Author'
|
||||
|
||||
|
||||
class TestStructureReaderConcrete:
|
||||
"""Test concrete StructureReader implementation."""
|
||||
|
||||
def test_extract_structure(self):
|
||||
"""Test structure extraction."""
|
||||
reader = ConcreteStructureReader()
|
||||
doc = Document()
|
||||
structure = reader.extract_structure('source', doc)
|
||||
|
||||
assert isinstance(structure, list)
|
||||
assert len(structure) == 2
|
||||
assert structure[0] == 'heading1'
|
||||
assert structure[1] == 'heading2'
|
||||
|
||||
|
||||
class TestContentReaderConcrete:
|
||||
"""Test concrete ContentReader implementation."""
|
||||
|
||||
def test_extract_content(self):
|
||||
"""Test content extraction."""
|
||||
reader = ConcreteContentReader()
|
||||
doc = Document()
|
||||
content = reader.extract_content('source', doc)
|
||||
|
||||
assert content == "Test content"
|
||||
|
||||
|
||||
class TestResourceReaderConcrete:
|
||||
"""Test concrete ResourceReader implementation."""
|
||||
|
||||
def test_extract_resources(self):
|
||||
"""Test resource extraction."""
|
||||
reader = ConcreteResourceReader()
|
||||
doc = Document()
|
||||
resources = reader.extract_resources('source', doc)
|
||||
|
||||
assert isinstance(resources, dict)
|
||||
assert 'image1.png' in resources
|
||||
assert 'style.css' in resources
|
||||
assert doc.get_resource('image1.png') == b'fake image data'
|
||||
assert doc.get_resource('style.css') == 'fake css'
|
||||
|
||||
|
||||
class TestCompositeReader:
|
||||
"""Test CompositeReader functionality."""
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test composite reader initialization."""
|
||||
reader = ConcreteCompositeReader()
|
||||
assert reader._metadata_reader is None
|
||||
assert reader._structure_reader is None
|
||||
assert reader._content_reader is None
|
||||
assert reader._resource_reader is None
|
||||
|
||||
def test_set_metadata_reader(self):
|
||||
"""Test setting metadata reader."""
|
||||
reader = ConcreteCompositeReader()
|
||||
metadata_reader = ConcreteMetadataReader()
|
||||
reader.set_metadata_reader(metadata_reader)
|
||||
assert reader._metadata_reader is metadata_reader
|
||||
|
||||
def test_set_structure_reader(self):
|
||||
"""Test setting structure reader."""
|
||||
reader = ConcreteCompositeReader()
|
||||
structure_reader = ConcreteStructureReader()
|
||||
reader.set_structure_reader(structure_reader)
|
||||
assert reader._structure_reader is structure_reader
|
||||
|
||||
def test_set_content_reader(self):
|
||||
"""Test setting content reader."""
|
||||
reader = ConcreteCompositeReader()
|
||||
content_reader = ConcreteContentReader()
|
||||
reader.set_content_reader(content_reader)
|
||||
assert reader._content_reader is content_reader
|
||||
|
||||
def test_set_resource_reader(self):
|
||||
"""Test setting resource reader."""
|
||||
reader = ConcreteCompositeReader()
|
||||
resource_reader = ConcreteResourceReader()
|
||||
reader.set_resource_reader(resource_reader)
|
||||
assert reader._resource_reader is resource_reader
|
||||
|
||||
def test_read_with_all_readers(self):
|
||||
"""Test reading with all readers configured."""
|
||||
reader = ConcreteCompositeReader()
|
||||
reader.set_metadata_reader(ConcreteMetadataReader())
|
||||
reader.set_structure_reader(ConcreteStructureReader())
|
||||
reader.set_content_reader(ConcreteContentReader())
|
||||
reader.set_resource_reader(ConcreteResourceReader())
|
||||
|
||||
doc = reader.read('test_source')
|
||||
|
||||
# Verify metadata was extracted
|
||||
assert doc.get_metadata('title') == 'Test Title'
|
||||
assert doc.get_metadata('author') == 'Test Author'
|
||||
|
||||
# Verify resources were extracted
|
||||
assert doc.get_resource('image1.png') == b'fake image data'
|
||||
assert doc.get_resource('style.css') == 'fake css'
|
||||
|
||||
def test_read_with_no_readers(self):
|
||||
"""Test reading with no readers configured."""
|
||||
reader = ConcreteCompositeReader()
|
||||
doc = reader.read('test_source')
|
||||
|
||||
# Should create an empty document
|
||||
assert isinstance(doc, Document)
|
||||
|
||||
def test_read_with_only_metadata_reader(self):
|
||||
"""Test reading with only metadata reader."""
|
||||
reader = ConcreteCompositeReader()
|
||||
reader.set_metadata_reader(ConcreteMetadataReader())
|
||||
|
||||
doc = reader.read('test_source')
|
||||
assert doc.get_metadata('title') == 'Test Title'
|
||||
|
||||
def test_read_with_options(self):
|
||||
"""Test reading with options."""
|
||||
reader = ConcreteCompositeReader()
|
||||
reader.set_metadata_reader(ConcreteMetadataReader())
|
||||
|
||||
doc = reader.read('test_source', font_size=14, encoding='utf-8')
|
||||
|
||||
# Verify options were stored
|
||||
assert reader.get_option('font_size') == 14
|
||||
assert reader.get_option('encoding') == 'utf-8'
|
||||
|
||||
def test_can_read_implemented(self):
|
||||
"""Test that can_read is implemented in ConcreteCompositeReader."""
|
||||
reader = ConcreteCompositeReader()
|
||||
assert reader.can_read('test_source') is True
|
||||
|
||||
|
||||
class TestCompositeReaderIntegration:
|
||||
"""Integration tests for CompositeReader."""
|
||||
|
||||
def test_full_document_reading_workflow(self):
|
||||
"""Test complete document reading workflow."""
|
||||
# Create and configure composite reader
|
||||
reader = ConcreteCompositeReader()
|
||||
reader.set_metadata_reader(ConcreteMetadataReader())
|
||||
reader.set_structure_reader(ConcreteStructureReader())
|
||||
reader.set_content_reader(ConcreteContentReader())
|
||||
reader.set_resource_reader(ConcreteResourceReader())
|
||||
|
||||
# Read document with options
|
||||
doc = reader.read('complex_document.test', font_size=16, page_width=800)
|
||||
|
||||
# Verify all components worked together
|
||||
assert doc.get_metadata('title') == 'Test Title'
|
||||
assert doc.get_metadata('author') == 'Test Author'
|
||||
assert doc.get_resource('image1.png') is not None
|
||||
assert reader.get_option('font_size') == 16
|
||||
assert reader.get_option('page_width') == 800
|
||||
@ -1,8 +1,17 @@
|
||||
"""
|
||||
Tests for the EbookReader application interface.
|
||||
Comprehensive tests for the EbookReader application interface.
|
||||
|
||||
Tests the high-level EbookReader API including bidirectional navigation,
|
||||
image rendering consistency, and position management.
|
||||
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
|
||||
@ -11,19 +20,645 @@ import shutil
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import os
|
||||
|
||||
from pyWebLayout.layout.ereader_application import EbookReader
|
||||
from pyWebLayout.layout.ereader_application import EbookReader, create_ebook_reader
|
||||
|
||||
|
||||
class TestEbookReaderNavigation(unittest.TestCase):
|
||||
"""Test EbookReader navigation functionality"""
|
||||
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"
|
||||
|
||||
# Verify test EPUB exists
|
||||
if not Path(self.epub_path).exists():
|
||||
self.skipTest(f"Test EPUB not found at {self.epub_path}")
|
||||
|
||||
@ -34,13 +669,6 @@ class TestEbookReaderNavigation(unittest.TestCase):
|
||||
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
|
||||
@ -57,145 +685,76 @@ class TestEbookReaderNavigation(unittest.TestCase):
|
||||
"""
|
||||
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
|
||||
buffer_size=0
|
||||
)
|
||||
|
||||
# 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.
|
||||
"""
|
||||
"""Test navigation behavior at document boundaries."""
|
||||
reader = EbookReader(
|
||||
page_size=(800, 1000),
|
||||
bookmarks_dir=self.temp_dir
|
||||
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
|
||||
# Should stay on first page or return None gracefully
|
||||
page = reader.previous_page()
|
||||
# Behavior depends on implementation - could be None or same page
|
||||
# Should return None or stay on same page
|
||||
|
||||
# Navigate forward until end
|
||||
pages_forward = 0
|
||||
max_pages = 100 # Safety limit
|
||||
max_pages = 100
|
||||
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")
|
||||
@ -203,8 +762,8 @@ class TestEbookReaderNavigation(unittest.TestCase):
|
||||
reader.close()
|
||||
|
||||
|
||||
class TestEbookReaderFeatures(unittest.TestCase):
|
||||
"""Test other EbookReader features"""
|
||||
class TestEbookReaderPositionManagement(unittest.TestCase):
|
||||
"""Test position tracking and bookmark features"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test environment"""
|
||||
@ -213,92 +772,60 @@ class TestEbookReaderFeatures(unittest.TestCase):
|
||||
|
||||
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_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
|
||||
def test_position_save_and_load(self):
|
||||
"""Test saving and loading positions"""
|
||||
# Navigate to a position
|
||||
for _ in range(3):
|
||||
reader.next_page()
|
||||
self.reader.next_page()
|
||||
|
||||
# Save position
|
||||
success = reader.save_position("test_position")
|
||||
success = self.reader.save_position("test_pos")
|
||||
self.assertTrue(success)
|
||||
|
||||
# Navigate away
|
||||
for _ in range(5):
|
||||
reader.next_page()
|
||||
self.reader.next_page()
|
||||
|
||||
# Load saved position
|
||||
page = reader.load_position("test_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")
|
||||
|
||||
# List positions
|
||||
positions = reader.list_saved_positions()
|
||||
self.assertIn("test_position", positions)
|
||||
positions = self.reader.list_saved_positions()
|
||||
|
||||
# Delete position
|
||||
success = reader.delete_position("test_position")
|
||||
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)
|
||||
|
||||
reader.close()
|
||||
positions = self.reader.list_saved_positions()
|
||||
self.assertNotIn("temp_pos", positions)
|
||||
|
||||
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()
|
||||
def test_delete_nonexistent_position(self):
|
||||
"""Test deleting a non-existent position"""
|
||||
success = self.reader.delete_position("nonexistent")
|
||||
self.assertFalse(success)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user