Frame now carries view state (scale/focus/query) to the main goroutine, which reads only the frame-receiver-stored snapshot. Renderer owns Gio widget editors (registered by ID) and the draw scale. Autosave timer sends a token to the logic goroutine instead of touching state; Shutdown waits for the owner to exit before FlushAll. Tests access state only through owner-side Inspect/WithState helpers (harness + in-package). Fixed double-Harness.Run race in two e2e tests, made TestWorkerPool_PriorityPreemption deterministic, and raised the lazy-loading test timeout that was too short under -race. go test -race ./... is now green.
102 lines
2.9 KiB
Go
102 lines
2.9 KiB
Go
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)
|
|
}
|
|
}
|