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.
109 lines
3.5 KiB
Go
109 lines
3.5 KiB
Go
package e2e_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"pad/internal/editor"
|
|
"pad/internal/test/e2e"
|
|
"pad/internal/ui"
|
|
)
|
|
|
|
// TestEditorClickToMoveCursorWithScroll verifies that a tap in the editor
|
|
// accounts for the current scroll offset and lands on the right line.
|
|
//
|
|
// State setup/assertions use the harness's owner-side helpers (WithState /
|
|
// CursorPosition / Inspect); the tap handler closure runs on the logic
|
|
// goroutine via SendInput, so its reads of editor.TheState are safe.
|
|
func TestEditorClickToMoveCursorWithScroll(t *testing.T) {
|
|
// NewHarnessWithDefaults already starts the logic goroutine.
|
|
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)
|
|
// Give it a moment to initialize
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Wait for frame
|
|
_, err := h.WaitForFrameCount(1, 5*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("timeout waiting for frame: %v", err)
|
|
}
|
|
|
|
// Setup:
|
|
// - 2 lines, each 10 chars + newline.
|
|
// - Line height = 14 * 1.2 = 16.8 (rounded?) Let's check EditorLineHeight()
|
|
// EditorLineHeight is 14 * 1.2 = 16.8.
|
|
lineHeight := 16.8
|
|
if err := h.WithState(func(st *editor.State) {
|
|
st.Editor.Buffer = "Line 1\nLine 2\nLine 3"
|
|
|
|
// Scroll to start of Line 2 (skip Line 1)
|
|
st.ScrollOffset = ui.Dp(lineHeight)
|
|
|
|
// GlyphLayout:
|
|
// Line 1: y = 0
|
|
// Line 2: y = 16.8
|
|
// Line 3: y = 33.6
|
|
st.Editor.GlyphLayout = ui.GlyphLayout{
|
|
ByteOffsets: []int{0, 7, 14}, // Start of each line
|
|
X: []ui.Dp{10, 10, 10},
|
|
Y: []ui.Dp{ui.Dp(0), ui.Dp(lineHeight), ui.Dp(lineHeight * 2)},
|
|
Advance: []ui.Dp{10, 10, 10},
|
|
}
|
|
}); err != nil {
|
|
t.Fatalf("WithState: %v", err)
|
|
}
|
|
|
|
// Tap at y=10 (within the text area).
|
|
// Because of scroll=16.8, this should correspond to:
|
|
// visualLine = (y + scroll) / lineHeight = (10 + 16.8) / 16.8 = 26.8 / 16.8 = 1.59 -> 1
|
|
// Line 1 is index 0. Line 2 is index 1.
|
|
// So visualLine 1 should be Line 2.
|
|
|
|
// The problem is that SetCursorFromPoint expects y in DP, but receives it as raw pixels
|
|
// if we're not careful. Let's pass the y value properly.
|
|
// The test harness sends raw pixel coordinates to handler.
|
|
// EditorLayout converts tap point:
|
|
// localY := float64(pt.Y - editorRegion.Y + TheState.ScrollOffset)
|
|
// So the handler receives Y as pt.Y, where pt.Y is relative to the screen.
|
|
// The test harness doesn't seem to account for region offset.
|
|
|
|
// Let's debug by printing in the test (owner-side read).
|
|
v, err := h.Inspect(func(st *editor.State) any { return float64(st.ScrollOffset) })
|
|
if err != nil {
|
|
t.Fatalf("Inspect: %v", err)
|
|
}
|
|
t.Logf("ScrollOffset: %v", v)
|
|
|
|
h.SendInput([]ui.InputEvent{
|
|
{
|
|
Handler: func(data any) {
|
|
if pt, ok := data.(ui.Point); ok {
|
|
// Manually simulate the offset correction that EditorLayout does
|
|
// editorRegion.Y is margin(10) + statusBarH(52) = 62.
|
|
// Let's set pt.Y to 10 + 62 = 72.
|
|
localY := float64(pt.Y) - 62.0 + float64(editor.TheState.ScrollOffset)
|
|
editor.SetCursorFromPoint(float64(pt.X), localY)
|
|
}
|
|
},
|
|
Data: ui.Point{X: 10, Y: 72},
|
|
},
|
|
})
|
|
|
|
// Assert cursor moved to start of Line 2 (offset 7)
|
|
time.Sleep(100 * time.Millisecond) // Give logic goroutine a moment to process input
|
|
pos, err := h.CursorPosition()
|
|
if err != nil {
|
|
t.Fatalf("CursorPosition: %v", err)
|
|
}
|
|
t.Logf("Cursor position: %d", pos)
|
|
if pos != 7 {
|
|
t.Errorf("expected cursor to move to 7 (Line 2), but got %d", pos)
|
|
}
|
|
}
|