package bonfire import ( "bytes" "errors" "strings" "testing" ) func TestNonTTYSpinner(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader(""), &buf, &buf) if err := out.Spinner("building", func() error { return nil }); err != nil { t.Fatal(err) } if got := buf.String(); got != "[...] building\n" { t.Fatalf("spinner = %q", got) } } func TestNonTTYProgress(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader(""), &buf, &buf) if err := out.Progress(10, func(p Progress) error { for i := 0; i < 10; i++ { p.Advance(1) } return nil }); err != nil { t.Fatal(err) } got := buf.String() for _, want := range []string{"[1/10] 10%", "[5/10] 50%", "[10/10] 100%"} { if !strings.Contains(got, want) { t.Fatalf("missing %q in %q", want, got) } } if strings.Contains(got, "\x1b[") { t.Fatalf("non-tty progress has ansi: %q", got) } } func TestNonTTYTable(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader(""), &buf, &buf) out.Table([]string{"plugin", "status"}, [][]string{{"golem15.blog", "active"}}) got := buf.String() if got != "plugin\tstatus\ngolem15.blog\tactive\n" { t.Fatalf("table = %q", got) } } func TestColorPolicy(t *testing.T) { t.Run("NO_COLOR", func(t *testing.T) { var buf bytes.Buffer out := newConsole(strings.NewReader(""), &buf, &buf, func(k string) string { if k == "NO_COLOR" { return "1" } if k == "FORCE_COLOR" { return "1" } return "" }, boolPtr(true), boolPtr(true)) out.Success("ok") if strings.Contains(buf.String(), "\x1b[") { t.Fatalf("NO_COLOR leaked ansi: %q", buf.String()) } }) t.Run("TERM=dumb", func(t *testing.T) { var buf bytes.Buffer out := newConsole(strings.NewReader(""), &buf, &buf, func(k string) string { if k == "TERM" { return "dumb" } if k == "FORCE_COLOR" { return "1" } return "" }, boolPtr(true), boolPtr(true)) out.Info("hi") if strings.Contains(buf.String(), "\x1b[") { t.Fatalf("TERM=dumb leaked ansi: %q", buf.String()) } }) t.Run("FORCE_COLOR", func(t *testing.T) { var buf bytes.Buffer out := newConsole(strings.NewReader(""), &buf, &buf, func(k string) string { if k == "FORCE_COLOR" { return "1" } return "" }, boolPtr(false), boolPtr(false)) out.Success("ok") if !strings.Contains(buf.String(), "\x1b[") { t.Fatalf("FORCE_COLOR did not enable ansi: %q", buf.String()) } }) t.Run("default non-tty", func(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader(""), &buf, &buf) out.Success("ok") if strings.Contains(buf.String(), "\x1b[") { t.Fatalf("non-tty default has ansi: %q", buf.String()) } }) } func TestPluginCommandRequiresColon(t *testing.T) { var buf bytes.Buffer _, err := NewRoot("app", []Command{{Name: "hello", Description: "bad"}}, &buf) if err == nil || !errors.Is(err, ErrCommandName) { t.Fatalf("want ErrCommandName, got %v", err) } if !strings.Contains(err.Error(), "hello") { t.Fatalf("error should name the command: %v", err) } } func TestKernelAndNamespacedCommands(t *testing.T) { var buf bytes.Buffer root, err := NewRoot("summer", []Command{ {Name: "build", Description: "Build the app"}, {Name: "dev", Description: "Watch and rebuild"}, {Name: "make:plugin", Description: "Scaffold a plugin from a vendor.plugin id"}, {Name: "greeter:hello", Description: "Say hello"}, }, &buf) if err != nil { t.Fatal(err) } root.SetArgs([]string{"make:plugin", "--help"}) if err := root.Execute(); err != nil { t.Fatal(err) } if !strings.Contains(buf.String(), "vendor.plugin") && !strings.Contains(buf.String(), "make:plugin") { t.Fatalf("help = %q", buf.String()) } } func TestHelpUsesSharedAdapter(t *testing.T) { var buf bytes.Buffer root, err := NewRoot("hello", []Command{{ Name: "greeter:hello", Description: "Print the configured application name", }}, &buf) if err != nil { t.Fatal(err) } root.SetArgs([]string{"greeter:hello", "--help"}) if err := root.Execute(); err != nil { t.Fatal(err) } got := buf.String() if !strings.Contains(got, "greeter:hello") || !strings.Contains(got, "Print the configured application name") { t.Fatalf("help = %q", got) } }