pyWebLayout/tests/layout/test_ereader_manager.py

785 lines
25 KiB
Python

"""
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"])