fix(sync): address code review issues in manual conflict resolution
Fixes from code review: - Fix duplicate check in merge logic (use else clause) - Handle io.EOF gracefully (treat as quit) - Add quit (q) option to abort resolution early - Add accept-all (a) option to auto-merge remaining conflicts - Fix skipped conflicts to keep local version (not auto-merge) - Handle json.MarshalIndent errors properly - Fix truncateText to use rune count for UTF-8 safety - Update help text with new options - Add UTF-8 and emoji test cases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,36 @@ func TestTruncateText(t *testing.T) {
|
||||
maxLen: 30,
|
||||
want: "line1 line2 line3",
|
||||
},
|
||||
{
|
||||
name: "very short max",
|
||||
input: "hello world",
|
||||
maxLen: 3,
|
||||
want: "...",
|
||||
},
|
||||
{
|
||||
name: "UTF-8 characters preserved",
|
||||
input: "Hello 世界!This is a test",
|
||||
maxLen: 12,
|
||||
want: "Hello 世界!...",
|
||||
},
|
||||
{
|
||||
name: "UTF-8 exact length",
|
||||
input: "日本語テスト",
|
||||
maxLen: 6,
|
||||
want: "日本語テスト",
|
||||
},
|
||||
{
|
||||
name: "UTF-8 truncate",
|
||||
input: "日本語テストです",
|
||||
maxLen: 6,
|
||||
want: "日本語...",
|
||||
},
|
||||
{
|
||||
name: "emoji handling",
|
||||
input: "Hello 🌍🌎🌏 World",
|
||||
maxLen: 12,
|
||||
want: "Hello 🌍🌎🌏...",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -164,6 +194,14 @@ func TestInteractiveConflictDisplay(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "both nil (edge case)",
|
||||
conflict: InteractiveConflict{
|
||||
IssueID: "test-6",
|
||||
Local: nil,
|
||||
Remote: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -177,22 +215,58 @@ func TestInteractiveConflictDisplay(t *testing.T) {
|
||||
func TestShowDetailedDiff(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
conflict := InteractiveConflict{
|
||||
IssueID: "test-1",
|
||||
Local: &beads.Issue{
|
||||
ID: "test-1",
|
||||
Title: "Local",
|
||||
UpdatedAt: now,
|
||||
tests := []struct {
|
||||
name string
|
||||
conflict InteractiveConflict
|
||||
}{
|
||||
{
|
||||
name: "both exist",
|
||||
conflict: InteractiveConflict{
|
||||
IssueID: "test-1",
|
||||
Local: &beads.Issue{
|
||||
ID: "test-1",
|
||||
Title: "Local",
|
||||
UpdatedAt: now,
|
||||
},
|
||||
Remote: &beads.Issue{
|
||||
ID: "test-1",
|
||||
Title: "Remote",
|
||||
UpdatedAt: now,
|
||||
},
|
||||
},
|
||||
},
|
||||
Remote: &beads.Issue{
|
||||
ID: "test-1",
|
||||
Title: "Remote",
|
||||
UpdatedAt: now,
|
||||
{
|
||||
name: "local nil",
|
||||
conflict: InteractiveConflict{
|
||||
IssueID: "test-2",
|
||||
Local: nil,
|
||||
Remote: &beads.Issue{
|
||||
ID: "test-2",
|
||||
Title: "Remote",
|
||||
UpdatedAt: now,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remote nil",
|
||||
conflict: InteractiveConflict{
|
||||
IssueID: "test-3",
|
||||
Local: &beads.Issue{
|
||||
ID: "test-3",
|
||||
Title: "Local",
|
||||
UpdatedAt: now,
|
||||
},
|
||||
Remote: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Just make sure it doesn't panic
|
||||
showDetailedDiff(conflict)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Just make sure it doesn't panic
|
||||
showDetailedDiff(tt.conflict)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintResolutionHelp(t *testing.T) {
|
||||
@@ -272,3 +346,34 @@ func TestInteractiveResolutionMerge(t *testing.T) {
|
||||
t.Errorf("Expected labels to contain 'bug' and 'feature', got %v", merged.Labels)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInteractiveResolutionChoices(t *testing.T) {
|
||||
// Test InteractiveResolution struct values
|
||||
tests := []struct {
|
||||
name string
|
||||
choice string
|
||||
issue *beads.Issue
|
||||
}{
|
||||
{"local", "local", &beads.Issue{ID: "test"}},
|
||||
{"remote", "remote", &beads.Issue{ID: "test"}},
|
||||
{"merged", "merged", &beads.Issue{ID: "test"}},
|
||||
{"skip", "skip", nil},
|
||||
{"quit", "quit", nil},
|
||||
{"accept-all", "accept-all", nil},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
res := InteractiveResolution{Choice: tt.choice, Issue: tt.issue}
|
||||
if res.Choice != tt.choice {
|
||||
t.Errorf("Expected choice %q, got %q", tt.choice, res.Choice)
|
||||
}
|
||||
if tt.issue == nil && res.Issue != nil {
|
||||
t.Errorf("Expected nil issue, got %v", res.Issue)
|
||||
}
|
||||
if tt.issue != nil && res.Issue == nil {
|
||||
t.Errorf("Expected non-nil issue, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user