package browser import ( "testing" "time" "pad/internal/io/pool" "pad/internal/io/pool/mock" ) // TestNewBrowserManager verifies that a BrowserManager can be constructed // with valid parameters and has correct initial state. func TestNewBrowserManager(t *testing.T) { state := NewBrowserState() fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } if bm == nil { t.Fatal("NewBrowserManager returned nil") } // Verify initial state if bm.state != state { t.Errorf("BrowserManager.state = %p, want %p", bm.state, state) } } // TestNavigateTo verifies that NavigateTo dispatches a BuildIndexTask // and resets browser state appropriately. func TestNavigateTo(t *testing.T) { state := NewBrowserState() fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) // Create some mock entries in the filesystem now := time.Now() fs.AddDir("/test/docs", now) fs.AddFile("/test/docs/file1.txt", []byte("content1"), now) fs.AddFile("/test/docs/file2.txt", []byte("content2"), now) fs.AddDir("/test/docs/subdir", now) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } // Navigate to a directory bm.NavigateTo("/test/docs") // Verify state was reset if state.CurrentPath != "/test/docs" { t.Errorf("CurrentPath = %q, want %q", state.CurrentPath, "/test/docs") } if state.ScrollOffset != 0 { t.Errorf("ScrollOffset = %f, want 0", state.ScrollOffset) } if state.SelectedIndex != -1 { t.Errorf("SelectedIndex = %d, want -1", state.SelectedIndex) } // Verify a task was dispatched (should have pending work) // The manager should have queued a BuildIndexTask if !state.Loading { t.Error("Expected Loading to be true after NavigateTo") } } // TestNavigateTo_NonExistentDirectory verifies behavior when navigating // to a directory that doesn't exist in the filesystem. func TestNavigateTo_NonExistentDirectory(t *testing.T) { state := NewBrowserState() fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } // Navigate to non-existent directory bm.NavigateTo("/non/existent/path") // Should still set the path (error handling happens async) if state.CurrentPath != "/non/existent/path" { t.Errorf("CurrentPath = %q, want %q", state.CurrentPath, "/non/existent/path") } } // TestHandleResult_BuildIndexSuccess verifies that a successful BuildIndex // result populates the SortIndex and triggers initial page loading. func TestHandleResult_BuildIndexSuccess(t *testing.T) { state := NewBrowserState() fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } // Simulate a successful BuildIndex result result := pool.Result{ TaskType: pool.TypeBuildIndex, Success: true, Data: []mock.DirEntry{}, } bm.HandleResult(result) // After successful BuildIndex, Loading should be false if state.Loading { t.Error("Expected Loading to be false after successful BuildIndex result") } } // TestHandleResult_BuildIndexFailure verifies that a failed BuildIndex // result is handled gracefully. func TestHandleResult_BuildIndexFailure(t *testing.T) { state := NewBrowserState() fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } // Simulate a failed BuildIndex result result := pool.Result{ TaskType: pool.TypeBuildIndex, Success: false, Error: ErrDirNotFound, } bm.HandleResult(result) // Should not panic, state should be recoverable if state.CurrentPath != "" { t.Errorf("Expected empty CurrentPath on error, got %q", state.CurrentPath) } } // TestHandleResult_LoadPagesSuccess verifies that a successful LoadPages // result populates the Pages map. func TestHandleResult_LoadPagesSuccess(t *testing.T) { state := NewBrowserState() state.TotalEntries = 500 fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } // Simulate a successful LoadPages result pageIndices := []int{0, 1} result := pool.Result{ TaskType: pool.TypeLoadPages, Success: true, Data: pageIndices, } bm.HandleResult(result) // Pages should be populated (or at least not cause a panic) // The exact behavior depends on implementation } // TestOnScroll_Prefetch verifies that scrolling triggers prefetch for // pages beyond the visible region. func TestOnScroll_Prefetch(t *testing.T) { state := NewBrowserState() state.TotalEntries = 1000 state.EntryHeight = 48.0 state.VisibleCount = 10 fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } // Set initial scroll position state.ScrollOffset = 0 // Simulate scrolling down state.ScrollOffset = 5000.0 // ~100 entries down bm.OnScroll() // Should trigger prefetch (exact behavior depends on implementation) // At minimum, should not panic } // TestOnScroll_Eviction verifies that scrolling triggers eviction of // pages far from the visible region. func TestOnScroll_Eviction(t *testing.T) { state := NewBrowserState() state.TotalEntries = 1000 state.EntryHeight = 48.0 state.VisibleCount = 10 fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } // Pre-populate some pages state.Pages[0] = NewPage(0, make([]Entry, 100)) state.Pages[5] = NewPage(5, make([]Entry, 100)) state.Pages[10] = NewPage(10, make([]Entry, 100)) // Scroll far down state.ScrollOffset = 50000.0 // ~1000 entries down bm.OnScroll() // Pages far from visible region should be evicted // (exact behavior depends on implementation) } // TestBrowserManager_StateOwnership verifies that the BrowserManager // does not directly modify state outside of designated methods. func TestBrowserManager_StateOwnership(t *testing.T) { state := NewBrowserState() fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } // Verify that the manager has a reference to the state // but does not own it (state is owned by the editor) if bm.state != state { t.Error("BrowserManager should have reference to BrowserState") } } // TestNavigateTo_ChangesDirectory verifies that navigating to a new // directory clears the previous directory's state. func TestNavigateTo_ChangesDirectory(t *testing.T) { state := NewBrowserState() state.CurrentPath = "/old/path" state.TotalEntries = 500 state.Pages[0] = NewPage(0, make([]Entry, 100)) fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } // Navigate to new directory bm.NavigateTo("/new/path") if state.CurrentPath != "/new/path" { t.Errorf("CurrentPath = %q, want %q", state.CurrentPath, "/new/path") } // Pages should be cleared for new directory if len(state.Pages) != 0 { t.Errorf("Expected Pages to be cleared, got %d pages", len(state.Pages)) } } // TestHandleResult_UnknownType verifies that unknown result types // are handled gracefully without panicking. func TestHandleResult_UnknownType(t *testing.T) { state := NewBrowserState() fs := mock.NewFileSystem() wp := pool.NewWorkerPool(1) bm, err := NewBrowserManager(state, wp, fs) if err != nil { t.Fatalf("NewBrowserManager failed: %v", err) } // Simulate an unknown result type result := pool.Result{ TaskType: pool.TypeReadFile, // Not a browser task Success: true, Data: nil, } // Should not panic bm.HandleResult(result) }