Compare commits

..

2 Commits

Author SHA1 Message Date
13d20c28c5 Api for changing sizes dynamically
All checks were successful
Python CI / test (push) Successful in 6m54s
2025-11-08 18:22:58 +01:00
f8baf155e9 ereader manager tests 2025-11-08 12:59:57 +01:00
5 changed files with 903 additions and 10 deletions

View File

@ -75,6 +75,15 @@ def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pr
)
text_align = concrete_style.text_align
# Apply page-level word spacing override if specified
if hasattr(page.style, 'word_spacing') and isinstance(page.style.word_spacing, int) and page.style.word_spacing > 0:
# Add the page-level word spacing to both min and max constraints
min_ws, max_ws = word_spacing_constraints
word_spacing_constraints = (
min_ws + page.style.word_spacing,
max_ws + page.style.word_spacing
)
# Apply alignment override if provided
if alignment_override is not None:
text_align = alignment_override
@ -91,9 +100,14 @@ def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pr
background=font.background
)
# Calculate baseline-to-baseline spacing using line spacing multiplier
# Calculate baseline-to-baseline spacing: font size + additional line spacing
# This is the vertical distance between baselines of consecutive lines
baseline_spacing = int(font.font_size * page.style.line_spacing_multiplier)
# Formula: baseline_spacing = font_size + line_spacing (absolute pixels)
line_spacing_value = getattr(page.style, 'line_spacing', 5)
# Ensure line_spacing is an int (could be Mock in tests)
if not isinstance(line_spacing_value, int):
line_spacing_value = 5
baseline_spacing = font.font_size + line_spacing_value
# Get font metrics for boundary checking
ascent, descent = font.font.getmetrics()

View File

@ -252,6 +252,11 @@ class BidirectionalLayouter:
# Block doesn't fit, we're done with this page
break
# Add inter-block spacing after successfully laying out a block
# Only add if we're not at the end of the document and there's space
if new_pos.block_index < len(self.blocks):
page._current_y_offset += self.page_style.inter_block_spacing
# Ensure new position doesn't go beyond bounds
if new_pos.block_index >= len(self.blocks):
# We've reached the end of the document

View File

@ -342,6 +342,96 @@ class EreaderLayoutManager:
"""Get the current font scale"""
return self.font_scale
def increase_line_spacing(self, amount: int = 2) -> Page:
"""
Increase line spacing and re-render current page.
Args:
amount: Pixels to add to line spacing (default: 2)
Returns:
Re-rendered page with increased line spacing
"""
self.page_style.line_spacing += amount
self.renderer.page_style = self.page_style # Update renderer's reference
self.renderer.buffer.invalidate_all() # Clear cache to force re-render
return self.get_current_page()
def decrease_line_spacing(self, amount: int = 2) -> Page:
"""
Decrease line spacing and re-render current page.
Args:
amount: Pixels to remove from line spacing (default: 2)
Returns:
Re-rendered page with decreased line spacing
"""
self.page_style.line_spacing = max(0, self.page_style.line_spacing - amount)
self.renderer.page_style = self.page_style # Update renderer's reference
self.renderer.buffer.invalidate_all() # Clear cache to force re-render
return self.get_current_page()
def increase_inter_block_spacing(self, amount: int = 5) -> Page:
"""
Increase spacing between blocks and re-render current page.
Args:
amount: Pixels to add to inter-block spacing (default: 5)
Returns:
Re-rendered page with increased block spacing
"""
self.page_style.inter_block_spacing += amount
self.renderer.page_style = self.page_style # Update renderer's reference
self.renderer.buffer.invalidate_all() # Clear cache to force re-render
return self.get_current_page()
def decrease_inter_block_spacing(self, amount: int = 5) -> Page:
"""
Decrease spacing between blocks and re-render current page.
Args:
amount: Pixels to remove from inter-block spacing (default: 5)
Returns:
Re-rendered page with decreased block spacing
"""
self.page_style.inter_block_spacing = max(0, self.page_style.inter_block_spacing - amount)
self.renderer.page_style = self.page_style # Update renderer's reference
self.renderer.buffer.invalidate_all() # Clear cache to force re-render
return self.get_current_page()
def increase_word_spacing(self, amount: int = 2) -> Page:
"""
Increase spacing between words and re-render current page.
Args:
amount: Pixels to add to word spacing (default: 2)
Returns:
Re-rendered page with increased word spacing
"""
self.page_style.word_spacing += amount
self.renderer.page_style = self.page_style # Update renderer's reference
self.renderer.buffer.invalidate_all() # Clear cache to force re-render
return self.get_current_page()
def decrease_word_spacing(self, amount: int = 2) -> Page:
"""
Decrease spacing between words and re-render current page.
Args:
amount: Pixels to remove from word spacing (default: 2)
Returns:
Re-rendered page with decreased word spacing
"""
self.page_style.word_spacing = max(0, self.page_style.word_spacing - amount)
self.renderer.page_style = self.page_style # Update renderer's reference
self.renderer.buffer.invalidate_all() # Clear cache to force re-render
return self.get_current_page()
def get_table_of_contents(self) -> List[Tuple[str, HeadingLevel, RenderingPosition]]:
"""
Get the table of contents.

View File

@ -14,8 +14,9 @@ class PageStyle:
border_color: Tuple[int, int, int] = (0, 0, 0)
# Spacing properties
line_spacing: int = 5
inter_block_spacing: int = 15
line_spacing: int = 5 # Additional pixels between lines (added to font size)
inter_block_spacing: int = 15 # Pixels between blocks (paragraphs, headings, etc.)
word_spacing: int = 0 # Additional pixels between words (0 = use font defaults)
# Padding (top, right, bottom, left)
padding: Tuple[int, int, int, int] = (20, 20, 20, 20)
@ -25,7 +26,6 @@ class PageStyle:
# Typography properties
max_font_size: int = 72 # Maximum font size allowed on a page
line_spacing_multiplier: float = 1.2 # Baseline-to-baseline spacing multiplier
@property
def padding_top(self) -> int:

View File

@ -0,0 +1,784 @@
"""
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
import tempfile
from pathlib import Path
from unittest.mock import Mock, MagicMock, patch
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(f"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")
manager = 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
)
page = 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"])