""" Unit tests for library interaction and tap detection. These tests demonstrate the issue with interactive images in the library and verify that tap detection works correctly. """ import unittest from pathlib import Path from unittest.mock import MagicMock from dreader import LibraryManager class TestLibraryInteraction(unittest.TestCase): """Test library browsing and tap interaction""" def setUp(self): """Set up test library""" self.library_path = Path(__file__).parent / 'data' / 'library-epub' self.library = LibraryManager( library_path=str(self.library_path), page_size=(800, 1200) ) def tearDown(self): """Clean up""" self.library.cleanup() def test_library_scan(self): """Test that library scanning finds books""" books = self.library.scan_library() # Should find at least one book self.assertGreater(len(books), 0, "Library should contain at least one book") # Each book should have required fields for book in books: self.assertIn('path', book) self.assertIn('title', book) self.assertIn('filename', book) def test_library_table_creation(self): """Test that library table can be created""" books = self.library.scan_library() table = self.library.create_library_table() # Table should exist self.assertIsNotNone(table) # Table should have body rows for 2-column grid layout # Each pair of books gets 2 rows (cover row + detail row) # So N books = ceil(N/2) * 2 rows body_rows = list(table.body_rows()) expected_rows = ((len(books) + 1) // 2) * 2 # Round up to nearest even number, then double self.assertEqual(len(body_rows), expected_rows) def test_library_rendering(self): """Test that library can be rendered to image""" self.library.scan_library() self.library.create_library_table() # Render library image = self.library.render_library() # Image should be created with correct size self.assertIsNotNone(image) self.assertEqual(image.size, self.library.page_size) def test_tap_detection_first_book(self): """Test that tapping on first book row selects it The entire row is interactive, so tapping anywhere in the row (not just on the cover image) will select the book. """ books = self.library.scan_library() self.library.create_library_table() self.library.render_library() # Tap anywhere in the first book's row # Based on layout: padding 30px, caption ~40px, first row starts at ~70px selected_path = self.library.handle_library_tap(x=100, y=100) # Should select the first book self.assertIsNotNone(selected_path, "Tap should select a book") self.assertEqual(selected_path, books[0]['path'], "Should select first book") def test_tap_detection_second_book(self): """Test that tapping on second book selects it""" books = self.library.scan_library() if len(books) < 2: self.skipTest("Need at least 2 books for this test") self.library.create_library_table() self.library.render_library() # Tap in the region of the second book # Row height is ~180px, so second book is at ~70 + 180 = 250px selected_path = self.library.handle_library_tap(x=400, y=250) # Should select the second book self.assertIsNotNone(selected_path, "Tap should select a book") self.assertEqual(selected_path, books[1]['path'], "Should select second book") def test_tap_outside_table(self): """Test that tapping outside table returns None""" self.library.scan_library() self.library.create_library_table() self.library.render_library() # Tap outside the table area (far right) selected_path = self.library.handle_library_tap(x=1000, y=100) # Should not select anything self.assertIsNone(selected_path, "Tap outside table should not select anything") def test_tap_above_table(self): """Test that tapping in caption area returns None""" self.library.scan_library() self.library.create_library_table() self.library.render_library() # Tap in caption area (above first row) selected_path = self.library.handle_library_tap(x=400, y=40) # Should not select anything self.assertIsNone(selected_path, "Tap in caption should not select anything") def test_tap_below_last_book(self): """Test that tapping below all books returns None""" books = self.library.scan_library() self.library.create_library_table() self.library.render_library() # Tap way below the last book # With 5 books and row height 180px: ~70 + (5 * 180) = 970px selected_path = self.library.handle_library_tap(x=400, y=1100) # Should not select anything self.assertIsNone(selected_path, "Tap below last book should not select anything") def test_multiple_taps(self): """Test that multiple taps work correctly with 2-column grid layout""" books = self.library.scan_library() if len(books) < 3: self.skipTest("Need at least 3 books for this test") self.library.create_library_table() self.library.render_library() # In 2-column layout: # Books 0 and 1 are in the first pair (rows 0-1: cover and detail) # Books 2 and 3 are in the second pair (rows 2-3: cover and detail) # Tap first book (left column, first pair cover row) path1 = self.library.handle_library_tap(x=100, y=100) self.assertEqual(path1, books[0]['path']) # Tap second book (right column, first pair cover row) path2 = self.library.handle_library_tap(x=500, y=100) self.assertEqual(path2, books[1]['path']) # Tap third book (left column, second pair cover row) path3 = self.library.handle_library_tap(x=100, y=360) self.assertEqual(path3, books[2]['path']) # All should be different self.assertNotEqual(path1, path2) self.assertNotEqual(path2, path3) self.assertNotEqual(path1, path3) def test_pagination(self): """Test library pagination with fake book data""" # Create fake books (20 books to ensure multiple pages) fake_books = [] for i in range(20): fake_books.append({ 'path': f'/fake/path/book_{i}.epub', 'title': f'Book Title {i}', 'author': f'Author {i}', 'filename': f'book_{i}.epub', 'cover_data': None, 'cover_path': None }) # Create library with 6 books per page library = LibraryManager( library_path=str(self.library_path), page_size=(800, 1200), books_per_page=6 ) library.books = fake_books # Test initial state self.assertEqual(library.current_page, 0) self.assertEqual(library.get_total_pages(), 4) # 20 books / 6 per page = 4 pages # Test creating table for first page table = library.create_library_table() self.assertIsNotNone(table) # 6 books = 3 pairs = 6 rows (3 cover rows + 3 detail rows) body_rows = list(table.body_rows()) self.assertEqual(len(body_rows), 6) # Test navigation to next page self.assertTrue(library.next_page()) self.assertEqual(library.current_page, 1) # Create table for second page table = library.create_library_table() body_rows = list(table.body_rows()) self.assertEqual(len(body_rows), 6) # Still 6 books on page 2 # Test navigation to last page library.set_page(3) self.assertEqual(library.current_page, 3) table = library.create_library_table() body_rows = list(table.body_rows()) # Page 4 has 2 books (20 - 18 = 2) = 1 pair = 2 rows self.assertEqual(len(body_rows), 2) # Test can't go beyond last page self.assertFalse(library.next_page()) self.assertEqual(library.current_page, 3) # Test navigation to previous page self.assertTrue(library.previous_page()) self.assertEqual(library.current_page, 2) # Test navigation to first page library.set_page(0) self.assertEqual(library.current_page, 0) # Test can't go before first page self.assertFalse(library.previous_page()) self.assertEqual(library.current_page, 0) # Test invalid page number self.assertFalse(library.set_page(-1)) self.assertFalse(library.set_page(100)) self.assertEqual(library.current_page, 0) # Should stay on current page library.cleanup() if __name__ == '__main__': unittest.main()