turbo-editors/turbo-corepublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

🛟 Updated. 28d5985 · on main · k33g · 4h ago
click_test.go · 140 lines · 4.7 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package editor

import (
	"testing"
	"time"

	"github.com/gdamore/tcell/v2"
)

// clickingView returns a view onto text with a clock the test drives, so a
// double click is a matter of what the test says rather than of how fast the
// machine is.
func clickingView(t *testing.T, text string) (*View, tcell.SimulationScreen, *time.Time) {
	t.Helper()

	view, screen, _ := newTestView(t, text, 40, 8)
	moment := time.Date(2026, 9, 2, 12, 0, 0, 0, time.UTC)
	view.SetClock(func() time.Time { return moment })
	return view, screen, &moment
}

// press sends one button-down at a cell, then the button-up that ends it.
func pressAt(view *View, x, y int) {
	view.HandleMouse(tcell.NewEventMouse(x, y, tcell.Button1, tcell.ModNone))
	view.HandleMouse(tcell.NewEventMouse(x, y, tcell.ButtonNone, tcell.ModNone))
}

func TestADoubleClickSelectsTheWord(t *testing.T) {
	view, _, _ := clickingView(t, "func mainLoop() {}\n")
	x := view.gutterWidth() + 8 // inside "mainLoop"

	pressAt(view, x, 0)
	pressAt(view, x, 0)

	if got := view.Buffer().SelectedText(); got != "mainLoop" {
		t.Errorf("the selection is %q, want %q", got, "mainLoop")
	}
}

func TestASingleClickSelectsNothing(t *testing.T) {
	view, _, _ := clickingView(t, "func mainLoop() {}\n")

	pressAt(view, view.gutterWidth()+8, 0)

	if got := view.Buffer().SelectedText(); got != "" {
		t.Errorf("one click selected %q", got)
	}
}

func TestTwoSlowClicksAreTwoClicks(t *testing.T) {
	// Two unrelated clicks in the same place happen all the time while
	// reading, and selecting a word each time would be noise.
	view, _, moment := clickingView(t, "func mainLoop() {}\n")
	x := view.gutterWidth() + 8

	pressAt(view, x, 0)
	*moment = moment.Add(doubleClickWithin + time.Millisecond)
	pressAt(view, x, 0)

	if got := view.Buffer().SelectedText(); got != "" {
		t.Errorf("two clicks %v apart selected %q", doubleClickWithin, got)
	}
}

func TestTwoClicksOnDifferentCellsAreTwoClicks(t *testing.T) {
	// A press one column over is a different word, and selecting the first
	// because the pointer drifted is worse than selecting nothing.
	view, _, _ := clickingView(t, "alpha beta\n")

	pressAt(view, view.gutterWidth()+1, 0)
	pressAt(view, view.gutterWidth()+2, 0)

	if got := view.Buffer().SelectedText(); got != "" {
		t.Errorf("two clicks on different cells selected %q", got)
	}
}

func TestADragIsNotASecondClick(t *testing.T) {
	// tcell sends Button1 for every event of a drag. Counting those would
	// turn a slow drag into a double click halfway through.
	view, _, _ := clickingView(t, "alpha beta gamma\n")
	x := view.gutterWidth()

	view.HandleMouse(tcell.NewEventMouse(x+1, 0, tcell.Button1, tcell.ModNone))
	for column := 2; column <= 9; column++ {
		view.HandleMouse(tcell.NewEventMouse(x+column, 0, tcell.Button1, tcell.ModNone))
	}
	view.HandleMouse(tcell.NewEventMouse(x+9, 0, tcell.ButtonNone, tcell.ModNone))

	if got := view.Buffer().SelectedText(); got != "lpha bet" {
		t.Errorf("the drag selected %q, want the characters it crossed", got)
	}
}

func TestADoubleClickOffAWordSelectsNothing(t *testing.T) {
	view, _, _ := clickingView(t, "alpha beta\n")
	x := view.gutterWidth() + 5 // the space

	pressAt(view, x, 0)
	pressAt(view, x, 0)

	if got := view.Buffer().SelectedText(); got != "" {
		t.Errorf("a double click on a space selected %q", got)
	}
}

func TestHoldingAfterADoubleClickExtendsFromTheWordsStart(t *testing.T) {
	// Not the word-by-word drag a large editor does — the cheap half of it.
	// The alternative is the word collapsing back to a single character the
	// moment the pointer twitches, which is worse than either.
	view, _, _ := clickingView(t, "alpha beta\n")
	x := view.gutterWidth() + 1

	pressAt(view, x, 0)                                                         // first click, released
	view.HandleMouse(tcell.NewEventMouse(x, 0, tcell.Button1, tcell.ModNone))   // second, and held
	view.HandleMouse(tcell.NewEventMouse(x+7, 0, tcell.Button1, tcell.ModNone)) // dragged
	view.HandleMouse(tcell.NewEventMouse(x+7, 0, tcell.ButtonNone, tcell.ModNone))

	if got := view.Buffer().SelectedText(); got != "alpha be" {
		t.Errorf("dragging after a double click left %q, want it extended from the word's start", got)
	}
}

func TestAThirdClickIsCountedButDoesNothingYet(t *testing.T) {
	// The counter keeps going so that a triple click can be given a meaning
	// later without reworking any of this. Today it selects the word again.
	view, _, _ := clickingView(t, "alpha beta\n")
	x := view.gutterWidth() + 1

	pressAt(view, x, 0)
	pressAt(view, x, 0)
	pressAt(view, x, 0)

	if got := view.lastClick.count; got != 3 {
		t.Errorf("the click count is %d, want 3", got)
	}
	if got := view.Buffer().SelectedText(); got != "alpha" {
		t.Errorf("the third click left %q selected", got)
	}
}