From 58aaad4c9c9078e5d7395d69800546ee596debd1 Mon Sep 17 00:00:00 2001 From: Kaj Kowalski Date: Thu, 18 Dec 2025 11:29:46 +0100 Subject: [PATCH] Add comprehensive edge case tests for status line functions --- main_test.go | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) diff --git a/main_test.go b/main_test.go index a6cbc97..c30a5b4 100644 --- a/main_test.go +++ b/main_test.go @@ -494,3 +494,328 @@ func TestFormatOutput_WithANSI(t *testing.T) { t.Errorf("formatOutput with ANSI = %q, expected both parts visible", stripped) } } + +// Additional edge case tests + +func TestFormatContextInfo_MaxTokens(t *testing.T) { + // Test when usage equals context size + usage := &TokenUsage{ + InputTokens: 200000, + CacheCreationTokens: 0, + CacheReadInputTokens: 0, + } + result := formatContextInfo(200000, usage) + expected := "200k/200k" + if result != expected { + t.Errorf("formatContextInfo(max) = %q, want %q", result, expected) + } +} + +func TestFormatContextInfo_OverflowTokens(t *testing.T) { + // Test when usage exceeds context size + usage := &TokenUsage{ + InputTokens: 250000, + CacheCreationTokens: 0, + CacheReadInputTokens: 0, + } + result := formatContextInfo(200000, usage) + expected := "250k/200k" + if result != expected { + t.Errorf("formatContextInfo(overflow) = %q, want %q", result, expected) + } +} + +func TestFormatContextInfo_AllCacheTypes(t *testing.T) { + // Test all cache token types contributing + usage := &TokenUsage{ + InputTokens: 10000, + CacheCreationTokens: 20000, + CacheReadInputTokens: 30000, + } + result := formatContextInfo(100000, usage) + expected := "60k/100k" + if result != expected { + t.Errorf("formatContextInfo(all cache) = %q, want %q", result, expected) + } +} + +func TestFormatContextInfo_OnlyCacheCreation(t *testing.T) { + usage := &TokenUsage{ + InputTokens: 0, + CacheCreationTokens: 5000, + CacheReadInputTokens: 0, + } + result := formatContextInfo(100000, usage) + expected := "5k/100k" + if result != expected { + t.Errorf("formatContextInfo(cache creation) = %q, want %q", result, expected) + } +} + +func TestFormatContextInfo_OnlyCacheRead(t *testing.T) { + usage := &TokenUsage{ + InputTokens: 0, + CacheCreationTokens: 0, + CacheReadInputTokens: 8000, + } + result := formatContextInfo(100000, usage) + expected := "8k/100k" + if result != expected { + t.Errorf("formatContextInfo(cache read) = %q, want %q", result, expected) + } +} + +func TestBuildStatusLine_EmptyDir(t *testing.T) { + data := &StatusInput{} + data.Model.DisplayName = "Model" + data.Workspace.CurrentDir = "" + data.ContextWindow.ContextWindowSize = 100000 + + left, right := buildStatusLine(data) + + // Should not panic, should produce valid output + if left == "" { + t.Error("buildStatusLine with empty dir should produce left output") + } + if right == "" { + t.Error("buildStatusLine with empty dir should produce right output") + } +} + +func TestBuildStatusLine_LongModelName(t *testing.T) { + data := &StatusInput{} + data.Model.DisplayName = "Claude 3.5 Sonnet with Extended Context" + data.Workspace.CurrentDir = "/home/user/my-very-long-project-name-here" + data.ContextWindow.ContextWindowSize = 200000 + data.ContextWindow.CurrentUsage = &TokenUsage{ + InputTokens: 50000, + } + + left, right := buildStatusLine(data) + + if !contains(left, "Claude 3.5 Sonnet with Extended Context") { + t.Errorf("long model name not in left: %q", left) + } + if !contains(left, "my-very-long-project-name-here") { + t.Errorf("long dir name not in left: %q", left) + } + if !contains(right, "50k/200k") { + t.Errorf("context info not in right: %q", right) + } +} + +func TestBuildStatusLine_NilUsage(t *testing.T) { + data := &StatusInput{} + data.Model.DisplayName = "Model" + data.Workspace.CurrentDir = "/test" + data.ContextWindow.ContextWindowSize = 100000 + data.ContextWindow.CurrentUsage = nil + + left, right := buildStatusLine(data) + + if !contains(right, "0/100k") { + t.Errorf("nil usage should show 0: %q", right) + } + if left == "" { + t.Error("left should not be empty") + } +} + +func TestBuildStatusLine_RootDir(t *testing.T) { + data := &StatusInput{} + data.Model.DisplayName = "Model" + data.Workspace.CurrentDir = "/" + data.ContextWindow.ContextWindowSize = 100000 + + left, _ := buildStatusLine(data) + + // filepath.Base("/") returns "/" + if !contains(left, "/") || !contains(left, "Model") { + t.Errorf("root dir handling failed: %q", left) + } +} + +func TestCalculatePadding_ExactFit(t *testing.T) { + // When content exactly fills the width + result := calculatePadding("12345", "67890", 10) + expected := 1 // max(10-5-5, 1) = max(0, 1) = 1 + if result != expected { + t.Errorf("calculatePadding(exact fit) = %d, want %d", result, expected) + } +} + +func TestCalculatePadding_LargeWidth(t *testing.T) { + result := calculatePadding("left", "right", 200) + expected := 200 - 4 - 5 // 191 + if result != expected { + t.Errorf("calculatePadding(large) = %d, want %d", result, expected) + } +} + +func TestStripANSI_256Color(t *testing.T) { + // 256-color escape sequences + input := "\033[38;5;196mred\033[0m" + result := stripANSI(input) + expected := "red" + if result != expected { + t.Errorf("stripANSI(256color) = %q, want %q", result, expected) + } +} + +func TestStripANSI_TrueColor(t *testing.T) { + // 24-bit true color escape sequences + input := "\033[38;2;255;0;0mred\033[0m" + result := stripANSI(input) + expected := "red" + if result != expected { + t.Errorf("stripANSI(truecolor) = %q, want %q", result, expected) + } +} + +func TestStripANSI_Multiline(t *testing.T) { + input := "\033[31mline1\033[0m\n\033[32mline2\033[0m" + result := stripANSI(input) + expected := "line1\nline2" + if result != expected { + t.Errorf("stripANSI(multiline) = %q, want %q", result, expected) + } +} + +func TestReadInputFromStdin_SingleLine(t *testing.T) { + reader := bufio.NewReader(strings.NewReader("single line")) + result := readInputFromStdin(reader) + if result != "single line" { + t.Errorf("readInputFromStdin(single) = %q, want 'single line'", result) + } +} + +func TestReadInputFromStdin_JSONLike(t *testing.T) { + jsonStr := `{"key": "value", "nested": {"a": 1}}` + reader := bufio.NewReader(strings.NewReader(jsonStr)) + result := readInputFromStdin(reader) + if result != jsonStr { + t.Errorf("readInputFromStdin(json) = %q, want %q", result, jsonStr) + } +} + +func TestParseStatusInput_AllFields(t *testing.T) { + jsonStr := `{ + "model": {"display_name": "FullTest"}, + "workspace": {"current_dir": "/full/path"}, + "context_window": { + "context_window_size": 150000, + "current_usage": { + "input_tokens": 1000, + "cache_creation_input_tokens": 2000, + "cache_read_input_tokens": 3000 + } + } + }` + data, err := parseStatusInput(jsonStr) + if err != nil { + t.Fatalf("parseStatusInput failed: %v", err) + } + if data.Model.DisplayName != "FullTest" { + t.Errorf("DisplayName = %q, want FullTest", data.Model.DisplayName) + } + if data.ContextWindow.CurrentUsage.CacheCreationTokens != 2000 { + t.Errorf("CacheCreationTokens = %d, want 2000", data.ContextWindow.CurrentUsage.CacheCreationTokens) + } + if data.ContextWindow.CurrentUsage.CacheReadInputTokens != 3000 { + t.Errorf("CacheReadInputTokens = %d, want 3000", data.ContextWindow.CurrentUsage.CacheReadInputTokens) + } +} + +func TestParseStatusInput_ExtraFields(t *testing.T) { + // JSON with extra unknown fields should still parse + jsonStr := `{"model": {"display_name": "Test", "unknown": "field"}, "extra": "data"}` + data, err := parseStatusInput(jsonStr) + if err != nil { + t.Fatalf("parseStatusInput with extra fields failed: %v", err) + } + if data.Model.DisplayName != "Test" { + t.Errorf("DisplayName = %q, want Test", data.Model.DisplayName) + } +} + +func TestTokenUsage_ZeroValues(t *testing.T) { + usage := &TokenUsage{ + InputTokens: 0, + CacheCreationTokens: 0, + CacheReadInputTokens: 0, + } + result := formatContextInfo(100000, usage) + expected := "0k/100k" + if result != expected { + t.Errorf("formatContextInfo(zero usage) = %q, want %q", result, expected) + } +} + +func TestStatuslineWidthOffset_Constant(t *testing.T) { + // Verify the constant is defined and reasonable + if statuslineWidthOffset <= 0 || statuslineWidthOffset > 20 { + t.Errorf("statuslineWidthOffset = %d, expected between 1 and 20", statuslineWidthOffset) + } +} + +// Benchmark tests + +func BenchmarkFormatContextInfo(b *testing.B) { + usage := &TokenUsage{ + InputTokens: 50000, + CacheCreationTokens: 10000, + CacheReadInputTokens: 5000, + } + for i := 0; i < b.N; i++ { + formatContextInfo(200000, usage) + } +} + +func BenchmarkStripANSI(b *testing.B) { + input := "\033[32m●\033[0m \033[35mOpus\033[0m \033[1;32m➜\033[0m \033[36mproject\033[0m" + for i := 0; i < b.N; i++ { + stripANSI(input) + } +} + +func BenchmarkParseStatusInput(b *testing.B) { + jsonStr := `{"model": {"display_name": "Test"}, "workspace": {"current_dir": "/test"}, "context_window": {"context_window_size": 200000, "current_usage": {"input_tokens": 50000}}}` + for i := 0; i < b.N; i++ { + parseStatusInput(jsonStr) + } +} + +func BenchmarkBuildStatusLine(b *testing.B) { + data := &StatusInput{} + data.Model.DisplayName = "Claude Opus" + data.Workspace.CurrentDir = "/home/user/project" + data.ContextWindow.ContextWindowSize = 200000 + data.ContextWindow.CurrentUsage = &TokenUsage{ + InputTokens: 50000, + } + for i := 0; i < b.N; i++ { + buildStatusLine(data) + } +} + +func BenchmarkCalculatePadding(b *testing.B) { + for i := 0; i < b.N; i++ { + calculatePadding("left side content", "right side", 120) + } +} + +func BenchmarkFormatOutput(b *testing.B) { + left := red + "left" + reset + right := green + "right" + reset + for i := 0; i < b.N; i++ { + formatOutput(left, right, 50) + } +} + +func BenchmarkReadInputFromStdin(b *testing.B) { + jsonStr := `{"model": {"display_name": "Test"}, "workspace": {"current_dir": "/test"}}` + for i := 0; i < b.N; i++ { + reader := bufio.NewReader(strings.NewReader(jsonStr)) + readInputFromStdin(reader) + } +}