package app import ( "os" "path/filepath" "strings" "testing" tea "github.com/charmbracelet/bubbletea" ) func typeInto(a *App, s string) { for _, r := range s { a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{r}}) } } func TestCtrlNOpensNamePrompt(t *testing.T) { a := newApp() a.Update(tea.WindowSizeMsg{Width: 100, Height: 20}) a.Update(tea.KeyMsg{Type: tea.KeyCtrlN}) if a.mode != ModeNamePrompt { t.Fatalf("mode = %d, want ModeNamePrompt", a.mode) } if a.dialog.Title() != "New note" { t.Errorf("dialog title = %q, want 'New note'", a.dialog.Title()) } } func TestNamePromptShowsTargetDir(t *testing.T) { dir := t.TempDir() a := newApp() a.Update(tea.WindowSizeMsg{Width: 100, Height: 20}) a.openNamePrompt(namingNew, dir, "") if !strings.Contains(a.View(), filepath.Base(dir)) { t.Errorf("View() does not show the target dir %q:\n%s", dir, a.View()) } } func TestEscCancelsBackToPreviousMode(t *testing.T) { dir := t.TempDir() p := filepath.Join(dir, "open.md") os.WriteFile(p, []byte("body"), 0o644) a := newApp() a.Update(tea.WindowSizeMsg{Width: 100, Height: 20}) a.Load(p) a.Update(tea.KeyMsg{Type: tea.KeyCtrlN}) a.Update(tea.KeyMsg{Type: tea.KeyEsc}) if a.mode != ModeEditor { t.Errorf("mode = %d after Esc, want ModeEditor", a.mode) } if a.path != p { t.Errorf("path = %q after Esc, want the file we were editing (%q)", a.path, p) } entries, _ := os.ReadDir(dir) if len(entries) != 1 { t.Errorf("Esc wrote %d entries to disk, want 1 (the pre-existing file)", len(entries)) } } func TestDirtyBufferConfirmsDiscardBeforePrompt(t *testing.T) { a := newApp() a.Update(tea.WindowSizeMsg{Width: 100, Height: 20}) a.editor.SetContent([]byte("x")) a.editor.Dirty = true a.Update(tea.KeyMsg{Type: tea.KeyCtrlN}) if a.mode == ModeNamePrompt { t.Fatal("first Ctrl+N on a dirty buffer opened the prompt; want a discard confirmation") } a.Update(tea.KeyMsg{Type: tea.KeyCtrlN}) if a.mode != ModeNamePrompt { t.Errorf("second Ctrl+N: mode = %d, want ModeNamePrompt", a.mode) } } func TestPickerCtrlNPrefillsQuery(t *testing.T) { dir := t.TempDir() a := newApp() a.Update(tea.WindowSizeMsg{Width: 100, Height: 20}) if err := a.StartPickerIn(dir); err != nil { t.Fatal(err) } typeInto(a, "draft") a.Update(tea.KeyMsg{Type: tea.KeyCtrlN}) if a.mode != ModeNamePrompt { t.Fatalf("mode = %d, want ModeNamePrompt", a.mode) } if a.dialog.Value() != "draft" { t.Errorf("dialog prefill = %q, want draft", a.dialog.Value()) } } func TestF2OpensRenamePrefilled(t *testing.T) { dir := t.TempDir() p := filepath.Join(dir, "before.md") os.WriteFile(p, []byte("body"), 0o644) a := newApp() a.Update(tea.WindowSizeMsg{Width: 100, Height: 20}) a.Load(p) a.Update(tea.KeyMsg{Type: tea.KeyF2}) if a.mode != ModeNamePrompt { t.Fatalf("mode = %d, want ModeNamePrompt", a.mode) } if a.dialog.Title() != "Rename" { t.Errorf("dialog title = %q, want Rename", a.dialog.Title()) } if a.dialog.Value() != "before" { t.Errorf("dialog prefill = %q, want before", a.dialog.Value()) } } func TestReentrantCtrlNDoesNotTrapTheDialog(t *testing.T) { dir := t.TempDir() a := newApp() a.Update(tea.WindowSizeMsg{Width: 100, Height: 20}) a.saveDir = dir a.Update(tea.KeyMsg{Type: tea.KeyCtrlN}) a.Update(tea.KeyMsg{Type: tea.KeyCtrlN}) // re-press while the prompt is open if a.prevMode == ModeNamePrompt { t.Fatal("prevMode became ModeNamePrompt; Esc would be a no-op") } a.Update(tea.KeyMsg{Type: tea.KeyEsc}) if a.mode == ModeNamePrompt { t.Error("Esc left the app in ModeNamePrompt; the dialog is inescapable") } } func TestDirLabelDoesNotCollapseSiblingSharingHomePrefix(t *testing.T) { home, err := os.UserHomeDir() if err != nil || home == "" { t.Skip("no $HOME to test against") } a := newApp() sibling := home + "bar" // shares home's string prefix but is not under it if got := a.dirLabel(sibling); strings.HasPrefix(got, "~") { t.Errorf("dirLabel(%q) = %q, want no ~ collapse for a sibling directory", sibling, got) } }