package editor import ( "testing" "git.gophernest.net/azpect/TextEditor/internal/core" ) // --- Insert Mode Entry Tests --- func TestEnterInsert(t *testing.T) { t.Run("test 'i' enters insert mode", func(t *testing.T) { tm := newTestModel(t) sendKeys(tm, "i") m := getFinalModel(t, tm) if m.mode != core.InsertMode { t.Errorf("mode = %d, want InsertMode (%d)", m.mode, core.InsertMode) } }) t.Run("test 'i' insert at beginning", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "i", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "Xhello" { t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test 'i' insert in middle", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0}) sendKeys(tm, "i", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "heXllo" { t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test 'i' cursor moves back on esc", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0}) sendKeys(tm, "i", "X", "esc") m := getFinalModel(t, tm) if m.ActiveWindow().Cursor.Col != 2 { t.Errorf("cursor.x = %d, want 2", m.ActiveWindow().Cursor.Col) } }) } func TestEnterInsertAfter(t *testing.T) { t.Run("test 'a' enters insert mode", func(t *testing.T) { tm := newTestModel(t) sendKeys(tm, "a") m := getFinalModel(t, tm) if m.mode != core.InsertMode { t.Errorf("mode = %d, want InsertMode (%d)", m.mode, core.InsertMode) } }) t.Run("test 'a' inserts after cursor", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "a", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "hXello" { t.Errorf("lines[0] = %q, want 'hXello'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test 'a' from middle of line", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0}) sendKeys(tm, "a", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "helXlo" { t.Errorf("lines[0] = %q, want 'helXlo'", m.ActiveBuffer().Lines[0].String()) } }) } func TestEnterInsertLineStart(t *testing.T) { t.Run("test 'I' enters insert mode at line start", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0}) sendKeys(tm, "I", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "Xhello" { t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test 'I' from end of line", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0}) sendKeys(tm, "I", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "Xhello" { t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String()) } }) } func TestEnterInsertLineEnd(t *testing.T) { t.Run("test 'A' enters insert mode at line end", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "A", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "helloX" { t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test 'A' from middle of line", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0}) sendKeys(tm, "A", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "helloX" { t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String()) } }) } // --- Open Line Tests --- func TestOpenLineBelow(t *testing.T) { t.Run("test 'o' creates line below", func(t *testing.T) { lines := []string{"line 1", "line 2"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "o", "n", "e", "w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 3 { t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[1].String() != "new" { t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1].String()) } }) t.Run("test 'o' from middle of file", func(t *testing.T) { lines := []string{"line 1", "line 2", "line 3"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1}) sendKeys(tm, "o", "n", "e", "w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 4 { t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[2].String() != "new" { t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2].String()) } }) t.Run("test 'o' at end of file", func(t *testing.T) { lines := []string{"line 1", "line 2"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1}) sendKeys(tm, "o", "n", "e", "w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 3 { t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[2].String() != "new" { t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2].String()) } }) t.Run("test 'o' cursor moves to new line", func(t *testing.T) { lines := []string{"line 1", "line 2"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "o", "esc") m := getFinalModel(t, tm) if m.ActiveWindow().Cursor.Line != 1 { t.Errorf("cursor.y = %d, want 1", m.ActiveWindow().Cursor.Line) } if m.ActiveWindow().Cursor.Col != 0 { t.Errorf("cursor.x = %d, want 0", m.ActiveWindow().Cursor.Col) } }) } func TestOpenLineBelowWithCount(t *testing.T) { t.Run("test '3o' creates 3 lines", func(t *testing.T) { lines := []string{"line 1"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "3", "o", "x", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 4 { t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount()) } for i := 1; i <= 3; i++ { if m.ActiveBuffer().Lines[i].String() != "x" { t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i].String()) } } }) t.Run("test '2o' with multiple chars", func(t *testing.T) { lines := []string{"line 1"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "2", "o", "a", "b", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 3 { t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[1].String() != "ab" { t.Errorf("lines[1] = %q, want 'ab'", m.ActiveBuffer().Lines[1].String()) } if m.ActiveBuffer().Lines[2].String() != "ab" { t.Errorf("lines[2] = %q, want 'ab'", m.ActiveBuffer().Lines[2].String()) } }) } func TestOpenLineAbove(t *testing.T) { t.Run("test 'O' creates line above", func(t *testing.T) { lines := []string{"line 1", "line 2"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1}) sendKeys(tm, "O", "n", "e", "w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 3 { t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[1].String() != "new" { t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1].String()) } }) t.Run("test 'O' at top of file", func(t *testing.T) { lines := []string{"line 1", "line 2"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "O", "n", "e", "w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 3 { t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[0].String() != "new" { t.Errorf("lines[0] = %q, want 'new'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test 'O' cursor at start of new line", func(t *testing.T) { lines := []string{"line 1", "line 2"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 1}) sendKeys(tm, "O", "esc") m := getFinalModel(t, tm) if m.ActiveWindow().Cursor.Col != 0 { t.Errorf("cursor.x = %d, want 0", m.ActiveWindow().Cursor.Col) } }) } func TestOpenLineAboveWithCount(t *testing.T) { t.Run("test '3O' creates 3 lines above", func(t *testing.T) { lines := []string{"line 1"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0}) sendKeys(tm, "3", "O", "x", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 4 { t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount()) } for i := 0; i < 3; i++ { if m.ActiveBuffer().Lines[i].String() != "x" { t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i].String()) } } }) } // --- Insert Mode Special Keys --- func TestInsertModeEnter(t *testing.T) { t.Run("test enter splits line", func(t *testing.T) { lines := []string{"hello world"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0}) sendKeys(tm, "i", "enter", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 2 { t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[0].String() != "hello" { t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) } if m.ActiveBuffer().Lines[1].String() != " world" { t.Errorf("lines[1] = %q, want ' world'", m.ActiveBuffer().Lines[1].String()) } }) t.Run("test enter at end of line", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0}) sendKeys(tm, "i", "enter", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 2 { t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[0].String() != "hello" { t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) } if m.ActiveBuffer().Lines[1].String() != "" { t.Errorf("lines[1] = %q, want ''", m.ActiveBuffer().Lines[1].String()) } }) t.Run("test enter at start of line", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "i", "enter", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 2 { t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[0].String() != "" { t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) } if m.ActiveBuffer().Lines[1].String() != "hello" { t.Errorf("lines[1] = %q, want 'hello'", m.ActiveBuffer().Lines[1].String()) } }) } func TestInsertModeBackspace(t *testing.T) { t.Run("test backspace deletes character", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0}) sendKeys(tm, "i", "backspace", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "helo" { t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test backspace at start of line joins lines", func(t *testing.T) { lines := []string{"hello", "world"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1}) sendKeys(tm, "i", "backspace", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 1 { t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[0].String() != "helloworld" { t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test backspace at start of first line does nothing", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "i", "backspace", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "hello" { t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test multiple backspaces", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0}) sendKeys(tm, "i", "backspace", "backspace", "backspace", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "he" { t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0].String()) } }) } func TestInsertModeDelete(t *testing.T) { t.Run("test delete deletes character", func(t *testing.T) { lines := []string{"world"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0}) sendKeys(tm, "i", "delete", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "word" { t.Errorf("lines[0] = %q, want 'word'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test delete at end of line joins lines", func(t *testing.T) { lines := []string{"hello", "world"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0}) sendKeys(tm, "i", "delete", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 1 { t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[0].String() != "helloworld" { t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test delete at start of empty line joins lines", func(t *testing.T) { lines := []string{"", "world"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "i", "delete", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 1 { t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[0].String() != "world" { t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test delete at end of last line does nothing", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0}) sendKeys(tm, "i", "delete", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "hello" { t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test multiple delete", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0}) sendKeys(tm, "i", "delete", "delete", "delete", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "ho" { t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0].String()) } }) } func TestInsertModeArrowKeys(t *testing.T) { t.Run("test left arrow moves cursor left", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0}) sendKeys(tm, "i", "left", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "heXllo" { t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test right arrow moves cursor right", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0}) sendKeys(tm, "i", "right", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "heXllo" { t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test up arrow moves cursor up", func(t *testing.T) { lines := []string{"hello", "world"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 1}) sendKeys(tm, "i", "up", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "heXllo" { t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) } if m.ActiveBuffer().Lines[1].String() != "world" { t.Errorf("lines[1] = %q, want 'world'", m.ActiveBuffer().Lines[1].String()) } }) t.Run("test down arrow moves cursor down", func(t *testing.T) { lines := []string{"hello", "world"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0}) sendKeys(tm, "i", "down", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "hello" { t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) } if m.ActiveBuffer().Lines[1].String() != "woXrld" { t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1].String()) } }) t.Run("test left arrow at start of line does nothing", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "i", "left", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "Xhello" { t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test right arrow at end of line does nothing", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0}) sendKeys(tm, "a", "right", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "helloX" { t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test up arrow at first line does nothing", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0}) sendKeys(tm, "i", "up", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "heXllo" { t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test down arrow at last line does nothing", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0}) sendKeys(tm, "i", "down", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "heXllo" { t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test up arrow clamps cursor to shorter line", func(t *testing.T) { lines := []string{"hi", "hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 1}) sendKeys(tm, "i", "up", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "hiX" { t.Errorf("lines[0] = %q, want 'hiX'", m.ActiveBuffer().Lines[0].String()) } }) t.Run("test down arrow clamps cursor to shorter line", func(t *testing.T) { lines := []string{"hello", "hi"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0}) sendKeys(tm, "i", "down", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[1].String() != "hiX" { t.Errorf("lines[1] = %q, want 'hiX'", m.ActiveBuffer().Lines[1].String()) } }) t.Run("test multiple arrow keys in sequence", func(t *testing.T) { lines := []string{"hello", "world"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "i", "right", "right", "down", "X", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[1].String() != "woXrld" { t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1].String()) } }) } func TestInsertModeDeletePreviousWord(t *testing.T) { t.Run("test 'ctrl+w' deletes word", func(t *testing.T) { lines := []string{"hello world"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0}) sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "hello " { t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0].String()) } if m.ActiveWindow().Cursor.Col != 5 { t.Errorf("CursorX() = %d, want '5'", m.ActiveWindow().Cursor.Col) } }) t.Run("test 'ctrl+w' deletes word with whitespace", func(t *testing.T) { lines := []string{"hello "} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0}) sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "" { t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) } if m.ActiveWindow().Cursor.Col != 0 { t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col) } }) t.Run("test 'ctrl+w' deletes word until period", func(t *testing.T) { lines := []string{"hello wo..."} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0}) sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "hello wo" { t.Errorf("lines[0] = %q, want 'hello wo'", m.ActiveBuffer().Lines[0].String()) } if m.ActiveWindow().Cursor.Col != 7 { t.Errorf("CursorX() = %d, want '7'", m.ActiveWindow().Cursor.Col) } }) t.Run("test 'ctrl+w' deletes line when blank", func(t *testing.T) { lines := []string{"", ""} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1}) sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 1 { t.Errorf("LineCount() = %d, want '1'", m.ActiveBuffer().LineCount()) } 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+w' deletes all whitespace when line is only whitespace", func(t *testing.T) { lines := []string{" "} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0}) sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 1 { t.Errorf("LineCount() = %d, want '1'", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[0].String() != "" { t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0].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+w' at start of first line does nothing", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLines(t, lines) sendKeys(tm, "i", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "hello" { t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) } if m.ActiveWindow().Cursor.Col != 0 { t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) } }) t.Run("test 'ctrl+w' from middle of word", func(t *testing.T) { lines := []string{"hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0}) sendKeys(tm, "i", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "lo" { t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0].String()) } if m.ActiveWindow().Cursor.Col != 0 { t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) } }) t.Run("test 'ctrl+w' deletes word after punctuation", func(t *testing.T) { lines := []string{"...hello"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 7, Line: 0}) sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "..." { t.Errorf("lines[0] = %q, want '...'", m.ActiveBuffer().Lines[0].String()) } if m.ActiveWindow().Cursor.Col != 2 { t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col) } }) t.Run("test 'ctrl+w' with tabs as whitespace", func(t *testing.T) { lines := []string{"hello\tworld"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0}) sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "hello\t" { t.Errorf("lines[0] = %q, want 'hello\\t'", m.ActiveBuffer().Lines[0].String()) } if m.ActiveWindow().Cursor.Col != 5 { t.Errorf("CursorX() = %d, want 5", m.ActiveWindow().Cursor.Col) } }) t.Run("test 'ctrl+w' at start of line merges with previous line content", func(t *testing.T) { lines := []string{"hello", "world"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1}) sendKeys(tm, "i", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().LineCount() != 1 { t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) } if m.ActiveBuffer().Lines[0].String() != "helloworld" { t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String()) } if m.ActiveWindow().Cursor.Col != 4 { t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col) } if m.ActiveWindow().Cursor.Line != 0 { t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line) } }) t.Run("test 'ctrl+w' with underscore in word", func(t *testing.T) { lines := []string{"hello_world"} tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0}) sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) if m.ActiveBuffer().Lines[0].String() != "" { t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) } if m.ActiveWindow().Cursor.Col != 0 { t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) } }) }