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