package browser import ( "fmt" "testing" "time" ) // --- Test: Load Page From Index At Offset --- func TestLoadPage_FromIndexAtOffset(t *testing.T) { // Create a BrowserState with a populated index s := NewBrowserState() s.TotalEntries = 250 // Simulate an index with 250 entries (3 pages) // Use zero-padded names so lexicographic order matches numeric order allEntries := make([]Entry, 250) for i := 0; i < 250; i++ { allEntries[i] = NewEntry( fmt.Sprintf("/path/file%03d.txt", i), fmt.Sprintf("file%03d.txt", i), int64(i*100), time.Time{}, false, ) } // Build position map for the default sort mode (NameAsc) s.SortIndex = &DirectoryIndex{ Entries: allEntries, SortOrders: map[string][]int{"name_asc": buildPositionMap(allEntries, SortModeNameAsc)}, } // Load page 1 (entries 100-199) page := loadPageFromIndex(s, 1) if page == nil { t.Fatal("expected page to be loaded, got nil") } if page.Index != 1 { t.Errorf("expected Index=1, got %d", page.Index) } if len(page.Entries) != PageSize { t.Errorf("expected %d entries, got %d", PageSize, len(page.Entries)) } // First entry of page 1 should be file100.txt if page.Entries[0].Name != "file100.txt" { t.Errorf("expected first entry name=file100.txt, got %s", page.Entries[0].Name) } // Last entry of page 1 should be file199.txt if page.Entries[99].Name != "file199.txt" { t.Errorf("expected last entry name=file199.txt, got %s", page.Entries[99].Name) } } // --- Test: Load Page - Last Page Partial --- func TestLoadPage_LastPagePartialFromIndex(t *testing.T) { s := NewBrowserState() s.TotalEntries = 150 // 1 full page + 50 entries in page 1 allEntries := make([]Entry, 150) for i := 0; i < 150; i++ { allEntries[i] = NewEntry( fmt.Sprintf("/path/file%d.txt", i), fmt.Sprintf("file%d.txt", i), int64(i*100), time.Time{}, false, ) } // Build position map for the default sort mode (NameAsc) s.SortIndex = &DirectoryIndex{ Entries: allEntries, SortOrders: map[string][]int{"name_asc": buildPositionMap(allEntries, SortModeNameAsc)}, } // Load page 1 (last page, should have only 50 entries) page := loadPageFromIndex(s, 1) if page == nil { t.Fatal("expected page to be loaded, got nil") } if page.Index != 1 { t.Errorf("expected Index=1, got %d", page.Index) } if len(page.Entries) != 50 { t.Errorf("expected 50 entries, got %d", len(page.Entries)) } } // --- Test: Load Page - Out Of Bounds --- func TestLoadPage_OutOfBoundsFromIndex(t *testing.T) { s := NewBrowserState() s.TotalEntries = 150 allEntries := make([]Entry, 150) for i := 0; i < 150; i++ { allEntries[i] = NewEntry( fmt.Sprintf("/path/file%d.txt", i), fmt.Sprintf("file%d.txt", i), int64(i*100), time.Time{}, false, ) } // Build position map for the default sort mode (NameAsc) s.SortIndex = &DirectoryIndex{ Entries: allEntries, SortOrders: map[string][]int{"name_asc": buildPositionMap(allEntries, SortModeNameAsc)}, } // Requesting page 5 (out of bounds) should return nil page := loadPageFromIndex(s, 5) if page != nil { t.Errorf("expected nil for out-of-bounds page, got page with %d entries", len(page.Entries)) } } // --- Test: Compute Visible Page Range --- func TestComputeVisiblePageRange(t *testing.T) { s := NewBrowserState() s.ScrollIndex = 250 s.VisibleCount = 20 minPage, maxPage := computeVisiblePageRange(s) // ScrollIndex=250 → page 2 // VisibleCount=20 → entries 250-269 → pages 2-2 (partial) // With prefetch: min=0, max=4 expectedMin := 0 // (250/100) - 2 = 0 expectedMax := 4 // (270/100) + 2 = 4 if minPage != expectedMin { t.Errorf("expected minPage=%d, got %d", expectedMin, minPage) } if maxPage != expectedMax { t.Errorf("expected maxPage=%d, got %d", expectedMax, maxPage) } } // --- Test: Compute Visible Page Range - At Start --- func TestComputeVisiblePageRange_AtStart(t *testing.T) { s := NewBrowserState() s.ScrollIndex = 0 s.VisibleCount = 10 minPage, maxPage := computeVisiblePageRange(s) // Should clamp min to 0 if minPage != 0 { t.Errorf("expected minPage=0, got %d", minPage) } // maxPage should be 0 + 2 = 2 if maxPage != 2 { t.Errorf("expected maxPage=2, got %d", maxPage) } } // --- Test: Get Entry By Index --- func TestGetEntryByIndex(t *testing.T) { s := NewBrowserState() s.TotalEntries = 250 // Use zero-padded names so lexicographic order matches numeric order allEntries := make([]Entry, 250) for i := 0; i < 250; i++ { allEntries[i] = NewEntry( fmt.Sprintf("/path/file%03d.txt", i), fmt.Sprintf("file%03d.txt", i), int64(i*100), time.Time{}, false, ) } // Build position map for the default sort mode (NameAsc) s.SortIndex = &DirectoryIndex{ Entries: allEntries, SortOrders: map[string][]int{"name_asc": buildPositionMap(allEntries, SortModeNameAsc)}, } // Load some pages s.Pages[0] = loadPageFromIndex(s, 0) s.Pages[1] = loadPageFromIndex(s, 1) // Get entry at index 150 (page 1, offset 50) entry, ok := getEntryByIndex(s, 150) if !ok { t.Fatal("expected entry to be found") } if entry.Name != "file150.txt" { t.Errorf("expected entry name=file150.txt, got %s", entry.Name) } } // --- Test: Get Entry By Index - Unloaded Page --- func TestGetEntryByIndex_UnloadedPage(t *testing.T) { s := NewBrowserState() s.TotalEntries = 250 allEntries := make([]Entry, 250) for i := 0; i < 250; i++ { allEntries[i] = NewEntry( fmt.Sprintf("/path/file%d.txt", i), fmt.Sprintf("file%d.txt", i), int64(i*100), time.Time{}, false, ) } // Build position map for the default sort mode (NameAsc) s.SortIndex = &DirectoryIndex{ Entries: allEntries, SortOrders: map[string][]int{"name_asc": buildPositionMap(allEntries, SortModeNameAsc)}, } // Only load page 0 s.Pages[0] = loadPageFromIndex(s, 0) // Try to get entry at index 150 (page 1, not loaded) _, ok := getEntryByIndex(s, 150) if ok { t.Error("expected entry not to be found for unloaded page") } } // --- Test: Get Entry By Index - Out Of Bounds --- func TestGetEntryByIndex_OutOfBounds(t *testing.T) { s := NewBrowserState() s.TotalEntries = 100 _, ok := getEntryByIndex(s, 200) if ok { t.Error("expected entry not to be found for out-of-bounds index") } } // --- Test: Needs Prefetch --- func TestNeedsPrefetch(t *testing.T) { s := NewBrowserState() s.ScrollIndex = 500 s.VisibleCount = 20 s.TotalEntries = 2000 // Page 3 is within visible range + prefetch needs, _ := needsPrefetch(s, 3) if !needs { t.Error("expected page 3 to need prefetch") } // Page 0 is far away, should not need prefetch needs, _ = needsPrefetch(s, 0) if needs { t.Error("expected page 0 to not need prefetch") } // Page 25 is far away, should not need prefetch needs, _ = needsPrefetch(s, 25) if needs { t.Error("expected page 25 to not need prefetch") } } // --- Test: Navigate To Directory --- func TestNavigateToDirectory(t *testing.T) { s := NewBrowserState() s.CurrentPath = "/old/path" s.ScrollIndex = 100 s.SelectedIndex = 5 s.Pages[0] = NewPage(0, nil) navigateToDirectory(s, "/new/path") if s.CurrentPath != "/new/path" { t.Errorf("expected CurrentPath=/new/path, got %s", s.CurrentPath) } if s.ScrollIndex != 0 { t.Errorf("expected ScrollIndex=0, got %d", s.ScrollIndex) } if len(s.Pages) != 0 { t.Errorf("expected Pages to be empty, got %d", len(s.Pages)) } } // --- Test: Compute Total Pages --- func TestComputeTotalPages(t *testing.T) { s := NewBrowserState() s.TotalEntries = 0 if pages := computeTotalPages(s); pages != 0 { t.Errorf("expected 0 pages for 0 entries, got %d", pages) } s.TotalEntries = 100 if pages := computeTotalPages(s); pages != 1 { t.Errorf("expected 1 page for 100 entries, got %d", pages) } s.TotalEntries = 101 if pages := computeTotalPages(s); pages != 2 { t.Errorf("expected 2 pages for 101 entries, got %d", pages) } s.TotalEntries = 250 if pages := computeTotalPages(s); pages != 3 { t.Errorf("expected 3 pages for 250 entries, got %d", pages) } } // --- Test: Clamp Scroll Index --- func TestClampScrollIndex(t *testing.T) { s := NewBrowserState() s.TotalEntries = 500 s.VisibleCount = 20 // Normal case s.ScrollIndex = 100 clampScrollIndex(s) if s.ScrollIndex != 100 { t.Errorf("expected ScrollIndex=100, got %d", s.ScrollIndex) } // Below minimum s.ScrollIndex = -10 clampScrollIndex(s) if s.ScrollIndex != 0 { t.Errorf("expected ScrollIndex=0, got %d", s.ScrollIndex) } // Above maximum s.ScrollIndex = 1000 clampScrollIndex(s) if s.ScrollIndex != 480 { t.Errorf("expected ScrollIndex=480, got %d", s.ScrollIndex) } } // --- Test: Filter Entries By Query --- func TestFilterEntriesByQuery(t *testing.T) { entries := []Entry{ NewEntry("/path/file0.txt", "file0.txt", 0, time.Time{}, false), NewEntry("/path/file1.txt", "file1.txt", 100, time.Time{}, false), NewEntry("/path/file2.txt", "file2.txt", 200, time.Time{}, false), NewEntry("/path/file10.txt", "file10.txt", 1000, time.Time{}, false), NewEntry("/path/file100.txt", "file100.txt", 10000, time.Time{}, false), } // Search for "file1" - should match file1.txt, file10.txt, file100.txt results := filterEntriesByQuery(entries, "file1") if len(results) != 3 { t.Errorf("expected 3 results for 'file1', got %d", len(results)) } expectedNames := map[string]bool{"file1.txt": true, "file10.txt": true, "file100.txt": true} for _, idx := range results { if !expectedNames[entries[idx].Name] { t.Errorf("unexpected result: %s", entries[idx].Name) } } } // --- Test: Filter Entries By Query - Empty Query --- func TestFilterEntriesByQuery_EmptyQuery(t *testing.T) { entries := []Entry{ NewEntry("/a", "a.txt", 100, time.Time{}, false), NewEntry("/b", "b.txt", 200, time.Time{}, false), } results := filterEntriesByQuery(entries, "") if len(results) != 0 { t.Errorf("expected 0 results for empty query, got %d", len(results)) } } // --- Test: Filter Entries By Query - No Match --- func TestFilterEntriesByQuery_NoMatch(t *testing.T) { entries := []Entry{ NewEntry("/a", "a.txt", 100, time.Time{}, false), NewEntry("/b", "b.txt", 200, time.Time{}, false), } results := filterEntriesByQuery(entries, "zzz") if len(results) != 0 { t.Errorf("expected 0 results for 'zzz', got %d", len(results)) } } // --- Test: Filter Entries By Query - Case Insensitive --- func TestFilterEntriesByQuery_CaseInsensitive(t *testing.T) { entries := []Entry{ NewEntry("/a", "Apple.txt", 100, time.Time{}, false), NewEntry("/b", "banana.txt", 200, time.Time{}, false), } results := filterEntriesByQuery(entries, "apple") if len(results) != 1 { t.Errorf("expected 1 result for 'apple', got %d", len(results)) } if results[0] != 0 { t.Errorf("expected result at index 0, got %d", results[0]) } }