Three user-reported selection bugs, one root cause each:
1. Start handle ungrabbable at line start. Two interacting causes:
a) The 48dp grab box straddles two visual lines; a finger in the
lower half mapped (by y-to-line) to the neighbouring line, whose
byte past the other handle clamped to a zero-length selection ->
cleared on the first drag event. The cleared selection
un-registered the drag op, so the router silently stopped
delivering drag events (the observed 'stream cutoff'). Fix:
handle drags now project the finger's x onto the anchor's own
visual line (visualLineOfByte + textPosOnLineAtX); the anchor
never crosses lines during a handle drag.
b) A horizontal flick from the line-start handle (screen x~26px)
started the system back gesture, which cancelled the touch
stream. Fix: report the handle grab rects as system gesture
exclusion rects (setSystemGestureExclusionRects, API 29+),
marshalled to the UI thread via a PadExcl smali Runnable
(generated identically by build_emu.sh/build_phone.sh).
2. End-handle drag downward made the menu chase the finger and cover
the selection. Fix: the menu anchors to the STABLE end of the
selection (the end not being dragged), so it stays parked by the
selection start, clear of the finger and the highlighted text.
3. Menu above the selection vanished permanently when the selection
was extended onto the top line. Fix: off-window anchors no longer
hide the menu while any part of the selection is visible (keep-last
rect, clamped); hiding happens only for fully off-window selections.
Also: registerDrag simplified (single shared drag path, body before
handles in z-order), debug logging removed, regression tests
(mutation-verified) for line projection and menu anchoring, docs
section 17. Verified on device: start-handle drag shrinks the word
without clearing or triggering back navigation; end-handle vertical
drag leaves menu/highlight/handles undisturbed; menu stays visible
with the selection at the top line.
455 lines
16 KiB
Go
455 lines
16 KiB
Go
package editor
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"pad/internal/ui"
|
|
)
|
|
|
|
// touchSelState sets up a fresh string-buffer state with a single-line
|
|
// GlyphLayout: 11 glyphs at x=10*i (advance 10), line top at y=0.
|
|
// EditorRegion is {X:16, Y:100, W:379, H:700} so text-local x = pt.X-16 and
|
|
// tapLocalY(y,100,0) = pt.Y-100 (scroll offset 0).
|
|
func touchSelState(content string) {
|
|
TheState = NewState()
|
|
TheState.Editor.Buffer = content
|
|
TheState.Editor.CursorPosition = len(content)
|
|
TheState.EditorRegion = ui.Region{X: 16, Y: 100, W: 379, H: 700}
|
|
TheState.ScrollOffset = 0
|
|
// The visible window (what layoutFrame sets every frame): the whole
|
|
// string buffer. bytePosToScreenXY bounds-checks against it.
|
|
TheState.Editor.IMEWindowStartByte = 0
|
|
TheState.Editor.IMEWindowText = content
|
|
n := len(content)
|
|
bo := make([]int, n)
|
|
xs := make([]ui.Dp, n)
|
|
ys := make([]ui.Dp, n)
|
|
ad := make([]ui.Dp, n)
|
|
for i := 0; i < n; i++ {
|
|
bo[i] = i
|
|
xs[i] = ui.Dp(10 * i)
|
|
ys[i] = 0
|
|
ad[i] = 10
|
|
}
|
|
TheState.Editor.GlyphLayout = ui.GlyphLayout{
|
|
ByteOffsets: bo,
|
|
X: xs,
|
|
Y: ys,
|
|
Advance: ad,
|
|
}
|
|
}
|
|
|
|
// assertMenu checks menu visibility and item icons.
|
|
func assertMenu(t *testing.T, wantVisible bool, wantIcons ...string) {
|
|
t.Helper()
|
|
e := TheState.Editor
|
|
if e.MenuVisible != wantVisible {
|
|
t.Fatalf("MenuVisible = %v, want %v", e.MenuVisible, wantVisible)
|
|
}
|
|
if wantVisible {
|
|
if len(e.MenuItems) != len(wantIcons) {
|
|
t.Fatalf("menu has %d items, want %d", len(e.MenuItems), len(wantIcons))
|
|
}
|
|
for i, it := range e.MenuItems {
|
|
if it.Icon != wantIcons[i] {
|
|
t.Errorf("menu item %d icon = %q, want %q", i, it.Icon, wantIcons[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWordRangeAt(t *testing.T) {
|
|
touchSelState("hello world")
|
|
|
|
// Inside a word.
|
|
s, e, ok := wordRangeAt(3)
|
|
if !ok || s != 0 || e != 5 {
|
|
t.Errorf("wordRangeAt(3) = [%d,%d) ok=%v, want [0,5) true", s, e, ok)
|
|
}
|
|
// Second word.
|
|
s, e, ok = wordRangeAt(7)
|
|
if !ok || s != 6 || e != 11 {
|
|
t.Errorf("wordRangeAt(7) = [%d,%d) ok=%v, want [6,11) true", s, e, ok)
|
|
}
|
|
// Word boundary: position right after a word (space after) still finds
|
|
// the word ending there.
|
|
s, e, ok = wordRangeAt(5)
|
|
if !ok || s != 0 || e != 5 {
|
|
t.Errorf("wordRangeAt(5) = [%d,%d) ok=%v, want [0,5) true", s, e, ok)
|
|
}
|
|
// EOF: the position is right after the last word -> that word.
|
|
s, e, ok = wordRangeAt(11)
|
|
if !ok || s != 6 || e != 11 {
|
|
t.Errorf("wordRangeAt(11) = [%d,%d) ok=%v, want [6,11) true", s, e, ok)
|
|
}
|
|
}
|
|
|
|
func TestWordRangeAt_PunctuationAndIdentifiers(t *testing.T) {
|
|
touchSelState("foo,bar_baz 123")
|
|
// "bar_baz" is one word (underscore joins).
|
|
s, e, ok := wordRangeAt(7)
|
|
if !ok || s != 4 || e != 11 {
|
|
t.Errorf("wordRangeAt(7) = [%d,%d) ok=%v, want [4,11) true", s, e, ok)
|
|
}
|
|
// "123".
|
|
s, e, ok = wordRangeAt(14)
|
|
if !ok || s != 12 || e != 15 {
|
|
t.Errorf("wordRangeAt(14) = [%d,%d) ok=%v, want [12,15) true", s, e, ok)
|
|
}
|
|
// Position at the comma, right after "foo": the word ending there.
|
|
s, e, ok = wordRangeAt(3)
|
|
if !ok || s != 0 || e != 3 {
|
|
t.Errorf("wordRangeAt(3) = [%d,%d) ok=%v, want [0,3) true", s, e, ok)
|
|
}
|
|
}
|
|
|
|
func TestHandleLongPressAt_SelectsWord(t *testing.T) {
|
|
touchSelState("hello world")
|
|
// Long press on the first glyph of "hello" (window pt: x=16+2=18, y=108).
|
|
HandleLongPressAt(18, 108)
|
|
assertSelection(t, 0, 5)
|
|
assertMenu(t, true, "copy", "cut", "paste")
|
|
if TheState.Editor.SelectionAnchor != 0 {
|
|
t.Errorf("anchor = %d, want 0", TheState.Editor.SelectionAnchor)
|
|
}
|
|
if TheState.Editor.CursorPosition != 5 {
|
|
t.Errorf("cursor = %d, want 5", TheState.Editor.CursorPosition)
|
|
}
|
|
}
|
|
|
|
func TestHandleLongPressAt_BlankGivesCaretDrag(t *testing.T) {
|
|
// Line ends with punctuation so the position right after the last glyph
|
|
// is NOT adjacent to a word rune (a blank spot, not a word edge).
|
|
touchSelState("hello world.")
|
|
// Long press far right of the last glyph (x=16+150=166, past x=120).
|
|
HandleLongPressAt(166, 108)
|
|
assertSelection(t, -1, -1)
|
|
if !TheState.Editor.CaretDrag {
|
|
t.Errorf("CaretDrag = false, want true (long press on blank)")
|
|
}
|
|
// No selection -> the menu offers only Paste.
|
|
assertMenu(t, true, "paste")
|
|
if TheState.Editor.CursorPosition != 12 {
|
|
t.Errorf("cursor = %d, want 12 (end of line)", TheState.Editor.CursorPosition)
|
|
}
|
|
}
|
|
|
|
func TestHandleLongPressAt_AfterLastWordSelectsIt(t *testing.T) {
|
|
// A long press in the blank just past the last word selects that word
|
|
// (the word adjacent to the tap), matching Android's word selection.
|
|
touchSelState("hello world")
|
|
HandleLongPressAt(166, 108)
|
|
assertSelection(t, 6, 11)
|
|
}
|
|
|
|
func TestHandleLongPressAt_InGapBetweenWords(t *testing.T) {
|
|
touchSelState("hello world") // two spaces
|
|
// Long press on the SECOND space (glyph 6, center x=65; pt 16+65=81):
|
|
// no word rune at or immediately before the position -> caret.
|
|
HandleLongPressAt(81, 108)
|
|
assertSelection(t, -1, -1)
|
|
if !TheState.Editor.CaretDrag {
|
|
t.Errorf("CaretDrag = false, want true (long press in word gap)")
|
|
}
|
|
if TheState.Editor.CursorPosition != 6 {
|
|
t.Errorf("cursor = %d, want 6", TheState.Editor.CursorPosition)
|
|
}
|
|
}
|
|
|
|
func TestHandleDoubleTapAt_SelectsWord(t *testing.T) {
|
|
touchSelState("hello world")
|
|
HandleDoubleTapAt(18, 108)
|
|
assertSelection(t, 0, 5)
|
|
assertMenu(t, true, "copy", "cut", "paste")
|
|
}
|
|
|
|
func TestHandleTapAt_MovesCaretAndClearsSelection(t *testing.T) {
|
|
touchSelState("hello world")
|
|
// Establish a selection, then a plain tap clears it and moves the caret.
|
|
HandleLongPressAt(18, 108)
|
|
assertSelection(t, 0, 5)
|
|
// Tap on the 'w' of "world" (glyph 6, x=60..70; local 62 -> pt 78).
|
|
HandleTapAt(78, 108)
|
|
assertSelection(t, -1, -1)
|
|
if TheState.Editor.CaretDrag {
|
|
t.Errorf("CaretDrag = true after plain tap, want false")
|
|
}
|
|
if TheState.Editor.CursorPosition != 6 {
|
|
t.Errorf("cursor = %d, want 6", TheState.Editor.CursorPosition)
|
|
}
|
|
}
|
|
|
|
func TestHandleTapAt_InsideMenuIsIgnored(t *testing.T) {
|
|
touchSelState("hello world")
|
|
HandleLongPressAt(18, 108)
|
|
if !TheState.Editor.MenuVisible {
|
|
t.Fatal("expected menu visible")
|
|
}
|
|
posBefore := TheState.Editor.CursorPosition
|
|
selStartBefore := TheState.Editor.SelectionStart
|
|
// Tap inside the menu (top-left item).
|
|
m := TheState.Editor.MenuRect
|
|
HandleTapAt(m.X+5, m.Y+5)
|
|
if TheState.Editor.CursorPosition != posBefore {
|
|
t.Errorf("cursor moved to %d after tap in menu, want %d (ignored)", TheState.Editor.CursorPosition, posBefore)
|
|
}
|
|
if TheState.Editor.SelectionStart != selStartBefore {
|
|
t.Errorf("selection changed after tap in menu")
|
|
}
|
|
if !TheState.Editor.MenuVisible {
|
|
t.Errorf("menu hidden after tap in menu, want still visible")
|
|
}
|
|
}
|
|
|
|
func TestSelDrag_EndHandleExtends(t *testing.T) {
|
|
touchSelState("hello world")
|
|
HandleLongPressAt(18, 108) // selects "hello" [0,5)
|
|
// Drag the end handle (which=1) past the last glyph (local 115 > 110)
|
|
// so the whole line is selected.
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 1, X: 16 + 115, Y: 108})
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 1, X: 16 + 115, Y: 108})
|
|
assertSelection(t, 0, 11)
|
|
}
|
|
|
|
func TestSelDrag_StartHandleExtends(t *testing.T) {
|
|
touchSelState("hello world")
|
|
HandleLongPressAt(18, 108) // selects "hello" [0,5)
|
|
// Drag the start handle (which=0) to the 'o' (glyph 4, x=40..50; local 45 -> pt 61).
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 0, X: 16 + 45, Y: 108})
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 0, X: 16 + 45, Y: 108})
|
|
assertSelection(t, 4, 5)
|
|
}
|
|
|
|
func TestSelDrag_StartHandleCollapseClears(t *testing.T) {
|
|
touchSelState("hello world")
|
|
HandleLongPressAt(18, 108) // selects "hello" [0,5)
|
|
// Drag the start handle to the end of the selection -> zero-length -> cleared.
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 0, X: 16 + 52, Y: 108})
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 0, X: 16 + 52, Y: 108})
|
|
assertSelection(t, -1, -1)
|
|
assertMenu(t, false)
|
|
}
|
|
|
|
func TestSelDrag_BodyMovesPreservingLength(t *testing.T) {
|
|
touchSelState("hello world")
|
|
HandleLongPressAt(18, 108) // selects "hello" [0,5)
|
|
// Grab the body above the 'l' (glyph 2, x=20..30; local 25 -> pt 41):
|
|
// SelDragRel = pos(2) - selStart(0) = 2.
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 2, X: 16 + 25, Y: 108})
|
|
// Move the body so the finger is above the 'r' of world (glyph 9, x=90..100; local 95 -> pt 111):
|
|
// pos = 9 -> new start = 9-2 = 7 -> [7,12) clamped to [7,11)...
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 2, X: 16 + 95, Y: 108})
|
|
s := TheState.Editor
|
|
if s.SelectionStart != 6 || s.SelectionEnd != 11 {
|
|
// length 5 preserved, clamped at EOF: [6,11)
|
|
t.Errorf("body drag selection = [%d,%d), want [6,11)", s.SelectionStart, s.SelectionEnd)
|
|
}
|
|
HandleSelDragEvt(ui.SelectionDragEnd{})
|
|
}
|
|
|
|
func TestSelDrag_CaretHandleMovesCursor(t *testing.T) {
|
|
touchSelState("hello world.") // punctuation keeps the right side blank
|
|
HandleLongPressAt(166, 108) // blank -> caret drag at 12
|
|
if !TheState.Editor.CaretDrag {
|
|
t.Fatal("expected caret drag mode")
|
|
}
|
|
// Drag the caret handle (which=3) to glyph 2 (local 25 -> pt 41).
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 3, X: 16 + 25, Y: 108})
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 3, X: 16 + 25, Y: 108})
|
|
if TheState.Editor.CursorPosition != 2 {
|
|
t.Errorf("cursor = %d after caret drag, want 2", TheState.Editor.CursorPosition)
|
|
}
|
|
// The handle stays up while dragging...
|
|
if !TheState.Editor.CaretDrag {
|
|
t.Errorf("CaretDrag cleared mid-drag, want true")
|
|
}
|
|
HandleSelDragEvt(ui.SelectionDragEnd{})
|
|
if TheState.Editor.CaretDrag {
|
|
t.Errorf("CaretDrag not cleared on drag end")
|
|
}
|
|
}
|
|
|
|
func TestMenuCopy(t *testing.T) {
|
|
touchSelState("hello world")
|
|
HandleLongPressAt(18, 108) // selects "hello"
|
|
m := TheState.Editor.MenuRect
|
|
// Tap the copy item (first item, X offset 0).
|
|
HandleMenuTap(m.X+10, m.Y+10)
|
|
select {
|
|
case text := <-TheState.clipboardSetChan:
|
|
if text != "hello" {
|
|
t.Errorf("clipboard = %q, want %q", text, "hello")
|
|
}
|
|
default:
|
|
t.Fatal("no clipboard write after copy")
|
|
}
|
|
// Copy does not clear the selection (the highlight stays), but the
|
|
// menu closes, as on Android.
|
|
assertSelection(t, 0, 5)
|
|
assertMenu(t, false)
|
|
}
|
|
|
|
func TestMenuCut(t *testing.T) {
|
|
touchSelState("hello world")
|
|
HandleLongPressAt(18, 108) // selects "hello"
|
|
m := TheState.Editor.MenuRect
|
|
// Tap the cut item (second item).
|
|
HandleMenuTap(m.X+ui.Dp(menuItemW)+10, m.Y+10)
|
|
select {
|
|
case text := <-TheState.clipboardSetChan:
|
|
if text != "hello" {
|
|
t.Errorf("clipboard = %q, want %q", text, "hello")
|
|
}
|
|
default:
|
|
t.Fatal("no clipboard write after cut")
|
|
}
|
|
if got := TheState.Editor.Buffer; got != " world" {
|
|
t.Errorf("buffer = %q after cut, want %q", got, " world")
|
|
}
|
|
assertSelection(t, -1, -1)
|
|
assertMenu(t, false)
|
|
if TheState.Editor.CursorPosition != 0 {
|
|
t.Errorf("cursor = %d after cut, want 0", TheState.Editor.CursorPosition)
|
|
}
|
|
}
|
|
|
|
func TestMenuPaste(t *testing.T) {
|
|
touchSelState("hello")
|
|
HandleLongPressAt(18, 108) // selects "hello"
|
|
// Paste replaces the live selection (via the same path the Run loop uses
|
|
// when the main goroutine forwards clipboard content).
|
|
HandlePaste("XY")
|
|
if got := TheState.Editor.Buffer; got != "XY" {
|
|
t.Errorf("buffer = %q after paste, want %q", got, "XY")
|
|
}
|
|
assertSelection(t, -1, -1)
|
|
assertMenu(t, false)
|
|
if TheState.Editor.CursorPosition != 2 {
|
|
t.Errorf("cursor = %d after paste, want 2", TheState.Editor.CursorPosition)
|
|
}
|
|
}
|
|
|
|
func TestMenuPaste_NoSelectionInsertsAtCursor(t *testing.T) {
|
|
touchSelState("hello")
|
|
TheState.Editor.CursorPosition = 5
|
|
HandlePaste(",")
|
|
if got := TheState.Editor.Buffer; got != "hello," {
|
|
t.Errorf("buffer = %q after paste, want %q", got, "hello,")
|
|
}
|
|
if TheState.Editor.CursorPosition != 6 {
|
|
t.Errorf("cursor = %d after paste, want 6", TheState.Editor.CursorPosition)
|
|
}
|
|
}
|
|
|
|
func TestMenuTapPaste_HidesMenuAndRequestsRead(t *testing.T) {
|
|
touchSelState("hello world")
|
|
HandleLongPressAt(18, 108) // selects "hello"
|
|
m := TheState.Editor.MenuRect
|
|
// Tap the paste item (third item).
|
|
HandleMenuTap(m.X+2*ui.Dp(menuItemW)+10, m.Y+10)
|
|
select {
|
|
case <-TheState.pasteReqChan:
|
|
default:
|
|
t.Fatal("no paste request after menu paste tap")
|
|
}
|
|
// The menu closes immediately (the read is asynchronous); the
|
|
// selection stays until the pasted content arrives.
|
|
assertMenu(t, false)
|
|
assertSelection(t, 0, 5)
|
|
}
|
|
|
|
func TestSelectionEdit_CutThenTypeReplaces(t *testing.T) {
|
|
touchSelState("hello world")
|
|
HandleLongPressAt(18, 108) // selects "hello"
|
|
HandleInsert("goodbye")
|
|
if got := TheState.Editor.Buffer; got != "goodbye world" {
|
|
t.Errorf("buffer = %q, want %q", got, "goodbye world")
|
|
}
|
|
}
|
|
|
|
// touchSelState2Lines sets up the same geometry as touchSelState but with a
|
|
// second line beneath the first: "hello world\nsecond line\n". Line 0 glyphs
|
|
// (bytes 0..10) sit at Y=0; line 1 glyphs (bytes 12..22) at Y=lineHeight.
|
|
// The 48dp handle grab box of a line-0 handle straddles into line 1's row,
|
|
// which is what the line-projection regression tests rely on.
|
|
func touchSelState2Lines() {
|
|
content := "hello world\nsecond line\n"
|
|
TheState = NewState()
|
|
TheState.Editor.Buffer = content
|
|
TheState.Editor.CursorPosition = len(content)
|
|
TheState.EditorRegion = ui.Region{X: 16, Y: 100, W: 379, H: 700}
|
|
TheState.ScrollOffset = 0
|
|
TheState.Editor.IMEWindowStartByte = 0
|
|
TheState.Editor.IMEWindowText = content
|
|
lh := ui.Dp(16.8) // EditorLineHeight at the default font scale
|
|
bo := []int{}
|
|
xs := []ui.Dp{}
|
|
s := []ui.Dp{}
|
|
ad := []ui.Dp{}
|
|
for i := 0; i < 11; i++ { // line 0: bytes 0..10
|
|
bo = append(bo, i)
|
|
xs = append(xs, ui.Dp(10*i))
|
|
s = append(s, 0)
|
|
ad = append(ad, 10)
|
|
}
|
|
for j := 0; j < 11; j++ { // line 1: bytes 12..22
|
|
bo = append(bo, 12+j)
|
|
xs = append(xs, ui.Dp(10*j))
|
|
s = append(s, lh)
|
|
ad = append(ad, 10)
|
|
}
|
|
TheState.Editor.GlyphLayout = ui.GlyphLayout{
|
|
LineHeight: lh,
|
|
ByteOffsets: bo,
|
|
X: xs,
|
|
Y: s,
|
|
Advance: ad,
|
|
}
|
|
}
|
|
|
|
// TestSelDrag_StartHandle_PressOnLineBelow_KeepsSelection is a regression
|
|
// test for the line-projection fix: the start handle's 48dp grab box
|
|
// straddles the neighbouring line, so a finger in the lower half of the box
|
|
// maps (by pure y-to-line) to the line BELOW the anchor. Mapping the finger
|
|
// to its own line yielded a byte past SelectionEnd, the clamp collapsed the
|
|
// selection to zero length, and the selection vanished on the very first
|
|
// drag event (the drag op then un-registered and the "stream" appeared to be
|
|
// cut off). The anchor must instead be projected onto its OWN visual line,
|
|
// so the same finger position shrinks the selection along that line.
|
|
func TestSelDrag_StartHandle_PressOnLineBelow_KeepsSelection(t *testing.T) {
|
|
touchSelState2Lines()
|
|
HandleLongPressAt(18, 108) // selects "hello" [0,5)
|
|
checkInitialSelection(t)
|
|
// First drag event of the start handle (which=0): finger at local
|
|
// (22, 25) — localY 25 is on line 1 (line height 16.8), localX 22 is
|
|
// above the 'c' of "second" on line 1 (byte 14, past SelectionEnd=5) but
|
|
// above the 'l' of "hello" on line 0 (byte 2). Pre-fix this event
|
|
// collapsed the selection to (5,5) -> cleared.
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 0, X: 16 + 22, Y: 100 + 25})
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 0, X: 16 + 22, Y: 100 + 25})
|
|
assertSelection(t, 2, 5) // "llo" — projected onto line 0, never cleared
|
|
}
|
|
|
|
// TestSelDrag_EndHandle_PressOnLineBelow_StaysOnAnchorLine is the end-handle
|
|
// counterpart: a finger in the lower half of the end handle's box must not
|
|
// lasso the selection across onto the next line; the end is projected onto
|
|
// its own visual line (line 0 here), so the selection only shrinks.
|
|
func TestSelDrag_EndHandle_PressOnLineBelow_StaysOnAnchorLine(t *testing.T) {
|
|
touchSelState2Lines()
|
|
HandleLongPressAt(18, 108) // selects "hello" [0,5)
|
|
// End handle (which=1), finger at local (22, 25): on line 1 that is byte
|
|
// 14 (pre-fix: the selection would extend to [0,14), across the newline),
|
|
// on line 0 it is byte 2 (post-fix: [0,2) = "he").
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 1, X: 16 + 22, Y: 100 + 25})
|
|
HandleSelDragEvt(ui.SelectionDragEvent{Which: 1, X: 16 + 22, Y: 100 + 25})
|
|
assertSelection(t, 0, 2)
|
|
}
|
|
|
|
func checkInitialSelection(t *testing.T) {
|
|
t.Helper()
|
|
s := TheState.Editor
|
|
if s.SelectionStart != 0 || s.SelectionEnd != 5 {
|
|
t.Fatalf("precondition: selection = [%d,%d), want [0,5)", s.SelectionStart, s.SelectionEnd)
|
|
}
|
|
}
|