All checks were successful
Run Test Suite / test (push) Successful in 17s
Also added tons of tests for the actual command mode, since that was all untested...
726 lines
19 KiB
Go
726 lines
19 KiB
Go
package action
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
// mockCommandRegistry for testing - returns nil for all commands (used for numeric goto)
|
|
type mockCommandRegistry struct{}
|
|
|
|
func (r *mockCommandRegistry) Execute(m Model, cmdLine string) (tea.Cmd, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// TestCommandExecute_GoToLine tests the :<num> command functionality
|
|
func TestCommandExecute_GoToLine(t *testing.T) {
|
|
t.Run("goto positive line number within bounds", func(t *testing.T) {
|
|
// Create buffer with 10 lines
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3", "line4", "line5", "line6", "line7", "line8", "line9", "line10"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
win.SetCursorLine(0)
|
|
|
|
// Set command to "5"
|
|
m.SetCommand("5")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should jump to line 5 (0-indexed: line 4)
|
|
if win.Cursor.Line != 4 {
|
|
t.Fatalf("expected cursor at line 4, got %d", win.Cursor.Line)
|
|
}
|
|
|
|
// Should exit command mode
|
|
if m.Mode() != core.NormalMode {
|
|
t.Fatalf("expected normal mode, got %v", m.Mode())
|
|
}
|
|
})
|
|
|
|
t.Run("goto line 1", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
win.SetCursorLine(3) // Start at line 4
|
|
|
|
m.SetCommand("1")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should jump to line 1 (0-indexed: line 0)
|
|
if win.Cursor.Line != 0 {
|
|
t.Fatalf("expected cursor at line 0, got %d", win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("goto last line", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
win.SetCursorLine(0)
|
|
|
|
m.SetCommand("5")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should jump to line 5 (0-indexed: line 4)
|
|
if win.Cursor.Line != 4 {
|
|
t.Fatalf("expected cursor at line 4, got %d", win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("goto line beyond buffer length clamps to last line", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
win.SetCursorLine(0)
|
|
|
|
m.SetCommand("999")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should clamp to last line (0-indexed: line 2)
|
|
// Note: cmdGoToLine uses min(line-1, buf.LineCount())
|
|
// LineCount() returns 3, so min(998, 3) = 3
|
|
// But SetCursorLine calls ClampCursor() which clamps to valid range [0, LineCount-1]
|
|
lastLine := buf.LineCount() - 1
|
|
if win.Cursor.Line != lastLine {
|
|
t.Fatalf("expected cursor at line %d (last line), got %d", lastLine, win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("goto line zero goes to beginning", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
win.SetCursorLine(3)
|
|
|
|
m.SetCommand("0")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Line 0 or below goes relative: max(0, currentLine + 0) = currentLine
|
|
// But with the logic: if line <= 0, newLine = max(0, win.Cursor.Line + line)
|
|
// So for line=0: max(0, 3+0) = 3 (stays at same line)
|
|
if win.Cursor.Line != 3 {
|
|
t.Fatalf("expected cursor at line 3, got %d", win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("negative number moves relative backwards", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3", "line4", "line5", "line6", "line7", "line8", "line9", "line10"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
win.SetCursorLine(5) // Start at line 6 (0-indexed)
|
|
|
|
m.SetCommand("-3")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should move back 3 lines: 5 + (-3) = 2
|
|
if win.Cursor.Line != 2 {
|
|
t.Fatalf("expected cursor at line 2, got %d", win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("negative number beyond start clamps to line 0", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
win.SetCursorLine(2)
|
|
|
|
m.SetCommand("-10")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should clamp to line 0: max(0, 2 + (-10)) = max(0, -8) = 0
|
|
if win.Cursor.Line != 0 {
|
|
t.Fatalf("expected cursor at line 0, got %d", win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("goto same line", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
win.SetCursorLine(2) // At line 3
|
|
|
|
m.SetCommand("3")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should stay at line 3 (0-indexed: line 2)
|
|
if win.Cursor.Line != 2 {
|
|
t.Fatalf("expected cursor at line 2, got %d", win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("empty buffer", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{""}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
|
|
m.SetCommand("1")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should stay at line 0
|
|
if win.Cursor.Line != 0 {
|
|
t.Fatalf("expected cursor at line 0, got %d", win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("single line buffer goto line 1", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"only line"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
|
|
m.SetCommand("1")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
if win.Cursor.Line != 0 {
|
|
t.Fatalf("expected cursor at line 0, got %d", win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("single line buffer goto beyond", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"only line"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
|
|
m.SetCommand("10")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should clamp to the available lines
|
|
if win.Cursor.Line > 0 {
|
|
t.Fatalf("expected cursor at line 0 or 1, got %d", win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("large line number", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
|
|
m.SetCommand("1000000")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should clamp to last available line
|
|
lineCount := buf.LineCount()
|
|
if win.Cursor.Line > lineCount {
|
|
t.Fatalf("expected cursor at or before line %d, got %d", lineCount, win.Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("command with leading/trailing spaces", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
win.SetCursorLine(0)
|
|
|
|
// strconv.Atoi should handle leading spaces
|
|
m.SetCommand(" 3 ")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// strconv.Atoi(" 3 ") will fail, so this won't be treated as a line number
|
|
// It should stay at the same position and potentially show an error
|
|
// depending on the CommandRegistry behavior
|
|
})
|
|
|
|
t.Run("mixed alphanumeric command not treated as line number", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
win := m.ActiveWindow()
|
|
initialLine := 1
|
|
win.SetCursorLine(initialLine)
|
|
|
|
m.SetCommand("3abc")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// "3abc" is not a valid number, so won't trigger goto line
|
|
// Cursor should stay at original position
|
|
if win.Cursor.Line != initialLine {
|
|
t.Fatalf("expected cursor at line %d, got %d", initialLine, win.Cursor.Line)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestCommandExecute_EmptyCommand tests empty command behavior
|
|
func TestCommandExecute_EmptyCommand(t *testing.T) {
|
|
t.Run("empty command does nothing", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
win := m.ActiveWindow()
|
|
initialLine := 0
|
|
win.SetCursorLine(initialLine)
|
|
|
|
m.SetCommand("")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
// Should exit command mode
|
|
if m.Mode() != core.NormalMode {
|
|
t.Fatalf("expected normal mode, got %v", m.Mode())
|
|
}
|
|
|
|
// Cursor should not move
|
|
if win.Cursor.Line != initialLine {
|
|
t.Fatalf("expected cursor at line %d, got %d", initialLine, win.Cursor.Line)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestCommandExecute_CommandHistory tests that commands are added to history
|
|
func TestCommandExecute_CommandHistory(t *testing.T) {
|
|
t.Run("numeric command added to history", func(t *testing.T) {
|
|
buf := core.NewBufferBuilder().
|
|
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
|
|
Build()
|
|
m := NewMockModelWithBuffer(&buf)
|
|
|
|
m.SetCommand("3")
|
|
m.SetMode(core.CommandMode)
|
|
m.SetCommandHistory([]string{})
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
history := m.CommandHistory()
|
|
if len(history) != 1 {
|
|
t.Fatalf("expected 1 item in history, got %d", len(history))
|
|
}
|
|
if history[0] != "3" {
|
|
t.Fatalf("expected '3' in history, got '%s'", history[0])
|
|
}
|
|
})
|
|
|
|
t.Run("commands prepended to existing history", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommandHistory([]string{"oldcommand1", "oldcommand2"})
|
|
|
|
m.SetCommand("5")
|
|
m.SetMode(core.CommandMode)
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
history := m.CommandHistory()
|
|
if len(history) != 3 {
|
|
t.Fatalf("expected 3 items in history, got %d", len(history))
|
|
}
|
|
if history[0] != "5" {
|
|
t.Fatalf("expected '5' as first item, got '%s'", history[0])
|
|
}
|
|
if history[1] != "oldcommand1" {
|
|
t.Fatalf("expected 'oldcommand1' as second item, got '%s'", history[1])
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestCommandExecute_ModeTransition tests command mode exit behavior
|
|
func TestCommandExecute_ModeTransition(t *testing.T) {
|
|
t.Run("exits command mode after execution", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetMode(core.CommandMode)
|
|
m.SetCommand("5")
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
if m.Mode() != core.NormalMode {
|
|
t.Fatalf("expected normal mode, got %v", m.Mode())
|
|
}
|
|
})
|
|
|
|
t.Run("resets command cursor to 0", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommandCursor(10)
|
|
m.SetCommand("5")
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
if m.CommandCursor() != 0 {
|
|
t.Fatalf("expected command cursor at 0, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
|
|
t.Run("resets command history cursor to 0", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommandHistoryCursor(5)
|
|
m.SetCommand("5")
|
|
|
|
action := CommandExecute{Registry: &mockCommandRegistry{}}
|
|
action.Execute(m)
|
|
|
|
if m.CommandHistoryCursor() != 0 {
|
|
t.Fatalf("expected command history cursor at 0, got %d", m.CommandHistoryCursor())
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestExitCommandMode tests the Esc key behavior
|
|
func TestExitCommandMode(t *testing.T) {
|
|
t.Run("exit command mode clears state", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetMode(core.CommandMode)
|
|
m.SetCommand("test command")
|
|
m.SetCommandCursor(5)
|
|
m.SetCommandHistoryCursor(3)
|
|
|
|
action := ExitCommandMode{}
|
|
action.Execute(m)
|
|
|
|
if m.Mode() != core.NormalMode {
|
|
t.Fatalf("expected normal mode, got %v", m.Mode())
|
|
}
|
|
if m.Command() != "" {
|
|
t.Fatalf("expected empty command, got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 0 {
|
|
t.Fatalf("expected command cursor at 0, got %d", m.CommandCursor())
|
|
}
|
|
if m.CommandHistoryCursor() != 0 {
|
|
t.Fatalf("expected command history cursor at 0, got %d", m.CommandHistoryCursor())
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestInsertCommandChar tests character insertion in command mode
|
|
func TestInsertCommandChar(t *testing.T) {
|
|
t.Run("insert character at empty command", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("")
|
|
m.SetCommandCursor(0)
|
|
|
|
action := InsertCommandChar{Char: "5"}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "5" {
|
|
t.Fatalf("expected '5', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 1 {
|
|
t.Fatalf("expected cursor at 1, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
|
|
t.Run("insert character at end", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("12")
|
|
m.SetCommandCursor(2)
|
|
|
|
action := InsertCommandChar{Char: "3"}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "123" {
|
|
t.Fatalf("expected '123', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 3 {
|
|
t.Fatalf("expected cursor at 3, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
|
|
t.Run("insert character in middle", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("13")
|
|
m.SetCommandCursor(1)
|
|
|
|
action := InsertCommandChar{Char: "2"}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "123" {
|
|
t.Fatalf("expected '123', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 2 {
|
|
t.Fatalf("expected cursor at 2, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestCommandBackspace tests backspace in command mode
|
|
func TestCommandBackspace(t *testing.T) {
|
|
t.Run("backspace at beginning does nothing", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("123")
|
|
m.SetCommandCursor(0)
|
|
|
|
action := CommandBackspace{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "123" {
|
|
t.Fatalf("expected '123', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 0 {
|
|
t.Fatalf("expected cursor at 0, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
|
|
t.Run("backspace in middle", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("123")
|
|
m.SetCommandCursor(2)
|
|
|
|
action := CommandBackspace{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "13" {
|
|
t.Fatalf("expected '13', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 1 {
|
|
t.Fatalf("expected cursor at 1, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
|
|
t.Run("backspace at end", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("123")
|
|
m.SetCommandCursor(3)
|
|
|
|
action := CommandBackspace{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "12" {
|
|
t.Fatalf("expected '12', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 2 {
|
|
t.Fatalf("expected cursor at 2, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestCommandDelete tests delete key in command mode
|
|
func TestCommandDelete(t *testing.T) {
|
|
t.Run("delete at cursor position 0 deletes character after cursor", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("123")
|
|
m.SetCommandCursor(0)
|
|
|
|
action := CommandDelete{}
|
|
action.Execute(m)
|
|
|
|
// At position 0, delete removes char at position 1 (the next char)
|
|
// Result: "1" + "3" = "13"
|
|
if m.Command() != "13" {
|
|
t.Fatalf("expected '13', got '%s'", m.Command())
|
|
}
|
|
})
|
|
|
|
t.Run("delete at cursor position 1", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("123")
|
|
m.SetCommandCursor(1)
|
|
|
|
action := CommandDelete{}
|
|
action.Execute(m)
|
|
|
|
// At position 1, delete removes char at position 2 (the next char)
|
|
// Result: "12" + "" = "12"
|
|
if m.Command() != "12" {
|
|
t.Fatalf("expected '12', got '%s'", m.Command())
|
|
}
|
|
})
|
|
|
|
t.Run("delete at last character", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("123")
|
|
m.SetCommandCursor(2)
|
|
|
|
action := CommandDelete{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "12" {
|
|
t.Fatalf("expected '12', got '%s'", m.Command())
|
|
}
|
|
})
|
|
|
|
t.Run("delete at end acts as backspace", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("123")
|
|
m.SetCommandCursor(3)
|
|
|
|
action := CommandDelete{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "12" {
|
|
t.Fatalf("expected '12', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 2 {
|
|
t.Fatalf("expected cursor at 2, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestCommandDeletePreviousWord tests Ctrl+W behavior in command mode
|
|
func TestCommandDeletePreviousWord(t *testing.T) {
|
|
t.Run("delete word from middle of text", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("hello world")
|
|
m.SetCommandCursor(11) // At end
|
|
|
|
action := CommandDeletePreviousWord{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "hello " {
|
|
t.Fatalf("expected 'hello ', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 6 {
|
|
t.Fatalf("expected cursor at 6, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
|
|
t.Run("delete word with leading spaces", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("hello world")
|
|
m.SetCommandCursor(13) // At end
|
|
|
|
action := CommandDeletePreviousWord{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "hello " {
|
|
t.Fatalf("expected 'hello ', got '%s'", m.Command())
|
|
}
|
|
})
|
|
|
|
t.Run("delete at beginning does nothing", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("hello")
|
|
m.SetCommandCursor(0)
|
|
|
|
action := CommandDeletePreviousWord{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "hello" {
|
|
t.Fatalf("expected 'hello', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 0 {
|
|
t.Fatalf("expected cursor at 0, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
|
|
t.Run("delete punctuation", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("hello...")
|
|
m.SetCommandCursor(8) // After the dots
|
|
|
|
action := CommandDeletePreviousWord{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "hello" {
|
|
t.Fatalf("expected 'hello', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 5 {
|
|
t.Fatalf("expected cursor at 5, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
|
|
t.Run("delete word in middle of command", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("one two three")
|
|
m.SetCommandCursor(7) // After "two"
|
|
|
|
action := CommandDeletePreviousWord{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "one three" {
|
|
t.Fatalf("expected 'one three', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 4 {
|
|
t.Fatalf("expected cursor at 4, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
|
|
t.Run("delete single character word", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("a b c")
|
|
m.SetCommandCursor(3) // After "b"
|
|
|
|
action := CommandDeletePreviousWord{}
|
|
action.Execute(m)
|
|
|
|
if m.Command() != "a c" {
|
|
t.Fatalf("expected 'a c', got '%s'", m.Command())
|
|
}
|
|
})
|
|
|
|
t.Run("delete from whitespace position", func(t *testing.T) {
|
|
m := NewMockModel()
|
|
m.SetCommand("hello world")
|
|
m.SetCommandCursor(6) // At the space after "hello"
|
|
|
|
action := CommandDeletePreviousWord{}
|
|
action.Execute(m)
|
|
|
|
// Should skip the space and delete "hello" and the space
|
|
if m.Command() != "world" {
|
|
t.Fatalf("expected 'world', got '%s'", m.Command())
|
|
}
|
|
if m.CommandCursor() != 0 {
|
|
t.Fatalf("expected cursor at 0, got %d", m.CommandCursor())
|
|
}
|
|
})
|
|
}
|