Vectrum
Sign In
143 lines · 4.2 KB · Go
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")
	}
}