mirror of
https://github.com/yarlson/lnk.git
synced 2025-08-29 17:49:47 +02:00
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
272 lines
6.4 KiB
Go
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)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|