|
8 | 8 |
|
9 | 9 | "github.com/github/gh-stack/internal/config" |
10 | 10 | "github.com/github/gh-stack/internal/git" |
| 11 | + "github.com/github/gh-stack/internal/github" |
11 | 12 | "github.com/github/gh-stack/internal/stack" |
12 | 13 | "github.com/stretchr/testify/assert" |
13 | 14 | "github.com/stretchr/testify/require" |
@@ -283,3 +284,135 @@ func TestViewShort_FullyMergedStack(t *testing.T) { |
283 | 284 | assert.Contains(t, output, "b1") |
284 | 285 | assert.Contains(t, output, "b2") |
285 | 286 | } |
| 287 | + |
| 288 | +// TestViewShort_QueuedStack verifies that --short output shows queued |
| 289 | +// branches with a "queued" separator and the ◎ icon. |
| 290 | +func TestViewShort_QueuedStack(t *testing.T) { |
| 291 | + s := stack.Stack{ |
| 292 | + Trunk: stack.BranchRef{Branch: "main"}, |
| 293 | + Branches: []stack.BranchRef{ |
| 294 | + {Branch: "b1", PullRequest: &stack.PullRequestRef{Number: 1}}, |
| 295 | + {Branch: "b2", PullRequest: &stack.PullRequestRef{Number: 2}}, |
| 296 | + {Branch: "b3", PullRequest: &stack.PullRequestRef{Number: 3}}, |
| 297 | + }, |
| 298 | + } |
| 299 | + |
| 300 | + tmpDir := t.TempDir() |
| 301 | + writeStackFile(t, tmpDir, s) |
| 302 | + |
| 303 | + restore := git.SetOps(&git.MockOps{ |
| 304 | + GitDirFn: func() (string, error) { return tmpDir, nil }, |
| 305 | + CurrentBranchFn: func() (string, error) { return "b3", nil }, |
| 306 | + IsAncestorFn: func(string, string) (bool, error) { return true, nil }, |
| 307 | + RevParseFn: func(ref string) (string, error) { return "sha-" + ref, nil }, |
| 308 | + }) |
| 309 | + defer restore() |
| 310 | + |
| 311 | + // Mock GitHub client to return b1 as queued (MergeQueueEntry set) |
| 312 | + cfg, outR, _ := config.NewTestConfig() |
| 313 | + cfg.GitHubClientOverride = &github.MockClient{ |
| 314 | + FindAnyPRForBranchFn: func(branch string) (*github.PullRequest, error) { |
| 315 | + switch branch { |
| 316 | + case "b1": |
| 317 | + return &github.PullRequest{ |
| 318 | + Number: 1, |
| 319 | + ID: "PR_1", |
| 320 | + MergeQueueEntry: &github.MergeQueueEntry{ID: "MQE_1"}, |
| 321 | + }, nil |
| 322 | + case "b2": |
| 323 | + return &github.PullRequest{Number: 2, ID: "PR_2"}, nil |
| 324 | + case "b3": |
| 325 | + return &github.PullRequest{Number: 3, ID: "PR_3"}, nil |
| 326 | + } |
| 327 | + return nil, nil |
| 328 | + }, |
| 329 | + } |
| 330 | + |
| 331 | + cmd := ViewCmd(cfg) |
| 332 | + cmd.SetArgs([]string{"--short"}) |
| 333 | + cmd.SetOut(io.Discard) |
| 334 | + cmd.SetErr(io.Discard) |
| 335 | + err := cmd.Execute() |
| 336 | + |
| 337 | + cfg.Out.Close() |
| 338 | + raw, _ := io.ReadAll(outR) |
| 339 | + output := string(raw) |
| 340 | + |
| 341 | + assert.NoError(t, err) |
| 342 | + assert.Contains(t, output, "b1") |
| 343 | + assert.Contains(t, output, "b2") |
| 344 | + assert.Contains(t, output, "b3") |
| 345 | + assert.Contains(t, output, "queued", "should show queued separator") |
| 346 | + assert.Contains(t, output, "◎", "should show queued icon for b1") |
| 347 | +} |
| 348 | + |
| 349 | +// TestViewShort_MixedQueuedAndMerged verifies that --short output shows |
| 350 | +// both "queued" and "merged" separators in the correct order. |
| 351 | +func TestViewShort_MixedQueuedAndMerged(t *testing.T) { |
| 352 | + s := stack.Stack{ |
| 353 | + Trunk: stack.BranchRef{Branch: "main"}, |
| 354 | + Branches: []stack.BranchRef{ |
| 355 | + {Branch: "b1", PullRequest: &stack.PullRequestRef{Number: 1, Merged: true}}, |
| 356 | + {Branch: "b2", PullRequest: &stack.PullRequestRef{Number: 2}}, |
| 357 | + {Branch: "b3", PullRequest: &stack.PullRequestRef{Number: 3}}, |
| 358 | + }, |
| 359 | + } |
| 360 | + |
| 361 | + tmpDir := t.TempDir() |
| 362 | + writeStackFile(t, tmpDir, s) |
| 363 | + |
| 364 | + restore := git.SetOps(&git.MockOps{ |
| 365 | + GitDirFn: func() (string, error) { return tmpDir, nil }, |
| 366 | + CurrentBranchFn: func() (string, error) { return "b3", nil }, |
| 367 | + IsAncestorFn: func(string, string) (bool, error) { return true, nil }, |
| 368 | + RevParseFn: func(ref string) (string, error) { return "sha-" + ref, nil }, |
| 369 | + }) |
| 370 | + defer restore() |
| 371 | + |
| 372 | + // b1 is merged (persisted), b2 is queued (from API) |
| 373 | + cfg, outR, _ := config.NewTestConfig() |
| 374 | + cfg.GitHubClientOverride = &github.MockClient{ |
| 375 | + FindAnyPRForBranchFn: func(branch string) (*github.PullRequest, error) { |
| 376 | + switch branch { |
| 377 | + case "b2": |
| 378 | + return &github.PullRequest{ |
| 379 | + Number: 2, |
| 380 | + ID: "PR_2", |
| 381 | + MergeQueueEntry: &github.MergeQueueEntry{ID: "MQE_2"}, |
| 382 | + }, nil |
| 383 | + case "b3": |
| 384 | + return &github.PullRequest{Number: 3, ID: "PR_3"}, nil |
| 385 | + } |
| 386 | + return nil, nil |
| 387 | + }, |
| 388 | + } |
| 389 | + |
| 390 | + cmd := ViewCmd(cfg) |
| 391 | + cmd.SetArgs([]string{"--short"}) |
| 392 | + cmd.SetOut(io.Discard) |
| 393 | + cmd.SetErr(io.Discard) |
| 394 | + err := cmd.Execute() |
| 395 | + |
| 396 | + cfg.Out.Close() |
| 397 | + raw, _ := io.ReadAll(outR) |
| 398 | + output := string(raw) |
| 399 | + |
| 400 | + assert.NoError(t, err) |
| 401 | + assert.Contains(t, output, "queued", "should show queued separator") |
| 402 | + assert.Contains(t, output, "merged", "should show merged separator") |
| 403 | + |
| 404 | + // "merged" section (b1) should appear below "queued" section (b2) in output |
| 405 | + // Since we render top-to-bottom: b3 (active) -> queued separator -> b2 -> merged separator -> b1 |
| 406 | + queuedIdx := indexOf(output, "queued") |
| 407 | + mergedIdx := indexOf(output, "merged") |
| 408 | + assert.Less(t, queuedIdx, mergedIdx, "queued separator should appear before merged separator") |
| 409 | +} |
| 410 | + |
| 411 | +func indexOf(s, substr string) int { |
| 412 | + for i := 0; i <= len(s)-len(substr); i++ { |
| 413 | + if s[i:i+len(substr)] == substr { |
| 414 | + return i |
| 415 | + } |
| 416 | + } |
| 417 | + return -1 |
| 418 | +} |
0 commit comments