Pad/internal/browser/browser_test.go
Greg Pomerantz 06b1444207 gofmt: format all remaining files with the go1.27 toolchain
The tree was formatted with an older gofmt; go1.27's gofmt additionally
wants: EOF exactly one newline (no trailing blank lines), imports sorted
alphabetically within a block, mixed-precedence binary expressions
re-spaced for grouping ((a+b)/c), single-field composite literals
un-aligned, adjacent one-line method signatures aligned, and one-line
bodies containing a compound statement expanded. Applied repo-wide
(31 files under internal/); pure formatting, no semantic changes —
build and the full test suite pass.
2026-08-23 10:03:27 -04:00

452 lines
11 KiB
Go

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 maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: allEntries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
},
}
// 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 maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: allEntries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
},
}
// 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 maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: allEntries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
},
}
// 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.EntryHeight = 48.0
s.ScrollOffset = 250 * 48 // 250 entries worth of pixels
s.VisibleCount = 20
minPage, maxPage := computeVisiblePageRange(s)
// ScrollOffset=250*48 → GetScrollIndex()=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.EntryHeight = 48.0
s.ScrollOffset = 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 maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: allEntries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
},
}
// 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 maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: allEntries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
},
}
// 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.EntryHeight = 48.0
s.ScrollOffset = 500 * 48 // 500 entries worth of pixels
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.EntryHeight = 48.0
s.CurrentPath = "/old/path"
s.ScrollOffset = 100 * 48 // 100 entries worth of pixels
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.ScrollOffset != 0 {
t.Errorf("expected ScrollOffset=0, got %.1f", s.ScrollOffset)
}
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 TestClampScrollOffset(t *testing.T) {
s := NewBrowserState()
s.TotalEntries = 500
s.VisibleCount = 20
s.EntryHeight = 48.0
// Normal case
s.ScrollOffset = 100 * 48
clampScrollOffset(s)
expected := float64(100 * 48)
if s.ScrollOffset != expected {
t.Errorf("expected ScrollOffset=%.1f, got %.1f", expected, s.ScrollOffset)
}
// Below minimum
s.ScrollOffset = -10 * 48
clampScrollOffset(s)
if s.ScrollOffset != 0 {
t.Errorf("expected ScrollOffset=0, got %.1f", s.ScrollOffset)
}
// Above maximum
s.ScrollOffset = 1000 * 48
clampScrollOffset(s)
maxScroll := float64(500-20) * 48
if s.ScrollOffset != maxScroll {
t.Errorf("expected ScrollOffset=%.1f, got %.1f", maxScroll, s.ScrollOffset)
}
}
// --- 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])
}
}