Pad/internal/browser/manager_test.go

302 lines
7.9 KiB
Go

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)
}