From cc375fe467481b57ca8a542b02e957a3ad7bba6f Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Thu, 2 Jun 2022 17:30:42 +0200 Subject: [PATCH 01/15] Added an interface for all the public functions of the survey package Also includes a struct with function pass throughs to all the direct survey calls. --- survey.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/survey.go b/survey.go index 95136ebe..8b5f3fad 100644 --- a/survey.go +++ b/survey.go @@ -12,6 +12,55 @@ import ( "github.com/AlecAivazis/survey/v2/terminal" ) +//SurveyInterface includes all the functions a normal user uses when using survey +type SurveyInterface interface { + WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt + WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt + WithKeepFilter(KeepFilter bool) AskOpt + WithValidator(v Validator) AskOpt + WithPageSize(pageSize int) AskOpt + WithHelpInput(r rune) AskOpt + WithIcons(setIcons func(*IconSet)) AskOpt + WithShowCursor(ShowCursor bool) AskOpt + AskOne(p Prompt, response interface{}, opts ...AskOpt) error + Ask(qs []*Question, response interface{}, opts ...AskOpt) error +} + +type Survey struct{} + +//the following ten functions are just passthroughs to the actual functions, but they make sure that the survey struct is backwards compatible to calling the functions directly + +func (survey Survey) WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt { + return WithStdio(in, out, err) +} +func (survey Survey) WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt { + return WithFilter(filter) +} +func (survey Survey) WithKeepFilter(KeepFilter bool) AskOpt { + return WithKeepFilter(KeepFilter) +} +func (survey Survey) WithValidator(v Validator) AskOpt { + return WithValidator(v) +} +func (survey Survey) WithPageSize(pageSize int) AskOpt { + return WithPageSize(pageSize) +} +func (survey Survey) WithHelpInput(r rune) AskOpt { + return WithHelpInput(r) +} +func (survey Survey) WithIcons(setIcons func(*IconSet)) AskOpt { + return WithIcons(setIcons) +} +func (survey Survey) WithShowCursor(ShowCursor bool) AskOpt { + return WithShowCursor(ShowCursor) +} +func (survey Survey) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { + return AskOne(p, response, opts...) +} +func (survey Survey) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { + return Ask(qs, response, opts...) +} + // DefaultAskOptions is the default options on ask, using the OS stdio. func defaultAskOptions() *AskOptions { return &AskOptions{ From dc306a6fda3371a29fae32f669b350c958037759 Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Thu, 2 Jun 2022 18:04:53 +0200 Subject: [PATCH 02/15] Added a simple mock struct that always returns nil --- surveyMock.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 surveyMock.go diff --git a/surveyMock.go b/surveyMock.go new file mode 100644 index 00000000..1189e54c --- /dev/null +++ b/surveyMock.go @@ -0,0 +1,41 @@ +package survey + +import ( + "io" + + "github.com/AlecAivazis/survey/v2/terminal" +) + + +type SurveyMock struct{} + +//TODO implement the mock functionality +func (survey *SurveyMock) WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt { + return nil +} +func (survey *SurveyMock) WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt { + return nil +} +func (survey *SurveyMock) WithKeepFilter(KeepFilter bool) AskOpt { + return nil +} +func (survey *SurveyMock) WithValidator(v Validator) AskOpt { + return nil +} +func (survey *SurveyMock) WithPageSize(pageSize int) AskOpt { + return nil +} +func (survey *SurveyMock) WithHelpInput(r rune) AskOpt { + return nil +} +func (survey *SurveyMock) WithIcons(setIcons func(*IconSet)) AskOpt { + return nil +} +func (survey *SurveyMock) WithShowCursor(ShowCursor bool) AskOpt { + return nil +} +func (survey *SurveyMock) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { + return nil +func (survey *SurveyMock) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { + return nil +} From 5e8fe73081b75a7bf437435bff76e2c2531ab602 Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Mon, 20 Jun 2022 09:34:14 +0200 Subject: [PATCH 03/15] Added missing bracket --- surveyMock.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surveyMock.go b/surveyMock.go index 1189e54c..b91b4906 100644 --- a/surveyMock.go +++ b/surveyMock.go @@ -6,7 +6,6 @@ import ( "github.com/AlecAivazis/survey/v2/terminal" ) - type SurveyMock struct{} //TODO implement the mock functionality @@ -36,6 +35,7 @@ func (survey *SurveyMock) WithShowCursor(ShowCursor bool) AskOpt { } func (survey *SurveyMock) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { return nil +} func (survey *SurveyMock) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { return nil } From 155eb185b3364bc52c9a8df0bad28e153cded17b Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Mon, 20 Jun 2022 13:38:12 +0200 Subject: [PATCH 04/15] Implemented the mock functionality --- surveyMock.go | 68 ++++++++++++++++++++++++++++++++++++++-------- surveyMock_test.go | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 surveyMock_test.go diff --git a/surveyMock.go b/surveyMock.go index b91b4906..4c6dd1f3 100644 --- a/surveyMock.go +++ b/surveyMock.go @@ -3,39 +3,83 @@ package survey import ( "io" + "github.com/AlecAivazis/survey/v2/core" "github.com/AlecAivazis/survey/v2/terminal" ) -type SurveyMock struct{} +type SurveyMock struct { + singleResponse interface{} + multipleResponses map[string]interface{} + LastPrompt Prompt +} -//TODO implement the mock functionality -func (survey *SurveyMock) WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt { +// Not implemented, because it does not affect the mock +func (mock *SurveyMock) WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt { return nil } -func (survey *SurveyMock) WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt { + +// Not implemented, because it does not affect the mock +func (mock *SurveyMock) WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt { return nil } -func (survey *SurveyMock) WithKeepFilter(KeepFilter bool) AskOpt { + +// Not implemented, because it does not affect the mock +func (mock *SurveyMock) WithKeepFilter(KeepFilter bool) AskOpt { return nil } -func (survey *SurveyMock) WithValidator(v Validator) AskOpt { + +// Not implemented, because it does not affect the mock +func (mock *SurveyMock) WithValidator(v Validator) AskOpt { return nil } -func (survey *SurveyMock) WithPageSize(pageSize int) AskOpt { + +// Not implemented, because it does not affect the mock +func (mock *SurveyMock) WithPageSize(pageSize int) AskOpt { return nil } -func (survey *SurveyMock) WithHelpInput(r rune) AskOpt { + +// Not implemented, because it does not affect the mock +func (mock *SurveyMock) WithHelpInput(r rune) AskOpt { return nil } -func (survey *SurveyMock) WithIcons(setIcons func(*IconSet)) AskOpt { + +// Not implemented, because it does not affect the mock +func (mock *SurveyMock) WithIcons(setIcons func(*IconSet)) AskOpt { return nil } -func (survey *SurveyMock) WithShowCursor(ShowCursor bool) AskOpt { + +// Not implemented, because it does not affect the mock +func (mock *SurveyMock) WithShowCursor(ShowCursor bool) AskOpt { return nil } -func (survey *SurveyMock) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { + +func (mock *SurveyMock) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { + err := core.WriteAnswer(response, "", mock.singleResponse) + if err != nil { + // panicing is fine inside a mock + panic(err) + } return nil } -func (survey *SurveyMock) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { + +func (mock *SurveyMock) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { + for _, q := range qs { + + err := core.WriteAnswer(response, q.Name, mock.multipleResponses[q.Name]) + if err != nil { + // panicing is fine inside a mock + panic(err) + } + + } + return nil } + +func (mock *SurveyMock) SetResponse(response interface{}) { + if val, ok := response.(map[string]interface{}); ok { + mock.multipleResponses = val + } else { + mock.singleResponse = response + } +} diff --git a/surveyMock_test.go b/surveyMock_test.go new file mode 100644 index 00000000..07b73e16 --- /dev/null +++ b/surveyMock_test.go @@ -0,0 +1,50 @@ +package survey + +import ( + "testing" +) + +func TestSettingResponseAskOne(t *testing.T) { + + mock := SurveyMock{} + mock.SetResponse(true) + + prompt := &Confirm{ + Message: "test", + } + + var response bool + mock.AskOne(prompt, &response) + + if !response { + t.Fatalf("Response was false but should have been true!") + } +} + +func TestSettingResponseAsk(t *testing.T) { + + mock := SurveyMock{} + + test := make(map[string]interface{}) + test["test"] = true + + mock.SetResponse(test) + + questions := []*Question{ + { + Name: "test", + Prompt: &Confirm{ + Message: "testing", + }, + }, + } + + answer := struct { + Test bool + }{} + mock.Ask(questions, &answer) + + if !answer.Test { + t.Fatalf("Response was false but should have been true!") + } +} From ed80995a62a2574cefab9a5178fa2c0f08223d50 Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Mon, 20 Jun 2022 13:52:18 +0200 Subject: [PATCH 05/15] Added documentation for the new mock --- README.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/README.md b/README.md index ac2c6fc7..6ac34834 100644 --- a/README.md +++ b/README.md @@ -442,6 +442,10 @@ survey.AskOne( ## Testing +There are two ways to test a program using survey. Using a mock is recommended because it leads to faster, less brittle tests. + +### Using a simulated terminal + You can test your program's interactive prompts using [go-expect](https://github.com/Netflix/go-expect). The library can be used to expect a match on stdout and respond on stdin. Since `os.Stdout` in a `go test` process is not a TTY, if you are manipulating the cursor or using `survey`, you will need a way to interpret terminal / ANSI escape sequences @@ -450,6 +454,65 @@ stdio to an in-memory [virtual terminal](https://github.com/hinshun/vt10x). For some examples, you can see any of the tests in this repo. +### Using a mock + +Instead of calling the survey functions directly, you can create a survey struct and call the functions from there. + +```golang + +survey := survey.Survey{} + +response := false +prompt := &survey.Confirm{ + Message: "Do you like pie?", +} +survey.AskOne(prompt, &response) + +``` + +If you create only one survey struct at the top level of your program and pass it to all functions, you can test those functions by replacing the struct with a mock provided by survey. + +#### main + +```golang +func main() { + survey := survey.Survey{} + + AskForPie(survey) +} + +func AskForPie(survey survey.SurveyInterface) bool { + response := false + prompt := &survey.Confirm{ + Message: "Do you like pie?", + } + survey.AskOne(prompt, &response) + + return response +} + +``` + +#### Test + +```golang +func TestAskForPie(t *testing.T) { + + //create mock + mock := survey.SurveyMock{} + //set the response the "user" should select + mock.setResponse(true) + + result := AskForPie(mock) + + //check output of the function + if result != true { + t.Fatalf("AskForPie returned false, but it should have returned true!") + } +} + +``` + ## FAQ ### What kinds of IO are supported by `survey`? From 96b15b2fa2cc445e1067d58eecc86b9008aeee50 Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Mon, 20 Jun 2022 16:00:53 +0200 Subject: [PATCH 06/15] Improved test structure to allow for further expantion --- surveyMock_test.go | 65 ++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/surveyMock_test.go b/surveyMock_test.go index 07b73e16..6e4ba277 100644 --- a/surveyMock_test.go +++ b/surveyMock_test.go @@ -4,47 +4,50 @@ import ( "testing" ) -func TestSettingResponseAskOne(t *testing.T) { +func TestMockAskOne(t *testing.T) { + t.Run("Setting Response", func(t *testing.T) { + mock := SurveyMock{} + mock.SetResponse(true) - mock := SurveyMock{} - mock.SetResponse(true) + prompt := &Confirm{ + Message: "test", + } - prompt := &Confirm{ - Message: "test", - } + var response bool + mock.AskOne(prompt, &response) - var response bool - mock.AskOne(prompt, &response) + if !response { + t.Fatalf("Response was false but should have been true!") + } + }) - if !response { - t.Fatalf("Response was false but should have been true!") - } } -func TestSettingResponseAsk(t *testing.T) { +func TestMockAsk(t *testing.T) { + t.Run("Setting Response", func(t *testing.T) { + mock := SurveyMock{} - mock := SurveyMock{} + test := make(map[string]interface{}) + test["test"] = true - test := make(map[string]interface{}) - test["test"] = true + mock.SetResponse(test) - mock.SetResponse(test) - - questions := []*Question{ - { - Name: "test", - Prompt: &Confirm{ - Message: "testing", + questions := []*Question{ + { + Name: "test", + Prompt: &Confirm{ + Message: "testing", + }, }, - }, - } + } - answer := struct { - Test bool - }{} - mock.Ask(questions, &answer) + answer := struct { + Test bool + }{} + mock.Ask(questions, &answer) - if !answer.Test { - t.Fatalf("Response was false but should have been true!") - } + if !answer.Test { + t.Fatalf("Response was false but should have been true!") + } + }) } From 80f6737335c6fa0eca1f47de4e8bab95296336b7 Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Mon, 20 Jun 2022 16:01:24 +0200 Subject: [PATCH 07/15] Removed old property that was left over from experimentation --- surveyMock.go | 1 - 1 file changed, 1 deletion(-) diff --git a/surveyMock.go b/surveyMock.go index 4c6dd1f3..38de34ad 100644 --- a/surveyMock.go +++ b/surveyMock.go @@ -10,7 +10,6 @@ import ( type SurveyMock struct { singleResponse interface{} multipleResponses map[string]interface{} - LastPrompt Prompt } // Not implemented, because it does not affect the mock From a462a5c3896c3e1119b9e03b7f6bddf76b52c736 Mon Sep 17 00:00:00 2001 From: SirRegion <88833410+SirRegion@users.noreply.github.com> Date: Tue, 21 Jun 2022 12:33:36 +0200 Subject: [PATCH 08/15] Removed unneccessary formated output from example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mislav Marohnić --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ac34834..34bb79ff 100644 --- a/README.md +++ b/README.md @@ -507,7 +507,7 @@ func TestAskForPie(t *testing.T) { //check output of the function if result != true { - t.Fatalf("AskForPie returned false, but it should have returned true!") + t.Fatal("AskForPie returned false, but it should have returned true") } } From b9c3cb5dff94306f46bffd0d482ce1ca870cea58 Mon Sep 17 00:00:00 2001 From: SirRegion <88833410+SirRegion@users.noreply.github.com> Date: Tue, 21 Jun 2022 14:01:29 +0200 Subject: [PATCH 09/15] Removed verbose comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mislav Marohnić --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34bb79ff..e93f7871 100644 --- a/README.md +++ b/README.md @@ -506,7 +506,7 @@ func TestAskForPie(t *testing.T) { result := AskForPie(mock) //check output of the function - if result != true { + if !result { t.Fatal("AskForPie returned false, but it should have returned true") } } From 5b784308ebd829fb43d326a9c01c8f8ba753838e Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Tue, 21 Jun 2022 14:38:39 +0200 Subject: [PATCH 10/15] Renamed the survey struct to Surveyor and updated all variables accordingly --- README.md | 10 ++++---- survey.go | 28 +++++++++++----------- surveyMock.go => surveyorMock.go | 24 +++++++++---------- surveyMock_test.go => surveyorMock_test.go | 4 ++-- 4 files changed, 33 insertions(+), 33 deletions(-) rename surveyMock.go => surveyorMock.go (58%) rename surveyMock_test.go => surveyorMock_test.go (94%) diff --git a/README.md b/README.md index 6ac34834..b4502f43 100644 --- a/README.md +++ b/README.md @@ -476,17 +476,17 @@ If you create only one survey struct at the top level of your program and pass i ```golang func main() { - survey := survey.Survey{} + surveyor := survey.Surveyor{} - AskForPie(survey) + AskForPie(surveyor) } -func AskForPie(survey survey.SurveyInterface) bool { +func AskForPie(surveyor survey.SurveyInterface) bool { response := false prompt := &survey.Confirm{ Message: "Do you like pie?", } - survey.AskOne(prompt, &response) + surveyor.AskOne(prompt, &response) return response } @@ -499,7 +499,7 @@ func AskForPie(survey survey.SurveyInterface) bool { func TestAskForPie(t *testing.T) { //create mock - mock := survey.SurveyMock{} + mock := survey.SurveyorMock{} //set the response the "user" should select mock.setResponse(true) diff --git a/survey.go b/survey.go index 8b5f3fad..6957978e 100644 --- a/survey.go +++ b/survey.go @@ -12,8 +12,8 @@ import ( "github.com/AlecAivazis/survey/v2/terminal" ) -//SurveyInterface includes all the functions a normal user uses when using survey -type SurveyInterface interface { +//SurveyorInterface includes all the functions a normal user uses when using survey +type SurveyorInterface interface { WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt WithKeepFilter(KeepFilter bool) AskOpt @@ -26,38 +26,38 @@ type SurveyInterface interface { Ask(qs []*Question, response interface{}, opts ...AskOpt) error } -type Survey struct{} +type Surveyor struct{} -//the following ten functions are just passthroughs to the actual functions, but they make sure that the survey struct is backwards compatible to calling the functions directly +//the following ten functions are just passthroughs to the actual functions, but they make sure that the surveyor struct is backwards compatible to calling the functions directly -func (survey Survey) WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt { +func (surveyor Surveyor) WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt { return WithStdio(in, out, err) } -func (survey Survey) WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt { +func (surveyor Surveyor) WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt { return WithFilter(filter) } -func (survey Survey) WithKeepFilter(KeepFilter bool) AskOpt { +func (surveyor Surveyor) WithKeepFilter(KeepFilter bool) AskOpt { return WithKeepFilter(KeepFilter) } -func (survey Survey) WithValidator(v Validator) AskOpt { +func (surveyor Surveyor) WithValidator(v Validator) AskOpt { return WithValidator(v) } -func (survey Survey) WithPageSize(pageSize int) AskOpt { +func (surveyor Surveyor) WithPageSize(pageSize int) AskOpt { return WithPageSize(pageSize) } -func (survey Survey) WithHelpInput(r rune) AskOpt { +func (surveyor Surveyor) WithHelpInput(r rune) AskOpt { return WithHelpInput(r) } -func (survey Survey) WithIcons(setIcons func(*IconSet)) AskOpt { +func (surveyor Surveyor) WithIcons(setIcons func(*IconSet)) AskOpt { return WithIcons(setIcons) } -func (survey Survey) WithShowCursor(ShowCursor bool) AskOpt { +func (surveyor Surveyor) WithShowCursor(ShowCursor bool) AskOpt { return WithShowCursor(ShowCursor) } -func (survey Survey) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { +func (surveyor Surveyor) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { return AskOne(p, response, opts...) } -func (survey Survey) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { +func (surveyor Surveyor) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { return Ask(qs, response, opts...) } diff --git a/surveyMock.go b/surveyorMock.go similarity index 58% rename from surveyMock.go rename to surveyorMock.go index 38de34ad..11a4652a 100644 --- a/surveyMock.go +++ b/surveyorMock.go @@ -7,52 +7,52 @@ import ( "github.com/AlecAivazis/survey/v2/terminal" ) -type SurveyMock struct { +type SurveyorMock struct { singleResponse interface{} multipleResponses map[string]interface{} } // Not implemented, because it does not affect the mock -func (mock *SurveyMock) WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt { +func (mock *SurveyorMock) WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt { return nil } // Not implemented, because it does not affect the mock -func (mock *SurveyMock) WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt { +func (mock *SurveyorMock) WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt { return nil } // Not implemented, because it does not affect the mock -func (mock *SurveyMock) WithKeepFilter(KeepFilter bool) AskOpt { +func (mock *SurveyorMock) WithKeepFilter(KeepFilter bool) AskOpt { return nil } // Not implemented, because it does not affect the mock -func (mock *SurveyMock) WithValidator(v Validator) AskOpt { +func (mock *SurveyorMock) WithValidator(v Validator) AskOpt { return nil } // Not implemented, because it does not affect the mock -func (mock *SurveyMock) WithPageSize(pageSize int) AskOpt { +func (mock *SurveyorMock) WithPageSize(pageSize int) AskOpt { return nil } // Not implemented, because it does not affect the mock -func (mock *SurveyMock) WithHelpInput(r rune) AskOpt { +func (mock *SurveyorMock) WithHelpInput(r rune) AskOpt { return nil } // Not implemented, because it does not affect the mock -func (mock *SurveyMock) WithIcons(setIcons func(*IconSet)) AskOpt { +func (mock *SurveyorMock) WithIcons(setIcons func(*IconSet)) AskOpt { return nil } // Not implemented, because it does not affect the mock -func (mock *SurveyMock) WithShowCursor(ShowCursor bool) AskOpt { +func (mock *SurveyorMock) WithShowCursor(ShowCursor bool) AskOpt { return nil } -func (mock *SurveyMock) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { +func (mock *SurveyorMock) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { err := core.WriteAnswer(response, "", mock.singleResponse) if err != nil { // panicing is fine inside a mock @@ -61,7 +61,7 @@ func (mock *SurveyMock) AskOne(p Prompt, response interface{}, opts ...AskOpt) e return nil } -func (mock *SurveyMock) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { +func (mock *SurveyorMock) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { for _, q := range qs { err := core.WriteAnswer(response, q.Name, mock.multipleResponses[q.Name]) @@ -75,7 +75,7 @@ func (mock *SurveyMock) Ask(qs []*Question, response interface{}, opts ...AskOpt return nil } -func (mock *SurveyMock) SetResponse(response interface{}) { +func (mock *SurveyorMock) SetResponse(response interface{}) { if val, ok := response.(map[string]interface{}); ok { mock.multipleResponses = val } else { diff --git a/surveyMock_test.go b/surveyorMock_test.go similarity index 94% rename from surveyMock_test.go rename to surveyorMock_test.go index 6e4ba277..11966c85 100644 --- a/surveyMock_test.go +++ b/surveyorMock_test.go @@ -6,7 +6,7 @@ import ( func TestMockAskOne(t *testing.T) { t.Run("Setting Response", func(t *testing.T) { - mock := SurveyMock{} + mock := SurveyorMock{} mock.SetResponse(true) prompt := &Confirm{ @@ -25,7 +25,7 @@ func TestMockAskOne(t *testing.T) { func TestMockAsk(t *testing.T) { t.Run("Setting Response", func(t *testing.T) { - mock := SurveyMock{} + mock := SurveyorMock{} test := make(map[string]interface{}) test["test"] = true From 5ba9322cb9c70a212631b99e6cdc912704f0d3ec Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Tue, 21 Jun 2022 14:54:50 +0200 Subject: [PATCH 11/15] Replaced all direct ask/askOne calls in the readme with struct calls --- README.md | 73 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 0e8ae1d8..7e3c3f01 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,10 @@ func main() { Age int // if the types don't match, survey will convert it }{} + surveyor := survey.Surveyor{} + // perform the questions - err := survey.Ask(qs, &answers) + err := surveyor.Ask(qs, &answers) if err != nil { fmt.Println(err.Error()) return @@ -87,13 +89,15 @@ prompt := &Select{ Validate: survey.Required, } +surveyor := survey.Surveyor{} + // or define a default for the single call to `AskOne` // the answer will get written to the color variable -survey.AskOne(prompt, &color, survey.WithValidator(survey.Required)) +surveyor.AskOne(prompt, &color, survey.WithValidator(survey.Required)) // or define a default for every entry in a list of questions // the answer will get copied into the matching field of the struct as shown above -survey.Ask(questions, &answers, survey.WithValidator(survey.Required)) +surveyor.Ask(questions, &answers, survey.WithValidator(survey.Required)) ``` ## Prompts @@ -107,7 +111,9 @@ name := "" prompt := &survey.Input{ Message: "ping", } -survey.AskOne(prompt, &name) + +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &name) ``` #### Suggestion Options @@ -123,8 +129,9 @@ prompt := &survey.Input{ return files }, } -} -survey.AskOne(prompt, &file) + +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &file) ``` ### Multiline @@ -136,7 +143,9 @@ text := "" prompt := &survey.Multiline{ Message: "ping", } -survey.AskOne(prompt, &text) + +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &text) ``` ### Password @@ -148,7 +157,9 @@ password := "" prompt := &survey.Password{ Message: "Please type your password", } -survey.AskOne(prompt, &password) + +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &password) ``` ### Confirm @@ -160,7 +171,9 @@ name := false prompt := &survey.Confirm{ Message: "Do you like pie?", } -survey.AskOne(prompt, &name) + +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &name) ``` ### Select @@ -173,7 +186,8 @@ prompt := &survey.Select{ Message: "Choose a color:", Options: []string{"red", "blue", "green"}, } -survey.AskOne(prompt, &color) +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &color) ``` Fields and values that come from a `Select` prompt can be one of two different things. If you pass an `int` @@ -190,7 +204,7 @@ and will paginate lists of options longer than that. This can be changed a numbe prompt := &survey.MultiSelect{..., PageSize: 10} // or as an option to Ask or AskOne -survey.AskOne(prompt, &days, survey.WithPageSize(10)) +surveyor.AskOne(prompt, &days, survey.WithPageSize(10)) ``` #### Select options description @@ -209,7 +223,9 @@ prompt := &survey.Select{ return "" }, } -survey.AskOne(prompt, &color) + +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &color) // Assuming that the user chose "red - My favorite color": fmt.Println(color) //=> "red" @@ -225,7 +241,9 @@ prompt := &survey.MultiSelect{ Message: "What days do you prefer:", Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}, } -survey.AskOne(prompt, &days) + +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &days) ``` Fields and values that come from a `MultiSelect` prompt can be one of two different things. If you pass an `int` @@ -242,7 +260,7 @@ and will paginate lists of options longer than that. This can be changed a numbe prompt := &survey.MultiSelect{..., PageSize: 10} // or as an option to Ask or AskOne -survey.AskOne(prompt, &days, survey.WithPageSize(10)) +surveyor.AskOne(prompt, &days, survey.WithPageSize(10)) ``` ### Editor @@ -259,8 +277,8 @@ prompt := &survey.Editor{ Message: "Shell code snippet", FileName: "*.sh", } - -survey.AskOne(prompt, &content) +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &content) ``` ## Filtering Options @@ -284,7 +302,7 @@ func myFilter(filterValue string, optValue string, optIndex int) bool { } // or define a default for all of the questions -survey.AskOne(prompt, &color, survey.WithFilter(myFilter)) +surveyor.AskOne(prompt, &color, survey.WithFilter(myFilter)) ``` ## Keeping the filter active @@ -302,7 +320,7 @@ However the user can prevent this from happening and keep the filter active for } // or define a default for all of the questions -survey.AskOne(prompt, &color, survey.WithKeepFilter(true)) +surveyor.AskOne(prompt, &color, survey.WithKeepFilter(true)) ``` ## Validation @@ -373,7 +391,8 @@ prompt := &survey.Input{ Help: "I couldn't come up with one.", } -survey.AskOne(prompt, &number, survey.WithHelpInput('^')) +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &number, survey.WithHelpInput('^')) ``` ## Changing the Icons @@ -392,7 +411,8 @@ prompt := &survey.Input{ Help: "I couldn't come up with one.", } -survey.AskOne(prompt, &number, survey.WithIcons(func(icons *survey.IconSet) { +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &number, survey.WithIcons(func(icons *survey.IconSet) { // you can set any icons icons.Question.Text = "⁇" // for more information on formatting the icons, see here: https://github.com/mgutz/ansi#style-format @@ -432,7 +452,9 @@ func (my *MyValue) WriteAnswer(name string, value interface{}) error { } myval := MyValue{} -survey.AskOne( + +surveyor := survey.Surveyor{} +surveyor.AskOne( &survey.Input{ Message: "Enter something:", }, @@ -459,14 +481,13 @@ For some examples, you can see any of the tests in this repo. Instead of calling the survey functions directly, you can create a survey struct and call the functions from there. ```golang - -survey := survey.Survey{} - response := false prompt := &survey.Confirm{ Message: "Do you like pie?", } -survey.AskOne(prompt, &response) + +surveyor := survey.Surveyor{} +surveyor.AskOne(prompt, &response) ``` @@ -530,7 +551,7 @@ When Survey reads a ^C byte (ASCII \x03, "end of text"), it interrupts the curre If you want to stop the process, handle the returned error in your code: ```go -err := survey.AskOne(prompt, &myVar) +err := surveyor.AskOne(prompt, &myVar) if err != nil { if err == terminal.InterruptErr { log.Fatal("interrupted") From 79a6d7505420502b01de9a7e1859b03e93825d3c Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Tue, 21 Jun 2022 15:24:56 +0200 Subject: [PATCH 12/15] Removed all functions (except Ask/askOne) from the surveyor interface --- survey.go | 35 +---------------------------------- surveyorMock.go | 43 ------------------------------------------- 2 files changed, 1 insertion(+), 77 deletions(-) diff --git a/survey.go b/survey.go index 6957978e..be4af5cd 100644 --- a/survey.go +++ b/survey.go @@ -14,46 +14,13 @@ import ( //SurveyorInterface includes all the functions a normal user uses when using survey type SurveyorInterface interface { - WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt - WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt - WithKeepFilter(KeepFilter bool) AskOpt - WithValidator(v Validator) AskOpt - WithPageSize(pageSize int) AskOpt - WithHelpInput(r rune) AskOpt - WithIcons(setIcons func(*IconSet)) AskOpt - WithShowCursor(ShowCursor bool) AskOpt AskOne(p Prompt, response interface{}, opts ...AskOpt) error Ask(qs []*Question, response interface{}, opts ...AskOpt) error } type Surveyor struct{} -//the following ten functions are just passthroughs to the actual functions, but they make sure that the surveyor struct is backwards compatible to calling the functions directly - -func (surveyor Surveyor) WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt { - return WithStdio(in, out, err) -} -func (surveyor Surveyor) WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt { - return WithFilter(filter) -} -func (surveyor Surveyor) WithKeepFilter(KeepFilter bool) AskOpt { - return WithKeepFilter(KeepFilter) -} -func (surveyor Surveyor) WithValidator(v Validator) AskOpt { - return WithValidator(v) -} -func (surveyor Surveyor) WithPageSize(pageSize int) AskOpt { - return WithPageSize(pageSize) -} -func (surveyor Surveyor) WithHelpInput(r rune) AskOpt { - return WithHelpInput(r) -} -func (surveyor Surveyor) WithIcons(setIcons func(*IconSet)) AskOpt { - return WithIcons(setIcons) -} -func (surveyor Surveyor) WithShowCursor(ShowCursor bool) AskOpt { - return WithShowCursor(ShowCursor) -} +//the following two functions are just passthroughs to the actual functions, but they make sure that the surveyor struct is backwards compatible to calling the functions directly func (surveyor Surveyor) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { return AskOne(p, response, opts...) } diff --git a/surveyorMock.go b/surveyorMock.go index 11a4652a..768de7e1 100644 --- a/surveyorMock.go +++ b/surveyorMock.go @@ -1,10 +1,7 @@ package survey import ( - "io" - "github.com/AlecAivazis/survey/v2/core" - "github.com/AlecAivazis/survey/v2/terminal" ) type SurveyorMock struct { @@ -12,46 +9,6 @@ type SurveyorMock struct { multipleResponses map[string]interface{} } -// Not implemented, because it does not affect the mock -func (mock *SurveyorMock) WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt { - return nil -} - -// Not implemented, because it does not affect the mock -func (mock *SurveyorMock) WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt { - return nil -} - -// Not implemented, because it does not affect the mock -func (mock *SurveyorMock) WithKeepFilter(KeepFilter bool) AskOpt { - return nil -} - -// Not implemented, because it does not affect the mock -func (mock *SurveyorMock) WithValidator(v Validator) AskOpt { - return nil -} - -// Not implemented, because it does not affect the mock -func (mock *SurveyorMock) WithPageSize(pageSize int) AskOpt { - return nil -} - -// Not implemented, because it does not affect the mock -func (mock *SurveyorMock) WithHelpInput(r rune) AskOpt { - return nil -} - -// Not implemented, because it does not affect the mock -func (mock *SurveyorMock) WithIcons(setIcons func(*IconSet)) AskOpt { - return nil -} - -// Not implemented, because it does not affect the mock -func (mock *SurveyorMock) WithShowCursor(ShowCursor bool) AskOpt { - return nil -} - func (mock *SurveyorMock) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { err := core.WriteAnswer(response, "", mock.singleResponse) if err != nil { From fe82ce8923aa69eb9d5199b0ad131383d67b07c3 Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Thu, 23 Jun 2022 08:48:29 +0200 Subject: [PATCH 13/15] Revert "Replaced all direct ask/askOne calls in the readme with struct calls" This reverts commit 5ba9322cb9c70a212631b99e6cdc912704f0d3ec. --- README.md | 73 ++++++++++++++++++++----------------------------------- 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 7e3c3f01..0e8ae1d8 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,8 @@ func main() { Age int // if the types don't match, survey will convert it }{} - surveyor := survey.Surveyor{} - // perform the questions - err := surveyor.Ask(qs, &answers) + err := survey.Ask(qs, &answers) if err != nil { fmt.Println(err.Error()) return @@ -89,15 +87,13 @@ prompt := &Select{ Validate: survey.Required, } -surveyor := survey.Surveyor{} - // or define a default for the single call to `AskOne` // the answer will get written to the color variable -surveyor.AskOne(prompt, &color, survey.WithValidator(survey.Required)) +survey.AskOne(prompt, &color, survey.WithValidator(survey.Required)) // or define a default for every entry in a list of questions // the answer will get copied into the matching field of the struct as shown above -surveyor.Ask(questions, &answers, survey.WithValidator(survey.Required)) +survey.Ask(questions, &answers, survey.WithValidator(survey.Required)) ``` ## Prompts @@ -111,9 +107,7 @@ name := "" prompt := &survey.Input{ Message: "ping", } - -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &name) +survey.AskOne(prompt, &name) ``` #### Suggestion Options @@ -129,9 +123,8 @@ prompt := &survey.Input{ return files }, } - -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &file) +} +survey.AskOne(prompt, &file) ``` ### Multiline @@ -143,9 +136,7 @@ text := "" prompt := &survey.Multiline{ Message: "ping", } - -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &text) +survey.AskOne(prompt, &text) ``` ### Password @@ -157,9 +148,7 @@ password := "" prompt := &survey.Password{ Message: "Please type your password", } - -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &password) +survey.AskOne(prompt, &password) ``` ### Confirm @@ -171,9 +160,7 @@ name := false prompt := &survey.Confirm{ Message: "Do you like pie?", } - -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &name) +survey.AskOne(prompt, &name) ``` ### Select @@ -186,8 +173,7 @@ prompt := &survey.Select{ Message: "Choose a color:", Options: []string{"red", "blue", "green"}, } -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &color) +survey.AskOne(prompt, &color) ``` Fields and values that come from a `Select` prompt can be one of two different things. If you pass an `int` @@ -204,7 +190,7 @@ and will paginate lists of options longer than that. This can be changed a numbe prompt := &survey.MultiSelect{..., PageSize: 10} // or as an option to Ask or AskOne -surveyor.AskOne(prompt, &days, survey.WithPageSize(10)) +survey.AskOne(prompt, &days, survey.WithPageSize(10)) ``` #### Select options description @@ -223,9 +209,7 @@ prompt := &survey.Select{ return "" }, } - -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &color) +survey.AskOne(prompt, &color) // Assuming that the user chose "red - My favorite color": fmt.Println(color) //=> "red" @@ -241,9 +225,7 @@ prompt := &survey.MultiSelect{ Message: "What days do you prefer:", Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}, } - -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &days) +survey.AskOne(prompt, &days) ``` Fields and values that come from a `MultiSelect` prompt can be one of two different things. If you pass an `int` @@ -260,7 +242,7 @@ and will paginate lists of options longer than that. This can be changed a numbe prompt := &survey.MultiSelect{..., PageSize: 10} // or as an option to Ask or AskOne -surveyor.AskOne(prompt, &days, survey.WithPageSize(10)) +survey.AskOne(prompt, &days, survey.WithPageSize(10)) ``` ### Editor @@ -277,8 +259,8 @@ prompt := &survey.Editor{ Message: "Shell code snippet", FileName: "*.sh", } -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &content) + +survey.AskOne(prompt, &content) ``` ## Filtering Options @@ -302,7 +284,7 @@ func myFilter(filterValue string, optValue string, optIndex int) bool { } // or define a default for all of the questions -surveyor.AskOne(prompt, &color, survey.WithFilter(myFilter)) +survey.AskOne(prompt, &color, survey.WithFilter(myFilter)) ``` ## Keeping the filter active @@ -320,7 +302,7 @@ However the user can prevent this from happening and keep the filter active for } // or define a default for all of the questions -surveyor.AskOne(prompt, &color, survey.WithKeepFilter(true)) +survey.AskOne(prompt, &color, survey.WithKeepFilter(true)) ``` ## Validation @@ -391,8 +373,7 @@ prompt := &survey.Input{ Help: "I couldn't come up with one.", } -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &number, survey.WithHelpInput('^')) +survey.AskOne(prompt, &number, survey.WithHelpInput('^')) ``` ## Changing the Icons @@ -411,8 +392,7 @@ prompt := &survey.Input{ Help: "I couldn't come up with one.", } -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &number, survey.WithIcons(func(icons *survey.IconSet) { +survey.AskOne(prompt, &number, survey.WithIcons(func(icons *survey.IconSet) { // you can set any icons icons.Question.Text = "⁇" // for more information on formatting the icons, see here: https://github.com/mgutz/ansi#style-format @@ -452,9 +432,7 @@ func (my *MyValue) WriteAnswer(name string, value interface{}) error { } myval := MyValue{} - -surveyor := survey.Surveyor{} -surveyor.AskOne( +survey.AskOne( &survey.Input{ Message: "Enter something:", }, @@ -481,13 +459,14 @@ For some examples, you can see any of the tests in this repo. Instead of calling the survey functions directly, you can create a survey struct and call the functions from there. ```golang + +survey := survey.Survey{} + response := false prompt := &survey.Confirm{ Message: "Do you like pie?", } - -surveyor := survey.Surveyor{} -surveyor.AskOne(prompt, &response) +survey.AskOne(prompt, &response) ``` @@ -551,7 +530,7 @@ When Survey reads a ^C byte (ASCII \x03, "end of text"), it interrupts the curre If you want to stop the process, handle the returned error in your code: ```go -err := surveyor.AskOne(prompt, &myVar) +err := survey.AskOne(prompt, &myVar) if err != nil { if err == terminal.InterruptErr { log.Fatal("interrupted") From f62075ab2b8261b444ff9beefd42df26e25a7557 Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Thu, 23 Jun 2022 08:53:00 +0200 Subject: [PATCH 14/15] Updated Readme with warning about the unstable mock interface --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e8ae1d8..1e2b1256 100644 --- a/README.md +++ b/README.md @@ -442,7 +442,7 @@ survey.AskOne( ## Testing -There are two ways to test a program using survey. Using a mock is recommended because it leads to faster, less brittle tests. +There are two ways to test a program using survey: ### Using a simulated terminal @@ -454,7 +454,14 @@ stdio to an in-memory [virtual terminal](https://github.com/hinshun/vt10x). For some examples, you can see any of the tests in this repo. -### Using a mock +### Using a mock (unstable API) + +#### Warning: + +> The Mock API is currently still unstable and subject to change. +> Once it's done, it will be the recommended way to test survey. + +> If you are unsure what to use right now, use a simulated terminal. Instead of calling the survey functions directly, you can create a survey struct and call the functions from there. From d70c6af4fe037f4226bd128fb79f553b9a7ba23b Mon Sep 17 00:00:00 2001 From: Jakob Gerstmayer Date: Thu, 23 Jun 2022 08:56:19 +0200 Subject: [PATCH 15/15] Styled warning a bit better --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e2b1256..c587b2f6 100644 --- a/README.md +++ b/README.md @@ -456,11 +456,9 @@ For some examples, you can see any of the tests in this repo. ### Using a mock (unstable API) -#### Warning: - +>**Warning:** > The Mock API is currently still unstable and subject to change. > Once it's done, it will be the recommended way to test survey. - > If you are unsure what to use right now, use a simulated terminal. Instead of calling the survey functions directly, you can create a survey struct and call the functions from there.