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