397 lines
15 KiB
Python
397 lines
15 KiB
Python
"""
|
|
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 a larger EPUB for spacing tests
|
|
epub_dir = Path(__file__).parent / 'data' / 'library-epub'
|
|
epubs = list(epub_dir.glob('*.epub'))
|
|
if not epubs:
|
|
self.skipTest("No test EPUB files available")
|
|
|
|
# Prefer larger EPUBs for better testing of spacing changes
|
|
# Skip minimal-test.epub as it has too little content
|
|
epubs = [e for e in epubs if 'minimal' not in e.name]
|
|
if not epubs:
|
|
epubs = list(epub_dir.glob('*.epub'))
|
|
|
|
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 from the active overlay sub-application
|
|
overlay_subapp = self.reader._active_overlay
|
|
if not overlay_subapp:
|
|
self.skipTest("No active overlay sub-application")
|
|
|
|
overlay_reader = overlay_subapp._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 active overlay sub-application
|
|
overlay_subapp = self.reader._active_overlay
|
|
if not overlay_subapp:
|
|
self.skipTest("No active overlay sub-application")
|
|
|
|
# Change a setting programmatically
|
|
self.reader.increase_font_size()
|
|
new_page = self.reader.get_current_page(include_highlights=False)
|
|
|
|
# Refresh overlay
|
|
refreshed_image = overlay_subapp.refresh(
|
|
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()
|