package ui import ( "bufio" "bytes" "context" "strings" "testing" "github.com/johannSo/codey/internal/ask" ) func askOn(t *testing.T, input string, req ask.Request) (ask.Response, string) { t.Helper() var out bytes.Buffer a := &Asker{In: bufio.NewScanner(strings.NewReader(input)), Out: &out} resp, err := a.Prompt(context.Background(), req) if err != nil { t.Fatalf("Prompt: %v", err) } return resp, out.String() } func storage() ask.Request { return ask.Request{Questions: []ask.Question{{ Header: "Storage", Question: "How should sessions be stored?", Options: []ask.Option{ {Label: "Postgres", Description: "already running"}, {Label: "SQLite"}, }, }}} } func TestLineAskerTakesANumberedAnswer(t *testing.T) { got, printed := askOn(t, "2\n", storage()) if got.Dismissed || len(got.Answers) != 1 || got.Answers[0].Chosen[0] != "SQLite" { t.Fatalf("response = %+v", got) } for _, want := range []string{"Storage", "How should sessions be stored?", "1.", "Postgres", "already running"} { if !strings.Contains(printed, want) { t.Errorf("the prompt never showed %q:\n%s", want, printed) } } } // Anything that is not a number is the user answering in their own words, // which every question allows. func TestLineAskerTakesWordsAsTheAnswer(t *testing.T) { got, _ := askOn(t, "neither, keep them in memory\n", storage()) if got.Answers[0].Other != "neither, keep them in memory" { t.Fatalf("Other = %q", got.Answers[0].Other) } if len(got.Answers[0].Chosen) != 0 { t.Error("typed words are not a chosen option") } } func TestLineAskerRefusesANumberThatIsNotAnOption(t *testing.T) { got, printed := askOn(t, "7\n1\n", storage()) if !strings.Contains(printed, "no option 7") { t.Errorf("expected the mistake to be named:\n%s", printed) } if got.Answers[0].Chosen[0] != "Postgres" { t.Fatalf("response = %+v, want the answer given second", got) } } func TestLineAskerTakesSeveralWhenTheQuestionAllowsIt(t *testing.T) { req := ask.Request{Questions: []ask.Question{{ Header: "Scope", Question: "What should it cover?", MultiSelect: true, Options: []ask.Option{{Label: "the CLI"}, {Label: "the TUI"}, {Label: "the docs"}}, }}} got, _ := askOn(t, "1,3\n", req) if want := "the CLI,the docs"; strings.Join(got.Answers[0].Chosen, ",") != want { t.Fatalf("chosen = %v, want %s", got.Answers[0].Chosen, want) } } // One answer means one. Two numbers is a misunderstanding worth correcting // rather than a free-text answer to file. func TestLineAskerRefusesSeveralAnswersToOneQuestion(t *testing.T) { got, printed := askOn(t, "1,2\n2\n", storage()) if !strings.Contains(printed, "takes one answer") { t.Errorf("expected a correction:\n%s", printed) } if got.Answers[0].Chosen[0] != "SQLite" { t.Fatalf("response = %+v", got) } } // An empty line is a skip, and a skipped question abandons the request: half // the answers reported as if they were all of them is worse than none. func TestLineAskerSkipOnEmptyLine(t *testing.T) { req := storage() req.Questions = append(req.Questions, ask.Question{ Header: "Tests", Question: "How far?", Options: []ask.Option{{Label: "unit"}, {Label: "e2e"}}, }) got, _ := askOn(t, "1\n\n", req) if !got.Dismissed { t.Fatalf("response = %+v, want a dismissal", got) } if len(got.Answers) != 0 { t.Error("a dismissed request carries no answers") } } // Nobody is there: a piped run reaches EOF, and the tool turns that into an // instruction to decide alone rather than waiting forever. func TestLineAskerClosedStdinIsNotAnAnswer(t *testing.T) { got, _ := askOn(t, "", storage()) if !got.Dismissed { t.Fatalf("response = %+v, want a dismissal", got) } } func TestLineAskerAsksEveryQuestion(t *testing.T) { req := storage() req.Questions = append(req.Questions, ask.Question{ Header: "Tests", Question: "How far should the tests go?", Options: []ask.Option{{Label: "unit"}, {Label: "e2e"}}, }) got, printed := askOn(t, "1\n2\n", req) if len(got.Answers) != 2 { t.Fatalf("got %d answers, want one per question", len(got.Answers)) } if got.Answers[1].Chosen[0] != "e2e" { t.Errorf("second answer = %v", got.Answers[1].Chosen) } if !strings.Contains(printed, "How far should the tests go?") { t.Error("the second question was never put") } }