Merge pull request #7116 from cli/even-more-prompts

New prompt testing utility
This commit is contained in:
Nate Smith 2023-03-10 10:43:41 -08:00 committed by GitHub
commit 661d962112
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 451 additions and 298 deletions

View file

@ -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,

View file

@ -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")
}

View file

@ -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, ","))
}
}

View file

@ -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)
}

View file

@ -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

View file

@ -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")