package editor import ( "fmt" "testing" "git.gophernest.net/azpect/TextEditor/internal/core" ) func TestScreenTopMotion(t *testing.T) { t.Run("H moves to the top visible line first non-blank", func(t *testing.T) { lines := screenMotionLines(100) lines[82] = " top" tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "H") m := getFinalModel(t, tm) assertCursorPos(t, m, 82, 4) }) t.Run("3H moves to third visible line from top", func(t *testing.T) { lines := screenMotionLines(100) lines[84] = " third" tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "3", "H") m := getFinalModel(t, tm) assertCursorPos(t, m, 84, 2) }) t.Run("H count clamps to bottom visible line", func(t *testing.T) { tm := newTestModelWithTermSize(t, screenMotionLines(100), core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "9", "9", "9", "H") m := getFinalModel(t, tm) assertCursorPos(t, m, 99, 0) }) } func TestScreenMiddleMotion(t *testing.T) { t.Run("M moves to middle visible line first non-blank", func(t *testing.T) { lines := screenMotionLines(100) lines[90] = "\tmiddle" tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "M") m := getFinalModel(t, tm) assertCursorPos(t, m, 90, 1) }) t.Run("count before M is ignored", func(t *testing.T) { tm := newTestModelWithTermSize(t, screenMotionLines(100), core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "9", "M") m := getFinalModel(t, tm) assertCursorPos(t, m, 90, 0) }) } func TestScreenBottomMotion(t *testing.T) { t.Run("L moves to bottom visible line first non-blank", func(t *testing.T) { lines := screenMotionLines(100) lines[99] = " bottom" tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "L") m := getFinalModel(t, tm) assertCursorPos(t, m, 99, 2) }) t.Run("3L moves to third visible line from bottom", func(t *testing.T) { lines := screenMotionLines(100) lines[97] = " above" tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "3", "L") m := getFinalModel(t, tm) assertCursorPos(t, m, 97, 4) }) t.Run("L count clamps to top visible line", func(t *testing.T) { tm := newTestModelWithTermSize(t, screenMotionLines(100), core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "9", "9", "9", "L") m := getFinalModel(t, tm) assertCursorPos(t, m, 82, 0) }) } func TestScreenMotionsEdgeCases(t *testing.T) { t.Run("small file: H and L clamp to file bounds", func(t *testing.T) { lines := []string{" one", "two", "three", "four", "five"} tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "L") sendKeys(tm, "H") m := getFinalModel(t, tm) assertCursorPos(t, m, 0, 4) }) t.Run("M on blank middle line lands at column 0", func(t *testing.T) { lines := screenMotionLines(100) lines[90] = "" tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "M") m := getFinalModel(t, tm) assertCursorPos(t, m, 90, 0) }) } func TestScreenMotionsIntegration(t *testing.T) { t.Run("dH treats H as a linewise motion", func(t *testing.T) { tm := newTestModelWithTermSize(t, screenMotionLines(100), core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "d", "H") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 82 { t.Errorf("LineCount() = %d, want 82", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Line(81) != "line 81" { t.Errorf("Line(81) = %q, want %q", m.ActiveBuffer().Line(81), "line 81") } }) t.Run("H works as a visual line motion", func(t *testing.T) { tm := newTestModelWithTermSize(t, screenMotionLines(100), core.Position{Col: 0, Line: 0}, 80, 20) sendKeys(tm, "G", "V", "H", "d") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 82 { t.Errorf("LineCount() = %d, want 82", m.ActiveBuffer().LineCount()) } if m.Mode() != core.NormalMode { t.Errorf("Mode() = %v, want %v", m.Mode(), core.NormalMode) } }) } func screenMotionLines(n int) []string { lines := make([]string, n) for i := range n { lines[i] = fmt.Sprintf("line %d", i) } return lines } func assertCursorPos(t *testing.T, m *Model, wantLine, wantCol int) { t.Helper() if m.ActiveWindow().Cursor.Line != wantLine { t.Errorf("Cursor.Line = %d, want %d", m.ActiveWindow().Cursor.Line, wantLine) } if m.ActiveWindow().Cursor.Col != wantCol { t.Errorf("Cursor.Col = %d, want %d", m.ActiveWindow().Cursor.Col, wantCol) } }