Repository: cli/cli
Pull request #7859 (2023-08-17 21:00:43)

switch to prompter in pr shared code
OKAY FINALLY the thing I put off the longest; a port of the metadata prompting shared by issue/pr create/edit to the new prompter.

Please note that there **were no tests** for the survey-based metadata **editing**. I did a thorough manual test but did not have the time to add new tests.

There were tests for adding metadata in **creating** issues/PRs and those have been updated accordingly.

- use prompter in shared editable code
- use MultiSelect for metadata survey in pr, issue create
- prompter for reviewers
- prompter for assignees
- use prompter for labels
- prompter for projects
- prompter for milestone



Patch:
diff --git a/pkg/cmd/issue/create/create.go b/pkg/cmd/issue/create/create.go
index a4db9d3a1..577f08728 100644
--- a/pkg/cmd/issue/create/create.go
+++ b/pkg/cmd/issue/create/create.go
@@ -268,7 +268,7 @@ func createRun(opts *CreateOptions) (err error) {
 				Repo:      baseRepo,
 				State:     &tb,
 			}
-			err = prShared.MetadataSurvey(opts.IO, baseRepo, fetcher, &tb)
+			err = prShared.MetadataSurvey(opts.Prompter, opts.IO, baseRepo, fetcher, &tb)
 			if err != nil {
 				return
 			}
diff --git a/pkg/cmd/issue/edit/edit.go b/pkg/cmd/issue/edit/edit.go
index 2fe4c62b7..53400fb63 100644
--- a/pkg/cmd/issue/edit/edit.go
+++ b/pkg/cmd/issue/edit/edit.go
@@ -21,10 +21,11 @@ type EditOptions struct {
 	HttpClient func() (*http.Client, error)
 	IO         *iostreams.IOStreams
 	BaseRepo   func() (ghrepo.Interface, error)
+	Prompter   prShared.EditPrompter
 
 	DetermineEditor    func() (string, error)
-	FieldsToEditSurvey func(*prShared.Editable) error
-	EditFieldsSurvey   func(*prShared.Editable, string) error
+	FieldsToEditSurvey func(prShared.EditPrompter, *prShared.Editable) error
+	EditFieldsSurvey   func(prShared.EditPrompter, *prShared.Editable, string) error
 	FetchOptions       func(*api.Client, ghrepo.Interface, *prShared.Editable) error
 
 	SelectorArgs []string
@@ -41,6 +42,7 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman
 		FieldsToEditSurvey: prShared.FieldsToEditSurvey,
 		EditFieldsSurvey:   prShared.EditFieldsSurvey,
 		FetchOptions:       prShared.FetchOptions,
+		Prompter:           f.Prompter,
 	}
 
 	var bodyFile string
