1package tui
2
3import (
4 "strings"
5 "testing"
6)
7
8// TestWrapLineFirstContinuationHasIndent is a regression test for a
9// bug where wrapLine()'s internal newLine() toggled the firstLine
10// flag and THEN checked it, so the very first wrap continuation
11// flushed without the cont indent. Any subsequent continuation
12// (second wrap onwards) got the indent. That was visible as a
13// misaligned second row and caused the editor's cursor to land in
14// the wrong column after a multi-line paste (locateCursor assumes
15// continuations carry cont, so when they didn't, the cursor drifted
16// one-indent-worth to the right).
17func TestWrapLineFirstContinuationHasIndent(t *testing.T) {
18 s := "prefix this is a long line that will wrap around at forty cells"
19 out := wrapLine(s, 40, " ")
20
21 if len(out) < 2 {
22 t.Fatalf("want at least 2 wrapped rows, got %d: %v", len(out), out)
23 }
24 // Row 0 is the first line (no indent; it's the lead).
25 // Every row from index 1 onward is a continuation and MUST start
26 // with the cont prefix.
27 for i := 1; i < len(out); i++ {
28 if !strings.HasPrefix(out[i], " ") {
29 t.Errorf("row %d missing cont indent: %q", i, out[i])
30 }
31 }
32}
33
34// TestEditorCursorAfterMultilinePaste is the downstream test: the
35// rendered editor cursor must land at the logical end of the paste,
36// with its visual column equal to leadW + runewidth(last-line).
37func TestEditorCursorAfterMultilinePaste(t *testing.T) {
38 e := NewEditor("▌ ")
39 e.HandleKey(Key{Kind: KeyPaste, Paste: "aaa\nbbb\nccccc"})
40
41 // Logical end: last line "ccccc", cursor past its 5 runes.
42 if e.CursorR != 2 || e.CursorC != 5 {
43 t.Fatalf("logical cursor: want (2, 5), got (%d, %d)", e.CursorR, e.CursorC)
44 }
45
46 lines, row, col := e.Render(80)
47 if len(lines) != 3 {
48 t.Fatalf("want 3 rendered rows, got %d: %v", len(lines), lines)
49 }
50 // Row 0 "▌ aaa", row 1 " bbb", row 2 " ccccc".
51 // Cursor lives at row 2; column = 2 (cont indent) + 5 = 7.
52 if row != 2 {
53 t.Errorf("visual row: want 2, got %d", row)
54 }
55 if col != 7 {
56 t.Errorf("visual col: want 7, got %d", col)
57 }
58}
59
60// TestEditorCursorAfterLongPasteWithWrap verifies the cursor lands
61// correctly when a pasted line is long enough to wrap at the given
62// render width. This is the scenario that was broken: before the
63// fix, the first wrap continuation missed its cont indent, so the
64// terminal cursor drifted when typed after pasting a wrapped path.
65func TestEditorCursorAfterLongPasteWithWrap(t *testing.T) {
66 e := NewEditor("▌ ")
67 e.HandleKey(Key{Kind: KeyPaste, Paste: "this is a very long line that should wrap\nshort"})
68
69 lines, row, col := e.Render(30)
70
71 // Every continuation row (anything after row 0) must be
72 // cont-indented so locateCursor's rune-counting stays honest.
73 for i := 1; i < len(lines); i++ {
74 if !strings.HasPrefix(lines[i], " ") {
75 t.Errorf("continuation row %d missing indent: %q", i, lines[i])
76 }
77 }
78 // Cursor should be at the end of "short" on the last rendered row.
79 if row != len(lines)-1 {
80 t.Errorf("visual row: want %d (last), got %d", len(lines)-1, row)
81 }
82 if col != 2+5 { // cont indent + len("short")
83 t.Errorf("visual col: want 7, got %d", col)
84 }
85}