learning_go/pkg/mod/github.com/dixonwille/wmenu@v4.0.2+incompatible/menu_test.go
a.pivkin 59666d0dd6 wer
2025-09-06 13:02:52 +03:00

711 lines
19 KiB
Go

package wmenu
import (
"bytes"
"errors"
"fmt"
"log"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
wlog "gopkg.in/dixonwille/wlog.v2"
)
var newMenuCases = []string{"Testing this menu.", "", "!@#$%^&*()"}
var setSeperatorCases = []string{"", ".", ",", "~"}
var defaultIconCases = []string{"", ",", "*", "~", "!", "@"}
var setTriesCases = []int{0, -4, 5}
var optionCases = []struct {
name string
value interface{}
def bool
function func(Opt) error
}{
{"Options", "Options", true, func(Opt) error { fmt.Println("testing option"); return nil }},
{"", "Options", true, func(Opt) error { fmt.Println("testing option"); return nil }},
{"Options", "", false, func(Opt) error { fmt.Println("testing option"); return nil }},
{"", "", false, nil},
{"", nil, false, nil},
}
var actionCases = []func([]Opt) error{
func(opts []Opt) error { fmt.Println(opts); return nil },
nil,
}
var addColorCases = []struct {
opt wlog.Color
que wlog.Color
res wlog.Color
err wlog.Color
}{
{wlog.None, wlog.None, wlog.None, wlog.None},
{wlog.Red, wlog.Green, wlog.Blue, wlog.Black},
{wlog.Yellow, wlog.White, wlog.Cyan, wlog.Magenta},
{wlog.BrightBlack, wlog.BrightBlue, wlog.BrightCyan, wlog.BrightGreen},
{wlog.BrightRed, wlog.BrightWhite, wlog.BrightYellow, wlog.BrightMagenta},
}
var errorCases = []struct {
input string
optFunction func(option Opt) error
defFunction func([]Opt) error
expected string
singDef bool
multiDef bool
}{
{"0\r\n", func(option Opt) error { return errors.New("Oops") }, nil, "Oops", false, false},
{"0\r\n", nil, func(opts []Opt) error { return errors.New("Oops") }, "Oops", false, false},
{"0 1\r\n", nil, func(opts []Opt) error { return errors.New("Oops") }, "Too many responses", false, false},
{"\r\n", func(option Opt) error { return errors.New("Oops") }, nil, "Oops", true, false},
{"\r\n", nil, func(opts []Opt) error { return errors.New("Oops") }, "Oops", true, false},
{"\r\n", nil, func(opts []Opt) error { return errors.New("Oops") }, "Oops", false, false},
{"\r\n", nil, func(opts []Opt) error { return errors.New("Oops") }, "Oops", true, true},
}
var ynCases = []struct {
input string
defFunc func([]Opt) error
expected string
def int
}{
{"\r\n", func(opts []Opt) error { return errors.New(opts[0].Text) }, "y", 0},
{"\r\n", func(opts []Opt) error { return errors.New(opts[0].Text) }, "n", 1},
{"YES\r\n", func(opts []Opt) error { return errors.New(opts[0].Text) }, "y", 0},
{"n\r\n", func(opts []Opt) error { return errors.New(opts[0].Text) }, "n", 0},
{" Yes \r\n", func(opts []Opt) error { return errors.New(opts[0].Text) }, "y", 0},
{"ahh\r\n", func(opts []Opt) error { return errors.New(opts[0].Text) }, "Invalid response: ahh", 0},
{"boo ahh\r\n", func(opts []Opt) error { return errors.New(opts[0].Text) }, "Too many responses", 0},
}
var trimCases = []struct {
input string
del string
expected string
}{
{"0 \r\n", " ", "0\r\n"},
{" 0\r\n", " ", "0\r\n"},
{" 0 \r\n", " ", "0\r\n"},
{"0 1 \r\n", " ", "0 1\r\n"},
{" 0 1\r\n", " ", "0 1\r\n"},
{" 0 1 \r\n", " ", "0 1\r\n"},
{" 0, 1 \r\n", ",", "0 1\r\n"},
{"0, 1,\r\n", ",", "0 1\r\n"},
{"0, 1, \r\n", ",", "0 1\r\n"},
}
func Example_optionValue() {
type NameEntity struct {
FirstName string
LastName string
}
reader := strings.NewReader("1\r\n") //Simulates the user typing "1" and hitting the [enter] key
optFunc := func(option Opt) error {
fmt.Println("Option 0 was chosen.")
return nil
}
actFunc := func(opts []Opt) error {
name, ok := opts[0].Value.(NameEntity)
if !ok {
log.Fatal("Could not cast option's value to NameEntity")
}
fmt.Printf("%s has an id of %d.\n", opts[0].Text, opts[0].ID)
fmt.Printf("Hello, %s %s.\n", name.FirstName, name.LastName)
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
menu.Action(actFunc)
menu.Option("Option 0", NameEntity{"Bill", "Bob"}, true, optFunc)
menu.Option("Option 1", NameEntity{"John", "Doe"}, false, nil)
menu.Option("Option 2", NameEntity{"Jane", "Doe"}, false, nil)
err := menu.Run()
if err != nil {
log.Fatal(err)
}
//Output:
//0) *Option 0
//1) Option 1
//2) Option 2
//Choose an option.
//Option 1 has an id of 1.
//Hello, John Doe.
}
func Example_yesNo() {
reader := strings.NewReader("y\r\n") //Simulates the user typing "y" and hitting the [enter] key
actFunc := func(opts []Opt) error {
fmt.Printf("%s has an id of %d.\n", opts[0].Text, opts[0].ID)
fmt.Printf("But has a value of %s.\n", opts[0].Value.(string))
return nil
}
menu := NewMenu("Would you like to start?")
menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
menu.Action(actFunc)
menu.IsYesNo(0)
err := menu.Run()
if err != nil {
log.Fatal(err)
}
//Output:
//Would you like to start? (Y/n)
//y has an id of 0.
//But has a value of yes.
}
func Example_simpleDefault() {
reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
optFunc := func(option Opt) error {
fmt.Fprint(os.Stdout, "Option 0 was chosen.")
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
menu.Option("Option 0", "0", true, optFunc)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", false, nil)
err := menu.Run()
if err != nil {
log.Fatal(err)
}
//Output:
//0) *Option 0
//1) Option 1
//2) Option 2
//Choose an option.
//Option 0 was chosen.
}
func Example_multiple() {
reader := strings.NewReader("1,2\r\n") //Simulates the user typing "1,2" and hitting the [enter] key
optFunc := func(option Opt) error {
fmt.Println("Option 0 was chosen.")
return nil
}
actFunc := func(opts []Opt) error {
for _, opt := range opts {
fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
}
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
menu.Action(actFunc)
menu.AllowMultiple()
menu.SetSeparator(",")
menu.Option("Option 0", "0", true, optFunc)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", true, nil)
err := menu.Run()
if err != nil {
log.Fatal(err)
}
//Output:
//0) *Option 0
//1) Option 1
//2) *Option 2
//Choose an option.
//Option 1 has an id of 1.
//Option 2 has an id of 2.
}
func Example_multipleDefault() {
reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
optFunc := func(option Opt) error {
fmt.Println("Option 0 was chosen.")
return nil
}
actFunc := func(opts []Opt) error {
for _, opt := range opts {
fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
}
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
menu.Action(actFunc)
menu.AllowMultiple()
menu.Option("Option 0", "0", true, optFunc)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", true, nil)
err := menu.Run()
if err != nil {
log.Fatal(err)
}
//Output:
//0) *Option 0
//1) Option 1
//2) *Option 2
//Choose an option.
//Option 0 has an id of 0.
//Option 2 has an id of 2.
}
func Example_errorNoResponse() {
reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
optFunc := func(option Opt) error {
fmt.Println("Option 0 was chosen.")
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
menu.Option("Option 0", "0", false, optFunc)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", false, nil)
err := menu.Run()
if err != nil {
if IsNoResponseErr(err) {
fmt.Println("We caught the err: " + err.Error())
} else {
log.Fatal(err)
}
}
//Output:
//0) Option 0
//1) Option 1
//2) Option 2
//Choose an option.
//We caught the err: No response
}
func Example_errorInvalid() {
reader := strings.NewReader("3\r\n") //Simulates the user typing "3" and hitting the [enter] key
optFunc := func(option Opt) error {
fmt.Println("Option 0 was chosen.")
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
menu.Option("Option 0", "0", false, optFunc)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", false, nil)
err := menu.Run()
if err != nil {
if IsInvalidErr(err) {
fmt.Println("We caught the err: " + err.Error())
} else {
log.Fatal(err)
}
}
//Output:
//0) Option 0
//1) Option 1
//2) Option 2
//Choose an option.
//We caught the err: Invalid response: 3
}
func Example_errorTooMany() {
reader := strings.NewReader("1 2\r\n") //Simulates the user typing "1 2" and hitting the [enter] key
optFunc := func(option Opt) error {
fmt.Println("Option 0 was chosen.")
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
menu.Option("Option 0", "0", false, optFunc)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", false, nil)
err := menu.Run()
if err != nil {
if IsTooManyErr(err) {
fmt.Println("We caught the err: " + err.Error())
} else {
log.Fatal(err)
}
}
//Output:
//0) Option 0
//1) Option 1
//2) Option 2
//Choose an option.
//We caught the err: Too many responses
}
func Example_errorDuplicate() {
reader := strings.NewReader("1 1\r\n") //Simulates the user typing "1 1" and hitting the [enter] key
optFunc := func(option Opt) error {
fmt.Println("Option 0 was chosen.")
return nil
}
actFunc := func(opts []Opt) error {
for _, opt := range opts {
fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
}
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
menu.Action(actFunc)
menu.AllowMultiple()
menu.Option("Option 0", "0", false, optFunc)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", false, nil)
err := menu.Run()
if err != nil {
if IsDuplicateErr(err) {
fmt.Println("We caught the err: " + err.Error())
} else {
log.Fatal(err)
}
}
//Output:
//0) Option 0
//1) Option 1
//2) Option 2
//Choose an option.
//We caught the err: Duplicated response: 1
}
func TestNewMenu(t *testing.T) {
assert := assert.New(t)
for _, c := range newMenuCases {
menu := NewMenu(c)
assert.Equal(c, menu.question)
assert.Nil(menu.function)
assert.Nil(menu.options)
assert.Equal(" ", menu.multiSeparator)
assert.False(menu.allowMultiple)
assert.False(menu.loopOnInvalid)
assert.False(menu.clear)
assert.NotNil(menu.ui)
assert.Equal(3, menu.tries)
}
}
func TestClearOnMenuRun(t *testing.T) {
menu := NewMenu("Testing")
menu.ClearOnMenuRun()
assert.True(t, menu.clear)
}
func TestSetSeperator(t *testing.T) {
menu := NewMenu("Testing")
for _, c := range setSeperatorCases {
menu.SetSeparator(c)
assert.Equal(t, c, menu.multiSeparator)
}
}
func TestSetTries(t *testing.T) {
menu := NewMenu("Testing")
for _, c := range setTriesCases {
menu.SetTries(c)
assert.Equal(t, c, menu.tries)
}
}
func TestLoopOnInvalid(t *testing.T) {
menu := NewMenu("Testing")
menu.LoopOnInvalid()
assert.True(t, menu.loopOnInvalid)
}
func TestOption(t *testing.T) {
assert := assert.New(t)
menu := NewMenu("Testing")
for i, c := range optionCases {
menu.Option(c.name, c.value, c.def, c.function)
require.Equal(t, i+1, len(menu.options))
assert.Equal(i, menu.options[i].ID)
assert.Equal(c.name, menu.options[i].Text)
assert.Equal(c.def, menu.options[i].isDefault)
assert.Equal(c.value, menu.options[i].Value)
if c.function != nil {
assert.NotNil(menu.options[i].function)
} else {
assert.Nil(menu.options[i].function)
}
}
}
func TestAction(t *testing.T) {
menu := NewMenu("Testing")
for _, c := range actionCases {
menu.Action(c)
if c != nil {
assert.NotNil(t, menu.function)
} else {
assert.Nil(t, menu.function)
}
}
}
func TestAllowMultiple(t *testing.T) {
menu := NewMenu("Testing")
menu.AllowMultiple()
assert.True(t, menu.allowMultiple)
}
func TestAddColor(t *testing.T) {
menu := NewMenu("Testing")
for _, c := range addColorCases {
menu.AddColor(c.opt, c.que, c.res, c.err)
//Nothing to assert on just make sure the function does not fail
}
}
func TestSetDefaultIcon(t *testing.T) {
menu := NewMenu("Testing")
for _, c := range defaultIconCases {
menu.SetDefaultIcon(c)
assert.Equal(t, c, menu.defIcon)
}
}
func TestClearInAsk(t *testing.T) {
stdOut := initTest()
reader := strings.NewReader("1\r\n") //Simulates the user typing "1" and hitting the [enter] key
optFunc := func(option Opt) error {
assert.Fail(t, "Should not have called Option 0's function")
return nil
}
actFunc := func(opts []Opt) error {
assert.Len(t, opts, 1)
assert.Equal(t, 1, opts[0].ID)
assert.Equal(t, "Option 1", opts[0].Text)
assert.Nil(t, opts[0].function)
assert.False(t, opts[0].isDefault)
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, stdOut, stdOut)
menu.Action(actFunc)
menu.ClearOnMenuRun()
menu.Option("Option 0", "0", true, optFunc)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", true, nil)
err := menu.Run()
if err != nil {
assert.Fail(t, err.Error())
}
}
func TestDefaultAction(t *testing.T) {
stdOut := initTest()
reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
optFunc := func(option Opt) error {
assert.Fail(t, "Should not have called option 0's function")
return nil
}
actFunc := func(opts []Opt) error {
assert.Len(t, opts, 1)
assert.Equal(t, -1, opts[0].ID)
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, stdOut, stdOut)
menu.Action(actFunc)
menu.Option("Option 0", "0", false, optFunc)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", false, nil)
// spew.Dump(menu)
err := menu.Run()
if err != nil {
assert.Fail(t, err.Error())
}
}
func TestDefaultActionWithDefaultOption(t *testing.T) {
stdOut := initTest()
reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
optFunc := func(option Opt) error {
assert.Fail(t, "Should not have called option 0's function")
return nil
}
actFunc := func(opts []Opt) error {
assert.Len(t, opts, 1)
assert.Equal(t, 1, opts[0].ID)
assert.Equal(t, "Option 1", opts[0].Text)
assert.Nil(t, opts[0].function)
assert.True(t, opts[0].isDefault)
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, stdOut, stdOut)
menu.Action(actFunc)
menu.Option("Option 0", "0", false, optFunc)
menu.Option("Option 1", "1", true, nil)
menu.Option("Option 2", "2", false, nil)
err := menu.Run()
if err != nil {
assert.Fail(t, err.Error())
}
}
func TestOptionsFunction(t *testing.T) {
stdOut := initTest()
reader := strings.NewReader("0\r\n") //Simulates the user typing "0" and hitting the [enter] key
optFunc := func(option Opt) error {
return nil
}
actFunc := func(opts []Opt) error {
assert.Fail(t, "Should not have called the menu's default function")
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, stdOut, stdOut)
menu.Action(actFunc)
menu.Option("Option 0", "0", false, optFunc)
menu.Option("Option 1", "1", true, nil)
menu.Option("Option 2", "2", false, nil)
err := menu.Run()
if err != nil {
assert.Fail(t, err.Error())
}
}
func TestWlogAskErr(t *testing.T) {
stdOut := initTest()
reader := strings.NewReader("1") //Simulates the user typing "1" without hitting [enter]. Can't happen when reader is os.Stdin
optFunc := func(option Opt) error {
assert.Fail(t, "Should not have called option 0's function")
return nil
}
actFunc := func(opts []Opt) error {
assert.Fail(t, "Should not have called the menu's default function")
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, stdOut, stdOut)
menu.Action(actFunc)
menu.Option("Option 0", "0", false, optFunc)
menu.Option("Option 1", "1", true, nil)
menu.Option("Option 2", "2", false, nil)
err := menu.Run()
if err != nil {
assert.Equal(t, "EOF", err.Error())
} else {
assert.Fail(t, "Expected to get an error of EOF but instead got no error")
}
}
func TestLetterForResponse(t *testing.T) {
stdOut := initTest()
reader := strings.NewReader("a\r\n") //Simulates the user typing "a" and hitting [enter].
optFunc := func(option Opt) error {
assert.Fail(t, "Should not have called option 0's function")
return nil
}
actFunc := func(opts []Opt) error {
assert.Fail(t, "Should not have called the menu's default function")
return nil
}
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, stdOut, stdOut)
menu.Action(actFunc)
menu.Option("Option 0", "0", false, optFunc)
menu.Option("Option 1", "1", true, nil)
menu.Option("Option 2", "2", false, nil)
err := menu.Run()
if err != nil {
require.True(t, IsInvalidErr(err))
e := err.(*MenuError)
assert.Equal(t, "a", e.Res)
} else {
assert.Fail(t, "Expected to get an Invalid Response error but instead got no error")
}
}
func TestActionError(t *testing.T) {
stdout := initTest()
for _, c := range errorCases {
reader := strings.NewReader(c.input)
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, stdout, stdout)
menu.Action(c.defFunction)
if c.multiDef {
menu.AllowMultiple()
}
menu.Option("Option 0", "0", c.singDef, c.optFunction)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", c.multiDef, nil)
err := menu.Run()
if err != nil {
assert.Equal(t, c.expected, err.Error())
} else {
assert.Fail(t, "Expected an error but did not get one")
}
}
}
func TestLoopAndTries(t *testing.T) {
stdOut := initTest()
optFunc := func(option Opt) error {
assert.Fail(t, "Should not have called option 0's function")
return nil
}
for _, c := range setTriesCases {
reader := strings.NewReader("a") //Simulates the user typing "a" and not hitting [enter].
menu := NewMenu("Choose an option.")
menu.ChangeReaderWriter(reader, stdOut, stdOut)
menu.SetTries(c)
menu.LoopOnInvalid()
menu.ClearOnMenuRun()
menu.Option("Option 0", "0", false, optFunc)
menu.Option("Option 1", "1", false, nil)
menu.Option("Option 2", "2", false, nil)
err := menu.Run()
if err != nil {
require.True(t, IsMenuErr(err))
e := err.(*MenuError)
assert.Equal(t, 0, e.TriesLeft)
} else {
assert.Fail(t, "Expected to get a general Menu error but instead got no error")
}
}
}
func TestYesNo(t *testing.T) {
for _, c := range ynCases {
stdOut := initTest()
reader := strings.NewReader(c.input)
menu := NewMenu("Yes or No")
menu.ChangeReaderWriter(reader, stdOut, stdOut)
menu.IsYesNo(c.def)
menu.Action(c.defFunc)
err := menu.Run()
if err != nil {
assert.Equal(t, c.expected, err.Error())
}
}
}
func TestTrim(t *testing.T) {
for _, c := range trimCases {
stdOut := initTest()
actOut := initTest()
reader := strings.NewReader(c.input)
menu := NewMenu("Testing")
menu.ChangeReaderWriter(reader, stdOut, stdOut)
menu.SetSeparator(c.del)
menu.Action(func(opts []Opt) error {
var actual []string
for _, opt := range opts {
actual = append(actual, opt.Text)
}
fmt.Fprintf(actOut, "%s\r\n", strings.Join(actual, " "))
return nil
})
menu.AllowMultiple()
menu.Option("0", nil, false, nil)
menu.Option("1", nil, false, nil)
err := menu.Run()
if err != nil {
assert.Fail(t, err.Error())
}
act, err := actOut.ReadString((byte)('\n'))
if err != nil {
assert.Fail(t, err.Error())
}
assert.Equal(t, c.expected, act)
}
}
func initTest() *bytes.Buffer {
var b []byte
return bytes.NewBuffer(b)
}