package e2e_test import ( "strings" "testing" "time" "pad/internal/editor" "pad/internal/test/e2e" "pad/internal/ui" ) // TestEditLifecycle verifies that edits made through the editor are persisted // when the file is closed (via GoToBrowser -> FlushAll). // // All state access goes through the harness's owner-side helpers: the logic // goroutine is the sole owner of State (architecture.md ยง1). func TestEditLifecycle(t *testing.T) { h := e2e.NewHarnessWithDefaults() defer h.Cleanup() // 1. Go to browser page if err := h.WithState(func(st *editor.State) { editor.GoToBrowser(nil) }); err != nil { t.Fatalf("GoToBrowser: %v", err) } time.Sleep(200 * time.Millisecond) // 2. Open File filename := "/notes.txt" if err := h.WithState(func(st *editor.State) { editor.OpenFile(filename) }); err != nil { t.Fatalf("OpenFile: %v", err) } // Wait for file to load success := false for i := 0; i < 20; i++ { loaded, err := h.FileLoaded() if err != nil { t.Fatalf("FileLoaded: %v", err) } if loaded { success = true break } time.Sleep(100 * time.Millisecond) } if !success { t.Fatal("Timed out waiting for file to load") } // 3. Edit File // Original content of notes.txt is 198 bytes, let's append " UPDATED" if err := h.WithState(func(st *editor.State) { editor.HandleInsert(" UPDATED") }); err != nil { t.Fatalf("HandleInsert: %v", err) } time.Sleep(100 * time.Millisecond) // 4. Close File (Go back to browser) // This will trigger FlushAll() if err := h.WithState(func(st *editor.State) { editor.GoToBrowser(nil) }); err != nil { t.Fatalf("GoToBrowser: %v", err) } time.Sleep(500 * time.Millisecond) // Ensure it had time to process close // 5. Re-open File if err := h.WithState(func(st *editor.State) { editor.OpenFile(filename) }); err != nil { t.Fatalf("OpenFile: %v", err) } time.Sleep(1000 * time.Millisecond) // Wait for re-open and load // 6. Verify Edits fullContent, err := h.FullContent() if err != nil { t.Fatalf("Failed to reconstruct content: %v", err) } if len(fullContent) <= 198 { t.Errorf("Content did not persist. Length: %d, expected > 198", len(fullContent)) } // 7. Verify Size on Screen (check bottom bar label) frame := e2e.GetLastFrame(h) var sizeText string for _, elem := range frame { if container, ok := elem.(ui.Container); ok { // The bottom bar is a container, look for labels inside for _, child := range container.Children { if label, ok := child.(ui.Label); ok { // The cursor position text is in the middle, containing "/" if len(label.Text) > 0 && label.Text[0] != '/' && strings.Contains(label.Text, "/") { sizeText = label.Text } } } } } expectedText := "0 / 206" // 198 original + " UPDATED" (8) = 206, cursor at top if sizeText != expectedText { t.Errorf("Bottom bar size label mismatch: got %q, expected %q", sizeText, expectedText) } }