gitlab.com/prarit/lab@v0.14.0/cmd/ci_view_test.go (about) 1 package cmd 2 3 import ( 4 "strconv" 5 "testing" 6 "time" 7 8 "github.com/gdamore/tcell" 9 "github.com/rivo/tview" 10 "github.com/stretchr/testify/assert" 11 "github.com/xanzy/go-gitlab" 12 ) 13 14 func assertScreen(t *testing.T, screen tcell.Screen, expected []string) { 15 sx, sy := screen.Size() 16 assert.Equal(t, len(expected), sy) 17 assert.Equal(t, len([]rune(expected[0])), sx) 18 actual := make([]string, sy) 19 for y, str := range expected { 20 runes := make([]rune, len(str)) 21 row := []rune(str) 22 for x, expectedRune := range row { 23 r, _, _, _ := screen.GetContent(x, y) 24 runes[x] = r 25 assert.Equal(t, expectedRune, r, "%s != %s at (%d,%d)", 26 strconv.QuoteRune(expectedRune), strconv.QuoteRune(r), x, y) 27 } 28 actual[y] = string(runes) 29 } 30 t.Logf("Expected w: %d l: %d", len([]rune(expected[0])), len(expected)) 31 for _, str := range expected { 32 t.Log(str) 33 } 34 t.Logf("Actual w: %d l: %d", len([]rune(actual[0])), len(actual)) 35 for _, str := range actual { 36 t.Log(str) 37 } 38 } 39 40 func Test_line(t *testing.T) { 41 tests := []struct { 42 desc string 43 lineF func(screen tcell.Screen, x, y, l int) 44 x, y, l int 45 expected []string 46 }{ 47 { 48 "hline", 49 hline, 50 2, 2, 5, 51 []string{ 52 " ", 53 " ", 54 " ━━━━━ ", 55 " ", 56 " ", 57 " ", 58 " ", 59 " ", 60 " ", 61 " ", 62 }, 63 }, 64 { 65 "hline overflow", 66 hline, 67 2, 2, 10, 68 []string{ 69 " ", 70 " ", 71 " ━━━━━━━━", 72 " ", 73 " ", 74 " ", 75 " ", 76 " ", 77 " ", 78 " ", 79 }, 80 }, 81 { 82 "vline", 83 vline, 84 2, 2, 5, 85 []string{ 86 " ", 87 " ", 88 " ┃ ", 89 " ┃ ", 90 " ┃ ", 91 " ┃ ", 92 " ┃ ", 93 " ", 94 " ", 95 " ", 96 }, 97 }, 98 { 99 "vline overflow", 100 vline, 101 2, 2, 10, 102 []string{ 103 " ", 104 " ", 105 " ┃ ", 106 " ┃ ", 107 " ┃ ", 108 " ┃ ", 109 " ┃ ", 110 " ┃ ", 111 " ┃ ", 112 " ┃ ", 113 }, 114 }, 115 } 116 117 for _, test := range tests { 118 screen := tcell.NewSimulationScreen("UTF-8") 119 err := screen.Init() 120 if err != nil { 121 t.Fatal(err) 122 } 123 // Set screen to matrix size 124 screen.SetSize(len(test.expected), len(test.expected[0])) 125 126 test := test 127 t.Run(test.desc, func(t *testing.T) { 128 t.Parallel() 129 test.lineF(screen, test.x, test.y, test.l) 130 screen.Show() 131 assertScreen(t, screen, test.expected) 132 }) 133 } 134 } 135 136 func testbox(x, y, w, h int) *tview.TextView { 137 b := tview.NewTextView() 138 b.SetBorder(true) 139 b.SetRect(x, y, w, h) 140 return b 141 } 142 143 func Test_connect(t *testing.T) { 144 tests := []struct { 145 desc string 146 b1, b2 *tview.Box 147 first, last bool 148 expected []string 149 }{ 150 { 151 "first stage", 152 testbox(2, 1, 3, 3).Box, testbox(2, 5, 3, 3).Box, 153 true, false, 154 []string{ 155 " ", 156 " ┌─┐ ", 157 " │ │ ", 158 " └─┘ ┃ ", 159 " ┃ ", 160 " ┌─┐ ┃ ", 161 " │ │━┛ ", 162 " └─┘ ", 163 " ", 164 " ", 165 }, 166 }, 167 { 168 "last stage", 169 testbox(5, 1, 3, 3).Box, testbox(5, 5, 3, 3).Box, 170 false, true, 171 []string{ 172 " ", 173 " ┌─┐ ", 174 " ┳ │ │ ", 175 " ┃ └─┘ ", 176 " ┃ ", 177 " ┃ ┌─┐ ", 178 " ┗━│ │ ", 179 " └─┘ ", 180 " ", 181 " ", 182 }, 183 }, 184 { 185 "cross stage", 186 testbox(1, 1, 3, 3).Box, testbox(7, 1, 3, 3).Box, 187 false, false, 188 []string{ 189 " ", 190 " ┌─┐ ┌─┐", 191 " │ │━━━│ │", 192 " └─┘ └─┘", 193 " ", 194 " ", 195 " ", 196 " ", 197 " ", 198 " ", 199 }, 200 }, 201 } 202 203 for _, test := range tests { 204 screen := tcell.NewSimulationScreen("UTF-8") 205 err := screen.Init() 206 if err != nil { 207 t.Fatal(err) 208 } 209 // Set screen to matrix size 210 screen.SetSize(len(test.expected), len(test.expected[0])) 211 212 test.b1.Draw(screen) 213 test.b2.Draw(screen) 214 215 test := test 216 t.Run(test.desc, func(t *testing.T) { 217 t.Parallel() 218 connect(screen, test.b1, test.b2, 2, test.first, test.last) 219 screen.Show() 220 assertScreen(t, screen, test.expected) 221 }) 222 } 223 } 224 225 func Test_connectJobs(t *testing.T) { 226 expected := []string{ 227 " ", 228 " ┌─┐ ┌─┐ ┌─┐ ", 229 " │ │┳━┳│ │┳━┳│ │ ", 230 " └─┘┃ ┃└─┘┃ ┃└─┘ ", 231 " ┃ ┃ ┃ ┃ ", 232 " ┌─┐┃ ┃┌─┐┃ ┃┌─┐ ", 233 " │ │┫ ┣│ │┫ ┗│ │ ", 234 " └─┘┃ ┃└─┘┃ └─┘ ", 235 " ┃ ┃ ┃ ", 236 " ┌─┐┃ ┃┌─┐┃ ", 237 " │ │┫ ┗│ │┛ ", 238 " └─┘┃ └─┘ ", 239 " ┃ ", 240 " ┌─┐┃ ", 241 " │ │┛ ", 242 " └─┘ ", 243 " ", 244 } 245 jobs := []*gitlab.Job{ 246 &gitlab.Job{ 247 Name: "stage1-job1", 248 Stage: "stage1", 249 }, 250 &gitlab.Job{ 251 Name: "stage1-job2", 252 Stage: "stage1", 253 }, 254 &gitlab.Job{ 255 Name: "stage1-job3", 256 Stage: "stage1", 257 }, 258 &gitlab.Job{ 259 Name: "stage1-job4", 260 Stage: "stage1", 261 }, 262 &gitlab.Job{ 263 Name: "stage2-job1", 264 Stage: "stage2", 265 }, 266 &gitlab.Job{ 267 Name: "stage2-job2", 268 Stage: "stage2", 269 }, 270 &gitlab.Job{ 271 Name: "stage2-job3", 272 Stage: "stage2", 273 }, 274 &gitlab.Job{ 275 Name: "stage3-job1", 276 Stage: "stage3", 277 }, 278 &gitlab.Job{ 279 Name: "stage3-job2", 280 Stage: "stage3", 281 }, 282 } 283 boxes := map[string]*tview.TextView{ 284 "jobs-stage1-job1": testbox(1, 1, 3, 3), 285 "jobs-stage1-job2": testbox(1, 5, 3, 3), 286 "jobs-stage1-job3": testbox(1, 9, 3, 3), 287 "jobs-stage1-job4": testbox(1, 13, 3, 3), 288 289 "jobs-stage2-job1": testbox(7, 1, 3, 3), 290 "jobs-stage2-job2": testbox(7, 5, 3, 3), 291 "jobs-stage2-job3": testbox(7, 9, 3, 3), 292 293 "jobs-stage3-job1": testbox(13, 1, 3, 3), 294 "jobs-stage3-job2": testbox(13, 5, 3, 3), 295 } 296 297 screen := tcell.NewSimulationScreen("UTF-8") 298 err := screen.Init() 299 if err != nil { 300 t.Fatal(err) 301 } 302 // Set screen to matrix size 303 screen.SetSize(len(expected), len(expected[0])) 304 305 for _, b := range boxes { 306 b.Draw(screen) 307 } 308 309 err = connectJobs(screen, jobs, boxes) 310 if err != nil { 311 t.Fatal(err) 312 } 313 314 screen.Show() 315 assertScreen(t, screen, expected) 316 } 317 318 func Test_connectJobsNegative(t *testing.T) { 319 tests := []struct { 320 desc string 321 jobs []*gitlab.Job 322 boxes map[string]*tview.TextView 323 }{ 324 { 325 "determinePadding -- first job missing", 326 []*gitlab.Job{ 327 &gitlab.Job{ 328 Name: "stage1-job1", 329 Stage: "stage1", 330 }, 331 }, 332 map[string]*tview.TextView{ 333 "jobs-stage2-job1": testbox(1, 5, 3, 3), 334 "jobs-stage2-job2": testbox(1, 9, 3, 3), 335 }, 336 }, 337 { 338 "determinePadding -- second job missing", 339 []*gitlab.Job{ 340 &gitlab.Job{ 341 Name: "stage1-job1", 342 Stage: "stage1", 343 }, 344 &gitlab.Job{ 345 Name: "stage2-job1", 346 Stage: "stage2", 347 }, 348 &gitlab.Job{ 349 Name: "stage2-job2", 350 Stage: "stage2", 351 }, 352 }, 353 map[string]*tview.TextView{ 354 "jobs-stage1-job1": testbox(1, 1, 3, 3), 355 "jobs-stage2-job2": testbox(1, 9, 3, 3), 356 }, 357 }, 358 { 359 "connect -- third job missing", 360 []*gitlab.Job{ 361 &gitlab.Job{ 362 Name: "stage1-job1", 363 Stage: "stage1", 364 }, 365 &gitlab.Job{ 366 Name: "stage2-job1", 367 Stage: "stage2", 368 }, 369 &gitlab.Job{ 370 Name: "stage2-job2", 371 Stage: "stage2", 372 }, 373 }, 374 map[string]*tview.TextView{ 375 "jobs-stage1-job1": testbox(1, 1, 3, 3), 376 "jobs-stage2-job1": testbox(1, 5, 3, 3), 377 }, 378 }, 379 { 380 "connect -- third job missing", 381 []*gitlab.Job{ 382 &gitlab.Job{ 383 Name: "stage1-job1", 384 Stage: "stage1", 385 }, 386 &gitlab.Job{ 387 Name: "stage2-job1", 388 Stage: "stage2", 389 }, 390 &gitlab.Job{ 391 Name: "stage2-job2", 392 Stage: "stage2", 393 }, 394 }, 395 map[string]*tview.TextView{ 396 "jobs-stage1-job1": testbox(1, 1, 3, 3), 397 "jobs-stage2-job1": testbox(1, 5, 3, 3), 398 }, 399 }, 400 } 401 for _, test := range tests { 402 screen := tcell.NewSimulationScreen("UTF-8") 403 err := screen.Init() 404 if err != nil { 405 t.Fatal(err) 406 } 407 test := test 408 t.Run(test.desc, func(t *testing.T) { 409 t.Parallel() 410 assert.Error(t, connectJobs(screen, test.jobs, test.boxes)) 411 412 }) 413 } 414 } 415 416 func Test_jobsView(t *testing.T) { 417 expected := []string{ 418 " ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ ", 419 " │ Stage1 │ │ Stage2 │ │ Stage3 │ ", 420 " └────────────────────┘ └────────────────────┘ └────────────────────┘ ", 421 " ", 422 " ╔✔ stage1-job1-reall…╗ ┌───● stage2-job1────┐ ┌───● stage3-job1────┐ ", 423 " ║ ║ │ │ │ │ ", 424 " ║ 01m 01s║━┳━━┳━│ │━┳━━┳━│ │ ", 425 " ╚════════════════════╝ ┃ ┃ └────────────────────┘ ┃ ┃ └────────────────────┘ ", 426 " ┃ ┃ ┃ ┃ ", 427 " ┌───✔ stage1-job2────┐ ┃ ┃ ┌───● stage2-job2────┐ ┃ ┃ ┌───● stage3-job2────┐ ", 428 " │ │ ┃ ┃ │ │ ┃ ┃ │ │ ", 429 " │ │━┫ ┣━│ │━┫ ┗━│ │ ", 430 " └────────────────────┘ ┃ ┃ └────────────────────┘ ┃ └────────────────────┘ ", 431 " ┃ ┃ ┃ ", 432 " ┌───✔ stage1-job3────┐ ┃ ┃ ┌───● stage2-job3────┐ ┃ ", 433 " │ │ ┃ ┃ │ │ ┃ ", 434 " │ │━┫ ┗━│ │━┛ ", 435 " └────────────────────┘ ┃ └────────────────────┘ ", 436 " ┃ ", 437 " ┌───✘ stage1-job4────┐ ┃ ", 438 " │ │ ┃ ", 439 " │ │━┛ ", 440 " └────────────────────┘ ", 441 " ", 442 " ", 443 " ", 444 } 445 now := time.Now() 446 past := now.Add(time.Second * -61) 447 jobs := []*gitlab.Job{ 448 &gitlab.Job{ 449 Name: "stage1-job1-really-long", 450 Stage: "stage1", 451 Status: "success", 452 StartedAt: &past, // relies on test running in <1s we'll see how it goes 453 FinishedAt: &now, 454 }, 455 &gitlab.Job{ 456 Name: "stage1-job2", 457 Stage: "stage1", 458 Status: "success", 459 }, 460 &gitlab.Job{ 461 Name: "stage1-job3", 462 Stage: "stage1", 463 Status: "success", 464 }, 465 &gitlab.Job{ 466 Name: "stage1-job4", 467 Stage: "stage1", 468 Status: "failed", 469 }, 470 &gitlab.Job{ 471 Name: "stage2-job1", 472 Stage: "stage2", 473 Status: "running", 474 }, 475 &gitlab.Job{ 476 Name: "stage2-job2", 477 Stage: "stage2", 478 Status: "running", 479 }, 480 &gitlab.Job{ 481 Name: "stage2-job3", 482 Stage: "stage2", 483 Status: "pending", 484 }, 485 &gitlab.Job{ 486 Name: "stage3-job1", 487 Stage: "stage3", 488 Status: "manual", 489 }, 490 &gitlab.Job{ 491 Name: "stage3-job2", 492 Stage: "stage3", 493 Status: "manual", 494 }, 495 } 496 497 boxes = make(map[string]*tview.TextView) 498 jobsCh := make(chan []*gitlab.Job) 499 root := tview.NewPages() 500 root.SetBorderPadding(1, 1, 2, 2) 501 502 screen := tcell.NewSimulationScreen("UTF-8") 503 err := screen.Init() 504 if err != nil { 505 t.Fatal(err) 506 } 507 // Set screen to matrix size 508 screen.SetSize(len([]rune(expected[0])), len(expected)) 509 w, h := screen.Size() 510 root.SetRect(0, 0, w, h) 511 512 go func() { 513 jobsCh <- jobs 514 }() 515 jobsView(nil, jobsCh, root)(screen) 516 root.Draw(screen) 517 connectJobsView(nil)(screen) 518 screen.Sync() 519 assertScreen(t, screen, expected) 520 } 521 522 func Test_latestJobs(t *testing.T) { 523 tests := []struct { 524 desc string 525 jobs []*gitlab.Job 526 expected []*gitlab.Job 527 }{ 528 { 529 desc: "no newer jobs", 530 jobs: []*gitlab.Job{ 531 &gitlab.Job{ 532 ID: 1, 533 Name: "stage1-job1", 534 Stage: "stage1", 535 }, 536 &gitlab.Job{ 537 ID: 2, 538 Name: "stage1-job2", 539 Stage: "stage1", 540 }, 541 &gitlab.Job{ 542 ID: 3, 543 Name: "stage1-job3", 544 Stage: "stage1", 545 }, 546 }, 547 expected: []*gitlab.Job{ 548 &gitlab.Job{ 549 ID: 1, 550 Name: "stage1-job1", 551 Stage: "stage1", 552 }, 553 &gitlab.Job{ 554 ID: 2, 555 Name: "stage1-job2", 556 Stage: "stage1", 557 }, 558 &gitlab.Job{ 559 ID: 3, 560 Name: "stage1-job3", 561 Stage: "stage1", 562 }, 563 }, 564 }, 565 { 566 desc: "1 newer", 567 jobs: []*gitlab.Job{ 568 &gitlab.Job{ 569 ID: 1, 570 Name: "stage1-job1", 571 Stage: "stage1", 572 }, 573 &gitlab.Job{ 574 ID: 2, 575 Name: "stage1-job2", 576 Stage: "stage1", 577 }, 578 &gitlab.Job{ 579 ID: 3, 580 Name: "stage1-job3", 581 Stage: "stage1", 582 }, 583 &gitlab.Job{ 584 ID: 4, 585 Name: "stage1-job1", 586 Stage: "stage1", 587 }, 588 }, 589 expected: []*gitlab.Job{ 590 &gitlab.Job{ 591 ID: 4, 592 Name: "stage1-job1", 593 Stage: "stage1", 594 }, 595 &gitlab.Job{ 596 ID: 2, 597 Name: "stage1-job2", 598 Stage: "stage1", 599 }, 600 &gitlab.Job{ 601 ID: 3, 602 Name: "stage1-job3", 603 Stage: "stage1", 604 }, 605 }, 606 }, 607 { 608 desc: "2 newer", 609 jobs: []*gitlab.Job{ 610 &gitlab.Job{ 611 ID: 1, 612 Name: "stage1-job1", 613 Stage: "stage1", 614 }, 615 &gitlab.Job{ 616 ID: 2, 617 Name: "stage1-job2", 618 Stage: "stage1", 619 }, 620 &gitlab.Job{ 621 ID: 3, 622 Name: "stage1-job3", 623 Stage: "stage1", 624 }, 625 &gitlab.Job{ 626 ID: 4, 627 Name: "stage1-job3", 628 Stage: "stage1", 629 }, 630 &gitlab.Job{ 631 ID: 5, 632 Name: "stage1-job1", 633 Stage: "stage1", 634 }, 635 }, 636 expected: []*gitlab.Job{ 637 &gitlab.Job{ 638 ID: 5, 639 Name: "stage1-job1", 640 Stage: "stage1", 641 }, 642 &gitlab.Job{ 643 ID: 2, 644 Name: "stage1-job2", 645 Stage: "stage1", 646 }, 647 &gitlab.Job{ 648 ID: 4, 649 Name: "stage1-job3", 650 Stage: "stage1", 651 }, 652 }, 653 }, 654 } 655 656 for _, test := range tests { 657 test := test 658 t.Run(test.desc, func(t *testing.T) { 659 t.Parallel() 660 jobs := latestJobs(test.jobs) 661 assert.Equal(t, test.expected, jobs) 662 }) 663 } 664 } 665 666 func Test_adjacentStages(t *testing.T) { 667 tests := []struct { 668 desc string 669 stage string 670 jobs []*gitlab.Job 671 expectedPrev, expectedNext string 672 }{ 673 { 674 "no jobs", 675 "1", 676 []*gitlab.Job{}, 677 "", "", 678 }, 679 { 680 "first stage", 681 "1", 682 []*gitlab.Job{ 683 &gitlab.Job{ 684 Stage: "1", 685 }, 686 &gitlab.Job{ 687 Stage: "1", 688 }, 689 &gitlab.Job{ 690 Stage: "1", 691 }, 692 &gitlab.Job{ 693 Stage: "2", 694 }, 695 }, 696 "1", "2", 697 }, 698 { 699 "mid stage", 700 "2", 701 []*gitlab.Job{ 702 &gitlab.Job{ 703 Stage: "1", 704 }, 705 &gitlab.Job{ 706 Stage: "1", 707 }, 708 &gitlab.Job{ 709 Stage: "1", 710 }, 711 &gitlab.Job{ 712 Stage: "2", 713 }, 714 &gitlab.Job{ 715 Stage: "2", 716 }, 717 &gitlab.Job{ 718 Stage: "3", 719 }, 720 }, 721 "1", "3", 722 }, 723 { 724 "last stage", 725 "3", 726 []*gitlab.Job{ 727 &gitlab.Job{ 728 Stage: "1", 729 }, 730 &gitlab.Job{ 731 Stage: "1", 732 }, 733 &gitlab.Job{ 734 Stage: "1", 735 }, 736 &gitlab.Job{ 737 Stage: "2", 738 }, 739 &gitlab.Job{ 740 Stage: "2", 741 }, 742 &gitlab.Job{ 743 Stage: "3", 744 }, 745 }, 746 "2", "3", 747 }, 748 } 749 750 for _, test := range tests { 751 test := test 752 t.Run(test.desc, func(t *testing.T) { 753 t.Parallel() 754 prev, next := adjacentStages(test.jobs, test.stage) 755 assert.Equal(t, test.expectedPrev, prev) 756 assert.Equal(t, test.expectedNext, next) 757 }) 758 } 759 } 760 761 func Test_stageBounds(t *testing.T) { 762 tests := []struct { 763 desc string 764 stage string 765 jobs []*gitlab.Job 766 expectedLower, expectedUpper int 767 }{ 768 { 769 "no jobs", 770 "1", 771 []*gitlab.Job{}, 772 0, 0, 773 }, 774 { 775 "first stage", 776 "1", 777 []*gitlab.Job{ 778 &gitlab.Job{ 779 Stage: "1", 780 }, 781 &gitlab.Job{ 782 Stage: "1", 783 }, 784 &gitlab.Job{ 785 Stage: "1", 786 }, 787 &gitlab.Job{ 788 Stage: "2", 789 }, 790 }, 791 0, 2, 792 }, 793 { 794 "mid stage", 795 "2", 796 []*gitlab.Job{ 797 &gitlab.Job{ 798 Stage: "1", 799 }, 800 &gitlab.Job{ 801 Stage: "1", 802 }, 803 &gitlab.Job{ 804 Stage: "1", 805 }, 806 &gitlab.Job{ 807 Stage: "2", 808 }, 809 &gitlab.Job{ 810 Stage: "2", 811 }, 812 &gitlab.Job{ 813 Stage: "3", 814 }, 815 }, 816 3, 4, 817 }, 818 { 819 "last stage", 820 "3", 821 []*gitlab.Job{ 822 &gitlab.Job{ 823 Stage: "1", 824 }, 825 &gitlab.Job{ 826 Stage: "1", 827 }, 828 &gitlab.Job{ 829 Stage: "1", 830 }, 831 &gitlab.Job{ 832 Stage: "2", 833 }, 834 &gitlab.Job{ 835 Stage: "2", 836 }, 837 &gitlab.Job{ 838 Stage: "3", 839 }, 840 }, 841 5, 5, 842 }, 843 } 844 845 for _, test := range tests { 846 test := test 847 t.Run(test.desc, func(t *testing.T) { 848 t.Parallel() 849 lower, upper := stageBounds(test.jobs, test.stage) 850 assert.Equal(t, test.expectedLower, lower) 851 assert.Equal(t, test.expectedUpper, upper) 852 }) 853 } 854 } 855 856 func Test_handleNavigation(t *testing.T) { 857 jobs := []*gitlab.Job{ 858 &gitlab.Job{ 859 Name: "stage1-job1-really-long", 860 Stage: "stage1", 861 Status: "success", 862 }, 863 &gitlab.Job{ 864 Name: "stage1-job2", 865 Stage: "stage1", 866 Status: "success", 867 }, 868 &gitlab.Job{ 869 Name: "stage1-job3", 870 Stage: "stage1", 871 Status: "success", 872 }, 873 &gitlab.Job{ 874 Name: "stage1-job4", 875 Stage: "stage1", 876 Status: "failed", 877 }, 878 &gitlab.Job{ 879 Name: "stage2-job1", 880 Stage: "stage2", 881 Status: "running", 882 }, 883 &gitlab.Job{ 884 Name: "stage2-job2", 885 Stage: "stage2", 886 Status: "running", 887 }, 888 &gitlab.Job{ 889 Name: "stage2-job3", 890 Stage: "stage2", 891 Status: "pending", 892 }, 893 &gitlab.Job{ 894 Name: "stage3-job1", 895 Stage: "stage3", 896 Status: "manual", 897 }, 898 &gitlab.Job{ 899 Name: "stage3-job2", 900 Stage: "stage3", 901 Status: "manual", 902 }, 903 } 904 tests := []struct { 905 desc string 906 input []*tcell.EventKey 907 expected int 908 }{ 909 { 910 "do nothing", 911 []*tcell.EventKey{}, 912 0, 913 }, 914 { 915 "arrows down", 916 []*tcell.EventKey{ 917 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 918 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 919 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 920 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 921 }, 922 3, 923 }, 924 { 925 "arrows down bottom boundary", 926 []*tcell.EventKey{ 927 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 928 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 929 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 930 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 931 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 932 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 933 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 934 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 935 }, 936 3, 937 }, 938 { 939 "arrows down bottom middle boundary", 940 []*tcell.EventKey{ 941 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 942 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 943 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 944 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 945 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 946 tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone), 947 }, 948 6, 949 }, 950 { 951 "arrows down last (3rd) column bottom boundary", 952 []*tcell.EventKey{ 953 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 954 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 955 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 956 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 957 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 958 tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone), 959 tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone), 960 }, 961 8, 962 }, 963 { 964 "arrows down persistent depth bottom boundary", 965 []*tcell.EventKey{ 966 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 967 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 968 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 969 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 970 tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), 971 tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone), 972 tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone), 973 tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone), 974 tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone), 975 }, 976 3, 977 }, 978 { 979 "arrows left boundary", 980 []*tcell.EventKey{ 981 tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone), 982 tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone), 983 }, 984 0, 985 }, 986 { 987 "arrows up boundary", 988 []*tcell.EventKey{ 989 tcell.NewEventKey(tcell.KeyUp, 0, tcell.ModNone), 990 tcell.NewEventKey(tcell.KeyUp, 0, tcell.ModNone), 991 }, 992 0, 993 }, 994 { 995 "arrows right boundary", 996 []*tcell.EventKey{ 997 tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone), 998 tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone), 999 tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone), 1000 }, 1001 7, 1002 }, 1003 { 1004 "hjkl down", 1005 []*tcell.EventKey{ 1006 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1007 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1008 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1009 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1010 }, 1011 3, 1012 }, 1013 { 1014 "hjkl down bottom boundary", 1015 []*tcell.EventKey{ 1016 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1017 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1018 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1019 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1020 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1021 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1022 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1023 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1024 }, 1025 3, 1026 }, 1027 { 1028 "hjkl down bottom middle boundary", 1029 []*tcell.EventKey{ 1030 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1031 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1032 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1033 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1034 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1035 tcell.NewEventKey(tcell.KeyRune, 'l', tcell.ModNone), 1036 }, 1037 6, 1038 }, 1039 { 1040 "hjkl down last (3rd) column bottom boundary", 1041 []*tcell.EventKey{ 1042 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1043 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1044 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1045 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1046 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1047 tcell.NewEventKey(tcell.KeyRune, 'l', tcell.ModNone), 1048 tcell.NewEventKey(tcell.KeyRune, 'l', tcell.ModNone), 1049 }, 1050 8, 1051 }, 1052 { 1053 "hjkl down persistent depth bottom boundary", 1054 []*tcell.EventKey{ 1055 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1056 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1057 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1058 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1059 tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone), 1060 tcell.NewEventKey(tcell.KeyRune, 'l', tcell.ModNone), 1061 tcell.NewEventKey(tcell.KeyRune, 'l', tcell.ModNone), 1062 tcell.NewEventKey(tcell.KeyRune, 'h', tcell.ModNone), 1063 tcell.NewEventKey(tcell.KeyRune, 'h', tcell.ModNone), 1064 }, 1065 3, 1066 }, 1067 { 1068 "hjkl left boundary", 1069 []*tcell.EventKey{ 1070 tcell.NewEventKey(tcell.KeyRune, 'h', tcell.ModNone), 1071 tcell.NewEventKey(tcell.KeyRune, 'h', tcell.ModNone), 1072 }, 1073 0, 1074 }, 1075 { 1076 "hjkl up boundary", 1077 []*tcell.EventKey{ 1078 tcell.NewEventKey(tcell.KeyRune, 'k', tcell.ModNone), 1079 tcell.NewEventKey(tcell.KeyRune, 'k', tcell.ModNone), 1080 }, 1081 0, 1082 }, 1083 { 1084 "hjkl right boundary", 1085 []*tcell.EventKey{ 1086 tcell.NewEventKey(tcell.KeyRune, 'l', tcell.ModNone), 1087 tcell.NewEventKey(tcell.KeyRune, 'l', tcell.ModNone), 1088 tcell.NewEventKey(tcell.KeyRune, 'l', tcell.ModNone), 1089 }, 1090 7, 1091 }, 1092 { 1093 "G boundary", 1094 []*tcell.EventKey{ 1095 tcell.NewEventKey(tcell.KeyRune, 'G', tcell.ModNone), 1096 }, 1097 3, 1098 }, 1099 { 1100 "Gg boundary", 1101 []*tcell.EventKey{ 1102 tcell.NewEventKey(tcell.KeyRune, 'G', tcell.ModNone), 1103 tcell.NewEventKey(tcell.KeyRune, 'g', tcell.ModNone), 1104 }, 1105 0, 1106 }, 1107 } 1108 1109 for _, test := range tests { 1110 test := test 1111 t.Run(test.desc, func(t *testing.T) { 1112 t.Parallel() 1113 var navi navigator 1114 for _, e := range test.input { 1115 navi.Navigate(jobs, e) 1116 } 1117 assert.Equal(t, test.expected, navi.idx) 1118 }) 1119 } 1120 }