turbo-editors/turbo-corepublic Fork 0
v1.0.2
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.

line_test.go · 248 lines · 6.8 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 18h ago1package buffer
2
3import "testing"
4
5// atLine returns a buffer holding text with the cursor on a line.
6func atLine(text string, line, col int) *Buffer {
7 b := NewFromString(text)
8 b.SetCursor(Position{Line: line, Col: col})
9 return b
10}
11
12func TestInsertLineAbovePushesTheCurrentLineDown(t *testing.T) {
13 b := atLine("one\ntwo\nthree\n", 1, 2)
14
15 b.InsertLineAbove()
16
17 if got := b.Text(); got != "one\n\ntwo\nthree\n" {
18 t.Errorf("Text() = %q, want a blank line before \"two\"", got)
19 }
20}
21
22func TestInsertLineAboveLeavesTheCursorOnItsOwnText(t *testing.T) {
23 // The point of inserting *above*: you keep looking at what you were
24 // looking at, with room made over it.
25 b := atLine("one\ntwo\n", 1, 2)
26
27 b.InsertLineAbove()
28
29 if got := b.Cursor(); got.Line != 2 || got.Col != 2 {
30 t.Errorf("Cursor() = %+v, want line 2 column 2 — the same text, one line lower", got)
31 }
32 if got := b.Line(b.Cursor().Line); got != "two" {
33 t.Errorf("the cursor is on %q, want the line it started on", got)
34 }
35}
36
37func TestInsertLineAboveWorksOnTheFirstLine(t *testing.T) {
38 b := atLine("one\n", 0, 1)
39
40 b.InsertLineAbove()
41
42 if got := b.Text(); got != "\none\n" {
43 t.Errorf("Text() = %q", got)
44 }
45 if got := b.Cursor().Line; got != 1 {
46 t.Errorf("Cursor().Line = %d, want 1", got)
47 }
48}
49
50func TestTheInsertedLineIsBlank(t *testing.T) {
51 // Not indented like its neighbour: an indent nobody asked for becomes
52 // trailing whitespace the moment they change their mind.
53 b := atLine("\t\tdeep\n", 0, 3)
54
55 b.InsertLineAbove()
56
57 if got := b.Line(0); got != "" {
58 t.Errorf("the new line is %q, want it empty", got)
59 }
60}
61
62func TestDeleteLineClosesTheGap(t *testing.T) {
63 b := atLine("one\ntwo\nthree\n", 1, 0)
64
65 b.DeleteLine()
66
67 if got := b.Text(); got != "one\nthree\n" {
68 t.Errorf("Text() = %q", got)
69 }
70}
71
72func TestDeleteLineLeavesTheCursorWhereTheNextLineNowIs(t *testing.T) {
73 // So that holding the key deletes a run, which is the whole point.
74 b := atLine("one\ntwo\nthree\nfour\n", 1, 0)
75
76 b.DeleteLine()
77 b.DeleteLine()
78
79 if got := b.Text(); got != "one\nfour\n" {
80 t.Errorf("Text() = %q, want two lines gone", got)
81 }
82 if got := b.Cursor().Line; got != 1 {
83 t.Errorf("Cursor().Line = %d, want 1", got)
84 }
85}
86
87func TestDeletingTheLastLineTakesTheNewlineBeforeIt(t *testing.T) {
88 // There is no newline after the last line to take, so its own leading one
89 // goes instead. Taking nothing would leave a blank line behind.
90 b := NewFromString("one\ntwo")
91 b.SetCursor(Position{Line: 1, Col: 0})
92
93 b.DeleteLine()
94
95 if got := b.Text(); got != "one" {
96 t.Errorf("Text() = %q, want the line and the newline before it gone", got)
97 }
98 if got := b.LineCount(); got != 1 {
99 t.Errorf("LineCount() = %d, want 1", got)
100 }
101}
102
103func TestDeletingTheOnlyLineEmptiesItRatherThanRemovingIt(t *testing.T) {
104 // Every other operation here assumes there is always a line to be on.
105 b := atLine("alone", 0, 3)
106
107 b.DeleteLine()
108
109 if got := b.LineCount(); got != 1 {
110 t.Fatalf("LineCount() = %d, want 1 — a buffer always has a line", got)
111 }
112 if got := b.Text(); got != "" {
113 t.Errorf("Text() = %q, want it empty", got)
114 }
115}
116
117func TestDeleteLineOnTheLastOfSeveralLeavesACursorInTheBuffer(t *testing.T) {
118 b := NewFromString("one\ntwo")
119 b.SetCursor(Position{Line: 1, Col: 3})
120
121 b.DeleteLine()
122
123 if got := b.Cursor().Line; got >= b.LineCount() {
124 t.Errorf("Cursor().Line = %d with %d lines — the cursor is off the end", got, b.LineCount())
125 }
126}
127
128func TestBothLineEditsAreUndoneInOneStep(t *testing.T) {
129 // They go through ReplaceRange, the single mutation path, so the undo
130 // history gets one entry each rather than a splice nobody recorded.
131 for name, edit := range map[string]func(*Buffer){
132 "insert": (*Buffer).InsertLineAbove,
133 "delete": (*Buffer).DeleteLine,
134 } {
135 t.Run(name, func(t *testing.T) {
136 b := atLine("one\ntwo\nthree\n", 1, 1)
137 before := b.Text()
138
139 edit(b)
140 if b.Text() == before {
141 t.Fatal("the edit changed nothing")
142 }
143 if !b.Undo() {
144 t.Fatal("Undo() reported nothing to undo")
145 }
146
147 if got := b.Text(); got != before {
148 t.Errorf("after one undo Text() = %q, want %q", got, before)
149 }
150 })
151 }
152}
153
154func TestALineEditClearsTheSelection(t *testing.T) {
155 // ReplaceRange clears it, and a selection left behind after the text under
156 // it moved would highlight the wrong characters.
157 b := atLine("one\ntwo\nthree\n", 1, 0)
158 b.StartSelection()
159 b.SetCursorKeepingSelection(Position{Line: 1, Col: 3})
160
161 b.DeleteLine()
162
163 if _, has := b.Selection(); has {
164 t.Error("a selection survived the line being deleted")
165 }
166}
167
168// selected returns the text a buffer's selection covers, and whether there is
169// one at all.
170func selected(b *Buffer) (string, bool) {
171 if _, has := b.Selection(); !has {
172 return "", false
173 }
174 return b.SelectedText(), true
175}
176
177func TestSelectWordTakesTheWholeWordFromAnywhereInIt(t *testing.T) {
178 const line = "func mainLoop() {}"
179
180 for _, col := range []int{5, 9, 12} {
181 b := NewFromString(line)
182 b.SelectWord(Position{Line: 0, Col: col})
183
184 got, has := selected(b)
185 if !has || got != "mainLoop" {
186 t.Errorf("from column %d the selection is %q (any: %v), want %q", col, got, has, "mainLoop")
187 }
188 }
189}
190
191func TestSelectWordCountsUnderscoresAndDigits(t *testing.T) {
192 // The same rule as Ctrl-Left and the completion popup: one editor, one
193 // idea of a word.
194 b := NewFromString("var max_retries2 = 3")
195 b.SelectWord(Position{Line: 0, Col: 6})
196
197 if got, _ := selected(b); got != "max_retries2" {
198 t.Errorf("the selection is %q", got)
199 }
200}
201
202func TestSelectWordStopsAtPunctuation(t *testing.T) {
203 b := NewFromString("fmt.Println(x)")
204 b.SelectWord(Position{Line: 0, Col: 5})
205
206 if got, _ := selected(b); got != "Println" {
207 t.Errorf("the selection is %q, want the dot and bracket left out", got)
208 }
209}
210
211func TestSelectWordOffAWordSelectsNothing(t *testing.T) {
212 // Editors disagree about what a run of punctuation means; nothing is at
213 // least predictable.
214 // "var x = fmt.Println" — a space at 3, a dot at 11, and nothing at 40.
215 for name, col := range map[string]int{"a space": 3, "a dot": 11, "past the end": 40} {
216 t.Run(name, func(t *testing.T) {
217 b := NewFromString("var x = fmt.Println")
218 b.SelectWord(Position{Line: 0, Col: col})
219
220 if got, has := selected(b); has {
221 t.Errorf("selected %q, want nothing", got)
222 }
223 })
224 }
225}
226
227func TestSelectWordOnAnEmptyLineSelectsNothing(t *testing.T) {
228 b := NewFromString("one\n\nthree\n")
229 b.SelectWord(Position{Line: 1, Col: 0})
230
231 if _, has := selected(b); has {
232 t.Error("an empty line has a word on it")
233 }
234 if got := b.Cursor(); got.Line != 1 {
235 t.Errorf("Cursor() = %+v, want the cursor moved to the click", got)
236 }
237}
238
239func TestSelectWordLeavesTheCursorAtTheEndOfTheWord(t *testing.T) {
240 // So that typing replaces the word, and Shift-Right extends from its end,
241 // which is what a selection made by dragging would do.
242 b := NewFromString("alpha beta")
243 b.SelectWord(Position{Line: 0, Col: 7})
244
245 if got := b.Cursor().Col; got != 10 {
246 t.Errorf("Cursor().Col = %d, want 10 — the end of \"beta\"", got)
247 }
248}