Something went wrong. Try again.
A lightweight terminal messaging toolkit for Go CLIs
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332package msg_test
import ( "bytes" "errors" "fmt" "testing"
"go.followtheprocess.codes/msg" "go.followtheprocess.codes/snapshot" "go.followtheprocess.codes/test")
const ( successCode = "\x1b[1;32m" errorCode = "\x1b[1;31m" causeCode = "\x1b[1m" warnCode = "\x1b[1;33m" infoCode = "\x1b[1;36m" titleCode = "\x1b[96m" resetCode = "\x1b[0m")
func TestMain(m *testing.M) { // Disable colour auto-detection so CI passes msg.ColorEnabled(true) m.Run()}
func TestSuccess(t *testing.T) { buf := new(bytes.Buffer) msg.Fsuccess(buf, "Something went well: %v", 42)
want := fmt.Sprintf("%sSuccess%s: Something went well: 42\n", successCode, resetCode)
if got := buf.String(); got != want { t.Errorf("got %q, wanted %q", got, want) }}
func TestSuccessCaptured(t *testing.T) { successFunc := func() { msg.Success("Worked") } stdout, _ := test.CaptureOutput(t, func() error { successFunc()
return nil }) want := fmt.Sprintf("%sSuccess%s: Worked\n", successCode, resetCode)
if stdout != want { t.Errorf("got %q, wanted %q", stdout, want) }}
func TestError(t *testing.T) { buf := new(bytes.Buffer) msg.Ferror(buf, "Something broke: %v", true)
want := fmt.Sprintf("%sError%s: Something broke: true\n", errorCode, resetCode)
if got := buf.String(); got != want { t.Errorf("got %q, wanted %q", got, want) }}
func TestErrorCaptured(t *testing.T) { errorFunc := func() { msg.Error("Bad number (%v)", 42) } _, stderr := test.CaptureOutput(t, func() error { errorFunc()
return nil }) want := fmt.Sprintf("%sError%s: Bad number (42)\n", errorCode, resetCode)
if stderr != want { t.Errorf("got %q, wanted %q", stderr, want) }}
func TestErr(t *testing.T) { t.Run("nil", func(t *testing.T) { buf := new(bytes.Buffer)
var err error msg.Ferr(buf, err)
if buf.Len() != 0 { t.Fatalf("nil err wrote output: %s", buf.String()) } }) t.Run("plain", func(t *testing.T) { buf := new(bytes.Buffer) err := errors.New("Something broke") msg.Ferr(buf, err)
want := fmt.Sprintf("%sError%s: Something broke\n", errorCode, resetCode)
if got := buf.String(); got != want { t.Errorf("got %q, wanted %q", got, want) } })
t.Run("wrapped", func(t *testing.T) { buf := new(bytes.Buffer) root := errors.New("something broke") one := fmt.Errorf("could not frobnicate the baz: %w", root) two := fmt.Errorf("failed to process file: %w", one) three := fmt.Errorf("could not deposit money: %w", two) msg.Ferr(buf, three)
wantTemplate := `%[1]sError%[2]s: could not deposit money╰─ %[3]scause%[2]s: failed to process file ╰─ %[3]scause%[2]s: could not frobnicate the baz ╰─ %[3]scause%[2]s: something broke`
want := fmt.Sprintf(wantTemplate, errorCode, resetCode, causeCode)
if got := buf.String(); got != want { t.Errorf("got %q, wanted %q", got, want) } })}
func TestErrTree(t *testing.T) { cases := []struct { err error name string }{ { err: func() error { a := errors.New("disk full") b := errors.New("network down")
return fmt.Errorf("backup failed: %w", errors.Join(a, b)) }(), name: "multi at leaf", }, { err: func() error { deep := errors.New("connection refused") inner := fmt.Errorf("network: %w", deep) other := errors.New("disk full")
return fmt.Errorf("backup failed: %w", errors.Join(inner, other)) }(), name: "multi with chain in non-last branch", }, { err: func() error { deep := errors.New("connection refused") inner := fmt.Errorf("network: %w", deep) other := errors.New("disk full")
return fmt.Errorf("backup failed: %w", errors.Join(other, inner)) }(), name: "multi with chain in last branch", }, { err: func() error { deep := errors.New("connection refused") middle := fmt.Errorf("network down: %w", deep) other := errors.New("disk full") joined := errors.Join(other, middle) wrapped := fmt.Errorf("step 2: %w", joined)
return fmt.Errorf("pipeline failed: %w", wrapped) }(), name: "chain through multi to chain", }, }
for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { buf := new(bytes.Buffer) msg.Ferr(buf, tc.err)
snap := snapshot.New(t, snapshot.WithFormatter(snapshot.TextFormatter())) snap.Snap(buf.String()) }) }}
func TestErrCaptured(t *testing.T) { t.Run("plain", func(t *testing.T) { errorFunc := func() { err := errors.New("bang") msg.Err(err) } _, stderr := test.CaptureOutput(t, func() error { errorFunc()
return nil }) want := fmt.Sprintf("%sError%s: bang\n", errorCode, resetCode)
if stderr != want { t.Errorf("got %q, wanted %q", stderr, want) } }) t.Run("wrapped", func(t *testing.T) { errorFunc := func() { root := errors.New("bang") one := fmt.Errorf("dingle cannot dangle on version <2: %w", root) two := fmt.Errorf("could not read file: %w", one) three := fmt.Errorf("you have no money: %w", two) msg.Err(three) } wantTemplate := `%[1]sError%[2]s: you have no money╰─ %[3]scause%[2]s: could not read file ╰─ %[3]scause%[2]s: dingle cannot dangle on version <2 ╰─ %[3]scause%[2]s: bang`
_, stderr := test.CaptureOutput(t, func() error { errorFunc()
return nil }) want := fmt.Sprintf(wantTemplate, errorCode, resetCode, causeCode)
if stderr != want { t.Errorf("got %q, wanted %q", stderr, want) } })}
func TestWarn(t *testing.T) { buf := new(bytes.Buffer) msg.Fwarn(buf, "skipping directory %s", "./tmp")
want := fmt.Sprintf("%sWarning%s: skipping directory ./tmp\n", warnCode, resetCode)
if got := buf.String(); got != want { t.Errorf("got %q, wanted %q", got, want) }}
func TestWarnCaptured(t *testing.T) { warnFunc := func() { msg.Warn("Skipping something (%d)", 42) } stdout, _ := test.CaptureOutput(t, func() error { warnFunc()
return nil })
want := fmt.Sprintf("%sWarning%s: Skipping something (42)\n", warnCode, resetCode)
if stdout != want { t.Errorf("got %q, wanted %q", stdout, want) }}
func TestInfo(t *testing.T) { buf := new(bytes.Buffer) msg.Finfo(buf, "You have %d projects on GitHub", 27)
want := fmt.Sprintf("%sInfo%s: You have 27 projects on GitHub\n", infoCode, resetCode)
if got := buf.String(); got != want { t.Errorf("got %q, wanted %q", got, want) }}
func TestInfoCaptured(t *testing.T) { infoFunc := func() { msg.Info("You are %d years old", 29) } stdout, _ := test.CaptureOutput(t, func() error { infoFunc()
return nil })
want := fmt.Sprintf("%sInfo%s: You are 29 years old\n", infoCode, resetCode)
if stdout != want { t.Errorf("got %q, wanted %q", stdout, want) }}
func TestTitle(t *testing.T) { buf := new(bytes.Buffer) msg.Ftitle(buf, "Section Header")
want := fmt.Sprintf("\n%sSection Header%s\n\n", titleCode, resetCode)
if got := buf.String(); got != want { t.Errorf("got %q, wanted %q", got, want) }}
func TestTitleCaptured(t *testing.T) { titleFunc := func() { msg.Title("Section Header") }
stdout, _ := test.CaptureOutput(t, func() error { titleFunc()
return nil })
want := fmt.Sprintf("\n%sSection Header%s\n\n", titleCode, resetCode)
if stdout != want { t.Errorf("got %q, wanted %q", stdout, want) }}
func TestVisual(t *testing.T) { msg.Title("Your CLI output:") msg.Success("compiled 42 packages") msg.Warn("directory is empty, skipping") msg.Info("using value from config file")
fmt.Println()
err := errors.New("bad file permissions") one := fmt.Errorf("could not read DB config: %w", err) two := fmt.Errorf("failed to insert new record: %w", one) three := fmt.Errorf("could not complete transaction: %w", two)
msg.Err(three)}