Add godoc comments to exported symbols in internal/featuredetection and internal/prompter

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Kynan Ware 2026-03-04 16:01:52 -07:00
parent 7367596c83
commit f9fec823ad
4 changed files with 82 additions and 1 deletions

View file

@ -2,99 +2,122 @@ package featuredetection
import "github.com/cli/cli/v2/internal/gh" import "github.com/cli/cli/v2/internal/gh"
// DisabledDetectorMock is a mock Detector that returns zero-value features for all queries.
type DisabledDetectorMock struct{} type DisabledDetectorMock struct{}
// IssueFeatures returns empty issue features.
func (md *DisabledDetectorMock) IssueFeatures() (IssueFeatures, error) { func (md *DisabledDetectorMock) IssueFeatures() (IssueFeatures, error) {
return IssueFeatures{}, nil return IssueFeatures{}, nil
} }
// PullRequestFeatures returns empty pull request features.
func (md *DisabledDetectorMock) PullRequestFeatures() (PullRequestFeatures, error) { func (md *DisabledDetectorMock) PullRequestFeatures() (PullRequestFeatures, error) {
return PullRequestFeatures{}, nil return PullRequestFeatures{}, nil
} }
// RepositoryFeatures returns empty repository features.
func (md *DisabledDetectorMock) RepositoryFeatures() (RepositoryFeatures, error) { func (md *DisabledDetectorMock) RepositoryFeatures() (RepositoryFeatures, error) {
return RepositoryFeatures{}, nil return RepositoryFeatures{}, nil
} }
// ProjectsV1 returns unsupported for ProjectsV1.
func (md *DisabledDetectorMock) ProjectsV1() gh.ProjectsV1Support { func (md *DisabledDetectorMock) ProjectsV1() gh.ProjectsV1Support {
return gh.ProjectsV1Unsupported return gh.ProjectsV1Unsupported
} }
// ProjectFeatures returns empty project features.
func (md *DisabledDetectorMock) ProjectFeatures() (ProjectFeatures, error) { func (md *DisabledDetectorMock) ProjectFeatures() (ProjectFeatures, error) {
return ProjectFeatures{}, nil return ProjectFeatures{}, nil
} }
// SearchFeatures returns search features with advanced issue search disabled.
func (md *DisabledDetectorMock) SearchFeatures() (SearchFeatures, error) { func (md *DisabledDetectorMock) SearchFeatures() (SearchFeatures, error) {
return advancedIssueSearchNotSupported, nil return advancedIssueSearchNotSupported, nil
} }
// ReleaseFeatures returns empty release features.
func (md *DisabledDetectorMock) ReleaseFeatures() (ReleaseFeatures, error) { func (md *DisabledDetectorMock) ReleaseFeatures() (ReleaseFeatures, error) {
return ReleaseFeatures{}, nil return ReleaseFeatures{}, nil
} }
// ActionsFeatures returns empty actions features.
func (md *DisabledDetectorMock) ActionsFeatures() (ActionsFeatures, error) { func (md *DisabledDetectorMock) ActionsFeatures() (ActionsFeatures, error) {
return ActionsFeatures{}, nil return ActionsFeatures{}, nil
} }
// EnabledDetectorMock is a mock Detector that returns all features as enabled.
type EnabledDetectorMock struct{} type EnabledDetectorMock struct{}
// IssueFeatures returns all issue features enabled.
func (md *EnabledDetectorMock) IssueFeatures() (IssueFeatures, error) { func (md *EnabledDetectorMock) IssueFeatures() (IssueFeatures, error) {
return allIssueFeatures, nil return allIssueFeatures, nil
} }
// PullRequestFeatures returns all pull request features enabled.
func (md *EnabledDetectorMock) PullRequestFeatures() (PullRequestFeatures, error) { func (md *EnabledDetectorMock) PullRequestFeatures() (PullRequestFeatures, error) {
return allPullRequestFeatures, nil return allPullRequestFeatures, nil
} }
// RepositoryFeatures returns all repository features enabled.
func (md *EnabledDetectorMock) RepositoryFeatures() (RepositoryFeatures, error) { func (md *EnabledDetectorMock) RepositoryFeatures() (RepositoryFeatures, error) {
return allRepositoryFeatures, nil return allRepositoryFeatures, nil
} }
// ProjectsV1 returns supported for ProjectsV1.
func (md *EnabledDetectorMock) ProjectsV1() gh.ProjectsV1Support { func (md *EnabledDetectorMock) ProjectsV1() gh.ProjectsV1Support {
return gh.ProjectsV1Supported return gh.ProjectsV1Supported
} }
// ProjectFeatures returns all project features enabled.
func (md *EnabledDetectorMock) ProjectFeatures() (ProjectFeatures, error) { func (md *EnabledDetectorMock) ProjectFeatures() (ProjectFeatures, error) {
return allProjectFeatures, nil return allProjectFeatures, nil
} }
// SearchFeatures returns search features with advanced issue search disabled.
func (md *EnabledDetectorMock) SearchFeatures() (SearchFeatures, error) { func (md *EnabledDetectorMock) SearchFeatures() (SearchFeatures, error) {
return advancedIssueSearchNotSupported, nil return advancedIssueSearchNotSupported, nil
} }
// ReleaseFeatures returns release features with immutable releases enabled.
func (md *EnabledDetectorMock) ReleaseFeatures() (ReleaseFeatures, error) { func (md *EnabledDetectorMock) ReleaseFeatures() (ReleaseFeatures, error) {
return ReleaseFeatures{ return ReleaseFeatures{
ImmutableReleases: true, ImmutableReleases: true,
}, nil }, nil
} }
// ActionsFeatures returns actions features with dispatch run details enabled.
func (md *EnabledDetectorMock) ActionsFeatures() (ActionsFeatures, error) { func (md *EnabledDetectorMock) ActionsFeatures() (ActionsFeatures, error) {
return ActionsFeatures{ return ActionsFeatures{
DispatchRunDetails: true, DispatchRunDetails: true,
}, nil }, nil
} }
// AdvancedIssueSearchDetectorMock is a mock Detector with configurable search features.
type AdvancedIssueSearchDetectorMock struct { type AdvancedIssueSearchDetectorMock struct {
EnabledDetectorMock EnabledDetectorMock
searchFeatures SearchFeatures searchFeatures SearchFeatures
} }
// SearchFeatures returns the configured search features.
func (md *AdvancedIssueSearchDetectorMock) SearchFeatures() (SearchFeatures, error) { func (md *AdvancedIssueSearchDetectorMock) SearchFeatures() (SearchFeatures, error) {
return md.searchFeatures, nil return md.searchFeatures, nil
} }
// AdvancedIssueSearchUnsupported returns a mock detector where advanced issue search is not supported.
func AdvancedIssueSearchUnsupported() *AdvancedIssueSearchDetectorMock { func AdvancedIssueSearchUnsupported() *AdvancedIssueSearchDetectorMock {
return &AdvancedIssueSearchDetectorMock{ return &AdvancedIssueSearchDetectorMock{
searchFeatures: advancedIssueSearchNotSupported, searchFeatures: advancedIssueSearchNotSupported,
} }
} }
// AdvancedIssueSearchSupportedAsOptIn returns a mock detector where advanced issue search is opt-in.
func AdvancedIssueSearchSupportedAsOptIn() *AdvancedIssueSearchDetectorMock { func AdvancedIssueSearchSupportedAsOptIn() *AdvancedIssueSearchDetectorMock {
return &AdvancedIssueSearchDetectorMock{ return &AdvancedIssueSearchDetectorMock{
searchFeatures: advancedIssueSearchSupportedAsOptIn, searchFeatures: advancedIssueSearchSupportedAsOptIn,
} }
} }
// AdvancedIssueSearchSupportedAsOnlyBackend returns a mock detector where advanced issue search is the only backend.
func AdvancedIssueSearchSupportedAsOnlyBackend() *AdvancedIssueSearchDetectorMock { func AdvancedIssueSearchSupportedAsOnlyBackend() *AdvancedIssueSearchDetectorMock {
return &AdvancedIssueSearchDetectorMock{ return &AdvancedIssueSearchDetectorMock{
searchFeatures: advancedIssueSearchSupportedAsOnlyBackend, searchFeatures: advancedIssueSearchSupportedAsOnlyBackend,

View file

@ -1,4 +1,4 @@
package featuredetection package featuredetection
import ( import (
"net/http" "net/http"
@ -11,6 +11,7 @@ import (
ghauth "github.com/cli/go-gh/v2/pkg/auth" ghauth "github.com/cli/go-gh/v2/pkg/auth"
) )
// Detector queries a GitHub host to determine which API features are available.
type Detector interface { type Detector interface {
IssueFeatures() (IssueFeatures, error) IssueFeatures() (IssueFeatures, error)
PullRequestFeatures() (PullRequestFeatures, error) PullRequestFeatures() (PullRequestFeatures, error)
@ -22,6 +23,7 @@ type Detector interface {
ActionsFeatures() (ActionsFeatures, error) ActionsFeatures() (ActionsFeatures, error)
} }
// IssueFeatures describes the issue-related capabilities of a GitHub host.
type IssueFeatures struct { type IssueFeatures struct {
StateReason bool StateReason bool
StateReasonDuplicate bool StateReasonDuplicate bool
@ -34,6 +36,7 @@ var allIssueFeatures = IssueFeatures{
ActorIsAssignable: true, ActorIsAssignable: true,
} }
// PullRequestFeatures describes the pull-request-related capabilities of a GitHub host.
type PullRequestFeatures struct { type PullRequestFeatures struct {
MergeQueue bool MergeQueue bool
// CheckRunAndStatusContextCounts indicates whether the API supports // CheckRunAndStatusContextCounts indicates whether the API supports
@ -49,6 +52,7 @@ var allPullRequestFeatures = PullRequestFeatures{
CheckRunEvent: true, CheckRunEvent: true,
} }
// RepositoryFeatures describes the repository-related capabilities of a GitHub host.
type RepositoryFeatures struct { type RepositoryFeatures struct {
PullRequestTemplateQuery bool PullRequestTemplateQuery bool
VisibilityField bool VisibilityField bool
@ -61,6 +65,7 @@ var allRepositoryFeatures = RepositoryFeatures{
AutoMerge: true, AutoMerge: true,
} }
// ProjectFeatures describes the project-related capabilities of a GitHub host.
type ProjectFeatures struct { type ProjectFeatures struct {
// ProjectItemQuery indicates support for the `query` argument on // ProjectItemQuery indicates support for the `query` argument on
// ProjectV2.items (supported on github.com and GHES 3.20+). // ProjectV2.items (supported on github.com and GHES 3.20+).
@ -71,6 +76,7 @@ var allProjectFeatures = ProjectFeatures{
ProjectItemQuery: true, ProjectItemQuery: true,
} }
// SearchFeatures describes the search-related capabilities of a GitHub host.
type SearchFeatures struct { type SearchFeatures struct {
// AdvancedIssueSearch indicates whether the host supports advanced issue // AdvancedIssueSearch indicates whether the host supports advanced issue
// search via API calls. // search via API calls.
@ -108,10 +114,12 @@ var advancedIssueSearchSupportedAsOnlyBackend = SearchFeatures{
AdvancedIssueSearchAPIOptIn: false, AdvancedIssueSearchAPIOptIn: false,
} }
// ReleaseFeatures describes the release-related capabilities of a GitHub host.
type ReleaseFeatures struct { type ReleaseFeatures struct {
ImmutableReleases bool ImmutableReleases bool
} }
// ActionsFeatures describes the GitHub Actions capabilities of a GitHub host.
type ActionsFeatures struct { type ActionsFeatures struct {
// DispatchRunDetails indicates whether the API supports the `return_run_details` // DispatchRunDetails indicates whether the API supports the `return_run_details`
// field in workflow dispatches that, when set to true, will return the details // field in workflow dispatches that, when set to true, will return the details
@ -127,6 +135,7 @@ type detector struct {
httpClient *http.Client httpClient *http.Client
} }
// NewDetector creates a Detector that queries the given host using the provided HTTP client.
func NewDetector(httpClient *http.Client, host string) Detector { func NewDetector(httpClient *http.Client, host string) Detector {
return &detector{ return &detector{
httpClient: httpClient, httpClient: httpClient,
@ -134,6 +143,7 @@ func NewDetector(httpClient *http.Client, host string) Detector {
} }
} }
// IssueFeatures detects issue-related capabilities of the configured host.
func (d *detector) IssueFeatures() (IssueFeatures, error) { func (d *detector) IssueFeatures() (IssueFeatures, error) {
if !ghauth.IsEnterprise(d.host) { if !ghauth.IsEnterprise(d.host) {
return allIssueFeatures, nil return allIssueFeatures, nil
@ -182,6 +192,7 @@ func (d *detector) IssueFeatures() (IssueFeatures, error) {
return features, nil return features, nil
} }
// PullRequestFeatures detects pull-request-related capabilities of the configured host.
func (d *detector) PullRequestFeatures() (PullRequestFeatures, error) { func (d *detector) PullRequestFeatures() (PullRequestFeatures, error) {
// TODO: reinstate the short-circuit once the APIs are fully available on github.com // TODO: reinstate the short-circuit once the APIs are fully available on github.com
// https://github.com/cli/cli/issues/5778 // https://github.com/cli/cli/issues/5778
@ -251,6 +262,7 @@ func (d *detector) PullRequestFeatures() (PullRequestFeatures, error) {
return features, nil return features, nil
} }
// RepositoryFeatures detects repository-related capabilities of the configured host.
func (d *detector) RepositoryFeatures() (RepositoryFeatures, error) { func (d *detector) RepositoryFeatures() (RepositoryFeatures, error) {
if !ghauth.IsEnterprise(d.host) { if !ghauth.IsEnterprise(d.host) {
return allRepositoryFeatures, nil return allRepositoryFeatures, nil
@ -292,6 +304,7 @@ const (
enterpriseProjectsV1Removed = "3.17.0" enterpriseProjectsV1Removed = "3.17.0"
) )
// ProjectsV1 determines whether the configured host supports classic projects (v1).
func (d *detector) ProjectsV1() gh.ProjectsV1Support { func (d *detector) ProjectsV1() gh.ProjectsV1Support {
if !ghauth.IsEnterprise(d.host) { if !ghauth.IsEnterprise(d.host) {
return gh.ProjectsV1Unsupported return gh.ProjectsV1Unsupported
@ -307,6 +320,7 @@ func (d *detector) ProjectsV1() gh.ProjectsV1Support {
return gh.ProjectsV1Unsupported return gh.ProjectsV1Unsupported
} }
// ProjectFeatures detects project-related capabilities of the configured host.
func (d *detector) ProjectFeatures() (ProjectFeatures, error) { func (d *detector) ProjectFeatures() (ProjectFeatures, error) {
if !ghauth.IsEnterprise(d.host) { if !ghauth.IsEnterprise(d.host) {
return allProjectFeatures, nil return allProjectFeatures, nil
@ -356,6 +370,7 @@ const (
enterpriseAdvancedIssueSearchSupport = "3.18.0" enterpriseAdvancedIssueSearchSupport = "3.18.0"
) )
// SearchFeatures detects search-related capabilities of the configured host.
func (d *detector) SearchFeatures() (SearchFeatures, error) { func (d *detector) SearchFeatures() (SearchFeatures, error) {
// TODO advancedIssueSearchCleanup // TODO advancedIssueSearchCleanup
// Once GHES 3.17 support ends, we don't need this and, probably, the entire search feature detection. // Once GHES 3.17 support ends, we don't need this and, probably, the entire search feature detection.
@ -441,6 +456,7 @@ func (d *detector) SearchFeatures() (SearchFeatures, error) {
return feature, nil return feature, nil
} }
// ReleaseFeatures detects release-related capabilities of the configured host.
func (d *detector) ReleaseFeatures() (ReleaseFeatures, error) { func (d *detector) ReleaseFeatures() (ReleaseFeatures, error) {
// TODO: immutableReleaseFullSupport // TODO: immutableReleaseFullSupport
// Once all supported GHES versions fully support immutable releases, we can // Once all supported GHES versions fully support immutable releases, we can
@ -475,6 +491,7 @@ const (
enterpriseWorkflowDispatchRunDetailsSupport = "3.21.0" enterpriseWorkflowDispatchRunDetailsSupport = "3.21.0"
) )
// ActionsFeatures detects GitHub Actions capabilities of the configured host.
func (d *detector) ActionsFeatures() (ActionsFeatures, error) { func (d *detector) ActionsFeatures() (ActionsFeatures, error) {
// TODO workflowDispatchRunDetailsCleanup // TODO workflowDispatchRunDetailsCleanup
// Once GHES 3.20 support ends, we don't need feature detection for workflow dispatch (i.e. run details support). // Once GHES 3.20 support ends, we don't need feature detection for workflow dispatch (i.e. run details support).

View file

@ -14,6 +14,8 @@ import (
) )
//go:generate moq -rm -out prompter_mock.go . Prompter //go:generate moq -rm -out prompter_mock.go . Prompter
// Prompter defines an interface for interactive user prompts.
type Prompter interface { type Prompter interface {
// generic prompts from go-gh // generic prompts from go-gh
@ -52,6 +54,7 @@ type Prompter interface {
MarkdownEditor(prompt string, defaultValue string, blankAllowed bool) (string, error) MarkdownEditor(prompt string, defaultValue string, blankAllowed bool) (string, error)
} }
// New creates a Prompter backed by the given editor command and IO streams.
func New(editorCmd string, io *iostreams.IOStreams) Prompter { func New(editorCmd string, io *iostreams.IOStreams) Prompter {
if io.AccessiblePrompterEnabled() { if io.AccessiblePrompterEnabled() {
return &accessiblePrompter{ return &accessiblePrompter{
@ -104,6 +107,7 @@ func (p *accessiblePrompter) addDefaultsToPrompt(prompt string, defaultValues []
return prompt return prompt
} }
// Select prompts the user to select an option from a list using accessible forms.
func (p *accessiblePrompter) Select(prompt, defaultValue string, options []string) (int, error) { func (p *accessiblePrompter) Select(prompt, defaultValue string, options []string) (int, error) {
var result int var result int
@ -136,6 +140,7 @@ func (p *accessiblePrompter) Select(prompt, defaultValue string, options []strin
return result, err return result, err
} }
// MultiSelect prompts the user to select multiple options using accessible forms.
func (p *accessiblePrompter) MultiSelect(prompt string, defaults []string, options []string) ([]int, error) { func (p *accessiblePrompter) MultiSelect(prompt string, defaults []string, options []string) ([]int, error) {
var result []int var result []int
@ -174,6 +179,7 @@ func (p *accessiblePrompter) MultiSelect(prompt string, defaults []string, optio
return result, nil return result, nil
} }
// Input prompts the user to enter a string value using accessible forms.
func (p *accessiblePrompter) Input(prompt, defaultValue string) (string, error) { func (p *accessiblePrompter) Input(prompt, defaultValue string) (string, error) {
result := defaultValue result := defaultValue
prompt = p.addDefaultsToPrompt(prompt, []string{defaultValue}) prompt = p.addDefaultsToPrompt(prompt, []string{defaultValue})
@ -189,6 +195,7 @@ func (p *accessiblePrompter) Input(prompt, defaultValue string) (string, error)
return result, err return result, err
} }
// Password prompts the user to enter a password using accessible forms.
func (p *accessiblePrompter) Password(prompt string) (string, error) { func (p *accessiblePrompter) Password(prompt string) (string, error) {
var result string var result string
// EchoModePassword is not used as password masking is unsupported in huh. // EchoModePassword is not used as password masking is unsupported in huh.
@ -210,6 +217,7 @@ func (p *accessiblePrompter) Password(prompt string) (string, error) {
return result, nil return result, nil
} }
// Confirm prompts the user to confirm an action using accessible forms.
func (p *accessiblePrompter) Confirm(prompt string, defaultValue bool) (bool, error) { func (p *accessiblePrompter) Confirm(prompt string, defaultValue bool) (bool, error) {
result := defaultValue result := defaultValue
@ -233,6 +241,7 @@ func (p *accessiblePrompter) Confirm(prompt string, defaultValue bool) (bool, er
return result, nil return result, nil
} }
// AuthToken prompts the user to paste an authentication token using accessible forms.
func (p *accessiblePrompter) AuthToken() (string, error) { func (p *accessiblePrompter) AuthToken() (string, error) {
var result string var result string
// EchoModeNone and EchoModePassword both result in disabling echo mode // EchoModeNone and EchoModePassword both result in disabling echo mode
@ -257,6 +266,7 @@ func (p *accessiblePrompter) AuthToken() (string, error) {
return result, err return result, err
} }
// ConfirmDeletion prompts the user to type a value to confirm deletion using accessible forms.
func (p *accessiblePrompter) ConfirmDeletion(requiredValue string) error { func (p *accessiblePrompter) ConfirmDeletion(requiredValue string) error {
form := p.newForm( form := p.newForm(
huh.NewGroup( huh.NewGroup(
@ -274,6 +284,7 @@ func (p *accessiblePrompter) ConfirmDeletion(requiredValue string) error {
return form.Run() return form.Run()
} }
// InputHostname prompts the user to enter a hostname using accessible forms.
func (p *accessiblePrompter) InputHostname() (string, error) { func (p *accessiblePrompter) InputHostname() (string, error) {
var result string var result string
form := p.newForm( form := p.newForm(
@ -292,6 +303,7 @@ func (p *accessiblePrompter) InputHostname() (string, error) {
return result, nil return result, nil
} }
// MarkdownEditor prompts the user to edit markdown in an external editor using accessible forms.
func (p *accessiblePrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed bool) (string, error) { func (p *accessiblePrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed bool) (string, error) {
var result string var result string
skipOption := "skip" skipOption := "skip"
@ -329,6 +341,7 @@ func (p *accessiblePrompter) MarkdownEditor(prompt, defaultValue string, blankAl
return text, nil return text, nil
} }
// MultiSelectWithSearch prompts the user to select multiple options with search using accessible forms.
func (p *accessiblePrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) { func (p *accessiblePrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) {
return multiSelectWithSearch(p, prompt, searchPrompt, defaultValues, persistentValues, searchFunc) return multiSelectWithSearch(p, prompt, searchPrompt, defaultValues, persistentValues, searchFunc)
} }
@ -341,18 +354,22 @@ type surveyPrompter struct {
editorCmd string editorCmd string
} }
// Select prompts the user to select an option from a list using survey.
func (p *surveyPrompter) Select(prompt, defaultValue string, options []string) (int, error) { func (p *surveyPrompter) Select(prompt, defaultValue string, options []string) (int, error) {
return p.prompter.Select(prompt, defaultValue, options) return p.prompter.Select(prompt, defaultValue, options)
} }
// MultiSelect prompts the user to select multiple options using survey.
func (p *surveyPrompter) MultiSelect(prompt string, defaultValues, options []string) ([]int, error) { func (p *surveyPrompter) MultiSelect(prompt string, defaultValues, options []string) ([]int, error) {
return p.prompter.MultiSelect(prompt, defaultValues, options) return p.prompter.MultiSelect(prompt, defaultValues, options)
} }
// MultiSelectWithSearch prompts the user to select multiple options with search using survey.
func (p *surveyPrompter) MultiSelectWithSearch(prompt string, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) { func (p *surveyPrompter) MultiSelectWithSearch(prompt string, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) {
return multiSelectWithSearch(p, prompt, searchPrompt, defaultValues, persistentValues, searchFunc) return multiSelectWithSearch(p, prompt, searchPrompt, defaultValues, persistentValues, searchFunc)
} }
// MultiSelectSearchResult holds the results of a search within MultiSelectWithSearch.
type MultiSelectSearchResult struct { type MultiSelectSearchResult struct {
Keys []string Keys []string
Labels []string Labels []string
@ -503,18 +520,22 @@ func multiSelectWithSearch(p Prompter, prompt, searchPrompt string, defaultValue
} }
} }
// Input prompts the user to enter a string value using survey.
func (p *surveyPrompter) Input(prompt, defaultValue string) (string, error) { func (p *surveyPrompter) Input(prompt, defaultValue string) (string, error) {
return p.prompter.Input(prompt, defaultValue) return p.prompter.Input(prompt, defaultValue)
} }
// Password prompts the user to enter a password using survey.
func (p *surveyPrompter) Password(prompt string) (string, error) { func (p *surveyPrompter) Password(prompt string) (string, error) {
return p.prompter.Password(prompt) return p.prompter.Password(prompt)
} }
// Confirm prompts the user to confirm an action using survey.
func (p *surveyPrompter) Confirm(prompt string, defaultValue bool) (bool, error) { func (p *surveyPrompter) Confirm(prompt string, defaultValue bool) (bool, error) {
return p.prompter.Confirm(prompt, defaultValue) return p.prompter.Confirm(prompt, defaultValue)
} }
// AuthToken prompts the user to paste an authentication token using survey.
func (p *surveyPrompter) AuthToken() (string, error) { func (p *surveyPrompter) AuthToken() (string, error) {
var result string var result string
err := p.ask(&survey.Password{ err := p.ask(&survey.Password{
@ -523,6 +544,7 @@ func (p *surveyPrompter) AuthToken() (string, error) {
return result, err return result, err
} }
// ConfirmDeletion prompts the user to type a value to confirm deletion using survey.
func (p *surveyPrompter) ConfirmDeletion(requiredValue string) error { func (p *surveyPrompter) ConfirmDeletion(requiredValue string) error {
var result string var result string
return p.ask( return p.ask(
@ -539,6 +561,7 @@ func (p *surveyPrompter) ConfirmDeletion(requiredValue string) error {
})) }))
} }
// InputHostname prompts the user to enter a hostname using survey.
func (p *surveyPrompter) InputHostname() (string, error) { func (p *surveyPrompter) InputHostname() (string, error) {
var result string var result string
err := p.ask( err := p.ask(
@ -550,6 +573,7 @@ func (p *surveyPrompter) InputHostname() (string, error) {
return result, err return result, err
} }
// MarkdownEditor prompts the user to edit markdown in an external editor using survey.
func (p *surveyPrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed bool) (string, error) { func (p *surveyPrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed bool) (string, error) {
var result string var result string
err := p.ask(&surveyext.GhEditor{ err := p.ask(&surveyext.GhEditor{

View file

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// NewMockPrompter creates a MockPrompter for use in tests.
func NewMockPrompter(t *testing.T) *MockPrompter { func NewMockPrompter(t *testing.T) *MockPrompter {
m := &MockPrompter{ m := &MockPrompter{
t: t, t: t,
@ -22,6 +23,7 @@ func NewMockPrompter(t *testing.T) *MockPrompter {
return m return m
} }
// MockPrompter is a test double for Prompter that uses registered stubs for responses.
type MockPrompter struct { type MockPrompter struct {
t *testing.T t *testing.T
ghPrompter.PrompterMock ghPrompter.PrompterMock
@ -54,6 +56,7 @@ type multiSelectWithSearchStub struct {
fn func(string, string, []string, []string, func(string) MultiSelectSearchResult) ([]string, error) fn func(string, string, []string, []string, func(string) MultiSelectSearchResult) ([]string, error)
} }
// AuthToken returns a stubbed authentication token response.
func (m *MockPrompter) AuthToken() (string, error) { func (m *MockPrompter) AuthToken() (string, error) {
var s authTokenStub var s authTokenStub
if len(m.authTokenStubs) == 0 { if len(m.authTokenStubs) == 0 {
@ -64,6 +67,7 @@ func (m *MockPrompter) AuthToken() (string, error) {
return s.fn() return s.fn()
} }
// ConfirmDeletion returns a stubbed confirm-deletion response.
func (m *MockPrompter) ConfirmDeletion(prompt string) error { func (m *MockPrompter) ConfirmDeletion(prompt string) error {
var s confirmDeletionStub var s confirmDeletionStub
if len(m.confirmDeletionStubs) == 0 { if len(m.confirmDeletionStubs) == 0 {
@ -74,6 +78,7 @@ func (m *MockPrompter) ConfirmDeletion(prompt string) error {
return s.fn(prompt) return s.fn(prompt)
} }
// InputHostname returns a stubbed hostname input response.
func (m *MockPrompter) InputHostname() (string, error) { func (m *MockPrompter) InputHostname() (string, error) {
var s inputHostnameStub var s inputHostnameStub
if len(m.inputHostnameStubs) == 0 { if len(m.inputHostnameStubs) == 0 {
@ -84,6 +89,7 @@ func (m *MockPrompter) InputHostname() (string, error) {
return s.fn() return s.fn()
} }
// MarkdownEditor returns a stubbed markdown editor response.
func (m *MockPrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed bool) (string, error) { func (m *MockPrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed bool) (string, error) {
var s markdownEditorStub var s markdownEditorStub
if len(m.markdownEditorStubs) == 0 { if len(m.markdownEditorStubs) == 0 {
@ -97,6 +103,7 @@ func (m *MockPrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed
return s.fn(prompt, defaultValue, blankAllowed) return s.fn(prompt, defaultValue, blankAllowed)
} }
// MultiSelectWithSearch returns a stubbed multi-select with search response.
func (m *MockPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) { func (m *MockPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) {
var s multiSelectWithSearchStub var s multiSelectWithSearchStub
if len(m.multiSelectWithSearchStubs) == 0 { if len(m.multiSelectWithSearchStubs) == 0 {
@ -107,22 +114,27 @@ func (m *MockPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaul
return s.fn(prompt, searchPrompt, defaults, persistentOptions, searchFunc) return s.fn(prompt, searchPrompt, defaults, persistentOptions, searchFunc)
} }
// RegisterAuthToken registers a stub function for the AuthToken prompt.
func (m *MockPrompter) RegisterAuthToken(stub func() (string, error)) { func (m *MockPrompter) RegisterAuthToken(stub func() (string, error)) {
m.authTokenStubs = append(m.authTokenStubs, authTokenStub{fn: stub}) m.authTokenStubs = append(m.authTokenStubs, authTokenStub{fn: stub})
} }
// RegisterConfirmDeletion registers a stub function for the ConfirmDeletion prompt.
func (m *MockPrompter) RegisterConfirmDeletion(prompt string, stub func(string) error) { func (m *MockPrompter) RegisterConfirmDeletion(prompt string, stub func(string) error) {
m.confirmDeletionStubs = append(m.confirmDeletionStubs, confirmDeletionStub{prompt: prompt, fn: stub}) m.confirmDeletionStubs = append(m.confirmDeletionStubs, confirmDeletionStub{prompt: prompt, fn: stub})
} }
// RegisterInputHostname registers a stub function for the InputHostname prompt.
func (m *MockPrompter) RegisterInputHostname(stub func() (string, error)) { func (m *MockPrompter) RegisterInputHostname(stub func() (string, error)) {
m.inputHostnameStubs = append(m.inputHostnameStubs, inputHostnameStub{fn: stub}) m.inputHostnameStubs = append(m.inputHostnameStubs, inputHostnameStub{fn: stub})
} }
// RegisterMarkdownEditor registers a stub function for the MarkdownEditor prompt.
func (m *MockPrompter) RegisterMarkdownEditor(prompt string, stub func(string, string, bool) (string, error)) { func (m *MockPrompter) RegisterMarkdownEditor(prompt string, stub func(string, string, bool) (string, error)) {
m.markdownEditorStubs = append(m.markdownEditorStubs, markdownEditorStub{prompt: prompt, fn: stub}) m.markdownEditorStubs = append(m.markdownEditorStubs, markdownEditorStub{prompt: prompt, fn: stub})
} }
// Verify asserts that all registered stubs have been consumed.
func (m *MockPrompter) Verify() { func (m *MockPrompter) Verify() {
errs := []string{} errs := []string{}
if len(m.authTokenStubs) > 0 { if len(m.authTokenStubs) > 0 {
@ -143,10 +155,12 @@ func (m *MockPrompter) Verify() {
} }
} }
// AssertOptions asserts that the expected and actual option slices are equal.
func AssertOptions(t *testing.T, expected, actual []string) { func AssertOptions(t *testing.T, expected, actual []string) {
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
// IndexFor returns the index of the given answer in the options slice.
func IndexFor(options []string, answer string) (int, error) { func IndexFor(options []string, answer string) (int, error) {
for ix, a := range options { for ix, a := range options {
if a == answer { if a == answer {
@ -156,6 +170,7 @@ func IndexFor(options []string, answer string) (int, error) {
return -1, NoSuchAnswerErr(answer, options) return -1, NoSuchAnswerErr(answer, options)
} }
// IndexesFor returns the indices of the given answers in the options slice.
func IndexesFor(options []string, answers ...string) ([]int, error) { func IndexesFor(options []string, answers ...string) ([]int, error) {
indexes := make([]int, len(answers)) indexes := make([]int, len(answers))
for i, answer := range answers { for i, answer := range answers {
@ -168,10 +183,12 @@ func IndexesFor(options []string, answers ...string) ([]int, error) {
return indexes, nil return indexes, nil
} }
// NoSuchPromptErr returns an error indicating that no stub was registered for the given prompt.
func NoSuchPromptErr(prompt string) error { func NoSuchPromptErr(prompt string) error {
return fmt.Errorf("no such prompt '%s'", prompt) return fmt.Errorf("no such prompt '%s'", prompt)
} }
// NoSuchAnswerErr returns an error indicating that the answer was not found in the options.
func NoSuchAnswerErr(answer string, options []string) error { func NoSuchAnswerErr(answer string, options []string) error {
return fmt.Errorf("no such answer '%s' in [%s]", answer, strings.Join(options, ", ")) return fmt.Errorf("no such answer '%s' in [%s]", answer, strings.Join(options, ", "))
} }