Files
lnk/cmd/output_test.go
Yar Kravtsov 7f10e1ce8a feat(output): implement configurable color and emoji output
Add new output formatting system with flags for color and emoji control:
- Introduce OutputConfig and Writer structs for flexible output handling
- Add --colors and --emoji/--no-emoji global flags
- Refactor commands to use new Writer for consistent formatting
- Separate error content from presentation for better flexibility
2025-08-03 14:33:44 +03:00

272 lines
6.4 KiB
Go

package cmd
import (
"bytes"
"os"
"strings"
"testing"
"github.com/yarlson/lnk/internal/core"
)
func TestOutputConfig(t *testing.T) {
tests := []struct {
name string
colors string
emoji bool
expectError bool
expectedColors bool
expectedEmoji bool
}{
{
name: "auto mode",
colors: "auto",
emoji: true,
expectError: false,
expectedColors: false, // TTY detection will return false in tests
expectedEmoji: true,
},
{
name: "always mode",
colors: "always",
emoji: false,
expectError: false,
expectedColors: true,
expectedEmoji: false,
},
{
name: "never mode",
colors: "never",
emoji: true,
expectError: false,
expectedColors: false,
expectedEmoji: true,
},
{
name: "invalid mode",
colors: "invalid",
emoji: true,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear NO_COLOR for consistent testing
_ = os.Unsetenv("NO_COLOR")
err := SetGlobalConfig(tt.colors, tt.emoji)
if tt.expectError && err == nil {
t.Errorf("expected error but got none")
}
if !tt.expectError && err != nil {
t.Errorf("unexpected error: %v", err)
}
if !tt.expectError {
if globalConfig.Colors != tt.expectedColors {
t.Errorf("expected colors %v, got %v", tt.expectedColors, globalConfig.Colors)
}
if globalConfig.Emoji != tt.expectedEmoji {
t.Errorf("expected emoji %v, got %v", tt.expectedEmoji, globalConfig.Emoji)
}
}
})
}
}
func TestNOCOLOREnvironmentVariable(t *testing.T) {
// Test NO_COLOR environment variable with auto mode
_ = os.Setenv("NO_COLOR", "1")
defer func() { _ = os.Unsetenv("NO_COLOR") }()
err := SetGlobalConfig("auto", true)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if globalConfig.Colors != false {
t.Errorf("expected colors disabled when NO_COLOR is set, got %v", globalConfig.Colors)
}
}
func TestWriterOutput(t *testing.T) {
tests := []struct {
name string
config OutputConfig
message Message
expectedOutput string
}{
{
name: "full formatting",
config: OutputConfig{Colors: true, Emoji: true},
message: Message{
Text: "test message",
Color: ColorRed,
Emoji: "✅",
Bold: true,
},
expectedOutput: "✅ \033[1m\033[31mtest message\033[0m",
},
{
name: "colors only",
config: OutputConfig{Colors: true, Emoji: false},
message: Message{
Text: "test message",
Color: ColorRed,
Emoji: "✅",
Bold: true,
},
expectedOutput: "\033[1m\033[31mtest message\033[0m",
},
{
name: "emoji only",
config: OutputConfig{Colors: false, Emoji: true},
message: Message{
Text: "test message",
Color: ColorRed,
Emoji: "✅",
Bold: true,
},
expectedOutput: "✅ test message",
},
{
name: "no formatting",
config: OutputConfig{Colors: false, Emoji: false},
message: Message{
Text: "test message",
Color: ColorRed,
Emoji: "✅",
Bold: true,
},
expectedOutput: "test message",
},
{
name: "plain message",
config: OutputConfig{Colors: true, Emoji: true},
message: Plain("plain text"),
expectedOutput: "plain text",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer
writer := NewWriter(&buf, tt.config)
writer.Write(tt.message)
if err := writer.Err(); err != nil {
t.Errorf("unexpected error: %v", err)
}
if buf.String() != tt.expectedOutput {
t.Errorf("expected %q, got %q", tt.expectedOutput, buf.String())
}
})
}
}
func TestPredefinedMessages(t *testing.T) {
tests := []struct {
name string
creator func(string) Message
text string
}{
{"Success", Success, "operation succeeded"},
{"Error", Error, "something failed"},
{"Warning", Warning, "be careful"},
{"Info", Info, "useful information"},
{"Target", Target, "target reached"},
{"Rocket", Rocket, "launching"},
{"Sparkles", Sparkles, "amazing"},
{"Link", Link, "connected"},
{"Plain", Plain, "no formatting"},
{"Bold", Bold, "emphasis"},
}
var buf bytes.Buffer
writer := NewWriter(&buf, OutputConfig{Colors: true, Emoji: true})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf.Reset()
msg := tt.creator(tt.text)
writer.Write(msg)
if err := writer.Err(); err != nil {
t.Errorf("unexpected error: %v", err)
}
output := buf.String()
if !strings.Contains(output, tt.text) {
t.Errorf("output should contain text %q, got %q", tt.text, output)
}
})
}
}
func TestStructuredErrors(t *testing.T) {
tests := []struct {
name string
err *core.LnkError
config OutputConfig
contains []string
notContains []string
}{
{
name: "structured error with full formatting",
err: &core.LnkError{
Message: "Something went wrong",
Suggestion: "Try this instead",
Path: "/some/path",
ErrorType: "test_error",
},
config: OutputConfig{Colors: true, Emoji: true},
contains: []string{"❌", "Something went wrong", "/some/path", "💡", "Try this instead"},
},
{
name: "structured error without emojis",
err: &core.LnkError{
Message: "Something went wrong",
Suggestion: "Try this instead",
Path: "/some/path",
ErrorType: "test_error",
},
config: OutputConfig{Colors: true, Emoji: false},
contains: []string{"Something went wrong", "/some/path", "Try this instead"},
notContains: []string{"❌", "💡"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer
w := NewWriter(&buf, tt.config)
// Test the component messages directly
_ = w.Write(Error(tt.err.Message))
if tt.err.Path != "" {
_ = w.WriteString("\n ")
_ = w.Write(Colored(tt.err.Path, ColorRed))
}
if tt.err.Suggestion != "" {
_ = w.WriteString("\n ")
_ = w.Write(Info(tt.err.Suggestion))
}
output := buf.String()
for _, expected := range tt.contains {
if !strings.Contains(output, expected) {
t.Errorf("output should contain %q, got %q", expected, output)
}
}
for _, notExpected := range tt.notContains {
if strings.Contains(output, notExpected) {
t.Errorf("output should not contain %q, got %q", notExpected, output)
}
}
})
}
}