feat: implement item management handlers with corresponding tests
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type addItemHandler struct {
|
||||||
|
keys []string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddItemHandler() KeyMsgHandler {
|
||||||
|
return addItemHandler{
|
||||||
|
keys: []string{"n"},
|
||||||
|
description: "Add new item",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h addItemHandler) KeysHandled() []string {
|
||||||
|
return h.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h addItemHandler) Description() string {
|
||||||
|
return h.description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h addItemHandler) Match(s state.State, msg tea.KeyMsg) bool {
|
||||||
|
return s.Mode() == state.NavigationMode && slices.Contains(h.keys, msg.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h addItemHandler) Handle(s state.State, msg tea.KeyMsg) tea.Cmd {
|
||||||
|
return s.AddItem()
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package handlers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/handlers"
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewAddItemHandler(t *testing.T) {
|
||||||
|
h := handlers.NewAddItemHandler()
|
||||||
|
|
||||||
|
if h.Description() != "Add new item" {
|
||||||
|
t.Errorf("Expected description 'Add new item', got %q", h.Description())
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := h.KeysHandled()
|
||||||
|
|
||||||
|
if len(keys) != 1 || keys[0] != "n" {
|
||||||
|
t.Errorf("Expected keys ['n'], got %v", keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddItemHandler_Match(t *testing.T) {
|
||||||
|
h := handlers.NewAddItemHandler()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mode state.Mode
|
||||||
|
key string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Matches 'n' in Navigation Mode",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: "n",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores 'n' in Input Mode",
|
||||||
|
mode: state.InputMode,
|
||||||
|
key: "n",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores 'q' in Navigation Mode",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: "q",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := state.New(state.WithMode(tt.mode))
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(tt.key)}
|
||||||
|
|
||||||
|
if got := h.Match(s, msg); got != tt.expected {
|
||||||
|
t.Errorf("Match() = %v, want %v", got, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddItemHandler_Handle(t *testing.T) {
|
||||||
|
h := handlers.NewAddItemHandler()
|
||||||
|
|
||||||
|
s := state.New(state.WithMode(state.NavigationMode))
|
||||||
|
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("n")}
|
||||||
|
cmd := h.Handle(s, msg)
|
||||||
|
|
||||||
|
if s.Mode() != state.InputMode {
|
||||||
|
t.Errorf("Expected state to switch to InputMode, got %v", s.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedTitle := "Add new item"
|
||||||
|
|
||||||
|
if s.InputTitle() != expectedTitle {
|
||||||
|
t.Errorf("Expected input title %q, got %q", expectedTitle, s.InputTitle())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd == nil {
|
||||||
|
t.Error("Expected Handle to return a tea.Cmd (cursor blink), got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type deleteItemHandler struct {
|
||||||
|
keys []string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeleteItemHandler() KeyMsgHandler {
|
||||||
|
return deleteItemHandler{
|
||||||
|
keys: []string{"d", "backspace"},
|
||||||
|
description: "Delete item",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h deleteItemHandler) KeysHandled() []string {
|
||||||
|
return h.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h deleteItemHandler) Description() string {
|
||||||
|
return h.description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h deleteItemHandler) Match(s state.State, msg tea.KeyMsg) bool {
|
||||||
|
return s.Mode() == state.NavigationMode && slices.Contains(h.keys, msg.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h deleteItemHandler) Handle(s state.State, msg tea.KeyMsg) tea.Cmd {
|
||||||
|
return s.DeleteItem()
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package handlers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/core"
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/handlers"
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewDeleteItemHandler(t *testing.T) {
|
||||||
|
h := handlers.NewDeleteItemHandler()
|
||||||
|
|
||||||
|
if h.Description() != "Delete item" {
|
||||||
|
t.Errorf("Expected description 'Delete item', got %q", h.Description())
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := h.KeysHandled()
|
||||||
|
|
||||||
|
if !slices.Contains(keys, "d") || !slices.Contains(keys, "backspace") {
|
||||||
|
t.Errorf("Expected keys ['d', 'backspace'], got %v", keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteItemHandler_Match(t *testing.T) {
|
||||||
|
h := handlers.NewDeleteItemHandler()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mode state.Mode
|
||||||
|
key string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Matches 'd' in Navigation Mode",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: "d",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Matches 'backspace' in Navigation Mode",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: "backspace",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores 'd' in Input Mode",
|
||||||
|
mode: state.InputMode,
|
||||||
|
key: "d",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores other keys in Navigation Mode",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: "x",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := state.New(state.WithMode(tt.mode))
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(tt.key)}
|
||||||
|
|
||||||
|
if tt.key == "backspace" {
|
||||||
|
msg = tea.KeyMsg{Type: tea.KeyBackspace}
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := h.Match(s, msg); got != tt.expected {
|
||||||
|
t.Errorf("Match() = %v, want %v", got, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteItemHandler_Handle(t *testing.T) {
|
||||||
|
h := handlers.NewDeleteItemHandler()
|
||||||
|
|
||||||
|
tiers := make([]core.Tier, 6)
|
||||||
|
tiers[0] = core.Tier{Name: "S", Items: []core.Item{"Delete Me"}}
|
||||||
|
|
||||||
|
s := state.New(
|
||||||
|
state.WithTiers(tiers),
|
||||||
|
state.WithCursor(0, 0),
|
||||||
|
state.WithMode(state.NavigationMode),
|
||||||
|
)
|
||||||
|
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("d")}
|
||||||
|
h.Handle(s, msg)
|
||||||
|
|
||||||
|
if len(s.Tiers()[0].Items) != 0 {
|
||||||
|
t.Errorf("Expected item to be deleted, but %d items remain", len(s.Tiers()[0].Items))
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.StatusMsg() != "Item Deleted" {
|
||||||
|
t.Errorf("Expected status 'Item Deleted', got %q", s.StatusMsg())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type selectItemHandler struct {
|
||||||
|
keys []string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSelectItemHandler() KeyMsgHandler {
|
||||||
|
return selectItemHandler{
|
||||||
|
keys: []string{"enter", "space", " "},
|
||||||
|
description: "Select item",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h selectItemHandler) KeysHandled() []string {
|
||||||
|
return h.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h selectItemHandler) Description() string {
|
||||||
|
return h.description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h selectItemHandler) Match(s state.State, msg tea.KeyMsg) bool {
|
||||||
|
return s.Mode() == state.NavigationMode && slices.Contains(h.keys, msg.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h selectItemHandler) Handle(s state.State, msg tea.KeyMsg) tea.Cmd {
|
||||||
|
s.SelectItem()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package handlers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/core"
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/handlers"
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewSelectItemHandler(t *testing.T) {
|
||||||
|
h := handlers.NewSelectItemHandler()
|
||||||
|
|
||||||
|
if h.Description() != "Select item" {
|
||||||
|
t.Errorf("Expected description 'Select item', got %q", h.Description())
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := h.KeysHandled()
|
||||||
|
|
||||||
|
if !slices.Contains(keys, "enter") || !slices.Contains(keys, "space") {
|
||||||
|
t.Errorf("Expected keys ['enter', 'space'], got %v", keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectItemHandler_Match(t *testing.T) {
|
||||||
|
h := handlers.NewSelectItemHandler()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mode state.Mode
|
||||||
|
key string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Matches 'enter' in Navigation Mode",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: "enter",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Matches 'space' in Navigation Mode",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: " ",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Matches 'space' in Navigation Mode",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: "space",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores 'enter' in Input Mode",
|
||||||
|
mode: state.InputMode,
|
||||||
|
key: "enter",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores 'space' in Input Mode",
|
||||||
|
mode: state.InputMode,
|
||||||
|
key: " ",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores 'space' in Input Mode",
|
||||||
|
mode: state.InputMode,
|
||||||
|
key: "space",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores other keys",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: "a",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := state.New(state.WithMode(tt.mode))
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(tt.key)}
|
||||||
|
|
||||||
|
switch tt.key {
|
||||||
|
case "enter":
|
||||||
|
msg = tea.KeyMsg{Type: tea.KeyEnter}
|
||||||
|
case " ":
|
||||||
|
msg = tea.KeyMsg{Type: tea.KeySpace}
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := h.Match(s, msg); got != tt.expected {
|
||||||
|
t.Errorf("Match() = %v, want %v", got, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectItemHandler_Handle(t *testing.T) {
|
||||||
|
h := handlers.NewSelectItemHandler()
|
||||||
|
|
||||||
|
tiers := []core.Tier{{Name: "S", Items: []core.Item{"Item A"}}}
|
||||||
|
|
||||||
|
s := state.New(
|
||||||
|
state.WithTiers(tiers),
|
||||||
|
state.WithCursor(0, 0),
|
||||||
|
state.WithMode(state.NavigationMode),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run("Starts Dragging", func(t *testing.T) {
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyEnter}
|
||||||
|
cmd := h.Handle(s, msg)
|
||||||
|
|
||||||
|
if !s.Dragging() {
|
||||||
|
t.Error("Expected dragging to be true after selecting item")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd != nil {
|
||||||
|
t.Error("Expected nil command")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Stops Dragging", func(t *testing.T) {
|
||||||
|
if !s.Dragging() {
|
||||||
|
s.SelectItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyEnter}
|
||||||
|
|
||||||
|
h.Handle(s, msg)
|
||||||
|
|
||||||
|
if s.Dragging() {
|
||||||
|
t.Error("Expected dragging to be false after selecting again")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type textInputHandler struct {
|
||||||
|
keys []string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTextInputHandler() KeyMsgHandler {
|
||||||
|
return textInputHandler{
|
||||||
|
keys: []string{},
|
||||||
|
description: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h textInputHandler) KeysHandled() []string {
|
||||||
|
return h.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h textInputHandler) Description() string {
|
||||||
|
return h.description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h textInputHandler) Match(s state.State, msg tea.KeyMsg) bool {
|
||||||
|
return s.Mode() == state.InputMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h textInputHandler) Handle(s state.State, msg tea.KeyMsg) tea.Cmd {
|
||||||
|
switch msg.String() {
|
||||||
|
case "esc":
|
||||||
|
s.ExitInputMode()
|
||||||
|
return nil
|
||||||
|
case "enter":
|
||||||
|
return s.SubmitInput()
|
||||||
|
default:
|
||||||
|
return s.UpdateInput(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package handlers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/handlers"
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
||||||
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewTextInputHandler(t *testing.T) {
|
||||||
|
h := handlers.NewTextInputHandler()
|
||||||
|
|
||||||
|
if len(h.KeysHandled()) != 0 {
|
||||||
|
t.Errorf("Expected 0 keys handled, got %d", len(h.KeysHandled()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.Description() != "" {
|
||||||
|
t.Errorf("Expected empty description, got %q", h.Description())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTextInputHandler_Match(t *testing.T) {
|
||||||
|
h := handlers.NewTextInputHandler()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mode state.Mode
|
||||||
|
msg tea.KeyMsg
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Matches in Input Mode",
|
||||||
|
mode: state.InputMode,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores in Navigation Mode",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores in Help Mode",
|
||||||
|
mode: state.HelpMode,
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := state.New(state.WithMode(tt.mode))
|
||||||
|
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("a")}
|
||||||
|
|
||||||
|
if got := h.Match(s, msg); got != tt.expected {
|
||||||
|
t.Errorf("Match() = %v, want %v", got, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTextInputHandler_Handle(t *testing.T) {
|
||||||
|
h := handlers.NewTextInputHandler()
|
||||||
|
|
||||||
|
t.Run("Handle Escape (Exit Input Mode)", func(t *testing.T) {
|
||||||
|
s := state.New(state.WithMode(state.InputMode))
|
||||||
|
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyEsc}
|
||||||
|
cmd := h.Handle(s, msg)
|
||||||
|
|
||||||
|
if s.Mode() != state.NavigationMode {
|
||||||
|
t.Errorf("Expected mode to switch to NavigationMode, got %v", s.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd != nil {
|
||||||
|
t.Error("Expected nil command on escape")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handle Enter (Submit Input)", func(t *testing.T) {
|
||||||
|
s := state.New(state.WithMode(state.InputMode))
|
||||||
|
|
||||||
|
submitted := false
|
||||||
|
|
||||||
|
s.StartInputMode("Test", func(val string, s state.State) tea.Cmd {
|
||||||
|
submitted = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyEnter}
|
||||||
|
|
||||||
|
h.Handle(s, msg)
|
||||||
|
|
||||||
|
if !submitted {
|
||||||
|
t.Error("Expected Enter to submit input and trigger callback")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Mode() != state.NavigationMode {
|
||||||
|
t.Error("Expected mode to reset to NavigationMode after submit")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handle Typing (Update Input)", func(t *testing.T) {
|
||||||
|
ti := textinput.New()
|
||||||
|
ti.Focus()
|
||||||
|
|
||||||
|
s := state.New(
|
||||||
|
state.WithMode(state.InputMode),
|
||||||
|
state.WithInput(ti, "Test Input"),
|
||||||
|
)
|
||||||
|
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("a")}
|
||||||
|
cmd := h.Handle(s, msg)
|
||||||
|
|
||||||
|
if s.Input().Value() != "a" {
|
||||||
|
t.Errorf("Expected input value 'a', got %q", s.Input().Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd == nil {
|
||||||
|
t.Error("Expected command (blink) from text input update")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("b")}
|
||||||
|
h.Handle(s, msg)
|
||||||
|
|
||||||
|
if s.Input().Value() != "ab" {
|
||||||
|
t.Errorf("Expected input value 'ab', got %q", s.Input().Value())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type updateItemHandler struct {
|
||||||
|
keys []string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdateItemHandler() KeyMsgHandler {
|
||||||
|
return updateItemHandler{
|
||||||
|
keys: []string{"r"},
|
||||||
|
description: "Update item",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h updateItemHandler) KeysHandled() []string {
|
||||||
|
return h.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h updateItemHandler) Description() string {
|
||||||
|
return h.description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h updateItemHandler) Match(s state.State, msg tea.KeyMsg) bool {
|
||||||
|
return s.Mode() == state.NavigationMode && slices.Contains(h.keys, msg.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h updateItemHandler) Handle(s state.State, msg tea.KeyMsg) tea.Cmd {
|
||||||
|
return s.UpdateItem()
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package handlers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/core"
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/handlers"
|
||||||
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewUpdateItemHandler(t *testing.T) {
|
||||||
|
h := handlers.NewUpdateItemHandler()
|
||||||
|
|
||||||
|
if h.Description() != "Update item" {
|
||||||
|
t.Errorf("Expected description 'Update item', got %q", h.Description())
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := h.KeysHandled()
|
||||||
|
|
||||||
|
if !slices.Contains(keys, "r") {
|
||||||
|
t.Errorf("Expected keys ['r'], got %v", keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateItemHandler_Match(t *testing.T) {
|
||||||
|
h := handlers.NewUpdateItemHandler()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mode state.Mode
|
||||||
|
key string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Matches 'r' in Navigation Mode",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: "r",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores 'r' in Input Mode",
|
||||||
|
mode: state.InputMode,
|
||||||
|
key: "r",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ignores other keys",
|
||||||
|
mode: state.NavigationMode,
|
||||||
|
key: "a",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := state.New(state.WithMode(tt.mode))
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(tt.key)}
|
||||||
|
|
||||||
|
if got := h.Match(s, msg); got != tt.expected {
|
||||||
|
t.Errorf("Match() = %v, want %v", got, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateItemHandler_Handle(t *testing.T) {
|
||||||
|
h := handlers.NewUpdateItemHandler()
|
||||||
|
|
||||||
|
tiers := []core.Tier{{Name: "S", Items: []core.Item{"EditMe"}}}
|
||||||
|
|
||||||
|
s := state.New(
|
||||||
|
state.WithTiers(tiers),
|
||||||
|
state.WithCursor(0, 0),
|
||||||
|
state.WithMode(state.NavigationMode),
|
||||||
|
)
|
||||||
|
|
||||||
|
msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("r")}
|
||||||
|
cmd := h.Handle(s, msg)
|
||||||
|
|
||||||
|
if s.Mode() != state.InputMode {
|
||||||
|
t.Errorf("Expected state to switch to InputMode, got %v", s.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Input().Value() != "EditMe" {
|
||||||
|
t.Errorf("Expected input to be pre-filled with 'EditMe', got %q", s.Input().Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd == nil {
|
||||||
|
t.Error("Expected Handle to return a command, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user