package motion import ( "testing" "git.gophernest.net/azpect/TextEditor/internal/action" "git.gophernest.net/azpect/TextEditor/internal/core" ) // ================================================== // MoveCommandHistoryUp Tests // ================================================== func TestMoveCommandHistoryUp(t *testing.T) { t.Run("navigate up from start", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"}, CommandHistoryCur: 0, } action := MoveCommandHistoryUp{} action.Execute(m) // Should load first command in history if m.Command() != "cmd3" { t.Errorf("Command = %q, want %q", m.Command(), "cmd3") } // Cursor should move to end of command if m.CommandCursor() != len("cmd3") { t.Errorf("CommandCursor = %d, want %d", m.CommandCursor(), len("cmd3")) } // History cursor should advance if m.CommandHistoryCursor() != 1 { t.Errorf("CommandHistoryCursor = %d, want 1", m.CommandHistoryCursor()) } }) t.Run("navigate up multiple times", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"}, CommandHistoryCur: 0, } action := MoveCommandHistoryUp{} // First up action.Execute(m) if m.Command() != "cmd3" { t.Errorf("After 1st up: Command = %q, want 'cmd3'", m.Command()) } // Second up action.Execute(m) if m.Command() != "cmd2" { t.Errorf("After 2nd up: Command = %q, want 'cmd2'", m.Command()) } // Third up action.Execute(m) if m.Command() != "cmd1" { t.Errorf("After 3rd up: Command = %q, want 'cmd1'", m.Command()) } if m.CommandHistoryCursor() != 3 { t.Errorf("CommandHistoryCursor = %d, want 3", m.CommandHistoryCursor()) } }) t.Run("cannot navigate past end of history", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"}, CommandHistoryCur: 3, // Already at end } action := MoveCommandHistoryUp{} action.Execute(m) // Should not change command (still showing cmd1) if m.Command() != "" { t.Errorf("Command = %q, want empty (should not go past end)", m.Command()) } // Cursor should not advance if m.CommandHistoryCursor() != 3 { t.Errorf("CommandHistoryCursor = %d, want 3 (should not advance)", m.CommandHistoryCursor()) } }) t.Run("empty history does nothing", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "current", CommandHistoryList: []string{}, CommandHistoryCur: 0, } action := MoveCommandHistoryUp{} action.Execute(m) // Command should not change if m.Command() != "current" { t.Errorf("Command = %q, want 'current' (no history to navigate)", m.Command()) } if m.CommandHistoryCursor() != 0 { t.Errorf("CommandHistoryCursor = %d, want 0", m.CommandHistoryCursor()) } }) t.Run("command cursor moves to end", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandCursorVal: 0, CommandHistoryList: []string{"long command here"}, CommandHistoryCur: 0, } action := MoveCommandHistoryUp{} action.Execute(m) expectedLen := len("long command here") if m.CommandCursor() != expectedLen { t.Errorf("CommandCursor = %d, want %d (end of command)", m.CommandCursor(), expectedLen) } }) } // ================================================== // MoveCommandHistoryDown Tests // ================================================== func TestMoveCommandHistoryDown(t *testing.T) { t.Run("navigate down from middle of history", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "cmd2", CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"}, CommandHistoryCur: 2, // At cmd2 } action := MoveCommandHistoryDown{} action.Execute(m) // Should load cmd3 (more recent) if m.Command() != "cmd3" { t.Errorf("Command = %q, want 'cmd3'", m.Command()) } // Cursor should be at 1 if m.CommandHistoryCursor() != 1 { t.Errorf("CommandHistoryCursor = %d, want 1", m.CommandHistoryCursor()) } }) t.Run("navigate down to empty command", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "cmd3", CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"}, CommandHistoryCur: 1, // At first command } action := MoveCommandHistoryDown{} action.Execute(m) // Should clear command (back to current input) if m.Command() != "" { t.Errorf("Command = %q, want empty (back to current)", m.Command()) } if m.CommandCursor() != 0 { t.Errorf("CommandCursor = %d, want 0", m.CommandCursor()) } if m.CommandHistoryCursor() != 0 { t.Errorf("CommandHistoryCursor = %d, want 0", m.CommandHistoryCursor()) } }) t.Run("navigate down from cursor 0 does nothing", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"}, CommandHistoryCur: 0, // Already at bottom } action := MoveCommandHistoryDown{} action.Execute(m) // Should clear command if m.Command() != "" { t.Errorf("Command = %q, want empty", m.Command()) } if m.CommandHistoryCursor() != 0 { t.Errorf("CommandHistoryCursor = %d, want 0", m.CommandHistoryCursor()) } }) t.Run("command cursor moves to end", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandCursorVal: 0, CommandHistoryList: []string{"cmd3", "long command here"}, CommandHistoryCur: 2, } action := MoveCommandHistoryDown{} action.Execute(m) expectedLen := len("cmd3") if m.CommandCursor() != expectedLen { t.Errorf("CommandCursor = %d, want %d (end of command)", m.CommandCursor(), expectedLen) } }) } // ================================================== // Integration Tests // ================================================== func TestCommandHistoryIntegration(t *testing.T) { t.Run("up down up sequence", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"}, CommandHistoryCur: 0, } upAction := MoveCommandHistoryUp{} downAction := MoveCommandHistoryDown{} // Up twice upAction.Execute(m) // cursor=1, cmd=cmd3 upAction.Execute(m) // cursor=2, cmd=cmd2 // Down once downAction.Execute(m) // cursor=1, cmd=cmd3 // Up again upAction.Execute(m) // cursor=2, cmd=cmd2 if m.Command() != "cmd2" { t.Errorf("Command = %q, want 'cmd2'", m.Command()) } if m.CommandHistoryCursor() != 2 { t.Errorf("CommandHistoryCursor = %d, want 2", m.CommandHistoryCursor()) } }) t.Run("navigate up past end does not crash", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandHistoryList: []string{"cmd1", "cmd2"}, CommandHistoryCur: 0, } upAction := MoveCommandHistoryUp{} // Navigate to end upAction.Execute(m) // cursor = 1, cmd = cmd1 upAction.Execute(m) // cursor = 2, cmd = cmd2 // Try to go past end upAction.Execute(m) // Should do nothing if m.CommandHistoryCursor() != 2 { t.Errorf("CommandHistoryCursor = %d, want 2 (should stay at end)", m.CommandHistoryCursor()) } }) t.Run("navigate down past bottom does not crash", func(t *testing.T) { m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandHistoryList: []string{"cmd1"}, CommandHistoryCur: 0, } downAction := MoveCommandHistoryDown{} // Try to go down from bottom downAction.Execute(m) if m.CommandHistoryCursor() != 0 { t.Errorf("CommandHistoryCursor = %d, want 0 (should stay at bottom)", m.CommandHistoryCursor()) } // Try again downAction.Execute(m) if m.CommandHistoryCursor() != 0 { t.Errorf("CommandHistoryCursor = %d, want 0 (should stay at bottom)", m.CommandHistoryCursor()) } }) } // ================================================== // Long History Tests // ================================================== func TestCommandHistoryWithLongHistory(t *testing.T) { t.Run("navigate through 20 commands", func(t *testing.T) { history := make([]string, 20) for i := range 20 { history[i] = string(rune('A' + i)) } m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandHistoryList: history, CommandHistoryCur: 0, } upAction := MoveCommandHistoryUp{} // Navigate to 10th command for range 10 { upAction.Execute(m) } want := string(rune('A' + 9)) if m.Command() != want { t.Errorf("Command after 10 ups = %q, want %q", m.Command(), want) } if m.CommandHistoryCursor() != 10 { t.Errorf("CommandHistoryCursor = %d, want 10", m.CommandHistoryCursor()) } }) t.Run("navigate to very end of long history", func(t *testing.T) { history := make([]string, 50) for i := range 50 { history[i] = string(rune('0' + (i % 10))) } m := &action.MockModel{ ModeVal: core.CommandMode, CommandVal: "", CommandHistoryList: history, CommandHistoryCur: 0, } upAction := MoveCommandHistoryUp{} // Navigate all the way to the end for range 100 { // Try to go past end upAction.Execute(m) } // Should be at position 50 (end of 50-item array) if m.CommandHistoryCursor() != 50 { t.Errorf("CommandHistoryCursor = %d, want 50 (at end)", m.CommandHistoryCursor()) } // Should be showing last command want := string(rune('0' + 49%10)) if m.Command() != want { t.Errorf("Command = %q, want %q (last in history)", m.Command(), want) } }) }