diff --git a/internal/prompter/prompter.go b/internal/prompter/prompter.go index 005478e86..a9cf77992 100644 --- a/internal/prompter/prompter.go +++ b/internal/prompter/prompter.go @@ -13,7 +13,7 @@ import ( //go:generate moq -rm -out prompter_mock.go . Prompter type Prompter interface { Select(string, string, []string) (int, error) - MultiSelect(string, string, []string) (int, error) + MultiSelect(string, string, []string) ([]string, error) Input(string, string) (string, error) InputHostname() (string, error) Password(string) (string, error) @@ -72,7 +72,7 @@ func (p *surveyPrompter) Select(message, defaultValue string, options []string) return } -func (p *surveyPrompter) MultiSelect(message, defaultValue string, options []string) (result int, err error) { +func (p *surveyPrompter) MultiSelect(message, defaultValue string, options []string) (result []string, err error) { q := &survey.MultiSelect{ Message: message, Options: options, diff --git a/internal/prompter/prompter_mock.go b/internal/prompter/prompter_mock.go index 999922978..859832450 100644 --- a/internal/prompter/prompter_mock.go +++ b/internal/prompter/prompter_mock.go @@ -35,7 +35,7 @@ var _ Prompter = &PrompterMock{} // MarkdownEditorFunc: func(s1 string, s2 string, b bool) (string, error) { // panic("mock out the MarkdownEditor method") // }, -// MultiSelectFunc: func(s1 string, s2 string, strings []string) (int, error) { +// MultiSelectFunc: func(s1 string, s2 string, strings []string) ([]string, error) { // panic("mock out the MultiSelect method") // }, // PasswordFunc: func(s string) (string, error) { @@ -70,7 +70,7 @@ type PrompterMock struct { MarkdownEditorFunc func(s1 string, s2 string, b bool) (string, error) // MultiSelectFunc mocks the MultiSelect method. - MultiSelectFunc func(s1 string, s2 string, strings []string) (int, error) + MultiSelectFunc func(s1 string, s2 string, strings []string) ([]string, error) // PasswordFunc mocks the Password method. PasswordFunc func(s string) (string, error) @@ -348,7 +348,7 @@ func (mock *PrompterMock) MarkdownEditorCalls() []struct { } // MultiSelect calls MultiSelectFunc. -func (mock *PrompterMock) MultiSelect(s1 string, s2 string, strings []string) (int, error) { +func (mock *PrompterMock) MultiSelect(s1 string, s2 string, strings []string) ([]string, error) { if mock.MultiSelectFunc == nil { panic("PrompterMock.MultiSelectFunc: method is nil but Prompter.MultiSelect was just called") } diff --git a/internal/prompter/test.go b/internal/prompter/test.go index 19d38dd25..a2d0b83ee 100644 --- a/internal/prompter/test.go +++ b/internal/prompter/test.go @@ -2,6 +2,7 @@ package prompter import ( "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -29,3 +30,258 @@ func NoSuchAnswerErr(answer string) error { func NoSuchPromptErr(prompt string) error { return fmt.Errorf("no such prompt '%s'", prompt) } + +type SelectStub struct { + Prompt string + ExpectedOpts []string + Fn func(string, string, []string) (int, error) +} + +type InputStub struct { + Prompt string + Fn func(string, string) (string, error) +} + +type ConfirmStub struct { + Prompt string + Fn func(string, bool) (bool, error) +} + +type MultiSelectStub struct { + Prompt string + ExpectedOpts []string + Fn func(string, string, []string) ([]string, error) +} + +type InputHostnameStub struct { + Fn func() (string, error) +} + +type PasswordStub struct { + Prompt string + Fn func(string) (string, error) +} + +type AuthTokenStub struct { + Fn func() (string, error) +} + +type ConfirmDeletionStub struct { + Prompt string + Fn func(string) error +} + +type MockPrompter struct { + PrompterMock + t *testing.T + SelectStubs []SelectStub + InputStubs []InputStub + ConfirmStubs []ConfirmStub + MultiSelectStubs []MultiSelectStub + InputHostnameStubs []InputHostnameStub + PasswordStubs []PasswordStub + AuthTokenStubs []AuthTokenStub + ConfirmDeletionStubs []ConfirmDeletionStub +} + +// TODO thread safety + +func NewMockPrompter(t *testing.T) *MockPrompter { + m := &MockPrompter{ + t: t, + SelectStubs: []SelectStub{}, + InputStubs: []InputStub{}, + ConfirmStubs: []ConfirmStub{}, + MultiSelectStubs: []MultiSelectStub{}, + InputHostnameStubs: []InputHostnameStub{}, + PasswordStubs: []PasswordStub{}, + AuthTokenStubs: []AuthTokenStub{}, + ConfirmDeletionStubs: []ConfirmDeletionStub{}, + } + + t.Cleanup(m.Verify) + + m.SelectFunc = func(p, d string, opts []string) (int, error) { + var s SelectStub + + if len(m.SelectStubs) > 0 { + s = m.SelectStubs[0] + m.SelectStubs = m.SelectStubs[1:len(m.SelectStubs)] + } else { + return -1, NoSuchPromptErr(p) + } + + if s.Prompt != p { + return -1, NoSuchPromptErr(p) + } + + AssertOptions(m.t, s.ExpectedOpts, opts) + + return s.Fn(p, d, opts) + } + + m.MultiSelectFunc = func(p, d string, opts []string) ([]string, error) { + var s MultiSelectStub + if len(m.SelectStubs) > 0 { + s = m.MultiSelectStubs[0] + m.SelectStubs = m.SelectStubs[1:len(m.SelectStubs)] + } else { + return []string{}, NoSuchPromptErr(p) + } + + if s.Prompt != p { + return []string{}, NoSuchPromptErr(p) + } + + AssertOptions(m.t, s.ExpectedOpts, opts) + + return s.Fn(p, d, opts) + } + + m.InputFunc = func(p, d string) (string, error) { + var s InputStub + + if len(m.InputStubs) > 0 { + s = m.InputStubs[0] + m.InputStubs = m.InputStubs[1:len(m.InputStubs)] + } else { + return "", NoSuchPromptErr(p) + } + + if s.Prompt != p { + return "", NoSuchPromptErr(p) + } + + return s.Fn(p, d) + } + + m.ConfirmFunc = func(p string, d bool) (bool, error) { + var s ConfirmStub + + if len(m.ConfirmStubs) > 0 { + s = m.ConfirmStubs[0] + m.ConfirmStubs = m.ConfirmStubs[1:len(m.ConfirmStubs)] + } else { + return false, NoSuchPromptErr(p) + } + + if s.Prompt != p { + return false, NoSuchPromptErr(p) + } + + return s.Fn(p, d) + } + + m.InputHostnameFunc = func() (string, error) { + var s InputHostnameStub + + if len(m.InputHostnameStubs) > 0 { + s = m.InputHostnameStubs[0] + m.InputHostnameStubs = m.InputHostnameStubs[1:len(m.InputHostnameStubs)] + } else { + return "", NoSuchPromptErr("InputHostname") + } + + return s.Fn() + } + + m.PasswordFunc = func(p string) (string, error) { + var s PasswordStub + + if len(m.PasswordStubs) > 0 { + s = m.PasswordStubs[0] + m.PasswordStubs = m.PasswordStubs[1:len(m.PasswordStubs)] + } else { + return "", NoSuchPromptErr(p) + } + + if s.Prompt != p { + return "", NoSuchPromptErr(p) + } + + return s.Fn(p) + } + + m.AuthTokenFunc = func() (string, error) { + var s AuthTokenStub + + if len(m.AuthTokenStubs) > 0 { + s = m.AuthTokenStubs[0] + m.AuthTokenStubs = m.AuthTokenStubs[1:len(m.AuthTokenStubs)] + } else { + return "", NoSuchPromptErr("AuthToken") + } + + return s.Fn() + } + + m.ConfirmDeletionFunc = func(p string) error { + var s ConfirmDeletionStub + + if len(m.ConfirmDeletionStubs) > 0 { + s = m.ConfirmDeletionStubs[0] + m.ConfirmDeletionStubs = m.ConfirmDeletionStubs[1:len(m.ConfirmDeletionStubs)] + } else { + return NoSuchPromptErr("ConfirmDeletion") + } + + return s.Fn(p) + } + + // TODO MarkdownEditor(string, string, bool) (string, error) + + return m +} + +func (m *MockPrompter) RegisterSelect(prompt string, opts []string, stub func(_, _ string, _ []string) (int, error)) { + m.SelectStubs = append(m.SelectStubs, SelectStub{ + Prompt: prompt, + ExpectedOpts: opts, + Fn: stub}) +} + +func (m *MockPrompter) RegisterMultiSelect(prompt string, opts []string, stub func(_, _ string, _ []string) ([]string, error)) { + m.MultiSelectStubs = append(m.MultiSelectStubs, MultiSelectStub{ + Prompt: prompt, + ExpectedOpts: opts, + Fn: stub}) +} + +func (m *MockPrompter) RegisterInput(prompt string, stub func(_, _ string) (string, error)) { + m.InputStubs = append(m.InputStubs, InputStub{Prompt: prompt, Fn: stub}) +} + +func (m *MockPrompter) RegisterConfirm(prompt string, stub func(_ string, _ bool) (bool, error)) { + m.ConfirmStubs = append(m.ConfirmStubs, ConfirmStub{Prompt: prompt, Fn: stub}) +} + +func (m *MockPrompter) RegisterInputHostname(stub func() (string, error)) { + m.InputHostnameStubs = append(m.InputHostnameStubs, InputHostnameStub{Fn: stub}) +} + +func (m *MockPrompter) RegisterPassword(prompt string, stub func(string) (string, error)) { + m.PasswordStubs = append(m.PasswordStubs, PasswordStub{Fn: stub}) +} + +func (m *MockPrompter) RegisterConfirmDeletion(prompt string, stub func(string) error) { + m.ConfirmDeletionStubs = append(m.ConfirmDeletionStubs, ConfirmDeletionStub{Prompt: prompt, Fn: stub}) +} + +func (m *MockPrompter) Verify() { + errs := []string{} + if len(m.SelectStubs) > 0 { + errs = append(errs, "Select") + } + if len(m.InputStubs) > 0 { + errs = append(errs, "Input") + } + if len(m.ConfirmStubs) > 0 { + errs = append(errs, "Confirm") + } + // TODO other prompt types + + if len(errs) > 0 { + m.t.Helper() + m.t.Errorf("%d unmatched calls to %s", len(errs), strings.Join(errs, ",")) + } +} diff --git a/pkg/cmd/release/create/create_test.go b/pkg/cmd/release/create/create_test.go index ee5c1caac..e1b5f3341 100644 --- a/pkg/cmd/release/create/create_test.go +++ b/pkg/cmd/release/create/create_test.go @@ -926,7 +926,7 @@ func Test_createRun_interactive(t *testing.T) { tests := []struct { name string httpStubs func(*httpmock.Registry) - prompterStubs func(*testing.T, *prompter.PrompterMock) + prompterStubs func(*testing.T, *prompter.MockPrompter) runStubs func(*run.CommandStubber) opts *CreateOptions wantParams map[string]interface{} @@ -936,37 +936,28 @@ func Test_createRun_interactive(t *testing.T) { { name: "create a release from existing tag", opts: &CreateOptions{}, - prompterStubs: func(t *testing.T, pm *prompter.PrompterMock) { - pm.SelectFunc = func(p, d string, opts []string) (int, error) { - switch p { - case "Choose a tag": - prompter.AssertOptions(t, []string{"v1.2.3", "v1.2.2", "v1.0.0", "v0.1.2", "Create a new tag"}, opts) + prompterStubs: func(t *testing.T, pm *prompter.MockPrompter) { + pm.RegisterSelect("Choose a tag", + []string{"v1.2.3", "v1.2.2", "v1.0.0", "v0.1.2", "Create a new tag"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "v1.2.3") - case "Release notes": - prompter.AssertOptions(t, []string{"Write my own", "Write using generated notes as template", "Leave blank"}, opts) + }) + pm.RegisterSelect("Release notes", + []string{"Write my own", "Write using generated notes as template", "Leave blank"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Leave blank") - case "Submit?": - prompter.AssertOptions(t, []string{"Publish release", "Save as draft", "Cancel"}, opts) + }) + pm.RegisterSelect("Submit?", + []string{"Publish release", "Save as draft", "Cancel"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Publish release") - default: - return -1, prompter.NoSuchPromptErr(p) - } - } - pm.InputFunc = func(p, d string) (string, error) { - if p == "Title (optional)" { - return d, nil - } - - return "", prompter.NoSuchPromptErr(p) - } - pm.ConfirmFunc = func(p string, d bool) (bool, error) { - switch p { - case "Is this a prerelease?": - return false, nil - default: - return false, prompter.NoSuchPromptErr(p) - } - } + }) + pm.RegisterInput("Title (optional)", func(_, d string) (string, error) { + return d, nil + }) + pm.RegisterConfirm("Is this a prerelease?", func(_ string, _ bool) (bool, error) { + return false, nil + }) }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git tag --list`, 1, "") @@ -991,40 +982,31 @@ func Test_createRun_interactive(t *testing.T) { { name: "create a release from new tag", opts: &CreateOptions{}, - prompterStubs: func(t *testing.T, pm *prompter.PrompterMock) { - pm.SelectFunc = func(p, d string, opts []string) (int, error) { - switch p { - case "Choose a tag": - prompter.AssertOptions(t, []string{"v1.2.2", "v1.0.0", "v0.1.2", "Create a new tag"}, opts) + prompterStubs: func(t *testing.T, pm *prompter.MockPrompter) { + pm.RegisterSelect("Choose a tag", + []string{"v1.2.2", "v1.0.0", "v0.1.2", "Create a new tag"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Create a new tag") - case "Release notes": - prompter.AssertOptions(t, []string{"Write my own", "Write using generated notes as template", "Leave blank"}, opts) + }) + pm.RegisterSelect("Release notes", + []string{"Write my own", "Write using generated notes as template", "Leave blank"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Leave blank") - case "Submit?": - prompter.AssertOptions(t, []string{"Publish release", "Save as draft", "Cancel"}, opts) + }) + pm.RegisterSelect("Submit?", + []string{"Publish release", "Save as draft", "Cancel"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Publish release") - default: - return -1, prompter.NoSuchPromptErr(p) - } - } - pm.InputFunc = func(p, d string) (string, error) { - switch p { - case "Tag name": - return "v1.2.3", nil - case "Title (optional)": - return d, nil - default: - return "", prompter.NoSuchPromptErr(p) - } - } - pm.ConfirmFunc = func(p string, d bool) (bool, error) { - switch p { - case "Is this a prerelease?": - return false, nil - default: - return false, prompter.NoSuchPromptErr(p) - } - } + }) + pm.RegisterInput("Tag name", func(_, d string) (string, error) { + return "v1.2.3", nil + }) + pm.RegisterInput("Title (optional)", func(_, d string) (string, error) { + return d, nil + }) + pm.RegisterConfirm("Is this a prerelease?", func(_ string, _ bool) (bool, error) { + return false, nil + }) }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git tag --list`, 1, "") @@ -1051,35 +1033,23 @@ func Test_createRun_interactive(t *testing.T) { opts: &CreateOptions{ TagName: "v1.2.3", }, - prompterStubs: func(t *testing.T, pm *prompter.PrompterMock) { - pm.SelectFunc = func(p, d string, opts []string) (int, error) { - switch p { - case "Release notes": - prompter.AssertOptions(t, []string{"Write my own", "Write using generated notes as template", "Leave blank"}, opts) + prompterStubs: func(t *testing.T, pm *prompter.MockPrompter) { + pm.RegisterSelect("Release notes", + []string{"Write my own", "Write using generated notes as template", "Leave blank"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Write using generated notes as template") - case "Submit?": - prompter.AssertOptions(t, []string{"Publish release", "Save as draft", "Cancel"}, opts) + }) + pm.RegisterSelect("Submit?", + []string{"Publish release", "Save as draft", "Cancel"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Publish release") - default: - return -1, prompter.NoSuchPromptErr(p) - } - } - pm.InputFunc = func(p, d string) (string, error) { - switch p { - case "Title (optional)": - return d, nil - default: - return "", prompter.NoSuchPromptErr(p) - } - } - pm.ConfirmFunc = func(p string, d bool) (bool, error) { - switch p { - case "Is this a prerelease?": - return false, nil - default: - return false, prompter.NoSuchPromptErr(p) - } - } + }) + pm.RegisterInput("Title (optional)", func(_, d string) (string, error) { + return d, nil + }) + pm.RegisterConfirm("Is this a prerelease?", func(_ string, _ bool) (bool, error) { + return false, nil + }) }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git tag --list`, 1, "") @@ -1111,35 +1081,23 @@ func Test_createRun_interactive(t *testing.T) { opts: &CreateOptions{ TagName: "v1.2.3", }, - prompterStubs: func(t *testing.T, pm *prompter.PrompterMock) { - pm.SelectFunc = func(p, d string, opts []string) (int, error) { - switch p { - case "Release notes": - prompter.AssertOptions(t, []string{"Write my own", "Write using commit log as template", "Leave blank"}, opts) + prompterStubs: func(t *testing.T, pm *prompter.MockPrompter) { + pm.RegisterSelect("Release notes", + []string{"Write my own", "Write using commit log as template", "Leave blank"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Write using commit log as template") - case "Submit?": - prompter.AssertOptions(t, []string{"Publish release", "Save as draft", "Cancel"}, opts) + }) + pm.RegisterSelect("Submit?", + []string{"Publish release", "Save as draft", "Cancel"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Publish release") - default: - return -1, prompter.NoSuchPromptErr(p) - } - } - pm.InputFunc = func(p, d string) (string, error) { - switch p { - case "Title (optional)": - return d, nil - default: - return "", prompter.NoSuchPromptErr(p) - } - } - pm.ConfirmFunc = func(p string, d bool) (bool, error) { - switch p { - case "Is this a prerelease?": - return false, nil - default: - return false, prompter.NoSuchPromptErr(p) - } - } + }) + pm.RegisterInput("Title (optional)", func(_, d string) (string, error) { + return d, nil + }) + pm.RegisterConfirm("Is this a prerelease?", func(_ string, _ bool) (bool, error) { + return false, nil + }) }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git tag --list`, 1, "") @@ -1169,35 +1127,23 @@ func Test_createRun_interactive(t *testing.T) { opts: &CreateOptions{ TagName: "v1.2.3", }, - prompterStubs: func(t *testing.T, pm *prompter.PrompterMock) { - pm.SelectFunc = func(p, d string, opts []string) (int, error) { - switch p { - case "Release notes": - prompter.AssertOptions(t, []string{"Write my own", "Write using git tag message as template", "Leave blank"}, opts) + prompterStubs: func(t *testing.T, pm *prompter.MockPrompter) { + pm.RegisterSelect("Release notes", + []string{"Write my own", "Write using git tag message as template", "Leave blank"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Write using git tag message as template") - case "Submit?": - prompter.AssertOptions(t, []string{"Publish release", "Save as draft", "Cancel"}, opts) + }) + pm.RegisterSelect("Submit?", + []string{"Publish release", "Save as draft", "Cancel"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Publish release") - default: - return -1, prompter.NoSuchPromptErr(p) - } - } - pm.InputFunc = func(p, d string) (string, error) { - switch p { - case "Title (optional)": - return d, nil - default: - return "", prompter.NoSuchPromptErr(p) - } - } - pm.ConfirmFunc = func(p string, d bool) (bool, error) { - switch p { - case "Is this a prerelease?": - return false, nil - default: - return false, prompter.NoSuchPromptErr(p) - } - } + }) + pm.RegisterInput("Title (optional)", func(_, d string) (string, error) { + return d, nil + }) + pm.RegisterConfirm("Is this a prerelease?", func(_ string, _ bool) (bool, error) { + return false, nil + }) }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git tag --list`, 0, "hello from annotated tag") @@ -1243,35 +1189,23 @@ func Test_createRun_interactive(t *testing.T) { TagName: "v1.2.3", Target: "main", }, - prompterStubs: func(t *testing.T, pm *prompter.PrompterMock) { - pm.SelectFunc = func(p, d string, opts []string) (int, error) { - switch p { - case "Release notes": - prompter.AssertOptions(t, []string{"Write my own", "Write using generated notes as template", "Write using git tag message as template", "Leave blank"}, opts) + prompterStubs: func(t *testing.T, pm *prompter.MockPrompter) { + pm.RegisterSelect("Release notes", + []string{"Write my own", "Write using generated notes as template", "Write using git tag message as template", "Leave blank"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Leave blank") - case "Submit?": - prompter.AssertOptions(t, []string{"Publish release", "Save as draft", "Cancel"}, opts) + }) + pm.RegisterSelect("Submit?", + []string{"Publish release", "Save as draft", "Cancel"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Publish release") - default: - return -1, prompter.NoSuchPromptErr(p) - } - } - pm.InputFunc = func(p, d string) (string, error) { - switch p { - case "Title (optional)": - return d, nil - default: - return "", prompter.NoSuchPromptErr(p) - } - } - pm.ConfirmFunc = func(p string, d bool) (bool, error) { - switch p { - case "Is this a prerelease?": - return false, nil - default: - return false, prompter.NoSuchPromptErr(p) - } - } + }) + pm.RegisterInput("Title (optional)", func(_, d string) (string, error) { + return d, nil + }) + pm.RegisterConfirm("Is this a prerelease?", func(_ string, _ bool) (bool, error) { + return false, nil + }) }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git tag --list`, 0, "tag exists") @@ -1303,35 +1237,23 @@ func Test_createRun_interactive(t *testing.T) { TagName: "v1.2.3", NotesStartTag: "v1.1.0", }, - prompterStubs: func(t *testing.T, pm *prompter.PrompterMock) { - pm.SelectFunc = func(p, d string, opts []string) (int, error) { - switch p { - case "Release notes": - prompter.AssertOptions(t, []string{"Write my own", "Write using generated notes as template", "Leave blank"}, opts) + prompterStubs: func(t *testing.T, pm *prompter.MockPrompter) { + pm.RegisterSelect("Release notes", + []string{"Write my own", "Write using generated notes as template", "Leave blank"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Write using generated notes as template") - case "Submit?": - prompter.AssertOptions(t, []string{"Publish release", "Save as draft", "Cancel"}, opts) + }) + pm.RegisterSelect("Submit?", + []string{"Publish release", "Save as draft", "Cancel"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Publish release") - default: - return -1, prompter.NoSuchPromptErr(p) - } - } - pm.InputFunc = func(p, d string) (string, error) { - switch p { - case "Title (optional)": - return d, nil - default: - return "", prompter.NoSuchPromptErr(p) - } - } - pm.ConfirmFunc = func(p string, d bool) (bool, error) { - switch p { - case "Is this a prerelease?": - return false, nil - default: - return false, prompter.NoSuchPromptErr(p) - } - } + }) + pm.RegisterInput("Title (optional)", func(_, d string) (string, error) { + return d, nil + }) + pm.RegisterConfirm("Is this a prerelease?", func(_ string, _ bool) (bool, error) { + return false, nil + }) }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git tag --list`, 1, "") @@ -1369,35 +1291,23 @@ func Test_createRun_interactive(t *testing.T) { TagName: "v1.2.3", NotesStartTag: "v1.1.0", }, - prompterStubs: func(t *testing.T, pm *prompter.PrompterMock) { - pm.SelectFunc = func(p, d string, opts []string) (int, error) { - switch p { - case "Release notes": - prompter.AssertOptions(t, []string{"Write my own", "Write using commit log as template", "Leave blank"}, opts) + prompterStubs: func(t *testing.T, pm *prompter.MockPrompter) { + pm.RegisterSelect("Release notes", + []string{"Write my own", "Write using commit log as template", "Leave blank"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Write using commit log as template") - case "Submit?": - prompter.AssertOptions(t, []string{"Publish release", "Save as draft", "Cancel"}, opts) + }) + pm.RegisterSelect("Submit?", + []string{"Publish release", "Save as draft", "Cancel"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Publish release") - default: - return -1, prompter.NoSuchPromptErr(p) - } - } - pm.InputFunc = func(p, d string) (string, error) { - switch p { - case "Title (optional)": - return d, nil - default: - return "", prompter.NoSuchPromptErr(p) - } - } - pm.ConfirmFunc = func(p string, d bool) (bool, error) { - switch p { - case "Is this a prerelease?": - return false, nil - default: - return false, prompter.NoSuchPromptErr(p) - } - } + }) + pm.RegisterInput("Title (optional)", func(_, d string) (string, error) { + return d, nil + }) + pm.RegisterConfirm("Is this a prerelease?", func(_ string, _ bool) (bool, error) { + return false, nil + }) }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git tag --list`, 1, "") @@ -1427,35 +1337,25 @@ func Test_createRun_interactive(t *testing.T) { TagName: "v1.2.3", VerifyTag: true, }, - prompterStubs: func(t *testing.T, pm *prompter.PrompterMock) { - pm.SelectFunc = func(p, d string, opts []string) (int, error) { - switch p { - case "Release notes": - prompter.AssertOptions(t, []string{"Write my own", "Write using generated notes as template", "Write using git tag message as template", "Leave blank"}, opts) + prompterStubs: func(t *testing.T, pm *prompter.MockPrompter) { + pm.RegisterSelect("Release notes", + []string{"Write my own", "Write using generated notes as template", "Write using git tag message as template", "Leave blank"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Leave blank") - case "Submit?": - prompter.AssertOptions(t, []string{"Publish release", "Save as draft", "Cancel"}, opts) + }) + pm.RegisterSelect("Submit?", + []string{"Publish release", "Save as draft", "Cancel"}, + func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "Publish release") - default: - return -1, prompter.NoSuchPromptErr(p) - } - } - pm.InputFunc = func(p, d string) (string, error) { - switch p { - case "Title (optional)": - return d, nil - default: - return "", prompter.NoSuchPromptErr(p) - } - } - pm.ConfirmFunc = func(p string, d bool) (bool, error) { - switch p { - case "Is this a prerelease?": - return false, nil - default: - return false, prompter.NoSuchPromptErr(p) - } - } + }) + + pm.RegisterInput("Title (optional)", func(_, d string) (string, error) { + return d, nil + }) + + pm.RegisterConfirm("Is this a prerelease?", func(_ string, _ bool) (bool, error) { + return false, nil + }) }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git tag --list`, 0, "tag exists") @@ -1524,7 +1424,7 @@ func Test_createRun_interactive(t *testing.T) { tt.opts.GitClient = &git.Client{GitPath: "some/path/git"} t.Run(tt.name, func(t *testing.T) { - pm := &prompter.PrompterMock{} + pm := prompter.NewMockPrompter(t) if tt.prompterStubs != nil { tt.prompterStubs(t, pm) } diff --git a/pkg/cmd/repo/rename/rename.go b/pkg/cmd/repo/rename/rename.go index f979e8101..f7774b734 100644 --- a/pkg/cmd/repo/rename/rename.go +++ b/pkg/cmd/repo/rename/rename.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" - "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" ghContext "github.com/cli/cli/v2/context" @@ -14,14 +13,19 @@ import ( "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/spf13/cobra" ) +type iprompter interface { + Input(string, string) (string, error) + Confirm(string, bool) (bool, error) +} + type RenameOptions struct { HttpClient func() (*http.Client, error) GitClient *git.Client IO *iostreams.IOStreams + Prompter iprompter Config func() (config.Config, error) BaseRepo func() (ghrepo.Interface, error) Remotes func() (ghContext.Remotes, error) @@ -37,6 +41,7 @@ func NewCmdRename(f *cmdutil.Factory, runf func(*RenameOptions) error) *cobra.Co GitClient: f.GitClient, Remotes: f.Remotes, Config: f.Config, + Prompter: f.Prompter, } var confirm bool @@ -95,28 +100,17 @@ func renameRun(opts *RenameOptions) error { } if newRepoName == "" { - //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter - err = prompt.SurveyAskOne( - &survey.Input{ - Message: fmt.Sprintf("Rename %s to: ", ghrepo.FullName(currRepo)), - }, - &newRepoName, - ) - if err != nil { + if newRepoName, err = opts.Prompter.Input(fmt.Sprintf( + "Rename %s to:", ghrepo.FullName(currRepo)), ""); err != nil { return err } } if opts.DoConfirm { var confirmed bool - p := &survey.Confirm{ - Message: fmt.Sprintf("Rename %s to %s?", ghrepo.FullName(currRepo), newRepoName), - Default: false, - } - //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter - err = prompt.SurveyAskOne(p, &confirmed) - if err != nil { - return fmt.Errorf("failed to prompt: %w", err) + if confirmed, err = opts.Prompter.Confirm(fmt.Sprintf( + "Rename %s to %s?", ghrepo.FullName(currRepo), newRepoName), false); err != nil { + return err } if !confirmed { return nil diff --git a/pkg/cmd/repo/rename/rename_test.go b/pkg/cmd/repo/rename/rename_test.go index 611e3dcda..68499b2fa 100644 --- a/pkg/cmd/repo/rename/rename_test.go +++ b/pkg/cmd/repo/rename/rename_test.go @@ -9,11 +9,11 @@ import ( "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/internal/run" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/google/shlex" "github.com/stretchr/testify/assert" ) @@ -105,20 +105,21 @@ func TestNewCmdRename(t *testing.T) { func TestRenameRun(t *testing.T) { testCases := []struct { - name string - opts RenameOptions - httpStubs func(*httpmock.Registry) - execStubs func(*run.CommandStubber) - askStubs func(*prompt.AskStubber) - wantOut string - tty bool + name string + opts RenameOptions + httpStubs func(*httpmock.Registry) + execStubs func(*run.CommandStubber) + promptStubs func(*prompter.MockPrompter) + wantOut string + tty bool }{ { name: "none argument", wantOut: "✓ Renamed repository OWNER/NEW_REPO\n✓ Updated the \"origin\" remote\n", - askStubs: func(q *prompt.AskStubber) { - //nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt - q.StubOne("NEW_REPO") + promptStubs: func(pm *prompter.MockPrompter) { + pm.RegisterInput("Rename OWNER/REPO to:", func(_, _ string) (string, error) { + return "NEW_REPO", nil + }) }, httpStubs: func(reg *httpmock.Registry) { reg.Register( @@ -136,9 +137,10 @@ func TestRenameRun(t *testing.T) { HasRepoOverride: true, }, wantOut: "✓ Renamed repository OWNER/NEW_REPO\n", - askStubs: func(q *prompt.AskStubber) { - //nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt - q.StubOne("NEW_REPO") + promptStubs: func(pm *prompter.MockPrompter) { + pm.RegisterInput("Rename OWNER/REPO to:", func(_, _ string) (string, error) { + return "NEW_REPO", nil + }) }, httpStubs: func(reg *httpmock.Registry) { reg.Register( @@ -185,9 +187,10 @@ func TestRenameRun(t *testing.T) { DoConfirm: true, }, wantOut: "✓ Renamed repository OWNER/NEW_REPO\n✓ Updated the \"origin\" remote\n", - askStubs: func(q *prompt.AskStubber) { - //nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt - q.StubOne(true) + promptStubs: func(pm *prompter.MockPrompter) { + pm.RegisterConfirm("Rename OWNER/REPO to NEW_REPO?", func(_ string, _ bool) (bool, error) { + return true, nil + }) }, httpStubs: func(reg *httpmock.Registry) { reg.Register( @@ -206,20 +209,20 @@ func TestRenameRun(t *testing.T) { newRepoSelector: "NEW_REPO", DoConfirm: true, }, - askStubs: func(q *prompt.AskStubber) { - //nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt - q.StubOne(false) + promptStubs: func(pm *prompter.MockPrompter) { + pm.RegisterConfirm("Rename OWNER/REPO to NEW_REPO?", func(_ string, _ bool) (bool, error) { + return false, nil + }) }, wantOut: "", }, } for _, tt := range testCases { - //nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber - q, teardown := prompt.InitAskStubber() - defer teardown() - if tt.askStubs != nil { - tt.askStubs(q) + pm := prompter.NewMockPrompter(t) + tt.opts.Prompter = pm + if tt.promptStubs != nil { + tt.promptStubs(pm) } repo, _ := ghrepo.FromFullName("OWNER/REPO")