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.
106 lines
2.9 KiB
Go
106 lines
2.9 KiB
Go
package e2e_test
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"pad/internal/editor"
|
|
"pad/internal/test/e2e"
|
|
"pad/internal/ui"
|
|
)
|
|
|
|
// TestEditorInitialLayout tests the initial editor layout has expected structure.
|
|
func TestEditorInitialLayout(t *testing.T) {
|
|
h := e2e.NewHarnessWithDefaults()
|
|
defer h.Cleanup()
|
|
|
|
// Switch to editor page (owner-side)
|
|
if err := h.WithState(func(st *editor.State) { editor.GoToEditor(nil) }); err != nil {
|
|
t.Fatalf("GoToEditor: %v", err)
|
|
}
|
|
h.SendConfig(780, 1688)
|
|
|
|
// Wait for a new frame after switching to editor page
|
|
_, err := e2e.WaitForNewFrame(h, h.FrameCount(), 5*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("timeout waiting for frames: %v", err)
|
|
}
|
|
|
|
lastFrame := e2e.GetLastFrame(h)
|
|
ea := e2e.NewElementAssertions(t, lastFrame)
|
|
ea.HasElementCount(3)
|
|
ea.HasElementOfType(reflect.TypeOf(ui.Container{}))
|
|
ea.HasElementOfType(reflect.TypeOf(ui.TextField{}))
|
|
ea.HasElementWithID("editor_text")
|
|
}
|
|
|
|
// TestConfigChangeTriggersNewFrame tests that config changes produce new frames.
|
|
func TestConfigChangeTriggersNewFrame(t *testing.T) {
|
|
h := e2e.NewHarnessWithDefaults()
|
|
defer h.Cleanup()
|
|
|
|
_, err := h.WaitForFrameCount(1, 5*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("timeout waiting for initial frame: %v", err)
|
|
}
|
|
|
|
initialCount := h.FrameCount()
|
|
h.SendConfig(1000, 2000)
|
|
|
|
newCount, err := e2e.WaitForNewFrame(h, initialCount, 5*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("timeout waiting for new frame: %v", err)
|
|
}
|
|
if newCount <= initialCount {
|
|
t.Errorf("expected new frame after config change, got %d (was %d)", newCount, initialCount)
|
|
}
|
|
}
|
|
|
|
// TestElementPositionsValid tests that elements have valid regions and no overlaps.
|
|
func TestElementPositionsValid(t *testing.T) {
|
|
h := e2e.NewHarnessWithDefaults()
|
|
defer h.Cleanup()
|
|
|
|
frames, err := h.WaitForFrameCount(1, 5*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("timeout waiting for frames: %v", err)
|
|
}
|
|
|
|
lastFrame := frames[len(frames)-1]
|
|
e2e.AssertNoOverlappingElements(t, lastFrame)
|
|
e2e.AssertFrameHasNoEmptyRegions(t, lastFrame)
|
|
e2e.AssertFrameHasNoNegativeRegions(t, lastFrame)
|
|
e2e.AssertFrameHasNoInvisibleElements(t, lastFrame)
|
|
|
|
screenBounds := ui.Region{X: 0, Y: 0, W: 780, H: 1688}
|
|
e2e.AssertFrameHasNoOutOfBoundsElements(t, lastFrame, screenBounds)
|
|
}
|
|
|
|
// TestHarnessTimeout tests that timeout errors are returned correctly.
|
|
func TestHarnessTimeout(t *testing.T) {
|
|
h := e2e.NewHarness()
|
|
h.Run()
|
|
defer h.Cleanup()
|
|
|
|
// Wait for initial startup frames first
|
|
_, err := h.WaitForFrameCount(1, 5*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("timeout waiting for initial frames: %v", err)
|
|
}
|
|
|
|
initialCount := h.FrameCount()
|
|
|
|
// Now wait for a frame count that won't be reached (no more events sent)
|
|
_, err = h.WaitForFrameCount(initialCount+10, 100*time.Millisecond)
|
|
if err == nil {
|
|
t.Error("expected timeout error, got nil")
|
|
}
|
|
}
|
|
|
|
// TestHarnessCleanup tests that cleanup stops goroutines without deadlock.
|
|
func TestHarnessCleanup(t *testing.T) {
|
|
h := e2e.NewHarnessWithDefaults()
|
|
h.Cleanup()
|
|
}
|