package model_test import ( "bytes" "testing" "github.com/StevanFreeborn/term-tier/internal/core" "github.com/StevanFreeborn/term-tier/internal/handlers" "github.com/StevanFreeborn/term-tier/internal/model" "github.com/StevanFreeborn/term-tier/internal/state" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/x/exp/teatest" ) func TestModel_Update_DelegatesKeyMsg(t *testing.T) { s := state.New(state.WithMode(state.NavigationMode)) mockHandler := &MockKeyHandler{shouldMatch: true} m := model.New(s, model.WithKeyMsgHandlers([]handlers.KeyMsgHandler{mockHandler})) tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(300, 100)) tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("q")}) tm.WaitFinished(t) if !mockHandler.called { t.Error("Expected KeyMsgHandler to be called, but it was not") } } func TestModel_Update_DelegatesWinSizeMsg(t *testing.T) { s := state.New(state.WithWindowWidth(100)) mockHandler := &MockWinHandler{shouldMatch: true} m := model.New(s, model.WithWinSizeMsgHandlers([]handlers.WinSizeMsgHandler{mockHandler})) tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(80, 24)) tm.Send(tea.WindowSizeMsg{Width: 120, Height: 80}) tm.WaitFinished(t) if !mockHandler.called { t.Error("Expected WinSizeMsgHandler to be called") } if s.WindowWidth() != 120 { t.Errorf("Expected state to update WindowWidth to 120, got %d", s.WindowWidth()) } } func TestModel_View_NavMode(t *testing.T) { s := state.New( state.WithMode(state.NavigationMode), state.WithTiers([]core.Tier{ {Name: "S Tier", Color: lipgloss.Color("#FFFFFF"), Items: []core.Item{"Go", "Rust"}}, }), state.WithCursor(0, 0), state.WithWindowWidth(100), state.WithMode(state.NavigationMode), ) m := model.New(s) output := m.View() if !bytes.Contains([]byte(output), []byte("S Tier")) { t.Error("View output missing tier label 'S Tier'") } if !bytes.Contains([]byte(output), []byte("Go")) { t.Error("View output missing item 'Go'") } if bytes.Contains([]byte(output), []byte("Enter to Confirm")) { t.Error("View output contains Input Mode text while in Nav Mode") } } func TestModel_View_InputMode(t *testing.T) { ti := textinput.New() ti.SetValue("Test Value") s := state.New( state.WithMode(state.InputMode), state.WithInput(ti, "Create New Item"), ) m := model.New(s) output := m.View() if !bytes.Contains([]byte(output), []byte("Create New Item")) { t.Error("View output missing Input Title") } if !bytes.Contains([]byte(output), []byte("Test Value")) { t.Error("View output missing input value") } if !bytes.Contains([]byte(output), []byte("Esc to Cancel")) { t.Error("View output missing input help text") } } func TestModel_Init(t *testing.T) { s := state.New() m := model.New(s) cmd := m.Init() if cmd == nil { t.Error("Init should return a command (blink), got nil") } } type MockKeyHandler struct { called bool shouldMatch bool } func (m *MockKeyHandler) KeysHandled() []string { return []string{"q"} } func (m *MockKeyHandler) Description() string { return "Mock Quit Handler" } func (m *MockKeyHandler) Match(s state.State, msg tea.KeyMsg) bool { return m.shouldMatch } func (m *MockKeyHandler) Handle(s state.State, msg tea.KeyMsg) tea.Cmd { m.called = true return tea.Quit } type MockWinHandler struct { called bool shouldMatch bool targetWidth int } func (m *MockWinHandler) Match(s state.State, msg tea.WindowSizeMsg) bool { return m.shouldMatch } func (m *MockWinHandler) Handle(s state.State, msg tea.WindowSizeMsg) tea.Cmd { m.called = true s.SetWindowSize(msg.Width, msg.Height) return tea.Quit }