""" Tests for the ereader manager components. This module tests: - BookmarkManager: Bookmark and position persistence - EreaderLayoutManager: High-level ereader interface - create_ereader_manager: Convenience function """ import pytest import json from pathlib import Path from pyWebLayout.layout.ereader_manager import ( BookmarkManager, EreaderLayoutManager, create_ereader_manager ) from pyWebLayout.layout.ereader_layout import RenderingPosition 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 # ============================================================================ # Fixtures # ============================================================================ @pytest.fixture def temp_bookmarks_dir(tmp_path): """Create a temporary directory for bookmarks.""" bookmarks_dir = tmp_path / "bookmarks" bookmarks_dir.mkdir() return str(bookmarks_dir) @pytest.fixture def sample_font(): """Create a standard font for testing.""" return Font(font_size=12, colour=(0, 0, 0)) @pytest.fixture def sample_blocks(sample_font): """Create sample document blocks.""" blocks = [] # Heading h1 = Heading(HeadingLevel.H1, sample_font) h1.add_word(Word("Chapter", sample_font)) h1.add_word(Word("One", sample_font)) blocks.append(h1) # Paragraphs for i in range(5): p = Paragraph(sample_font) p.add_word(Word("Paragraph", sample_font)) p.add_word(Word(f"{i}", sample_font)) blocks.append(p) return blocks @pytest.fixture def sample_position(): """Create a sample rendering position.""" return RenderingPosition( chapter_index=1, block_index=5, word_index=10 ) # ============================================================================ # BookmarkManager Tests # ============================================================================ class TestBookmarkManager: """Tests for the BookmarkManager class.""" def test_initialization(self, temp_bookmarks_dir): """Test BookmarkManager initialization.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) assert manager.document_id == "test_doc" assert manager.bookmarks_dir == Path(temp_bookmarks_dir) assert manager.bookmarks_file.exists() or True # May not exist yet assert isinstance(manager._bookmarks, dict) def test_initialization_creates_directory(self, tmp_path): """Test that initialization creates bookmarks directory if needed.""" bookmarks_dir = str(tmp_path / "new_bookmarks") BookmarkManager("test_doc", bookmarks_dir) assert Path(bookmarks_dir).exists() assert Path(bookmarks_dir).is_dir() def test_add_bookmark(self, temp_bookmarks_dir, sample_position): """Test adding a bookmark.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) manager.add_bookmark("Chapter 1", sample_position) # Verify bookmark was added bookmark = manager.get_bookmark("Chapter 1") assert bookmark is not None assert bookmark == sample_position def test_add_multiple_bookmarks(self, temp_bookmarks_dir): """Test adding multiple bookmarks.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) pos1 = RenderingPosition(chapter_index=1, block_index=5) pos2 = RenderingPosition(chapter_index=2, block_index=10) pos3 = RenderingPosition(chapter_index=3, block_index=15) manager.add_bookmark("Bookmark 1", pos1) manager.add_bookmark("Bookmark 2", pos2) manager.add_bookmark("Bookmark 3", pos3) assert len(manager._bookmarks) == 3 assert manager.get_bookmark("Bookmark 1") == pos1 assert manager.get_bookmark("Bookmark 2") == pos2 assert manager.get_bookmark("Bookmark 3") == pos3 def test_remove_bookmark(self, temp_bookmarks_dir, sample_position): """Test removing a bookmark.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) manager.add_bookmark("Test", sample_position) assert manager.get_bookmark("Test") is not None result = manager.remove_bookmark("Test") assert result is True assert manager.get_bookmark("Test") is None def test_remove_nonexistent_bookmark(self, temp_bookmarks_dir): """Test removing a bookmark that doesn't exist.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) result = manager.remove_bookmark("Nonexistent") assert result is False def test_get_bookmark(self, temp_bookmarks_dir, sample_position): """Test getting a bookmark.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) manager.add_bookmark("Test", sample_position) retrieved = manager.get_bookmark("Test") assert retrieved == sample_position def test_get_nonexistent_bookmark(self, temp_bookmarks_dir): """Test getting a bookmark that doesn't exist.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) result = manager.get_bookmark("Nonexistent") assert result is None def test_list_bookmarks(self, temp_bookmarks_dir): """Test listing all bookmarks.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) pos1 = RenderingPosition(chapter_index=1, block_index=5) pos2 = RenderingPosition(chapter_index=2, block_index=10) manager.add_bookmark("First", pos1) manager.add_bookmark("Second", pos2) bookmarks = manager.list_bookmarks() assert len(bookmarks) == 2 assert ("First", pos1) in bookmarks assert ("Second", pos2) in bookmarks def test_list_bookmarks_empty(self, temp_bookmarks_dir): """Test listing bookmarks when none exist.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) bookmarks = manager.list_bookmarks() assert bookmarks == [] def test_save_reading_position(self, temp_bookmarks_dir, sample_position): """Test saving current reading position.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) manager.save_reading_position(sample_position) # Verify file was created assert manager.position_file.exists() # Verify content with open(manager.position_file, 'r') as f: data = json.load(f) assert data['chapter_index'] == sample_position.chapter_index assert data['block_index'] == sample_position.block_index def test_load_reading_position(self, temp_bookmarks_dir, sample_position): """Test loading saved reading position.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) manager.save_reading_position(sample_position) loaded = manager.load_reading_position() assert loaded is not None assert loaded == sample_position def test_load_reading_position_nonexistent(self, temp_bookmarks_dir): """Test loading reading position when file doesn't exist.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) loaded = manager.load_reading_position() assert loaded is None def test_bookmark_persistence(self, temp_bookmarks_dir, sample_position): """Test that bookmarks persist across manager instances.""" # Create first manager and add bookmark manager1 = BookmarkManager("test_doc", temp_bookmarks_dir) manager1.add_bookmark("Persistent", sample_position) # Create second manager and verify bookmark exists manager2 = BookmarkManager("test_doc", temp_bookmarks_dir) loaded = manager2.get_bookmark("Persistent") assert loaded is not None assert loaded == sample_position def test_position_persistence(self, temp_bookmarks_dir, sample_position): """Test that reading position persists across manager instances.""" # Save with first manager manager1 = BookmarkManager("test_doc", temp_bookmarks_dir) manager1.save_reading_position(sample_position) # Load with second manager manager2 = BookmarkManager("test_doc", temp_bookmarks_dir) loaded = manager2.load_reading_position() assert loaded == sample_position def test_corrupt_bookmarks_file(self, temp_bookmarks_dir): """Test handling of corrupt bookmarks file.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) # Write corrupt JSON with open(manager.bookmarks_file, 'w') as f: f.write("{ invalid json }}") # Should handle gracefully and load empty bookmarks manager2 = BookmarkManager("test_doc", temp_bookmarks_dir) assert manager2._bookmarks == {} def test_corrupt_position_file(self, temp_bookmarks_dir): """Test handling of corrupt position file.""" manager = BookmarkManager("test_doc", temp_bookmarks_dir) # Write corrupt JSON with open(manager.position_file, 'w') as f: f.write("{ invalid json }}") # Should handle gracefully loaded = manager.load_reading_position() assert loaded is None # ============================================================================ # EreaderLayoutManager Tests # ============================================================================ class TestEreaderLayoutManager: """Tests for the EreaderLayoutManager class.""" def test_initialization(self, sample_blocks, temp_bookmarks_dir): """Test EreaderLayoutManager initialization.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), document_id="test_doc", bookmarks_dir=temp_bookmarks_dir ) assert manager.blocks == sample_blocks assert manager.page_size == (800, 600) assert manager.document_id == "test_doc" assert manager.font_scale == 1.0 assert isinstance(manager.current_position, RenderingPosition) def test_initialization_with_custom_page_style( self, sample_blocks, temp_bookmarks_dir): """Test initialization with custom page style.""" custom_style = PageStyle() manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), page_style=custom_style, bookmarks_dir=temp_bookmarks_dir ) assert manager.page_style == custom_style def test_initialization_loads_saved_position( self, sample_blocks, temp_bookmarks_dir): """Test that initialization loads saved reading position.""" # Save a position first bookmark_mgr = BookmarkManager("test_doc", temp_bookmarks_dir) saved_pos = RenderingPosition(chapter_index=2, block_index=10) bookmark_mgr.save_reading_position(saved_pos) # Create manager - should load saved position manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), document_id="test_doc", bookmarks_dir=temp_bookmarks_dir ) assert manager.current_position == saved_pos def test_get_current_page(self, sample_blocks, temp_bookmarks_dir): """Test getting the current page.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) page = manager.get_current_page() from pyWebLayout.concrete.page import Page assert isinstance(page, Page) def test_next_page(self, sample_blocks, temp_bookmarks_dir): """Test advancing to next page.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) initial_pos = manager.current_position.copy() next_page = manager.next_page() # Position should have advanced assert manager.current_position != initial_pos or next_page is None def test_next_page_at_end(self, sample_blocks, temp_bookmarks_dir): """Test next_page when at end of document.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) # Move to end manager.current_position = RenderingPosition( block_index=len(sample_blocks) + 100 ) result = manager.next_page() # Should return None at end assert result is None def test_previous_page(self, sample_blocks, temp_bookmarks_dir): """Test going to previous page.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) # Move forward first manager.current_position = RenderingPosition(block_index=3) prev_page = manager.previous_page() # Should return a page or None if at beginning from pyWebLayout.concrete.page import Page assert prev_page is None or isinstance(prev_page, Page) def test_previous_page_at_beginning(self, sample_blocks, temp_bookmarks_dir): """Test previous_page when at beginning of document.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) # At beginning manager.current_position = RenderingPosition() result = manager.previous_page() assert result is None def test_jump_to_position(self, sample_blocks, temp_bookmarks_dir): """Test jumping to a specific position.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) target_pos = RenderingPosition(chapter_index=1, block_index=3) page = manager.jump_to_position(target_pos) assert manager.current_position == target_pos from pyWebLayout.concrete.page import Page assert isinstance(page, Page) def test_jump_to_chapter(self, sample_blocks, temp_bookmarks_dir): """Test jumping to a chapter by title.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) page = manager.jump_to_chapter("Chapter One") # May return page or None if chapter not found from pyWebLayout.concrete.page import Page assert page is None or isinstance(page, Page) def test_jump_to_chapter_not_found(self, sample_blocks, temp_bookmarks_dir): """Test jumping to non-existent chapter.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) result = manager.jump_to_chapter("Nonexistent Chapter") assert result is None def test_jump_to_chapter_index(self, sample_blocks, temp_bookmarks_dir): """Test jumping to chapter by index.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) page = manager.jump_to_chapter_index(0) from pyWebLayout.concrete.page import Page assert page is None or isinstance(page, Page) def test_jump_to_chapter_index_invalid(self, sample_blocks, temp_bookmarks_dir): """Test jumping to invalid chapter index.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) result = manager.jump_to_chapter_index(999) assert result is None def test_set_font_scale(self, sample_blocks, temp_bookmarks_dir): """Test changing font scale.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) page = manager.set_font_scale(1.5) assert manager.font_scale == 1.5 from pyWebLayout.concrete.page import Page assert isinstance(page, Page) def test_set_font_scale_same(self, sample_blocks, temp_bookmarks_dir): """Test setting font scale to same value.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) manager.set_font_scale(1.0) assert manager.font_scale == 1.0 def test_get_font_scale(self, sample_blocks, temp_bookmarks_dir): """Test getting current font scale.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) scale = manager.get_font_scale() assert scale == 1.0 def test_get_table_of_contents(self, sample_blocks, temp_bookmarks_dir): """Test getting table of contents.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) toc = manager.get_table_of_contents() assert isinstance(toc, list) def test_get_current_chapter(self, sample_blocks, temp_bookmarks_dir): """Test getting current chapter info.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) chapter = manager.get_current_chapter() # May be None or ChapterInfo assert chapter is None or hasattr(chapter, 'title') def test_add_bookmark(self, sample_blocks, temp_bookmarks_dir): """Test adding a bookmark at current position.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) result = manager.add_bookmark("Test Bookmark") assert result is True # Verify bookmark was added bookmark = manager.bookmark_manager.get_bookmark("Test Bookmark") assert bookmark is not None def test_remove_bookmark(self, sample_blocks, temp_bookmarks_dir): """Test removing a bookmark.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) manager.add_bookmark("Test") result = manager.remove_bookmark("Test") assert result is True def test_jump_to_bookmark(self, sample_blocks, temp_bookmarks_dir): """Test jumping to a bookmark.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) # Add bookmark at specific position manager.current_position = RenderingPosition(block_index=3) manager.add_bookmark("Test Position") # Move away manager.current_position = RenderingPosition(block_index=0) # Jump to bookmark page = manager.jump_to_bookmark("Test Position") from pyWebLayout.concrete.page import Page assert isinstance(page, Page) assert manager.current_position.block_index == 3 def test_jump_to_nonexistent_bookmark(self, sample_blocks, temp_bookmarks_dir): """Test jumping to non-existent bookmark.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) result = manager.jump_to_bookmark("Nonexistent") assert result is None def test_list_bookmarks(self, sample_blocks, temp_bookmarks_dir): """Test listing all bookmarks.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) manager.add_bookmark("Bookmark 1") manager.add_bookmark("Bookmark 2") bookmarks = manager.list_bookmarks() assert len(bookmarks) == 2 def test_get_reading_progress(self, sample_blocks, temp_bookmarks_dir): """Test getting reading progress percentage.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) progress = manager.get_reading_progress() assert 0.0 <= progress <= 1.0 def test_get_reading_progress_at_end(self, sample_blocks, temp_bookmarks_dir): """Test reading progress at end of document.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) manager.current_position = RenderingPosition( block_index=len(sample_blocks) - 1 ) progress = manager.get_reading_progress() assert progress == 1.0 def test_get_reading_progress_empty_document(self, temp_bookmarks_dir): """Test reading progress with empty document.""" manager = EreaderLayoutManager( [], page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) progress = manager.get_reading_progress() assert progress == 0.0 def test_get_position_info(self, sample_blocks, temp_bookmarks_dir): """Test getting detailed position information.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) info = manager.get_position_info() assert isinstance(info, dict) assert 'position' in info assert 'chapter' in info assert 'progress' in info assert 'font_scale' in info assert 'page_size' in info def test_get_cache_stats(self, sample_blocks, temp_bookmarks_dir): """Test getting cache statistics.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) stats = manager.get_cache_stats() assert isinstance(stats, dict) def test_position_changed_callback(self, sample_blocks, temp_bookmarks_dir): """Test position changed callback.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) callback_called = [] def callback(position): callback_called.append(position) manager.set_position_changed_callback(callback) manager.jump_to_position(RenderingPosition(block_index=3)) assert len(callback_called) > 0 def test_chapter_changed_callback(self, sample_blocks, temp_bookmarks_dir): """Test chapter changed callback.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), bookmarks_dir=temp_bookmarks_dir ) callback_called = [] def callback(chapter): callback_called.append(chapter) manager.set_chapter_changed_callback(callback) manager.jump_to_position(RenderingPosition(block_index=3)) assert len(callback_called) > 0 def test_shutdown(self, sample_blocks, temp_bookmarks_dir): """Test shutdown saves position.""" manager = EreaderLayoutManager( sample_blocks, page_size=(800, 600), document_id="test_doc", bookmarks_dir=temp_bookmarks_dir ) test_pos = RenderingPosition(block_index=5) manager.current_position = test_pos manager.shutdown() # Verify position was saved bookmark_mgr = BookmarkManager("test_doc", temp_bookmarks_dir) loaded = bookmark_mgr.load_reading_position() assert loaded == test_pos # ============================================================================ # Convenience Function Tests # ============================================================================ class TestCreateEreaderManager: """Tests for the create_ereader_manager convenience function.""" def test_create_with_defaults(self, sample_blocks): """Test creating manager with default parameters.""" manager = create_ereader_manager( sample_blocks, page_size=(800, 600) ) assert isinstance(manager, EreaderLayoutManager) assert manager.blocks == sample_blocks assert manager.page_size == (800, 600) assert manager.document_id == "default" def test_create_with_custom_document_id(self, sample_blocks): """Test creating manager with custom document ID.""" manager = create_ereader_manager( sample_blocks, page_size=(800, 600), document_id="custom_doc" ) assert manager.document_id == "custom_doc" def test_create_with_kwargs(self, sample_blocks, tmp_path): """Test creating manager with additional kwargs.""" bookmarks_dir = str(tmp_path / "custom_bookmarks") manager = create_ereader_manager( sample_blocks, page_size=(800, 600), document_id="test", buffer_size=10, bookmarks_dir=bookmarks_dir ) assert isinstance(manager, EreaderLayoutManager) if __name__ == "__main__": pytest.main([__file__, "-v"])