Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions internal/github/pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,15 @@ func (p *PRInfo) StatusDescription() string {
case PRStateDraft:
return "Draft PR"
case PRStateOpen:
// Check for merge conflicts first - this takes priority over check status
if p.Mergeable == "CONFLICTING" {
return "Has conflicts"
}
switch p.CheckState {
case CheckStatePassing:
if p.Mergeable == "MERGEABLE" {
return "Ready to merge"
}
if p.Mergeable == "CONFLICTING" {
return "Has conflicts"
}
return "Checks passing"
case CheckStateFailing:
return "Checks failing"
Expand Down
19 changes: 17 additions & 2 deletions internal/github/pr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,27 @@ func TestPRInfoStatusDescription(t *testing.T) {
expected: "Ready to merge",
},
{
name: "open PR with conflicts",
name: "open PR with conflicts and passing checks",
prInfo: &PRInfo{State: PRStateOpen, CheckState: CheckStatePassing, Mergeable: "CONFLICTING"},
expected: "Has conflicts",
},
{
name: "open PR with failing checks",
name: "open PR with conflicts and failing checks",
prInfo: &PRInfo{State: PRStateOpen, CheckState: CheckStateFailing, Mergeable: "CONFLICTING"},
expected: "Has conflicts",
},
{
name: "open PR with conflicts and pending checks",
prInfo: &PRInfo{State: PRStateOpen, CheckState: CheckStatePending, Mergeable: "CONFLICTING"},
expected: "Has conflicts",
},
{
name: "open PR with conflicts and no checks",
prInfo: &PRInfo{State: PRStateOpen, CheckState: CheckStateNone, Mergeable: "CONFLICTING"},
expected: "Has conflicts",
},
{
name: "open PR with failing checks (no conflicts)",
prInfo: &PRInfo{State: PRStateOpen, CheckState: CheckStateFailing},
expected: "Checks failing",
},
Expand Down
39 changes: 21 additions & 18 deletions internal/ui/styles.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,27 +191,30 @@ func PRStatusBadge(pr *github.PRInfo) string {
icon = "D"
color = ColorPRDraft
case github.PRStateOpen:
switch pr.CheckState {
case github.CheckStatePassing:
if pr.Mergeable == "MERGEABLE" {
icon = "R" // Ready to merge
color = ColorPROpen
} else if pr.Mergeable == "CONFLICTING" {
icon = "C" // Conflicts
// Check for merge conflicts first - this takes priority over check status
if pr.Mergeable == "CONFLICTING" {
icon = "C" // Conflicts
color = ColorPRFailing
} else {
switch pr.CheckState {
case github.CheckStatePassing:
if pr.Mergeable == "MERGEABLE" {
icon = "R" // Ready to merge
color = ColorPROpen
} else {
icon = "P" // Passing
color = ColorPROpen
}
case github.CheckStateFailing:
icon = "F" // Failing
color = ColorPRFailing
} else {
icon = "P" // Passing
case github.CheckStatePending:
icon = "W" // Waiting/running
color = ColorPRPending
default:
icon = "O" // Open, no checks
color = ColorPROpen
}
case github.CheckStateFailing:
icon = "F" // Failing
color = ColorPRFailing
case github.CheckStatePending:
icon = "W" // Waiting/running
color = ColorPRPending
default:
icon = "O" // Open, no checks
color = ColorPROpen
}
}

Expand Down
129 changes: 129 additions & 0 deletions internal/ui/styles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package ui

import (
"strings"
"testing"

"github.com/bborn/workflow/internal/github"
)

func TestPRStatusBadge(t *testing.T) {
tests := []struct {
name string
prInfo *github.PRInfo
expectEmpty bool
expectIcon string
}{
{
name: "nil PR returns empty",
prInfo: nil,
expectEmpty: true,
},
{
name: "merged PR shows M",
prInfo: &github.PRInfo{State: github.PRStateMerged},
expectIcon: "M",
},
{
name: "closed PR shows X",
prInfo: &github.PRInfo{State: github.PRStateClosed},
expectIcon: "X",
},
{
name: "draft PR shows D",
prInfo: &github.PRInfo{State: github.PRStateDraft},
expectIcon: "D",
},
{
name: "open PR ready to merge shows R",
prInfo: &github.PRInfo{State: github.PRStateOpen, CheckState: github.CheckStatePassing, Mergeable: "MERGEABLE"},
expectIcon: "R",
},
{
name: "open PR with conflicts and passing checks shows C",
prInfo: &github.PRInfo{State: github.PRStateOpen, CheckState: github.CheckStatePassing, Mergeable: "CONFLICTING"},
expectIcon: "C",
},
{
name: "open PR with conflicts and failing checks shows C",
prInfo: &github.PRInfo{State: github.PRStateOpen, CheckState: github.CheckStateFailing, Mergeable: "CONFLICTING"},
expectIcon: "C",
},
{
name: "open PR with conflicts and pending checks shows C",
prInfo: &github.PRInfo{State: github.PRStateOpen, CheckState: github.CheckStatePending, Mergeable: "CONFLICTING"},
expectIcon: "C",
},
{
name: "open PR with conflicts and no checks shows C",
prInfo: &github.PRInfo{State: github.PRStateOpen, CheckState: github.CheckStateNone, Mergeable: "CONFLICTING"},
expectIcon: "C",
},
{
name: "open PR with failing checks (no conflicts) shows F",
prInfo: &github.PRInfo{State: github.PRStateOpen, CheckState: github.CheckStateFailing},
expectIcon: "F",
},
{
name: "open PR with pending checks shows W",
prInfo: &github.PRInfo{State: github.PRStateOpen, CheckState: github.CheckStatePending},
expectIcon: "W",
},
{
name: "open PR with passing checks shows P",
prInfo: &github.PRInfo{State: github.PRStateOpen, CheckState: github.CheckStatePassing},
expectIcon: "P",
},
{
name: "open PR with no checks shows O",
prInfo: &github.PRInfo{State: github.PRStateOpen, CheckState: github.CheckStateNone},
expectIcon: "O",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PRStatusBadge(tt.prInfo)

if tt.expectEmpty {
if got != "" {
t.Errorf("PRStatusBadge() = %q, want empty", got)
}
return
}

// The badge includes ANSI escape codes for styling, so we check if it contains the icon
if !strings.Contains(got, tt.expectIcon) {
t.Errorf("PRStatusBadge() = %q, want icon %q", got, tt.expectIcon)
}
})
}
}

func TestPRStatusDescription(t *testing.T) {
tests := []struct {
name string
prInfo *github.PRInfo
expected string
}{
{
name: "nil PR returns empty",
prInfo: nil,
expected: "",
},
{
name: "delegates to PRInfo.StatusDescription",
prInfo: &github.PRInfo{State: github.PRStateMerged},
expected: "Merged",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PRStatusDescription(tt.prInfo)
if got != tt.expected {
t.Errorf("PRStatusDescription() = %q, want %q", got, tt.expected)
}
})
}
}