Root had grown to 30 entries, most of it generated output and one-off scripts. It now holds 12. Docs: - Merge HARDWARE_SETUP, HARDWARE_PINOUT, GPIO_BUTTONS and ACCELEROMETER_PAGE_FLIP into a single docs/HARDWARE.md - Move ARCHITECTURE, REQUIREMENTS and HAL_IMPLEMENTATION_SPEC to docs/, leaving only README.md in the root - Update cross-references in README, setup_rpi.py and install_hardware_drivers.sh, and re-base ARCHITECTURE's source links Merging surfaced three errors, reconciled against hardware_config.json: - The power button was documented as GPIO 21 to GND with a pull-up. It is active high (pull_up: false); wiring it as documented reads as permanently pressed. - GPIO_BUTTONS used GPIO 23 for next-page; it is GPIO 27. - The FT5316 INT pin was routed to GPIO 27, which collides with the next-page button. Now documented as a conflict. Scripts: - Move debug_overlay_links.py and debug_previous_page.py to scripts/debug/ - Rename test_pagination_visual.py to scripts/debug/visualize_pagination.py; it renders output and asserts nothing, so the test_ prefix was misleading - Fix the __file__-relative paths these three relied on - Track update_pyweblayout.sh under scripts/ Tests: - Reword the backward-navigation tests, which described a pyWebLayout bug that is now fixed, as regression tests Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
241 lines
7.4 KiB
Python
241 lines
7.4 KiB
Python
"""
|
|
Detailed regression tests for backward navigation.
|
|
|
|
These cover the ways backward navigation has broken before:
|
|
1. Complete failure (previous_page returns None)
|
|
2. Imprecise positioning (lands on wrong block)
|
|
3. Failure only after resuming from a saved position
|
|
4. Failure during continuous navigation
|
|
|
|
See test_backward_nav_minimal.py for the minimal cover-page case.
|
|
"""
|
|
|
|
import unittest
|
|
import tempfile
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
from dreader.application import EbookReader
|
|
|
|
|
|
class TestBackwardNavigationDetailed(unittest.TestCase):
|
|
"""Detailed backward navigation tests"""
|
|
|
|
def setUp(self):
|
|
"""Set up test environment"""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.epub_path = "tests/data/test.epub"
|
|
|
|
if not Path(self.epub_path).exists():
|
|
self.skipTest(f"Test EPUB not found at {self.epub_path}")
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_continuous_backward_navigation_no_resume(self):
|
|
"""
|
|
Test backward navigation without closing/resuming.
|
|
This checks if the issue is specific to resume or general.
|
|
"""
|
|
reader = EbookReader(
|
|
page_size=(800, 1000),
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
|
|
reader.load_epub(self.epub_path)
|
|
|
|
print("\n=== Test: Continuous backward navigation (no resume) ===")
|
|
|
|
# Record starting position
|
|
pos0 = reader.manager.current_position.copy()
|
|
print(f"Starting position: {pos0}")
|
|
|
|
# Go forward 5 pages, recording positions
|
|
forward_positions = [pos0]
|
|
for i in range(5):
|
|
page = reader.next_page()
|
|
if page is None:
|
|
print(f"Reached end at page {i}")
|
|
break
|
|
pos = reader.manager.current_position.copy()
|
|
forward_positions.append(pos)
|
|
print(f"Forward page {i+1}: block_index={pos.block_index}")
|
|
|
|
num_forward = len(forward_positions) - 1
|
|
print(f"\nNavigated forward {num_forward} pages")
|
|
|
|
# Now go backward the same number of times
|
|
print("\n--- Going backward ---")
|
|
backward_positions = []
|
|
for i in range(num_forward):
|
|
page = reader.previous_page()
|
|
|
|
if page is None:
|
|
print(f"ERROR: previous_page() returned None at step {i+1}")
|
|
self.fail(f"Backward navigation failed at step {i+1}")
|
|
|
|
pos = reader.manager.current_position.copy()
|
|
backward_positions.append(pos)
|
|
print(f"Backward step {i+1}: block_index={pos.block_index}")
|
|
|
|
# Check final position
|
|
final_pos = reader.manager.current_position.copy()
|
|
print(f"\nFinal position: {final_pos}")
|
|
print(f"Expected (pos0): {pos0}")
|
|
|
|
if final_pos != pos0:
|
|
print(f"WARNING: Position mismatch!")
|
|
print(f" Expected block_index: {pos0.block_index}")
|
|
print(f" Actual block_index: {final_pos.block_index}")
|
|
print(f" Difference: {final_pos.block_index - pos0.block_index} blocks")
|
|
|
|
self.assertEqual(
|
|
final_pos,
|
|
pos0,
|
|
f"After {num_forward} forward and {num_forward} backward, should be at start"
|
|
)
|
|
|
|
reader.close()
|
|
|
|
def test_backward_navigation_at_start(self):
|
|
"""
|
|
Test that previous_page() behaves correctly when at the start of the book.
|
|
"""
|
|
reader = EbookReader(
|
|
page_size=(800, 1000),
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
|
|
reader.load_epub(self.epub_path)
|
|
|
|
print("\n=== Test: Backward navigation at start ===")
|
|
|
|
pos_start = reader.manager.current_position.copy()
|
|
print(f"At start: {pos_start}")
|
|
|
|
# Try to go back from the very first page
|
|
page = reader.previous_page()
|
|
|
|
print(f"previous_page() returned: {page is not None}")
|
|
|
|
pos_after = reader.manager.current_position.copy()
|
|
print(f"Position after previous_page(): {pos_after}")
|
|
|
|
# Should either return None or stay at same position
|
|
if page is not None:
|
|
self.assertEqual(
|
|
pos_after,
|
|
pos_start,
|
|
"If previous_page() returns a page at start, position should not change"
|
|
)
|
|
|
|
reader.close()
|
|
|
|
def test_alternating_navigation(self):
|
|
"""
|
|
Test alternating forward/backward navigation.
|
|
"""
|
|
reader = EbookReader(
|
|
page_size=(800, 1000),
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
|
|
reader.load_epub(self.epub_path)
|
|
|
|
print("\n=== Test: Alternating forward/backward navigation ===")
|
|
|
|
pos0 = reader.manager.current_position.copy()
|
|
print(f"Start: block_index={pos0.block_index}")
|
|
|
|
# Go forward, back, forward, back pattern
|
|
operations = [
|
|
("forward", 1),
|
|
("backward", 1),
|
|
("forward", 2),
|
|
("backward", 1),
|
|
("forward", 1),
|
|
("backward", 2),
|
|
]
|
|
|
|
for op, count in operations:
|
|
for i in range(count):
|
|
if op == "forward":
|
|
page = reader.next_page()
|
|
else:
|
|
page = reader.previous_page()
|
|
|
|
self.assertIsNotNone(
|
|
page,
|
|
f"{op} navigation failed at iteration {i+1}"
|
|
)
|
|
|
|
pos = reader.manager.current_position.copy()
|
|
print(f"After {count}x {op}: block_index={pos.block_index}")
|
|
|
|
# We should end up at the starting position (net: +5 -4 = +1, then +1 -2 = -1, total = 0)
|
|
# Actually: +1 -1 +2 -1 +1 -2 = 0
|
|
final_pos = reader.manager.current_position.copy()
|
|
print(f"\nFinal: block_index={final_pos.block_index}")
|
|
print(f"Expected: block_index={pos0.block_index}")
|
|
|
|
self.assertEqual(
|
|
final_pos,
|
|
pos0,
|
|
"Alternating navigation should return to start"
|
|
)
|
|
|
|
reader.close()
|
|
|
|
def test_backward_then_forward(self):
|
|
"""
|
|
Test that forward navigation works correctly after backward navigation.
|
|
"""
|
|
reader = EbookReader(
|
|
page_size=(800, 1000),
|
|
bookmarks_dir=self.temp_dir,
|
|
buffer_size=0
|
|
)
|
|
|
|
reader.load_epub(self.epub_path)
|
|
|
|
print("\n=== Test: Backward then forward ===")
|
|
|
|
# Go forward 3 pages
|
|
positions = [reader.manager.current_position.copy()]
|
|
for i in range(3):
|
|
reader.next_page()
|
|
positions.append(reader.manager.current_position.copy())
|
|
|
|
print(f"Forward positions: {[p.block_index for p in positions]}")
|
|
|
|
# Go back 3 pages
|
|
for i in range(3):
|
|
reader.previous_page()
|
|
|
|
pos_after_back = reader.manager.current_position.copy()
|
|
print(f"After going back: block_index={pos_after_back.block_index}")
|
|
|
|
# Now go forward 3 pages again
|
|
for i in range(3):
|
|
reader.next_page()
|
|
|
|
final_pos = reader.manager.current_position.copy()
|
|
print(f"After going forward again: block_index={final_pos.block_index}")
|
|
print(f"Expected: block_index={positions[3].block_index}")
|
|
|
|
self.assertEqual(
|
|
final_pos,
|
|
positions[3],
|
|
"Forward after backward should reach same position"
|
|
)
|
|
|
|
reader.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|