All checks were successful
Run Test Suite / test (push) Successful in 56s
Not sure if this is perfect, but it seems to be working
588 lines
19 KiB
Go
588 lines
19 KiB
Go
package editor
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
|
)
|
|
|
|
// NOTE: Lots of AI tests here
|
|
|
|
// --- Visual Mode Selection State Tests ---
|
|
|
|
func TestVisualModeSelectionState(t *testing.T) {
|
|
t.Run("test 'v' enters visual mode and sets anchor at cursor", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualMode {
|
|
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Col != 3 {
|
|
t.Errorf("AnchorX() = %d, want 3", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 0 {
|
|
t.Errorf("AnchorY() = %d, want 0", m.ActiveWindow().Anchor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vl' moves cursor right, anchor stays", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "v", "l")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 0 {
|
|
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 1 {
|
|
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vh' creates backward selection", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "h")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 3 {
|
|
t.Errorf("AnchorX() = %d, want 3", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 2 {
|
|
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vj' extends selection down", func(t *testing.T) {
|
|
lines := []string{"hello", "world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "v", "j")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 2 {
|
|
t.Errorf("AnchorX() = %d, want 2", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 0 {
|
|
t.Errorf("AnchorY() = %d, want 0", m.ActiveWindow().Anchor.Line)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 1 {
|
|
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'V' enters visual line mode and sets anchor at cursor", func(t *testing.T) {
|
|
lines := []string{"hello", "world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
|
sendKeys(tm, "V")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualLineMode {
|
|
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 1 {
|
|
t.Errorf("AnchorY() = %d, want 1", m.ActiveWindow().Anchor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'ctrl+v' enters visual block mode and sets anchor at cursor", func(t *testing.T) {
|
|
lines := []string{"hello", "world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 1})
|
|
sendKeys(tm, "ctrl+v")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualBlockMode {
|
|
t.Errorf("Mode() = %v, want VisualBlockMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Col != 2 {
|
|
t.Errorf("AnchorX() = %d, want 2", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 1 {
|
|
t.Errorf("AnchorY() = %d, want 1", m.ActiveWindow().Anchor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'esc' returns to normal mode from visual", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "v", "l", "l", "esc")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.NormalMode {
|
|
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- Visual Mode Delete Tests ---
|
|
|
|
func TestVisualModeDelete(t *testing.T) {
|
|
t.Run("test 'vd' deletes single char under cursor", func(t *testing.T) {
|
|
lines := []string{"hello"}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "v", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "ello" {
|
|
t.Errorf("Line(0) = %q, want \"ello\"", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vlll d' deletes four chars on same line", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "v", "l", "l", "l", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "o world" {
|
|
t.Errorf("Line(0) = %q, want \"o world\"", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v' backward selection 'hh d' deletes correct range", func(t *testing.T) {
|
|
lines := []string{"hello"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "h", "h", "d")
|
|
|
|
// anchor=3, cursor=1 → normalized start=1, end=3 → delete "ell" → "ho"
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "ho" {
|
|
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 1 {
|
|
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vj d' deletes char selection across two lines", func(t *testing.T) {
|
|
lines := []string{"hello", "world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "v", "j", "d")
|
|
|
|
// start=(2,0), end=(2,1) → prefix="he", suffix="ld" → "held"
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().LineCount() != 1 {
|
|
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
|
}
|
|
if m.ActiveBuffer().Lines[0].String() != "held" {
|
|
t.Errorf("Line(0) = %q, want \"held\"", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 2 {
|
|
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'Vd' deletes current line", func(t *testing.T) {
|
|
lines := []string{"hello", "world"}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "V", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().LineCount() != 1 {
|
|
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
|
}
|
|
if m.ActiveBuffer().Lines[0].String() != "world" {
|
|
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'Vjd' deletes two lines", func(t *testing.T) {
|
|
lines := []string{"hello", "world", "testing"}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "V", "j", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().LineCount() != 1 {
|
|
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
|
}
|
|
if m.ActiveBuffer().Lines[0].String() != "testing" {
|
|
t.Errorf("Line(0) = %q, want \"testing\"", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'Vkd' deletes two lines with backward selection", func(t *testing.T) {
|
|
lines := []string{"hello", "world", "testing"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
|
sendKeys(tm, "V", "k", "d")
|
|
|
|
// anchor=line2, cursor=line1 → normalized start=line1, end=line2 → delete both
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().LineCount() != 1 {
|
|
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
|
}
|
|
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
|
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'ctrl+v ljd' deletes block selection", func(t *testing.T) {
|
|
lines := []string{"hello", "world"}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "ctrl+v", "l", "j", "d")
|
|
|
|
// anchor=(0,0), cursor=(1,1) → block cols 0-1, lines 0-1
|
|
// "hello"[:0]+"hello"[2:] = "llo"
|
|
// "world"[:0]+"world"[2:] = "rld"
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "llo" {
|
|
t.Errorf("Line(0) = %q, want \"llo\"", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
if m.ActiveBuffer().Lines[1].String() != "rld" {
|
|
t.Errorf("Line(1) = %q, want \"rld\"", m.ActiveBuffer().Lines[1].String())
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'ctrl+v' backward col selection deletes correct block", func(t *testing.T) {
|
|
lines := []string{"hello", "world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "ctrl+v", "h", "h", "j", "d")
|
|
|
|
// anchor=(3,0), cursor=(1,1) → cols min(3,1)=1 to max(3,1)=3, lines 0-1
|
|
// "hello"[:1]+"hello"[4:] = "h"+"o" = "ho"
|
|
// "world"[:1]+"world"[4:] = "w"+"d" = "wd"
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "ho" {
|
|
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
if m.ActiveBuffer().Lines[1].String() != "wd" {
|
|
t.Errorf("Line(1) = %q, want \"wd\"", m.ActiveBuffer().Lines[1].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- Visual Mode with Word Motions ---
|
|
|
|
func TestVisualModeWordMotions(t *testing.T) {
|
|
t.Run("test 'vw' selects to next word", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
|
)
|
|
sendKeys(tm, "v", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 0 {
|
|
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
// w moves to start of "world" at col 6
|
|
if m.ActiveWindow().Cursor.Col != 6 {
|
|
t.Errorf("CursorX() = %d, want 6", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vwd' deletes word plus space", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
|
)
|
|
sendKeys(tm, "v", "w", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Deletes from 0 to 6 inclusive = "hello w", leaves "orld"
|
|
if m.ActiveBuffer().Lines[0].String() != "orld" {
|
|
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 've' selects to end of word", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
|
)
|
|
sendKeys(tm, "v", "e")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 0 {
|
|
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
// e moves to end of "hello" at col 4
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'ved' deletes word", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
|
)
|
|
sendKeys(tm, "v", "e", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Deletes "hello"
|
|
if m.ActiveBuffer().Lines[0].String() != " world" {
|
|
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vb' selects backward word", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
|
)
|
|
sendKeys(tm, "v", "b")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 6 {
|
|
t.Errorf("AnchorX() = %d, want 6", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
// b moves to start of "hello" at col 0
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vbd' deletes backward to word start", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
|
)
|
|
sendKeys(tm, "v", "b", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Deletes from "h" (0) to "w" (6) inclusive
|
|
if m.ActiveBuffer().Lines[0].String() != "orld" {
|
|
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v2w' selects two words", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"one two three"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
|
)
|
|
sendKeys(tm, "v", "2", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// 2w moves past "one " and "two " to start of "three" at col 8
|
|
if m.ActiveWindow().Cursor.Col != 8 {
|
|
t.Errorf("CursorX() = %d, want 8", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- Visual Mode with Jump Motions ---
|
|
|
|
func TestVisualModeJumpMotions(t *testing.T) {
|
|
t.Run("test 'v$' selects to end of line", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
|
)
|
|
sendKeys(tm, "v", "$")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 0 {
|
|
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
// $ moves past end of line
|
|
if m.ActiveWindow().Cursor.Col != 11 {
|
|
t.Errorf("CursorX() = %d, want 11", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v$d' deletes to end of line", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
|
)
|
|
sendKeys(tm, "v", "$", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
|
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v0' selects to beginning of line", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
|
)
|
|
sendKeys(tm, "v", "0")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 6 {
|
|
t.Errorf("AnchorX() = %d, want 6", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v0d' deletes to beginning of line", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
|
)
|
|
sendKeys(tm, "v", "0", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Deletes from 'h' (0) to 'w' (6) inclusive
|
|
if m.ActiveBuffer().Lines[0].String() != "orld" {
|
|
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v_' selects to first non-whitespace", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{" hello world"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 10}), // on 'w'
|
|
)
|
|
sendKeys(tm, "v", "_")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 10 {
|
|
t.Errorf("AnchorX() = %d, want 10", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
// _ moves to first non-ws at col 4
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vG' selects to bottom of file", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"line 1", "line 2", "line 3"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
|
)
|
|
sendKeys(tm, "v", "G")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Line != 0 {
|
|
t.Errorf("AnchorY() = %d, want 0", m.ActiveWindow().Anchor.Line)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 2 {
|
|
t.Errorf("CursorY() = %d, want 2", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vGd' deletes to bottom of file", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"line 1", "line 2", "line 3"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 3}), // on 'e' of "line"
|
|
)
|
|
sendKeys(tm, "v", "G", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// G goes to last line at same col, deletes from (0,3) to (2,3)
|
|
// Keeps "lin" from first line + "e 3" from last line = "lin 3"
|
|
if m.ActiveBuffer().LineCount() != 1 {
|
|
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
|
}
|
|
if m.ActiveBuffer().Lines[0].String() != "lin 3" {
|
|
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vgg' selects to top of file", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"line 1", "line 2", "line 3"}),
|
|
WithCursorPos(core.Position{Line: 2, Col: 0}),
|
|
)
|
|
sendKeys(tm, "v", "g", "g")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Line != 2 {
|
|
t.Errorf("AnchorY() = %d, want 2", m.ActiveWindow().Anchor.Line)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vggd' deletes to top of file", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"line 1", "line 2", "line 3"}),
|
|
WithCursorPos(core.Position{Line: 2, Col: 3}),
|
|
)
|
|
sendKeys(tm, "v", "g", "g", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// gg goes to first line at same col, deletes selection
|
|
// Keeps "lin" from first line + " 3" from last line = "lin 3"
|
|
if m.ActiveBuffer().LineCount() != 1 {
|
|
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
|
}
|
|
if m.ActiveBuffer().Lines[0].String() != "lin 3" {
|
|
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- Visual Line Mode with Jump Motions ---
|
|
|
|
func TestVisualLineModeJumpMotions(t *testing.T) {
|
|
t.Run("test 'VG' selects all lines to bottom", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"line 1", "line 2", "line 3"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
|
)
|
|
sendKeys(tm, "V", "G")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Line != 0 {
|
|
t.Errorf("AnchorY() = %d, want 0", m.ActiveWindow().Anchor.Line)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 2 {
|
|
t.Errorf("CursorY() = %d, want 2", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'VGd' deletes all lines", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"line 1", "line 2", "line 3"}),
|
|
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
|
)
|
|
sendKeys(tm, "V", "G", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// All lines deleted, should have empty buffer
|
|
if m.ActiveBuffer().LineCount() != 1 {
|
|
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
|
}
|
|
if m.ActiveBuffer().Lines[0].String() != "" {
|
|
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'Vgg' selects lines to top", func(t *testing.T) {
|
|
tm := newTestModel(t,
|
|
WithLines([]string{"line 1", "line 2", "line 3"}),
|
|
WithCursorPos(core.Position{Line: 2, Col: 0}),
|
|
)
|
|
sendKeys(tm, "V", "g", "g")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Line != 2 {
|
|
t.Errorf("AnchorY() = %d, want 2", m.ActiveWindow().Anchor.Line)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
}
|