@@ -152,7 +154,7 @@ func editRun(opts *EditOptions) error {
 	// Prompt the user which fields they'd like to edit.
 	editable := opts.Editable
 	if opts.Interactive {
-		err = opts.FieldsToEditSurvey(&editable)
+		err = opts.FieldsToEditSurvey(opts.Prompter, &editable)
 		if err != nil {
 			return err
 		}
@@ -222,7 +224,7 @@ func editRun(opts *EditOptions) error {
 			if err != nil {
 				return err
 			}
-			err = opts.EditFieldsSurvey(&editable, editorCommand)
+			err = opts.EditFieldsSurvey(opts.Prompter, &editable, editorCommand)
 			if err != nil {
 				return err
 			}
diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go
index 6809528b3..7321d4f38 100644
--- a/pkg/cmd/pr/create/create.go
+++ b/pkg/cmd/pr/create/create.go
@@ -361,7 +361,7 @@ func createRun(opts *CreateOptions) (err error) {
 			Repo:      ctx.BaseRepo,
 			State:     state,
 		}
-		err = shared.MetadataSurvey(opts.IO, ctx.BaseRepo, fetcher, state)
+		err = shared.MetadataSurvey(opts.Prompter, opts.IO, ctx.BaseRepo, fetcher, state)
 		if err != nil {
 			return
 		}
diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go
index bab7a26d4..c606ae6bb 100644
--- a/pkg/cmd/pr/edit/edit.go
+++ b/pkg/cmd/pr/edit/edit.go
@@ -24,6 +24,7 @@ type EditOptions struct {
 	Surveyor        Surveyor
 	Fetcher         EditableOptionsFetcher
 	EditorRetriever EditorRetriever
+	Prompter        shared.EditPrompter
 
 	SelectorArg string
 	Interactive bool
@@ -35,9 +36,10 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman
 	opts := &EditOptions{
 		IO:              f.IOStreams,
 		HttpClient:      f.HttpClient,
-		Surveyor:        surveyor{},
+		Surveyor:        surveyor{P: f.Prompter},
 		Fetcher:         fetcher{},
 		EditorRetriever: editorRetriever{config: f.Config},
+		Prompter:        f.Prompter,
 	}
 
 	var bodyFile string
@@ -280,14 +282,16 @@ type Surveyor interface {
 	EditFields(*shared.Editable, string) error
 }
 
-type surveyor struct{}
+type surveyor struct {
+	P shared.EditPrompter
+}
 
 func (s surveyor) FieldsToEdit(editable *shared.Editable) error {
-	return shared.FieldsToEditSurvey(editable)
+	return shared.FieldsToEditSurvey(s.P, editable)
 }
 
 func (s surveyor) EditFields(editable *shared.Editable, editorCmd string) error {
-	return shared.EditFieldsSurvey(editable, editorCmd)
+	return shared.EditFieldsSurvey(s.P, editable, editorCmd)
 }
 
 type EditableOptionsFetcher interface {
diff --git a/pkg/cmd/pr/shared/editable.go b/pkg/cmd/pr/shared/editable.go
index 0fbe689fc..cec3bfe8c 100644
--- a/pkg/cmd/pr/shared/editable.go
+++ b/pkg/cmd/pr/shared/editable.go
@@ -4,12 +4,9 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/AlecAivazis/survey/v2"
 	"github.com/cli/cli/v2/api"
 	"github.com/cli/cli/v2/internal/ghrepo"
-	"github.com/cli/cli/v2/internal/prompter"
 	"github.com/cli/cli/v2/pkg/set"
-	"github.com/cli/cli/v2/pkg/surveyext"
 )
 
 type Editable struct {
@@ -255,34 +252,45 @@ func (ep *EditableProjects) clone() EditableProjects {
 	}
 }
 
-func EditFieldsSurvey(editable *Editable, editorCommand string) error {
+type EditPrompter interface {
+	Select(string, string, []string) (int, error)
+	Input(string, string) (string, error)
+	MarkdownEditor(string, string, bool) (string, error)
+	MultiSelect(string, []string, []string) ([]int, error)
+	Confirm(string, bool) (bool, error)
+}
+
+func EditFieldsSurvey(p EditPrompter, editable *Editable, editorCommand string) error {
 	var err error
 	if editable.Title.Edited {
-		editable.Title.Value, err = titleSurvey(editable.Title.Default)
+		editable.Title.Value, err = p.Input("Title", editable.Title.Default)
 		if err != nil {
 			return err
 		}
 	}
 	if editable.Body.Edited {
-		editable.Body.Value, err = bodySurvey(editable.Body.Default, editorCommand)
+		editable.Body.Value, err = p.MarkdownEditor("Body", editable.Body.Default, false)
 		if err != nil {
 			return err
 		}
 	}
 	if editable.Reviewers.Edited {
-		editable.Reviewers.Value, err = multiSelectSurvey("Reviewers", editable.Reviewers.Default, editable.Reviewers.Options)
+		editable.Reviewers.Value, err = multiSelectSurvey(
+			p, "Reviewers", editable.Reviewers.Default, editable.Reviewers.Options)
 		if err != nil {
 			return err
 		}
 	}
 	if editable.Assignees.Edited {
-		editable.Assignees.Value, err = multiSelectSurvey("Assignees", editable.Assignees.Default, editable.Assignees.Options)
+		editable.Assignees.Value, err = multiSelectSurvey(
+			p, "Assignees", editable.Assignees.Default, editable.Assignees.Options)
 		if err != nil {
 			return err
 		}
 	}
 	if editable.Labels.Edited {
-		editable.Labels.Add, err = multiSelectSurvey("Labels", editable.Labels.Default, editable.Labels.Options)
+		editable.Labels.Add, err = multiSelectSurvey(
+			p, "Labels", editable.Labels.Default, editable.Labels.Options)
 		if err != nil {
 			return err
 		}
@@ -300,18 +308,19 @@ func EditFieldsSurvey(editable *Editable, editorCommand string) error {
 		}
 	}
 	if editable.Projects.Edited {
-		editable.Projects.Value, err = multiSelectSurvey("Projects", editable.Projects.Default, editable.Projects.Options)
+		editable.Projects.Value, err = multiSelectSurvey(
+			p, "Projects", editable.Projects.Default, editable.Projects.Options)
 		if err != nil {
 			return err
 		}
 	}
 	if editable.Milestone.Edited {
-		editable.Milestone.Value, err = milestoneSurvey(editable.Milestone.Default, editable.Milestone.Options)
+		editable.Milestone.Value, err = milestoneSurvey(p, editable.Milestone.Default, editable.Milestone.Options)
 		if err != nil {
 			return err
 		}
 	}
-	confirm, err := confirmSurvey()
+	confirm, err := p.Confirm("Submit?", true)
 	if err != nil {
 		return err
 	}
@@ -322,7 +331,7 @@ func EditFieldsSurvey(editable *Editable, editorCommand string) error {
 	return nil
 }
 
-func FieldsToEditSurvey(editable *Editable) error {
+func FieldsToEditSurvey(p EditPrompter, editable *Editable) error {
 	contains := func(s []string, str string) bool {
 		for _, v := range s {
 			if v == str {
@@ -337,7 +346,7 @@ func FieldsToEditSurvey(editable *Editable) error {
 		opts = append(opts, "Reviewers")
 	}
 	opts = append(opts, "Assignees", "Labels", "Projects", "Milestone")
-	results, err := multiSelectSurvey("What would you like to edit?", []string{}, opts)
+	results, err := multiSelectSurvey(p, "What would you like to edit?", []string{}, opts)
 	if err != nil {
 		return err
 	}
@@ -414,67 +423,34 @@ func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable)
 	return nil
 }
 
-func titleSurvey(title string) (string, error) {
-	var result string
-	q := &survey.Input{
-		Message: "Title",
-		Default: title,
-	}
-	err := survey.AskOne(q, &result)
-	return result, err
-}
-
-func bodySurvey(body, editorCommand string) (string, error) {
-	var result string
-	q := &surveyext.GhEditor{
-		EditorCommand: editorCommand,
-		Editor: &survey.Editor{
-			Message:       "Body",
-			FileName:      "*.md",
-			Default:       body,
-			HideDefault:   true,
-			AppendDefault: true,
-		},
-	}
-	err := survey.AskOne(q, &result)
-	return result, err
-}
-
-func multiSelectSurvey(message string, defaults, options []string) ([]string, error) {
+func multiSelectSurvey(p EditPrompter, message string, defaults, options []string) (results []string, err error) {
 	if len(options) == 0 {
 		return nil, nil
 	}
-	var results []string
-	q := &survey.MultiSelect{
-		Message: message,
-		Options: options,
-		Default: defaults,
-		Filter:  prompter.LatinMatchingFilter,
+
+	var selected []int
+	selected, err = p.MultiSelect(message, defaults, options)
+	if err != nil {
+		return
+	}
+
+	for _, i := range selected {
+		results = append(results, options[i])
 	}
-	err := survey.AskOne(q, &results)
+
 	return results, err
 }
 
-func milestoneSurvey(title string, opts []string) (string, error) {
+func milestoneSurvey(p EditPrompter, title string, opts []string) (result string, err error) {
 	if len(opts) == 0 {
 		return "", nil
 	}
-	var result string
-	q := &survey.Select{
-		Message: "Milestone",
-		Options: opts,
-		Default: title,
+	var selected int
+	selected, err = p.Select("Milestone", title, opts)
+	if err != nil {
+		return
 	}
-	err := survey.AskOne(q, &result)
-	return result, err
-}
 
-func confirmSurvey() (bool, error) {
-	var result bool
-	q := &survey.Confirm{
-		Message: "Submit?",
-		Default: true,
-	}
-	err := survey.AskOne(q, &result)
-	return result, err
+	result = opts[selected]
+	return
 }
diff --git a/pkg/cmd/pr/shared/survey.go b/pkg/cmd/pr/shared/survey.go
index 4e1d1a421..393b87757 100644
--- a/pkg/cmd/pr/shared/survey.go
+++ b/pkg/cmd/pr/shared/survey.go
@@ -4,12 +4,9 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/AlecAivazis/survey/v2"
 	"github.com/cli/cli/v2/api"
 	"github.com/cli/cli/v2/internal/ghrepo"
-	"github.com/cli/cli/v2/internal/prompter"
 	"github.com/cli/cli/v2/pkg/iostreams"
-	"github.com/cli/cli/v2/pkg/prompt"
 )
 
 type Action int
@@ -37,6 +34,7 @@ type Prompt interface {
 	Select(string, string, []string) (int, error)
 	MarkdownEditor(string, string, bool) (string, error)
 	Confirm(string, bool) (bool, error)
+	MultiSelect(string, []string, []string) ([]int, error)
 }
 
 func ConfirmIssueSubmission(p Prompt, allowPreview bool, allowMetadata bool) (Action, error) {
@@ -142,7 +140,7 @@ type RepoMetadataFetcher interface {
 	RepoMetadataFetch(api.RepoMetadataInput) (*api.RepoMetadataResult, error)
 }
 
-func MetadataSurvey(io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher RepoMetadataFetcher, state *IssueMetadataState) error {
+func MetadataSurvey(p Prompt, io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher RepoMetadataFetcher, state *IssueMetadataState) error {
 	isChosen := func(m string) bool {
 		for _, c := range state.Metadata {
 			if m == c {
@@ -160,18 +158,12 @@ func MetadataSurvey(io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher
 	}
 	extraFieldsOptions = append(extraFieldsOptions, "Assignees", "Labels", "Projects", "Milestone")
 
-	//nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter
-	err := prompt.SurveyAsk([]*survey.Question{
-		{
-			Name: "metadata",
-			Prompt: &survey.MultiSelect{
-				Message: "What would you like to add?",
-				Options: extraFieldsOptions,
-			},
-		},
-	}, state)
+	selected, err := p.MultiSelect("What would you like to add?", nil, extraFieldsOptions)
 	if err != nil {
-		return fmt.Errorf("could not prompt: %w", err)
+		return err
+	}
+	for _, i := range selected {
+		state.Metadata = append(state.Metadata, extraFieldsOptions[i])
 	}
 
 	metadataInput := api.RepoMetadataInput{
@@ -215,63 +207,62 @@ func MetadataSurvey(io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher
 		milestones = append(milestones, m.Title)
 	}
 
-	var mqs []*survey.Question
+	values := struct {
+		Reviewers []string
+		Assignees []string
+		Labels    []string
+		Projects  []string
+		Milestone string
+	}{}
+
 	if isChosen("Reviewers") {
 		if len(reviewers) > 0 {
-			mqs = append(mqs, &survey.Question{
-				Name: "reviewers",
-				Prompt: &survey.MultiSelect{
-					Message: "Reviewers",
-					Options: reviewers,
-					Default: state.Reviewers,
-					Filter:  prompter.LatinMatchingFilter,
-				},
-			})
+			selected, err := p.MultiSelect("Reviewers", state.Reviewers, reviewers)
+			if err != nil {
+				return err
+			}
+			for _, i := range selected {
+				values.Reviewers = append(values.Reviewers, reviewers[i])
+			}
 		} else {
 			fmt.Fprintln(io.ErrOut, "warning: no available reviewers")
 		}
 	}
 	if isChosen("Assignees") {
 		if len(assignees) > 0 {
-			mqs = append(mqs, &survey.Question{
-				Name: "assignees",
-				Prompt: &survey.MultiSelect{
-					Message: "Assignees",
-					Options: assignees,
-					Default: state.Assignees,
-					Filter:  prompter.LatinMatchingFilter,
-				},
-			})
+			selected, err := p.MultiSelect("Assignees", state.Assignees, assignees)
+			if err != nil {
+				return err
+			}
+			for _, i := range selected {
+				values.Assignees = append(values.Assignees, assignees[i])
+			}
 		} else {
 			fmt.Fprintln(io.ErrOut, "warning: no assignable users")
 		}
 	}
 	if isChosen("Labels") {
 		if len(labels) > 0 {
-			mqs = append(mqs, &survey.Question{
-				Name: "labels",
-				Prompt: &survey.MultiSelect{
-					Message: "Labels",
-					Options: labels,
-					Default: state.Labels,
-					Filter:  prompter.LatinMatchingFilter,
-				},
-			})
+			selected, err := p.MultiSelect("Labels", state.Labels, labels)
+			if err != nil {
+				return err
+			}
+			for _, i := range selected {
+				values.Labels = append(values.Labels, labels[i])
+			}
 		} else {
 			fmt.Fprintln(io.ErrOut, "warning: no labels in the repository")
 		}
 	}
 	if isChosen("Projects") {
 		if len(projects) > 0 {
-			mqs = append(mqs, &survey.Question{
-				Name: "projects",
-				Prompt: &survey.MultiSelect{
-					Message: "Projects",
-					Options: projects,
-					Default: state.Projects,
-					Filter:  prompter.LatinMatchingFilter,
-				},
-			})
+			selected, err := p.MultiSelect("Projects", state.Projects, projects)
+			if err != nil {
+				return err
+			}
+			for _, i := range selected {
+				values.Projects = append(values.Projects, projects[i])
+			}
 		} else {
 			fmt.Fprintln(io.ErrOut, "warning: no projects to choose from")
 		}
@@ -284,33 +275,16 @@ func MetadataSurvey(io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher
 			} else {
 				milestoneDefault = milestones[1]
 			}
-			mqs = append(mqs, &survey.Question{
-				Name: "milestone",
-				Prompt: &survey.Select{
-					Message: "Milestone",
-					Options: milestones,
-					Default: milestoneDefault,
-				},
-			})
+			selected, err := p.Select("Milestone", milestoneDefault, milestones)
+			if err != nil {
+				return err
+			}
+			values.Milestone = milestones[selected]
 		} else {
 			fmt.Fprintln(io.ErrOut, "warning: no milestones in the repository")
 		}
 	}
 
-	values := struct {
-		Reviewers []string
-		Assignees []string
-		Labels    []string
-		Projects  []string
-		Milestone string
-	}{}
-
-	//nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter
-	err = prompt.SurveyAsk(mqs, &values)
-	if err != nil {
-		return fmt.Errorf("could not prompt: %w", err)
-	}
-
 	if isChosen("Reviewers") {
 		var logins []string
 		for _, r := range values.Reviewers {


Test patch:
diff --git a/pkg/cmd/issue/edit/edit_test.go b/pkg/cmd/issue/edit/edit_test.go
index d73132f35..db0b09d2d 100644
--- a/pkg/cmd/issue/edit/edit_test.go
+++ b/pkg/cmd/issue/edit/edit_test.go
@@ -511,7 +511,7 @@ func Test_editRun(t *testing.T) {
 			input: &EditOptions{
 				SelectorArgs: []string{"123"},
 				Interactive:  true,
-				FieldsToEditSurvey: func(eo *prShared.Editable) error {
+				FieldsToEditSurvey: func(p prShared.EditPrompter, eo *prShared.Editable) error {
 					eo.Title.Edited = true
 					eo.Body.Edited = true
 					eo.Assignees.Edited = true
@@ -520,7 +520,7 @@ func Test_editRun(t *testing.T) {
 					eo.Milestone.Edited = true
 					return nil
 				},
-				EditFieldsSurvey: func(eo *prShared.Editable, _ string) error {
+				EditFieldsSurvey: func(p prShared.EditPrompter, eo *prShared.Editable, _ string) error {
 					eo.Title.Value = "new title"
 					eo.Body.Value = "new body"
 					eo.Assignees.Value = []string{"monalisa", "hubot"}
diff --git a/pkg/cmd/pr/shared/survey_test.go b/pkg/cmd/pr/shared/survey_test.go
index 0bc27e8d6..4653ce08b 100644
--- a/pkg/cmd/pr/shared/survey_test.go
+++ b/pkg/cmd/pr/shared/survey_test.go
@@ -5,8 +5,8 @@ import (
 
 	"github.com/cli/cli/v2/api"
 	"github.com/cli/cli/v2/internal/ghrepo"
+	"github.com/cli/cli/v2/internal/prompter"
 	"github.com/cli/cli/v2/pkg/iostreams"
-	"github.com/cli/cli/v2/pkg/prompt"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -43,45 +43,32 @@ func TestMetadataSurvey_selectAll(t *testing.T) {
 		},
 	}
 
-	//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
-	as, restoreAsk := prompt.InitAskStubber()
-	defer restoreAsk()
-
-	//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
-	as.Stub([]*prompt.QuestionStub{
-		{
-			Name:  "metadata",
-			Value: []string{"Labels", "Projects", "Assignees", "Reviewers", "Milestone"},
-		},
+	pm := prompter.NewMockPrompter(t)
+	pm.RegisterMultiSelect("What would you like to add?",
+		[]string{}, []string{"Reviewers", "Assignees", "Labels", "Projects", "Milestone"}, func(_ string, _, _ []string) ([]int, error) {
+			return []int{0, 1, 2, 3, 4}, nil
+		})
+	pm.RegisterMultiSelect("Reviewers", []string{}, []string{"hubot", "monalisa"}, func(_ string, _, _ []string) ([]int, error) {
+		return []int{1}, nil
 	})
-	//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
-	as.Stub([]*prompt.QuestionStub{
-		{
-			Name:  "reviewers",
-			Value: []string{"monalisa"},
-		},
-		{
-			Name:  "assignees",
-			Value: []string{"hubot"},
-		},
-		{
-			Name:  "labels",
-			Value: []string{"good first issue"},
-		},
-		{
-			Name:  "projects",
-			Value: []string{"The road to 1.0"},
-		},
-		{
-			Name:  "milestone",
-			Value: "(none)",
-		},
+	pm.RegisterMultiSelect("Assignees", []string{}, []string{"hubot", "monalisa"}, func(_ string, _, _ []string) ([]int, error) {
+		return []int{0}, nil
+	})
+	pm.RegisterMultiSelect("Labels", []string{}, []string{"help wanted", "good first issue"}, func(_ string, _, _ []string) ([]int, error) {
+		return []int{1}, nil
+	})
+	pm.RegisterMultiSelect("Projects", []string{}, []string{"Huge Refactoring", "The road to 1.0"}, func(_ string, _, _ []string) ([]int, error) {
+		return []int{1}, nil
+	})
+	pm.RegisterSelect("Milestone", []string{"(none)", "1.2 patch release"}, func(_, _ string, _ []string) (int, error) {
+		return 0, nil
 	})
 
 	state := &IssueMetadataState{
 		Assignees: []string{"hubot"},
+		Type:      PRMetadata,
 	}
-	err := MetadataSurvey(ios, repo, fetcher, state)
+	err := MetadataSurvey(pm, ios, repo, fetcher, state)
 	assert.NoError(t, err)
 
 	assert.Equal(t, "", stdout.String())
@@ -112,33 +99,21 @@ func TestMetadataSurvey_keepExisting(t *testing.T) {
 		},
 	}
 
-	//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
-	as, restoreAsk := prompt.InitAskStubber()
-	defer restoreAsk()
-
-	//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
-	as.Stub([]*prompt.QuestionStub{
-		{
-			Name:  "metadata",
-			Value: []string{"Labels", "Projects"},
-		},
+	pm := prompter.NewMockPrompter(t)
+	pm.RegisterMultiSelect("What would you like to add?", []string{}, []string{"Assignees", "Labels", "Projects", "Milestone"}, func(_ string, _, _ []string) ([]int, error) {
+		return []int{1, 2}, nil
 	})
-	//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
-	as.Stub([]*prompt.QuestionStub{
-		{
-			Name:  "labels",
-			Value: []string{"good first issue"},
-		},
-		{
-			Name:  "projects",
-			Value: []string{"The road to 1.0"},
-		},
+	pm.RegisterMultiSelect("Labels", []string{}, []string{"help wanted", "good first issue"}, func(_ string, _, _ []string) ([]int, error) {
+		return []int{1}, nil
+	})
+	pm.RegisterMultiSelect("Projects", []string{}, []string{"Huge Refactoring", "The road to 1.0"}, func(_ string, _, _ []string) ([]int, error) {
+		return []int{1}, nil
 	})
 
 	state := &IssueMetadataState{
 		Assignees: []string{"hubot"},
 	}
-	err := MetadataSurvey(ios, repo, fetcher, state)
+	err := MetadataSurvey(pm, ios, repo, fetcher, state)
 	assert.NoError(t, err)
 
 	assert.Equal(t, "", stdout.String())