diff --git a/__snapshots__/test_complex_nested_structure.snap b/__snapshots__/test_complex_nested_structure.snap index f4a1406..567ef83 100644 --- a/__snapshots__/test_complex_nested_structure.snap +++ b/__snapshots__/test_complex_nested_structure.snap @@ -24,13 +24,13 @@ freeze_test.Post{ "user", }, Metadata: map[string]interface{}{ - "language": "en", + "language": "en-utf-8", "notifications": true, "preferences": map[string]interface{}{ - "email_frequency": "weekly", + "email_frequency": "bi-weekly", "notifications": true, }, - "theme": "dark", + "theme": "darker", }, }, Tags: []string{ diff --git a/__snapshots__/test_complex_nested_structure.snap.new b/__snapshots__/test_complex_nested_structure.snap.new index 567ef83..f4a1406 100644 --- a/__snapshots__/test_complex_nested_structure.snap.new +++ b/__snapshots__/test_complex_nested_structure.snap.new @@ -24,13 +24,13 @@ freeze_test.Post{ "user", }, Metadata: map[string]interface{}{ - "language": "en-utf-8", + "language": "en", "notifications": true, "preferences": map[string]interface{}{ - "email_frequency": "bi-weekly", + "email_frequency": "weekly", "notifications": true, }, - "theme": "darker", + "theme": "dark", }, }, Tags: []string{ diff --git a/cmd/tui/main.go b/cmd/tui/main.go index 40cd38b..6e46e4e 100644 --- a/cmd/tui/main.go +++ b/cmd/tui/main.go @@ -17,11 +17,11 @@ import ( var ( titleStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.AdaptiveColor{Light: "8", Dark: "8"}). + Foreground(lipgloss.AdaptiveColor{Light: "5", Dark: "5"}). Padding(0, 1) counterStyle = lipgloss.NewStyle(). - Foreground(lipgloss.AdaptiveColor{Light: "5", Dark: "5"}). + Foreground(lipgloss.AdaptiveColor{Light: "8", Dark: "8"}). Padding(0, 1) helpStyle = lipgloss.NewStyle(). diff --git a/freeze_test.go b/freeze_test.go index 6c26b5c..fa896f9 100644 --- a/freeze_test.go +++ b/freeze_test.go @@ -258,11 +258,11 @@ func TestComplexNestedStructure(t *testing.T) { CreatedAt: time.Date(2023, 1, 15, 10, 30, 0, 0, time.UTC), Roles: []string{"admin", "moderator", "user"}, Metadata: map[string]any{ - "theme": "darker", + "theme": "dark", "notifications": true, - "language": "en-utf-8", + "language": "en", "preferences": map[string]any{ - "email_frequency": "bi-weekly", + "email_frequency": "weekly", "notifications": true, }, }, diff --git a/internal/pretty/boxes.go b/internal/pretty/boxes.go index 9b13888..1eafc0b 100644 --- a/internal/pretty/boxes.go +++ b/internal/pretty/boxes.go @@ -18,7 +18,7 @@ func DiffSnapshotBox(old, newSnapshot *files.Snapshot, diffLines []diff.DiffLine snapshotFileName := files.SnapshotFileName(newSnapshot.Test) + ".snap" var sb strings.Builder - sb.WriteString("─── " + "Review Snapshot " + strings.Repeat("─", width-20) + "\n\n") + sb.WriteString("─── " + "Snapshot Diff " + strings.Repeat("─", width-15) + "\n\n") // TODO: maybe make helper functions for this, swap coloring between the key and the value // TODO: maybe show the snapshot file name in gray next to the "a/r/s" options @@ -29,10 +29,13 @@ func DiffSnapshotBox(old, newSnapshot *files.Snapshot, diffLines []diff.DiffLine sb.WriteString(Blue(" test: ") + newSnapshot.Test + "\n") sb.WriteString(Blue(" file: ") + snapshotFileName + "\n") sb.WriteString("\n") - sb.WriteString(strings.Repeat("─", width) + "\n") + // sb.WriteString(Red(" - old snapshot\n")) + // sb.WriteString(Green(" + new snapshot\n")) + // sb.WriteString("\n") // Calculate max line numbers for proper spacing - maxOldNum, maxNewNum := 0, 0 + maxOldNum := 0 + maxNewNum := 0 for _, dl := range diffLines { if dl.OldNumber > maxOldNum { maxOldNum = dl.OldNumber @@ -41,45 +44,67 @@ func DiffSnapshotBox(old, newSnapshot *files.Snapshot, diffLines []diff.DiffLine maxNewNum = dl.NewNumber } } - oldWidth := len(fmt.Sprintf("%d", maxOldNum)) - newWidth := len(fmt.Sprintf("%d", maxNewNum)) + // Use the larger of the two for consistent column width + maxLineNum := maxOldNum + if maxNewNum > maxLineNum { + maxLineNum = maxNewNum + } + lineNumWidth := len(fmt.Sprintf("%d", maxLineNum)) + + // Top bar with corner (account for both line number columns) + topBar := strings.Repeat("─", (lineNumWidth*2)+4) + "┬" + + strings.Repeat("─", width-(lineNumWidth*2)-1) + "\n" + sb.WriteString(topBar) for _, dl := range diffLines { - var oldNumStr, newNumStr string - var prefix string - var formatted string + var leftNum, rightNum, prefix, formatted string + // FIX: line number coloring is the same between old and new lines switch dl.Kind { case diff.DiffOld: - oldNumStr = fmt.Sprintf("%*d", oldWidth, dl.OldNumber) - newNumStr = strings.Repeat(" ", newWidth) - prefix = Red("−") + // For removed lines: show old line number on left, space on right, red - + leftNum = Red(fmt.Sprintf("%*d", lineNumWidth, dl.OldNumber)) + rightNum = strings.Repeat(" ", lineNumWidth) + prefix = Red("-") formatted = Red(dl.Line) case diff.DiffNew: - oldNumStr = strings.Repeat(" ", oldWidth) - newNumStr = fmt.Sprintf("%*d", newWidth, dl.NewNumber) + // For added lines: space on left, new line number on right, green + + leftNum = strings.Repeat(" ", lineNumWidth) + rightNum = Green(fmt.Sprintf("%*d", lineNumWidth, dl.NewNumber)) prefix = Green("+") formatted = Green(dl.Line) case diff.DiffShared: - oldNumStr = fmt.Sprintf("%*d", oldWidth, dl.OldNumber) - newNumStr = fmt.Sprintf("%*d", newWidth, dl.NewNumber) - prefix = " " + // For shared lines: show line number centered, │ separator (not gray) + leftNum = strings.Repeat(" ", lineNumWidth) + rightNum = Gray(fmt.Sprintf("%*d", lineNumWidth, dl.NewNumber)) + prefix = "│" formatted = dl.Line } - linePrefix := fmt.Sprintf("%s %s %s", Gray(oldNumStr), Gray(newNumStr), prefix) - display := fmt.Sprintf("%s %s", linePrefix, formatted) - // Adjust for actual display length considering ANSI codes - if len(dl.Line) > width-oldWidth-newWidth-8 { - formatted = formatted[:width-oldWidth-newWidth-11] + "..." - display = fmt.Sprintf("%s %s", linePrefix, formatted) + // Account for: 2 spaces padding + 2 line number columns + 2 spaces between + prefix + space + maxContentWidth := width - (lineNumWidth * 2) - 8 + if len(dl.Line) > maxContentWidth { + truncated := dl.Line[:maxContentWidth-3] + "..." + switch dl.Kind { + case diff.DiffOld: + formatted = Red(truncated) + case diff.DiffNew: + formatted = Green(truncated) + case diff.DiffShared: + formatted = truncated + } } + display := fmt.Sprintf("%s %s %s %s", leftNum, rightNum, prefix, formatted) sb.WriteString(fmt.Sprintf(" %s\n", display)) } - sb.WriteString(strings.Repeat("─", width) + "\n") + // Bottom bar with corner (account for both line number columns) + bottomBar := strings.Repeat("─", (lineNumWidth*2)+4) + "┴" + + strings.Repeat("─", width-(lineNumWidth*2)-1) + "\n" + sb.WriteString(bottomBar) + return sb.String() } diff --git a/internal/pretty/pretty_test.go b/internal/pretty/pretty_test.go index 4087d9c..740474c 100644 --- a/internal/pretty/pretty_test.go +++ b/internal/pretty/pretty_test.go @@ -1,9 +1,13 @@ package pretty_test import ( + "fmt" "os" + "strings" "testing" + "github.com/ptdewey/freeze/internal/diff" + "github.com/ptdewey/freeze/internal/files" "github.com/ptdewey/freeze/internal/pretty" ) @@ -156,3 +160,105 @@ func contains(s, substr string) bool { } return false } + +func TestDiffSnapshotBox(t *testing.T) { + os.Unsetenv("NO_COLOR") + os.Setenv("COLUMNS", "100") + defer os.Unsetenv("COLUMNS") + + oldContent := `line1 +line2 +line3` + + newContent := `line1 +modified +line3` + + oldSnap := &files.Snapshot{ + Title: "Test Snapshot", + Test: "TestExample", + Content: oldContent, + } + + newSnap := &files.Snapshot{ + Title: "Test Snapshot", + Test: "TestExample", + Content: newContent, + } + + diffLines := diff.Histogram(oldContent, newContent) + result := pretty.DiffSnapshotBox(oldSnap, newSnap, diffLines) + + // Check that result is not empty + if result == "" { + t.Error("DiffSnapshotBox returned empty string") + } + + // Check for header elements + if !strings.Contains(result, "Snapshot Diff") { + t.Error("Result should contain 'Snapshot Diff' header") + } + if !strings.Contains(result, "Test Snapshot") { + t.Error("Result should contain title") + } + if !strings.Contains(result, "TestExample") { + t.Error("Result should contain test name") + } + + // Check for diff content + if !strings.Contains(result, "line1") { + t.Error("Result should contain 'line1'") + } + if !strings.Contains(result, "modified") { + t.Error("Result should contain 'modified'") + } + if !strings.Contains(result, "line3") { + t.Error("Result should contain 'line3'") + } + + // Print the result for visual inspection + t.Logf("\n%s", result) +} + +func TestDiffSnapshotBoxLargeLineNumbers(t *testing.T) { + os.Unsetenv("NO_COLOR") + os.Setenv("COLUMNS", "120") + defer os.Unsetenv("COLUMNS") + + // Create content with more than 10 lines to test multi-digit line numbers + oldLines := make([]string, 15) + newLines := make([]string, 15) + for i := 0; i < 15; i++ { + oldLines[i] = fmt.Sprintf("line %d", i+1) + newLines[i] = fmt.Sprintf("line %d", i+1) + } + // Modify line 10 + oldLines[9] = "line 10 old" + newLines[9] = "line 10 new" + + oldContent := strings.Join(oldLines, "\n") + newContent := strings.Join(newLines, "\n") + + oldSnap := &files.Snapshot{ + Title: "Large Diff Test", + Test: "TestLargeDiff", + Content: oldContent, + } + + newSnap := &files.Snapshot{ + Title: "Large Diff Test", + Test: "TestLargeDiff", + Content: newContent, + } + + diffLines := diff.Histogram(oldContent, newContent) + result := pretty.DiffSnapshotBox(oldSnap, newSnap, diffLines) + + // Check that result is not empty + if result == "" { + t.Error("DiffSnapshotBox returned empty string") + } + + // Print the result for visual inspection + t.Logf("\n%s", result) +}