Testing implement and tests for 'hjkl' are in!
This commit is contained in:
parent
4003f411e0
commit
69ad105aa5
5
go.mod
5
go.mod
@ -9,9 +9,12 @@ require (
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/aymanbagabas/go-udiff v0.3.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.5 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a // indirect
|
||||
github.com/charmbracelet/x/exp/teatest v0.0.0-20260209132835-6b065b8ba62c // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.9.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
@ -27,5 +30,5 @@ require (
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
)
|
||||
|
||||
8
go.sum
8
go.sum
@ -1,5 +1,7 @@
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||
@ -10,6 +12,10 @@ github.com/charmbracelet/x/ansi v0.11.5 h1:NBWeBpj/lJPE3Q5l+Lusa4+mH6v7487OP8K0r
|
||||
github.com/charmbracelet/x/ansi v0.11.5/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/exp/teatest v0.0.0-20260209132835-6b065b8ba62c h1:uX4zKWQHqiMBzPo1Lwh+xMeU3qDm9Tw5yX/AXVQZGNo=
|
||||
github.com/charmbracelet/x/exp/teatest v0.0.0-20260209132835-6b065b8ba62c/go.mod h1:aPVjFrBwbJgj5Qz1F0IXsnbcOVJcMKgu1ySUfTAxh7k=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
||||
@ -46,3 +52,5 @@ golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
|
||||
3
main.go
3
main.go
@ -3,8 +3,9 @@ package main
|
||||
import tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
func main() {
|
||||
lines := []string{"Hello world", "line 2", "line 3", "line 4", "line 5"}
|
||||
tea.NewProgram(
|
||||
newModel(),
|
||||
newModel(lines),
|
||||
tea.WithAltScreen(),
|
||||
).Run()
|
||||
}
|
||||
|
||||
25
model.go
25
model.go
@ -31,16 +31,9 @@ type model struct {
|
||||
insertAction Action
|
||||
}
|
||||
|
||||
func newModel() model {
|
||||
func newModel(lines []string) model {
|
||||
return model{
|
||||
lines: []string{
|
||||
"Hello world",
|
||||
"line 2",
|
||||
"line 3",
|
||||
"line 4",
|
||||
"line 5",
|
||||
"line 6",
|
||||
},
|
||||
lines: lines,
|
||||
cursor: cursor{
|
||||
x: 0,
|
||||
y: 0,
|
||||
@ -52,6 +45,20 @@ func newModel() model {
|
||||
}
|
||||
}
|
||||
|
||||
func newModelWithPos(lines []string, pos Position) model {
|
||||
return model{
|
||||
lines: lines,
|
||||
cursor: cursor{
|
||||
x: pos.Col,
|
||||
y: pos.Line,
|
||||
},
|
||||
s_gutter: 5,
|
||||
mode: NormalMode,
|
||||
command: "",
|
||||
input: NewInputHandler(),
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
325
motion_test.go
Normal file
325
motion_test.go
Normal file
@ -0,0 +1,325 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/x/exp/teatest"
|
||||
)
|
||||
|
||||
// sendKeys sends a sequence of keys to the test model
|
||||
func sendKeys(tm *teatest.TestModel, keys ...string) {
|
||||
for _, key := range keys {
|
||||
switch key {
|
||||
case "esc":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyEscape})
|
||||
case "enter":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
case "backspace":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyBackspace})
|
||||
case "ctrl+c":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlC})
|
||||
case "ctrl+d":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlD})
|
||||
default:
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(key)})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newTestModel creates a test model with default content
|
||||
func newTestModel(t *testing.T) *teatest.TestModel {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4", "line 5", "line 6"}
|
||||
return teatest.NewTestModel(t, newModel(lines), teatest.WithInitialTermSize(80, 24))
|
||||
}
|
||||
|
||||
func newTestModelWithLines(t *testing.T, lines []string) *teatest.TestModel {
|
||||
return teatest.NewTestModel(t, newModel(lines), teatest.WithInitialTermSize(80, 24))
|
||||
}
|
||||
|
||||
func newTestModelWithCursorPos(t *testing.T, pos Position) *teatest.TestModel {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4", "line 5", "line 6"}
|
||||
return teatest.NewTestModel(t, newModelWithPos(lines, pos), teatest.WithInitialTermSize(80, 24))
|
||||
}
|
||||
|
||||
func newTestModelWithCursorPosAndLines(t *testing.T, lines []string, pos Position) *teatest.TestModel {
|
||||
return teatest.NewTestModel(t, newModelWithPos(lines, pos), teatest.WithInitialTermSize(80, 24))
|
||||
}
|
||||
|
||||
// getFinalModel extracts the final model state (sends ctrl+c to quit first)
|
||||
func getFinalModel(t *testing.T, tm *teatest.TestModel) model {
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlC})
|
||||
fm := tm.FinalModel(t, teatest.WithFinalTimeout(time.Second))
|
||||
return fm.(model)
|
||||
}
|
||||
|
||||
func TestMoveDown(t *testing.T) {
|
||||
t.Run("test 'j'", func(t *testing.T) {
|
||||
tm := newTestModel(t)
|
||||
sendKeys(tm, "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.y != 1 {
|
||||
t.Errorf("cursor.y = %d, want 1", m.cursor.y)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'jjjj'", func(t *testing.T) {
|
||||
tm := newTestModel(t)
|
||||
sendKeys(tm, "j", "j", "j", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.y != 4 {
|
||||
t.Errorf("cursor.y = %d, want 4", m.cursor.y)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'jjjjjjjjj's with overflow", func(t *testing.T) {
|
||||
tm := newTestModel(t)
|
||||
sendKeys(tm, "j", "j", "j", "j", "j", "j", "j", "j", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.y != 5 {
|
||||
t.Errorf("cursor.y = %d, want 5", m.cursor.y)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveDownWithCount(t *testing.T) {
|
||||
t.Run("test '3j'", func(t *testing.T) {
|
||||
tm := newTestModel(t)
|
||||
sendKeys(tm, "3", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.y != 3 {
|
||||
t.Errorf("cursor.y = %d, want 3", m.cursor.y)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test '10j' with overflow", func(t *testing.T) {
|
||||
tm := newTestModel(t)
|
||||
sendKeys(tm, "1", "0", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.y != 5 {
|
||||
t.Errorf("cursor.y = %d, want 5", m.cursor.y)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveDownWithOverflow(t *testing.T) {
|
||||
lines := []string{"long line", "small"}
|
||||
|
||||
t.Run("test 'j' with overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPosAndLines(t, lines, Position{Col: 8, Line: 0})
|
||||
sendKeys(tm, "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
want := len(lines[1])
|
||||
if m.cursor.x != want {
|
||||
t.Errorf("cursor.x = %d, want %d", m.cursor.x, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'j' without overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPosAndLines(t, lines, Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.x != 3 {
|
||||
t.Errorf("cursor.x = %d, want 3", m.cursor.x)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveUp(t *testing.T) {
|
||||
t.Run("test 'k'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.y != 1 {
|
||||
t.Errorf("cursor.y = %d, want 1", m.cursor.y)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'kkkk'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, Position{Col: 0, Line: 4})
|
||||
sendKeys(tm, "k", "k", "k", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.y != 0 {
|
||||
t.Errorf("cursor.y = %d, want 0", m.cursor.y)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'k' at top (no movement)", func(t *testing.T) {
|
||||
tm := newTestModel(t)
|
||||
sendKeys(tm, "k", "k", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.y != 0 {
|
||||
t.Errorf("cursor.y = %d, want 0", m.cursor.y)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveUpWithCount(t *testing.T) {
|
||||
t.Run("test '3k'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, Position{Col: 0, Line: 5})
|
||||
sendKeys(tm, "3", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.y != 2 {
|
||||
t.Errorf("cursor.y = %d, want 2", m.cursor.y)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test '10k' with overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, Position{Col: 0, Line: 3})
|
||||
sendKeys(tm, "1", "0", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.y != 0 {
|
||||
t.Errorf("cursor.y = %d, want 0", m.cursor.y)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveUpWithOverflow(t *testing.T) {
|
||||
lines := []string{"small", "long line"}
|
||||
|
||||
t.Run("test 'k' with overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPosAndLines(t, lines, Position{Col: 10, Line: 1})
|
||||
sendKeys(tm, "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
want := len(lines[0])
|
||||
if m.cursor.x != want {
|
||||
t.Errorf("cursor.x = %d, want %d", m.cursor.x, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'k' without overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPosAndLines(t, lines, Position{Col: 3, Line: 1})
|
||||
sendKeys(tm, "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.x != 3 {
|
||||
t.Errorf("cursor.x = %d, want 3", m.cursor.x)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveRight(t *testing.T) {
|
||||
t.Run("test 'l'", func(t *testing.T) {
|
||||
tm := newTestModel(t)
|
||||
sendKeys(tm, "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.x != 1 {
|
||||
t.Errorf("cursor.x = %d, want 1", m.cursor.x)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'llll'", func(t *testing.T) {
|
||||
tm := newTestModel(t)
|
||||
sendKeys(tm, "l", "l", "l", "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.x != 4 {
|
||||
t.Errorf("cursor.x = %d, want 4", m.cursor.x)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'l' at end of line (no movement past end)", func(t *testing.T) {
|
||||
lines := []string{"abc"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "l", "l", "l", "l", "l", "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
want := len(lines[0])
|
||||
if m.cursor.x != want {
|
||||
t.Errorf("cursor.x = %d, want %d", m.cursor.x, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveRightWithCount(t *testing.T) {
|
||||
t.Run("test '3l'", func(t *testing.T) {
|
||||
tm := newTestModel(t)
|
||||
sendKeys(tm, "3", "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.x != 3 {
|
||||
t.Errorf("cursor.x = %d, want 3", m.cursor.x)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test '10l' with overflow", func(t *testing.T) {
|
||||
lines := []string{"short"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "1", "0", "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
want := len(lines[0])
|
||||
if m.cursor.x != want {
|
||||
t.Errorf("cursor.x = %d, want %d", m.cursor.x, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveLeft(t *testing.T) {
|
||||
t.Run("test 'h'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.x != 2 {
|
||||
t.Errorf("cursor.x = %d, want 2", m.cursor.x)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'hhhh'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "h", "h", "h", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.x != 0 {
|
||||
t.Errorf("cursor.x = %d, want 0", m.cursor.x)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'h' at start (no movement)", func(t *testing.T) {
|
||||
tm := newTestModel(t)
|
||||
sendKeys(tm, "h", "h", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.x != 0 {
|
||||
t.Errorf("cursor.x = %d, want 0", m.cursor.x)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveLeftWithCount(t *testing.T) {
|
||||
t.Run("test '3h'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "3", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.x != 2 {
|
||||
t.Errorf("cursor.x = %d, want 2", m.cursor.x)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test '10h' with overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "1", "0", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.cursor.x != 0 {
|
||||
t.Errorf("cursor.x = %d, want 0", m.cursor.x)
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user