dreader-application/tests/test_toc_overlay.py
Duncan Tourolle fe140ba91f
Some checks failed
Python CI / test (push) Failing after 4m11s
refactor applications to delegate responsibilites
2025-11-08 19:46:49 +01:00

311 lines
11 KiB
Python

"""
Unit tests for TOC overlay functionality.
Tests the complete workflow of:
1. Opening TOC overlay with swipe up gesture
2. Selecting a chapter from the TOC
3. Closing overlay by tapping outside or swiping down
"""
import unittest
from pathlib import Path
from dreader import (
EbookReader,
TouchEvent,
GestureType,
ActionType,
OverlayState
)
class TestTOCOverlay(unittest.TestCase):
"""Test TOC overlay opening, interaction, and closing"""
def setUp(self):
"""Set up test reader with a book"""
self.reader = EbookReader(page_size=(800, 1200))
# Load a test EPUB
test_epub = Path(__file__).parent / 'data' / 'library-epub' / 'alice.epub'
if not test_epub.exists():
# Try to find any EPUB in test data
epub_dir = Path(__file__).parent / 'data' / 'library-epub'
epubs = list(epub_dir.glob('*.epub'))
if epubs:
test_epub = epubs[0]
else:
self.skipTest("No test EPUB files available")
success = self.reader.load_epub(str(test_epub))
self.assertTrue(success, "Failed to load test EPUB")
def tearDown(self):
"""Clean up"""
self.reader.close()
def test_overlay_manager_initialization(self):
"""Test that overlay manager is properly initialized"""
self.assertIsNotNone(self.reader.overlay_manager)
self.assertEqual(self.reader.overlay_manager.page_size, (800, 1200))
self.assertFalse(self.reader.is_overlay_open())
self.assertEqual(self.reader.get_overlay_state(), OverlayState.NONE)
def test_open_toc_overlay_directly(self):
"""Test opening TOC overlay using direct API call"""
# Initially no overlay
self.assertFalse(self.reader.is_overlay_open())
# Open TOC overlay
overlay_image = self.reader.open_toc_overlay()
# Should return an image
self.assertIsNotNone(overlay_image)
self.assertEqual(overlay_image.size, (800, 1200))
# Overlay should be open
self.assertTrue(self.reader.is_overlay_open())
self.assertEqual(self.reader.get_overlay_state(), OverlayState.TOC)
def test_close_toc_overlay_directly(self):
"""Test closing TOC overlay using direct API call"""
# Open overlay first
self.reader.open_toc_overlay()
self.assertTrue(self.reader.is_overlay_open())
# Close overlay
page_image = self.reader.close_overlay()
# Should return base page
self.assertIsNotNone(page_image)
# Overlay should be closed
self.assertFalse(self.reader.is_overlay_open())
self.assertEqual(self.reader.get_overlay_state(), OverlayState.NONE)
def test_swipe_up_from_bottom_opens_toc(self):
"""Test that swipe up from bottom of screen opens TOC overlay"""
# Create swipe up event from bottom of screen (y=1100, which is > 80% of 1200)
event = TouchEvent(
gesture=GestureType.SWIPE_UP,
x=400,
y=1100
)
# Handle gesture
response = self.reader.handle_touch(event)
# Should open overlay
self.assertEqual(response.action, ActionType.OVERLAY_OPENED)
self.assertEqual(response.data['overlay_type'], 'toc')
self.assertTrue(self.reader.is_overlay_open())
def test_swipe_up_from_middle_does_not_open_toc(self):
"""Test that swipe up from middle of screen does NOT open TOC"""
# Create swipe up event from middle of screen (y=600, which is < 80% of 1200)
event = TouchEvent(
gesture=GestureType.SWIPE_UP,
x=400,
y=600
)
# Handle gesture
response = self.reader.handle_touch(event)
# Should not open overlay
self.assertEqual(response.action, ActionType.NONE)
self.assertFalse(self.reader.is_overlay_open())
def test_swipe_down_closes_overlay(self):
"""Test that swipe down closes the overlay"""
# Open overlay first
self.reader.open_toc_overlay()
self.assertTrue(self.reader.is_overlay_open())
# Create swipe down event
event = TouchEvent(
gesture=GestureType.SWIPE_DOWN,
x=400,
y=300
)
# Handle gesture
response = self.reader.handle_touch(event)
# Should close overlay
self.assertEqual(response.action, ActionType.OVERLAY_CLOSED)
self.assertFalse(self.reader.is_overlay_open())
def test_tap_outside_overlay_closes_it(self):
"""Test that tapping outside the overlay panel closes it"""
# Open overlay first
self.reader.open_toc_overlay()
self.assertTrue(self.reader.is_overlay_open())
# Tap in the far left (outside the centered panel)
# Panel is 60% wide centered, so left edge is at 20%
event = TouchEvent(
gesture=GestureType.TAP,
x=50, # Well outside panel
y=600
)
# Handle gesture
response = self.reader.handle_touch(event)
# Should close overlay
self.assertEqual(response.action, ActionType.OVERLAY_CLOSED)
self.assertFalse(self.reader.is_overlay_open())
def test_tap_on_chapter_selects_and_closes(self):
"""Test that tapping on a chapter navigates to it and closes overlay"""
# Open overlay first
self.reader.open_toc_overlay()
chapters = self.reader.get_chapters()
if len(chapters) < 2:
self.skipTest("Need at least 2 chapters for this test")
# Calculate tap position for second chapter (index 1 - "Metamorphosis")
# Based on actual measurements from pyWebLayout query_point:
# Overlay bounds: (38, 138, 122, 16) -> X=[38,160], Y=[138,154]
# With panel offset (160, 180): Screen X=[198,320], Y=[318,334]
tap_x = 250 # Within the link text bounds
tap_y = 335 # Chapter 1 "Metamorphosis" at overlay Y=155 (138+16=154, screen 180+155=335)
event = TouchEvent(
gesture=GestureType.TAP,
x=tap_x,
y=tap_y
)
# Handle gesture
response = self.reader.handle_touch(event)
# Should select chapter
self.assertEqual(response.action, ActionType.CHAPTER_SELECTED)
self.assertIn('chapter_index', response.data)
# Overlay should be closed
self.assertFalse(self.reader.is_overlay_open())
def test_multiple_overlay_operations(self):
"""Test opening and closing overlay multiple times"""
# Open and close 3 times
for i in range(3):
# Open
self.reader.open_toc_overlay()
self.assertTrue(self.reader.is_overlay_open())
# Close
self.reader.close_overlay()
self.assertFalse(self.reader.is_overlay_open())
def test_overlay_with_page_navigation(self):
"""Test that overlay works correctly after navigating pages"""
# Navigate to page 2
self.reader.next_page()
# Open overlay
overlay_image = self.reader.open_toc_overlay()
self.assertIsNotNone(overlay_image)
self.assertTrue(self.reader.is_overlay_open())
# Close overlay
self.reader.close_overlay()
self.assertFalse(self.reader.is_overlay_open())
def test_toc_overlay_contains_all_chapters(self):
"""Test that TOC overlay includes all book chapters"""
chapters = self.reader.get_chapters()
# Open overlay (this generates the HTML with chapters)
overlay_image = self.reader.open_toc_overlay()
self.assertIsNotNone(overlay_image)
# Verify overlay manager has correct chapter count
# This is implicit in the rendering - if it renders without error,
# all chapters were included
self.assertTrue(self.reader.is_overlay_open())
def test_overlay_state_persistence_ready(self):
"""Test that overlay state can be tracked (for future state persistence)"""
# This test verifies the state tracking is ready for StateManager integration
# Start with no overlay
self.assertEqual(self.reader.current_overlay_state, OverlayState.NONE)
# Open TOC
self.reader.open_toc_overlay()
self.assertEqual(self.reader.current_overlay_state, OverlayState.TOC)
# Close overlay
self.reader.close_overlay()
self.assertEqual(self.reader.current_overlay_state, OverlayState.NONE)
class TestOverlayRendering(unittest.TestCase):
"""Test overlay rendering and compositing"""
def setUp(self):
"""Set up test reader"""
self.reader = EbookReader(page_size=(800, 1200))
test_epub = Path(__file__).parent / 'data' / 'library-epub' / 'alice.epub'
if not test_epub.exists():
epub_dir = Path(__file__).parent / 'data' / 'library-epub'
epubs = list(epub_dir.glob('*.epub'))
if epubs:
test_epub = epubs[0]
else:
self.skipTest("No test EPUB files available")
self.reader.load_epub(str(test_epub))
def tearDown(self):
"""Clean up"""
self.reader.close()
def test_overlay_image_size(self):
"""Test that overlay image matches page size"""
overlay_image = self.reader.open_toc_overlay()
self.assertEqual(overlay_image.size, (800, 1200))
def test_overlay_compositing(self):
"""Test that overlay is properly composited on base page"""
# Get base page
base_page = self.reader.get_current_page()
# Open overlay (creates composited image)
overlay_image = self.reader.open_toc_overlay()
# Composited image should be different from base page
self.assertIsNotNone(overlay_image)
# Images should have same size but different content
self.assertEqual(base_page.size, overlay_image.size)
def test_overlay_html_to_image_conversion(self):
"""Test that HTML overlay is correctly converted to image"""
from dreader.html_generator import generate_toc_overlay
# Get chapters
chapters = self.reader.get_chapters()
chapter_data = [{"index": idx, "title": title} for title, idx in chapters]
# Generate HTML
html = generate_toc_overlay(chapter_data)
self.assertIsNotNone(html)
self.assertIn("Table of Contents", html)
# Render HTML to image using overlay manager
overlay_manager = self.reader.overlay_manager
image = overlay_manager.render_html_to_image(html)
# Should produce valid image
self.assertIsNotNone(image)
self.assertEqual(image.size, (800, 1200))
if __name__ == '__main__':
unittest.main()