package dialog import ( "strings" "testing" "glint/internal/theme" "github.com/charmbracelet/bubbles/cursor" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) func newModel() *Model { return New(theme.FlexokiDark()) } func typeRunes(m *Model, s string) { for _, r := range s { m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{r}}) } } func TestOpenSetsTitleAndPrefill(t *testing.T) { m := newModel() m.Open("Rename", "~/Notes/", "my-note") if m.Title() != "Rename" { t.Errorf("Title() = %q, want Rename", m.Title()) } if m.Value() != "my-note" { t.Errorf("Value() = %q, want my-note", m.Value()) } } func TestTypingAppendsAfterPrefill(t *testing.T) { m := newModel() m.Open("New note", "~/Notes/", "draft") typeRunes(m, "-2") if m.Value() != "draft-2" { t.Errorf("Value() = %q, want draft-2 (cursor must start at end of prefill)", m.Value()) } } func TestEnterConfirms(t *testing.T) { m := newModel() m.Open("New note", "~/Notes/", "") typeRunes(m, "hello") res, _ := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) if res != ResultConfirm { t.Errorf("Enter gave %v, want ResultConfirm", res) } if m.Value() != "hello" { t.Errorf("Value() = %q, want hello", m.Value()) } } func TestEscCancels(t *testing.T) { m := newModel() m.Open("New note", "~/Notes/", "") res, _ := m.Update(tea.KeyMsg{Type: tea.KeyEsc}) if res != ResultCancel { t.Errorf("Esc gave %v, want ResultCancel", res) } } func TestOrdinaryKeysReturnNone(t *testing.T) { m := newModel() m.Open("New note", "~/Notes/", "") res, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'x'}}) if res != ResultNone { t.Errorf("rune key gave %v, want ResultNone", res) } } func TestValueTrimsSurroundingSpace(t *testing.T) { m := newModel() m.Open("New note", "~/Notes/", "") typeRunes(m, " spaced ") if m.Value() != "spaced" { t.Errorf("Value() = %q, want spaced", m.Value()) } } func TestOpenClearsPreviousError(t *testing.T) { m := newModel() m.Open("New note", "~/Notes/", "") m.SetError("Name taken") m.Open("New note", "~/Notes/", "") if m.err != "" { t.Errorf("err = %q after reopen, want empty", m.err) } } func TestTypingClearsError(t *testing.T) { m := newModel() m.Open("New note", "~/Notes/", "") m.SetError("Type a name first") typeRunes(m, "a") if m.err != "" { t.Errorf("err = %q after typing, want empty", m.err) } } func TestErrorSurvivesNonKeyMessages(t *testing.T) { m := newModel() m.Open("New note", "~/Notes/", "") m.SetError("Type a name first") m.Update(cursor.BlinkMsg{}) if m.err == "" { t.Error("err cleared by a cursor blink; only typing should clear it") } } func TestViewShowsTitleDirAndFooter(t *testing.T) { m := newModel() m.SetWidth(60) m.Open("New note", "~/Humdrum/Inbox/", "") out := m.View() for _, want := range []string{"New note", "~/Humdrum/Inbox/", "Enter", "Esc"} { if !strings.Contains(out, want) { t.Errorf("View() missing %q:\n%s", want, out) } } } func TestViewShowsErrorWhenSet(t *testing.T) { m := newModel() m.SetWidth(60) m.Open("New note", "~/Notes/", "") if strings.Contains(m.View(), "Name taken") { t.Fatal("View() shows the error before SetError") } m.SetError("Name taken") if !strings.Contains(m.View(), "Name taken") { t.Errorf("View() missing the error line:\n%s", m.View()) } } func TestViewFitsContentWidth(t *testing.T) { for _, w := range []int{40, 20, 12} { m := newModel() m.SetWidth(w) m.Open("New note", "~/Notes/", "") for i, ln := range strings.Split(m.View(), "\n") { if got := lipgloss.Width(ln); got > w { t.Errorf("width %d: line %d is %d cells, want <= %d", w, i, got, w) } } } }