Compare commits
2 Commits
b65c35d96d
...
13d20c28c5
| Author | SHA1 | Date | |
|---|---|---|---|
| 13d20c28c5 | |||
| f8baf155e9 |
@ -74,7 +74,16 @@ def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pr
|
|||||||
int(concrete_style.word_spacing_max)
|
int(concrete_style.word_spacing_max)
|
||||||
)
|
)
|
||||||
text_align = concrete_style.text_align
|
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
|
# Apply alignment override if provided
|
||||||
if alignment_override is not None:
|
if alignment_override is not None:
|
||||||
text_align = alignment_override
|
text_align = alignment_override
|
||||||
@ -91,9 +100,14 @@ def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pr
|
|||||||
background=font.background
|
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
|
# 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
|
# Get font metrics for boundary checking
|
||||||
ascent, descent = font.font.getmetrics()
|
ascent, descent = font.font.getmetrics()
|
||||||
|
|||||||
@ -247,11 +247,16 @@ class BidirectionalLayouter:
|
|||||||
|
|
||||||
# Try to fit the block on the current page
|
# Try to fit the block on the current page
|
||||||
success, new_pos = self._layout_block_on_page(scaled_block, page, current_pos, font_scale)
|
success, new_pos = self._layout_block_on_page(scaled_block, page, current_pos, font_scale)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
# Block doesn't fit, we're done with this page
|
# Block doesn't fit, we're done with this page
|
||||||
break
|
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
|
# Ensure new position doesn't go beyond bounds
|
||||||
if new_pos.block_index >= len(self.blocks):
|
if new_pos.block_index >= len(self.blocks):
|
||||||
# We've reached the end of the document
|
# We've reached the end of the document
|
||||||
|
|||||||
@ -341,7 +341,97 @@ class EreaderLayoutManager:
|
|||||||
def get_font_scale(self) -> float:
|
def get_font_scale(self) -> float:
|
||||||
"""Get the current font scale"""
|
"""Get the current font scale"""
|
||||||
return self.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]]:
|
def get_table_of_contents(self) -> List[Tuple[str, HeadingLevel, RenderingPosition]]:
|
||||||
"""
|
"""
|
||||||
Get the table of contents.
|
Get the table of contents.
|
||||||
|
|||||||
@ -14,18 +14,18 @@ class PageStyle:
|
|||||||
border_color: Tuple[int, int, int] = (0, 0, 0)
|
border_color: Tuple[int, int, int] = (0, 0, 0)
|
||||||
|
|
||||||
# Spacing properties
|
# Spacing properties
|
||||||
line_spacing: int = 5
|
line_spacing: int = 5 # Additional pixels between lines (added to font size)
|
||||||
inter_block_spacing: int = 15
|
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 (top, right, bottom, left)
|
||||||
padding: Tuple[int, int, int, int] = (20, 20, 20, 20)
|
padding: Tuple[int, int, int, int] = (20, 20, 20, 20)
|
||||||
|
|
||||||
# Background color
|
# Background color
|
||||||
background_color: Tuple[int, int, int] = (255, 255, 255)
|
background_color: Tuple[int, int, int] = (255, 255, 255)
|
||||||
|
|
||||||
# Typography properties
|
# Typography properties
|
||||||
max_font_size: int = 72 # Maximum font size allowed on a page
|
max_font_size: int = 72 # Maximum font size allowed on a page
|
||||||
line_spacing_multiplier: float = 1.2 # Baseline-to-baseline spacing multiplier
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def padding_top(self) -> int:
|
def padding_top(self) -> int:
|
||||||
|
|||||||
784
tests/layout/test_ereader_manager.py
Normal file
784
tests/layout/test_ereader_manager.py
Normal 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"])
|
||||||
Loading…
x
Reference in New Issue
Block a user