""" Unit tests for Settings overlay functionality. Tests the complete workflow of: 1. Opening settings overlay with swipe down gesture 2. Adjusting settings (font size, line spacing, etc.) 3. Live preview updates 4. Closing overlay """ import unittest from pathlib import Path from dreader import ( EbookReader, TouchEvent, GestureType, ActionType, OverlayState ) class TestSettingsOverlay(unittest.TestCase): """Test Settings overlay opening, interaction, and closing""" def setUp(self): """Set up test reader with a book""" import os import zipfile self.reader = EbookReader(page_size=(800, 1200)) # Load a test EPUB - use any available EPUB in test data epub_dir = Path(__file__).parent / 'data' / 'library-epub' epubs = list(epub_dir.glob('*.epub')) if not epubs: self.skipTest("No test EPUB files available") test_epub = epubs[0] # Debug logging print(f"\n=== EPUB Loading Debug Info ===") print(f"Test EPUB path: {test_epub}") print(f"Absolute path: {test_epub.absolute()}") print(f"File exists: {test_epub.exists()}") print(f"File size: {test_epub.stat().st_size if test_epub.exists() else 'N/A'}") print(f"Is file: {test_epub.is_file() if test_epub.exists() else 'N/A'}") print(f"Readable: {os.access(test_epub, os.R_OK) if test_epub.exists() else 'N/A'}") # Test if it's a valid ZIP if test_epub.exists(): # Check file magic bytes with open(test_epub, 'rb') as f: first_bytes = f.read(10) print(f"First 10 bytes (hex): {first_bytes.hex()}") print(f"First 10 bytes (ascii): {first_bytes[:4]}") print(f"Is PK header: {first_bytes[:2] == b'PK'}") try: with zipfile.ZipFile(test_epub, 'r') as zf: print(f"Valid ZIP: True") print(f"Files in ZIP: {len(zf.namelist())}") print(f"First 3 files: {zf.namelist()[:3]}") except Exception as e: print(f"ZIP validation error: {e}") # Try to load success = self.reader.load_epub(str(test_epub)) if not success: print(f"=== Load failed ===") # Try loading with pyWebLayout directly for more detailed error try: from pyWebLayout.io.readers.epub_reader import read_epub book = read_epub(str(test_epub)) print(f"Direct pyWebLayout load: SUCCESS (unexpected!)") except Exception as e: print(f"Direct pyWebLayout load error: {e}") import traceback traceback.print_exc() self.assertTrue(success, f"Failed to load test EPUB: {test_epub}") def tearDown(self): """Clean up""" self.reader.close() def test_open_settings_overlay_directly(self): """Test opening settings overlay using direct API call""" # Initially no overlay self.assertFalse(self.reader.is_overlay_open()) # Open settings overlay overlay_image = self.reader.open_settings_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.SETTINGS) def test_close_settings_overlay_directly(self): """Test closing settings overlay using direct API call""" # Open overlay first self.reader.open_settings_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_down_from_top_opens_settings(self): """Test that swipe down from top of screen opens settings overlay""" # Create swipe down event from top of screen (y=100, which is < 20% of 1200) event = TouchEvent( gesture=GestureType.SWIPE_DOWN, x=400, y=100 ) # Handle gesture response = self.reader.handle_touch(event) # Should open overlay self.assertEqual(response.action, ActionType.OVERLAY_OPENED) self.assertEqual(response.data['overlay_type'], 'settings') self.assertTrue(self.reader.is_overlay_open()) def test_swipe_down_from_middle_does_not_open_settings(self): """Test that swipe down from middle of screen does NOT open settings""" # Create swipe down event from middle of screen (y=600, which is > 20% of 1200) event = TouchEvent( gesture=GestureType.SWIPE_DOWN, 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_tap_outside_closes_settings_overlay(self): """Test that tapping outside the settings panel closes it""" # Open overlay first self.reader.open_settings_overlay() self.assertTrue(self.reader.is_overlay_open()) # Tap in the far left (outside the centered panel) 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_font_size_increase(self): """Test increasing font size through settings overlay""" # Open overlay self.reader.open_settings_overlay() initial_font_scale = self.reader.base_font_scale # Get overlay reader to query button positions overlay_manager = self.reader.overlay_manager overlay_reader = overlay_manager._overlay_reader if not overlay_reader or not overlay_reader.manager: self.skipTest("Overlay reader not available for querying") # Query the overlay to find the "A+" button link # We'll search for it by looking for links with "setting:font_increase" page = overlay_reader.manager.get_current_page() # Try multiple Y positions in the font size row to find the button # Panel is 60% of screen width (480px) centered (x offset = 160) # First setting row should be around y=100-150 in panel coordinates found_button = False tap_x = None tap_y = None for y in range(80, 180, 10): for x in range(300, 450, 20): # Right side of panel where buttons are # Translate to panel coordinates panel_x_offset = int((800 - 480) / 2) panel_y_offset = int((1200 - 840) / 2) panel_x = x - panel_x_offset panel_y = y - panel_y_offset if panel_x < 0 or panel_y < 0: continue result = page.query_point((panel_x, panel_y)) if result and result.link_target == "setting:font_increase": tap_x = x tap_y = y found_button = True break if found_button: break if not found_button: # Fallback: use approximate coordinates # Based on HTML layout: panel center + right side button tap_x = 550 tap_y = 350 # Tap the increase button (in screen coordinates) event = TouchEvent( gesture=GestureType.TAP, x=tap_x, y=tap_y ) response = self.reader.handle_touch(event) # Should either change setting or close (depending on whether we hit the button) if response.action == ActionType.SETTING_CHANGED: # Font size should have increased self.assertGreater(self.reader.base_font_scale, initial_font_scale) # Overlay should still be open self.assertTrue(self.reader.is_overlay_open()) else: # If we missed the button, that's OK for this test pass def test_line_spacing_adjustment(self): """Test adjusting line spacing through settings overlay""" # Open overlay self.reader.open_settings_overlay() initial_spacing = self.reader.page_style.line_spacing # Close overlay for this test (full interaction would require precise coordinates) self.reader.close_overlay() # Verify we can adjust line spacing programmatically self.reader.set_line_spacing(initial_spacing + 2) self.assertEqual(self.reader.page_style.line_spacing, initial_spacing + 2) def test_settings_values_displayed_in_overlay(self): """Test that current settings values are shown in the overlay""" # Set specific values self.reader.set_font_size(1.5) # 150% self.reader.set_line_spacing(10) # Open overlay overlay_image = self.reader.open_settings_overlay() self.assertIsNotNone(overlay_image) # Overlay should be open with current values # (Visual verification would show "150%" and "10px" in the HTML) self.assertTrue(self.reader.is_overlay_open()) def test_multiple_setting_changes(self): """Test making multiple setting changes in sequence""" initial_font = self.reader.base_font_scale initial_spacing = self.reader.page_style.line_spacing # Change font size self.reader.increase_font_size() self.assertNotEqual(self.reader.base_font_scale, initial_font) # Change line spacing self.reader.set_line_spacing(initial_spacing + 5) self.assertNotEqual(self.reader.page_style.line_spacing, initial_spacing) # Open overlay to verify values overlay_image = self.reader.open_settings_overlay() self.assertIsNotNone(overlay_image) def test_settings_persist_after_overlay_close(self): """Test that setting changes persist after closing overlay""" # Make a change initial_font = self.reader.base_font_scale self.reader.increase_font_size() new_font = self.reader.base_font_scale # Open and close overlay self.reader.open_settings_overlay() self.reader.close_overlay() # Settings should still be changed self.assertEqual(self.reader.base_font_scale, new_font) self.assertNotEqual(self.reader.base_font_scale, initial_font) def test_overlay_refresh_after_setting_change(self): """Test that overlay can be refreshed with updated values""" # Open overlay self.reader.open_settings_overlay() # Access refresh method through overlay manager overlay_manager = self.reader.overlay_manager # Change a setting programmatically self.reader.increase_font_size() new_page = self.reader.get_current_page(include_highlights=False) # Refresh overlay refreshed_image = overlay_manager.refresh_settings_overlay( updated_base_page=new_page, font_scale=self.reader.base_font_scale, line_spacing=self.reader.page_style.line_spacing, inter_block_spacing=self.reader.page_style.inter_block_spacing ) self.assertIsNotNone(refreshed_image) self.assertEqual(refreshed_image.size, (800, 1200)) def test_line_spacing_actually_changes_rendering(self): """Verify that line spacing changes produce different rendered images""" # Close any open overlay first if self.reader.is_overlay_open(): self.reader.close_overlay() # Set initial line spacing and get page self.reader.set_line_spacing(5) page1 = self.reader.get_current_page() self.assertIsNotNone(page1) # Change line spacing significantly self.reader.set_line_spacing(30) page2 = self.reader.get_current_page() self.assertIsNotNone(page2) # Images should be different (different line spacing should affect rendering) self.assertNotEqual(page1.tobytes(), page2.tobytes(), "Line spacing change should affect rendering") def test_inter_block_spacing_actually_changes_rendering(self): """Verify that inter-block spacing changes produce different rendered images""" # Close any open overlay first if self.reader.is_overlay_open(): self.reader.close_overlay() # Set initial inter-block spacing and get page self.reader.set_inter_block_spacing(15) page1 = self.reader.get_current_page() self.assertIsNotNone(page1) # Change inter-block spacing significantly self.reader.set_inter_block_spacing(50) page2 = self.reader.get_current_page() self.assertIsNotNone(page2) # Images should be different self.assertNotEqual(page1.tobytes(), page2.tobytes(), "Inter-block spacing change should affect rendering") def test_word_spacing_actually_changes_rendering(self): """Verify that word spacing changes produce different rendered images""" # Close any open overlay first if self.reader.is_overlay_open(): self.reader.close_overlay() # Set initial word spacing and get page self.reader.set_word_spacing(0) page1 = self.reader.get_current_page() self.assertIsNotNone(page1) # Change word spacing significantly self.reader.set_word_spacing(20) page2 = self.reader.get_current_page() self.assertIsNotNone(page2) # Images should be different self.assertNotEqual(page1.tobytes(), page2.tobytes(), "Word spacing change should affect rendering") if __name__ == '__main__': unittest.main()