github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/tests/BuildTests.elm (about) 1 module BuildTests exposing (all) 2 3 import Application.Application as Application 4 import Array 5 import Assets 6 import Build.Build as Build 7 import Build.Models as Models 8 import Build.StepTree.Models as STModels 9 import Char 10 import Colors 11 import Common exposing (defineHoverBehaviour, isColorWithStripes) 12 import Concourse exposing (BuildPrepStatus(..)) 13 import Concourse.BuildStatus exposing (BuildStatus(..)) 14 import Concourse.Pagination exposing (Direction(..)) 15 import DashboardTests exposing (iconSelector, middleGrey) 16 import Data 17 import Dict 18 import Expect 19 import Html.Attributes as Attr 20 import Http 21 import Json.Encode as Encode 22 import Keyboard 23 import Message.Callback as Callback 24 import Message.Effects as Effects 25 import Message.Message 26 import Message.ScrollDirection as ScrollDirection 27 import Message.Subscription as Subscription exposing (Delivery(..), Interval(..)) 28 import Message.TopLevelMessage as Msgs 29 import Routes 30 import StrictEvents exposing (DeltaMode(..)) 31 import Test exposing (..) 32 import Test.Html.Event as Event 33 import Test.Html.Query as Query 34 import Test.Html.Selector 35 exposing 36 ( attribute 37 , class 38 , containing 39 , id 40 , style 41 , tag 42 , text 43 ) 44 import Time 45 import Url 46 import UserState 47 48 49 all : Test 50 all = 51 describe "build page" <| 52 let 53 buildId = 54 Data.jobBuildId 55 56 flags = 57 { turbulenceImgSrc = "" 58 , notFoundImgSrc = "" 59 , csrfToken = csrfToken 60 , authToken = "" 61 , pipelineRunningKeyframes = "" 62 } 63 64 fetchBuild : BuildStatus -> Application.Model -> ( Application.Model, List Effects.Effect ) 65 fetchBuild status = 66 Application.handleCallback <| 67 Callback.BuildFetched <| 68 Ok (Data.jobBuild status) 69 70 fetchBuildWithStatus : 71 BuildStatus 72 -> Application.Model 73 -> Application.Model 74 fetchBuildWithStatus status = 75 Application.handleCallback 76 (Callback.BuildFetched <| Ok <| Data.jobBuild status) 77 >> Tuple.first 78 >> Application.handleCallback 79 (Callback.BuildHistoryFetched 80 (Ok 81 { pagination = 82 { previousPage = Nothing 83 , nextPage = Nothing 84 } 85 , content = 86 [ Data.jobBuild status 87 ] 88 } 89 ) 90 ) 91 >> Tuple.first 92 93 fetchJobDetails : 94 Application.Model 95 -> ( Application.Model, List Effects.Effect ) 96 fetchJobDetails = 97 Application.handleCallback <| 98 Callback.BuildJobDetailsFetched <| 99 Ok 100 (Data.job 0 |> Data.withShortPipelineId |> Data.withName "j") 101 102 fetchJobDetailsNoTrigger : 103 Application.Model 104 -> ( Application.Model, List Effects.Effect ) 105 fetchJobDetailsNoTrigger = 106 Application.handleCallback <| 107 Callback.BuildJobDetailsFetched <| 108 Ok 109 (Data.job 0 110 |> Data.withShortPipelineId 111 |> Data.withName "j" 112 |> Data.withDisableManualTrigger True 113 ) 114 115 fetchHistory : 116 Application.Model 117 -> ( Application.Model, List Effects.Effect ) 118 fetchHistory = 119 Application.handleCallback 120 (Callback.BuildHistoryFetched 121 (Ok 122 { pagination = 123 { previousPage = Nothing 124 , nextPage = Nothing 125 } 126 , content = [ Data.jobBuild BuildStatusSucceeded ] 127 } 128 ) 129 ) 130 131 csrfToken : String 132 csrfToken = 133 "csrf_token" 134 135 eventsUrl : String 136 eventsUrl = 137 "http://localhost:8080/api/v1/builds/1/events" 138 in 139 [ test "converts URL hash to highlighted line in view" <| 140 \_ -> 141 Application.init 142 flags 143 { protocol = Url.Http 144 , host = "" 145 , port_ = Nothing 146 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 147 , query = Nothing 148 , fragment = Just "Lstepid:1" 149 } 150 |> Tuple.first 151 |> fetchBuild BuildStatusStarted 152 |> Tuple.first 153 |> Application.handleCallback 154 (Callback.PlanAndResourcesFetched 1 <| 155 Ok <| 156 ( { id = "stepid" 157 , step = 158 Concourse.BuildStepTask 159 "step" 160 } 161 , { inputs = [], outputs = [] } 162 ) 163 ) 164 |> Tuple.first 165 |> Application.update 166 (Msgs.DeliveryReceived <| 167 EventsReceived <| 168 Ok <| 169 [ { url = eventsUrl 170 , data = 171 STModels.StartTask 172 { source = "stdout" 173 , id = "stepid" 174 } 175 (Time.millisToPosix 0) 176 } 177 , { url = eventsUrl 178 , data = 179 STModels.Log 180 { source = "stdout" 181 , id = "stepid" 182 } 183 "log message" 184 Nothing 185 } 186 ] 187 ) 188 |> Tuple.first 189 |> Common.queryView 190 |> Query.find 191 [ class "timestamped-line" 192 , containing [ text "log message" ] 193 ] 194 |> Query.has [ class "highlighted-line" ] 195 , test "scrolls to highlighted line" <| 196 \_ -> 197 Application.init 198 flags 199 { protocol = Url.Http 200 , host = "" 201 , port_ = Nothing 202 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 203 , query = Nothing 204 , fragment = Just "Lstepid:1" 205 } 206 |> Tuple.first 207 |> fetchBuild BuildStatusStarted 208 |> Tuple.first 209 |> Application.handleCallback 210 (Callback.PlanAndResourcesFetched 1 <| 211 Ok <| 212 ( { id = "stepid" 213 , step = 214 Concourse.BuildStepTask 215 "step" 216 } 217 , { inputs = [], outputs = [] } 218 ) 219 ) 220 |> Tuple.first 221 |> Application.update 222 (Msgs.DeliveryReceived <| 223 EventsReceived <| 224 Ok <| 225 [ { url = eventsUrl 226 , data = 227 STModels.StartTask 228 { source = "stdout" 229 , id = "stepid" 230 } 231 (Time.millisToPosix 0) 232 } 233 , { url = eventsUrl 234 , data = 235 STModels.Log 236 { source = "stdout" 237 , id = "stepid" 238 } 239 "log message" 240 Nothing 241 } 242 ] 243 ) 244 |> Tuple.second 245 |> Common.contains (Effects.Scroll (ScrollDirection.ToId "stepid:1") "build-body") 246 , test "scrolls to top of highlighted range" <| 247 \_ -> 248 Application.init 249 flags 250 { protocol = Url.Http 251 , host = "" 252 , port_ = Nothing 253 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 254 , query = Nothing 255 , fragment = Just "Lstepid:1:3" 256 } 257 |> Tuple.first 258 |> fetchBuild BuildStatusStarted 259 |> Tuple.first 260 |> Application.handleCallback 261 (Callback.PlanAndResourcesFetched 1 <| 262 Ok <| 263 ( { id = "stepid" 264 , step = 265 Concourse.BuildStepTask 266 "step" 267 } 268 , { inputs = [], outputs = [] } 269 ) 270 ) 271 |> Tuple.first 272 |> Application.update 273 (Msgs.DeliveryReceived <| 274 EventsReceived <| 275 Ok <| 276 [ { url = eventsUrl 277 , data = 278 STModels.StartTask 279 { source = "stdout" 280 , id = "stepid" 281 } 282 (Time.millisToPosix 0) 283 } 284 , { url = eventsUrl 285 , data = 286 STModels.Log 287 { source = "stdout" 288 , id = "stepid" 289 } 290 "log message" 291 Nothing 292 } 293 ] 294 ) 295 |> Tuple.second 296 |> Common.contains (Effects.Scroll (ScrollDirection.ToId "stepid:1") "build-body") 297 , test "does not scroll to an invalid range" <| 298 \_ -> 299 Application.init 300 flags 301 { protocol = Url.Http 302 , host = "" 303 , port_ = Nothing 304 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 305 , query = Nothing 306 , fragment = Just "Lstepid:2:1" 307 } 308 |> Tuple.first 309 |> fetchBuild BuildStatusStarted 310 |> Tuple.first 311 |> Application.handleCallback 312 (Callback.PlanAndResourcesFetched 1 <| 313 Ok <| 314 ( { id = "stepid" 315 , step = 316 Concourse.BuildStepTask 317 "step" 318 } 319 , { inputs = [], outputs = [] } 320 ) 321 ) 322 |> Tuple.first 323 |> Application.update 324 (Msgs.DeliveryReceived <| 325 EventsReceived <| 326 Ok <| 327 [ { url = eventsUrl 328 , data = 329 STModels.StartTask 330 { source = "stdout" 331 , id = "stepid" 332 } 333 (Time.millisToPosix 0) 334 } 335 , { url = eventsUrl 336 , data = 337 STModels.Log 338 { source = "stdout" 339 , id = "stepid" 340 } 341 "log message\n" 342 Nothing 343 } 344 , { url = eventsUrl 345 , data = 346 STModels.Log 347 { source = "stdout" 348 , id = "stepid" 349 } 350 "log message" 351 Nothing 352 } 353 ] 354 ) 355 |> Tuple.second 356 |> Common.notContains (Effects.Scroll (ScrollDirection.ToId "stepid:2") "build-body") 357 , test "does not re-scroll to an id multiple times" <| 358 \_ -> 359 Application.init 360 flags 361 { protocol = Url.Http 362 , host = "" 363 , port_ = Nothing 364 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 365 , query = Nothing 366 , fragment = Just "Lstepid:1" 367 } 368 |> Tuple.first 369 |> fetchBuild BuildStatusStarted 370 |> Tuple.first 371 |> Application.handleCallback 372 (Callback.PlanAndResourcesFetched 1 <| 373 Ok <| 374 ( { id = "stepid" 375 , step = 376 Concourse.BuildStepTask 377 "step" 378 } 379 , { inputs = [], outputs = [] } 380 ) 381 ) 382 |> Tuple.first 383 |> Application.handleDelivery 384 (EventsReceived <| 385 Ok <| 386 [ { url = eventsUrl 387 , data = 388 STModels.StartTask 389 { source = "stdout" 390 , id = "stepid" 391 } 392 (Time.millisToPosix 0) 393 } 394 , { url = eventsUrl 395 , data = 396 STModels.Log 397 { source = "stdout" 398 , id = "stepid" 399 } 400 "log message" 401 Nothing 402 } 403 ] 404 ) 405 |> Tuple.first 406 |> Application.handleDelivery 407 (EventsReceived <| Ok []) 408 |> Tuple.second 409 |> Common.notContains (Effects.Scroll (ScrollDirection.ToId "stepid:1") "build-body") 410 , test "auto-scroll disallowed before scrolled to highlighted line" <| 411 \_ -> 412 Application.init 413 flags 414 { protocol = Url.Http 415 , host = "" 416 , port_ = Nothing 417 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 418 , query = Nothing 419 , fragment = Just "Lstepid:1" 420 } 421 |> Tuple.first 422 |> fetchBuild BuildStatusStarted 423 |> Tuple.first 424 |> Application.handleCallback 425 (Callback.PlanAndResourcesFetched 1 <| 426 Ok <| 427 ( { id = "stepid" 428 , step = 429 Concourse.BuildStepTask 430 "step" 431 } 432 , { inputs = [], outputs = [] } 433 ) 434 ) 435 |> Tuple.first 436 |> Application.handleDelivery 437 (EventsReceived <| 438 Ok <| 439 [ { url = eventsUrl 440 , data = 441 STModels.StartTask 442 { source = "stdout" 443 , id = "stepid" 444 } 445 (Time.millisToPosix 0) 446 } 447 , { url = eventsUrl 448 , data = 449 STModels.Log 450 { source = "stdout" 451 , id = "stepid" 452 } 453 "log message" 454 Nothing 455 } 456 ] 457 ) 458 |> Tuple.first 459 |> Application.update 460 (Msgs.Update <| 461 Message.Message.Scrolled 462 { scrollHeight = 20 463 , scrollTop = 10 464 , clientHeight = 10 465 } 466 ) 467 |> Tuple.first 468 |> Application.handleDelivery 469 (EventsReceived <| Ok []) 470 |> Tuple.second 471 |> Common.notContains (Effects.Scroll ScrollDirection.ToBottom "build-body") 472 , test "auto-scroll disallowed before scrolling away from highlighted line" <| 473 \_ -> 474 Application.init 475 flags 476 { protocol = Url.Http 477 , host = "" 478 , port_ = Nothing 479 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 480 , query = Nothing 481 , fragment = Just "Lstepid:1" 482 } 483 |> Tuple.first 484 |> fetchBuild BuildStatusStarted 485 |> Tuple.first 486 |> Application.handleCallback 487 (Callback.PlanAndResourcesFetched 1 <| 488 Ok <| 489 ( { id = "stepid" 490 , step = 491 Concourse.BuildStepTask 492 "step" 493 } 494 , { inputs = [], outputs = [] } 495 ) 496 ) 497 |> Tuple.first 498 |> Application.handleDelivery 499 (EventsReceived <| 500 Ok <| 501 [ { url = eventsUrl 502 , data = 503 STModels.StartTask 504 { source = "stdout" 505 , id = "stepid" 506 } 507 (Time.millisToPosix 0) 508 } 509 , { url = eventsUrl 510 , data = 511 STModels.Log 512 { source = "stdout" 513 , id = "stepid" 514 } 515 "log message" 516 Nothing 517 } 518 ] 519 ) 520 |> Tuple.first 521 |> Application.update 522 (Msgs.Update <| 523 Message.Message.Scrolled 524 { scrollHeight = 20 525 , scrollTop = 10 526 , clientHeight = 10 527 } 528 ) 529 |> Tuple.first 530 |> Application.handleDelivery 531 (Subscription.ScrolledToId ( "Lstepid:1", "build-body" )) 532 |> Tuple.first 533 |> Application.handleDelivery 534 (EventsReceived <| Ok []) 535 |> Tuple.second 536 |> Common.notContains (Effects.Scroll ScrollDirection.ToBottom "build-body") 537 , test "auto-scroll allowed after scrolling away from highlighted line" <| 538 \_ -> 539 Application.init 540 flags 541 { protocol = Url.Http 542 , host = "" 543 , port_ = Nothing 544 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 545 , query = Nothing 546 , fragment = Just "Lstepid:1" 547 } 548 |> Tuple.first 549 |> fetchBuild BuildStatusStarted 550 |> Tuple.first 551 |> Application.handleCallback 552 (Callback.PlanAndResourcesFetched 1 <| 553 Ok <| 554 ( { id = "stepid" 555 , step = 556 Concourse.BuildStepTask 557 "step" 558 } 559 , { inputs = [], outputs = [] } 560 ) 561 ) 562 |> Tuple.first 563 |> Application.handleDelivery 564 (EventsReceived <| 565 Ok <| 566 [ { url = eventsUrl 567 , data = 568 STModels.StartTask 569 { source = "stdout" 570 , id = "stepid" 571 } 572 (Time.millisToPosix 0) 573 } 574 , { url = eventsUrl 575 , data = 576 STModels.Log 577 { source = "stdout" 578 , id = "stepid" 579 } 580 "log message" 581 Nothing 582 } 583 ] 584 ) 585 |> Tuple.first 586 |> Application.handleDelivery 587 (Subscription.ScrolledToId ( "Lstepid:1", "build-body" )) 588 |> Tuple.first 589 |> Application.update 590 (Msgs.Update <| 591 Message.Message.Scrolled 592 { scrollHeight = 20 593 , scrollTop = 10 594 , clientHeight = 10 595 } 596 ) 597 |> Tuple.first 598 |> Application.handleDelivery 599 (EventsReceived <| Ok []) 600 |> Tuple.second 601 |> Common.contains (Effects.Scroll ScrollDirection.ToBottom "build-body") 602 , test "subscribes to scrolled to id completion" <| 603 \_ -> 604 Application.init 605 flags 606 { protocol = Url.Http 607 , host = "" 608 , port_ = Nothing 609 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 610 , query = Nothing 611 , fragment = Just "Lstepid:1" 612 } 613 |> Tuple.first 614 |> Application.subscriptions 615 |> Common.contains Subscription.OnScrolledToId 616 , describe "page title" 617 [ test "with a job build" <| 618 \_ -> 619 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 620 |> fetchBuild BuildStatusSucceeded 621 |> Tuple.first 622 |> Application.view 623 |> .title 624 |> Expect.equal "j #1 - Concourse" 625 , test "with a one-off-build" <| 626 \_ -> 627 Common.init "/builds/1" 628 |> Application.handleCallback 629 (Callback.BuildFetched <| 630 Ok 631 { id = 1 632 , name = "1" 633 , job = Nothing 634 , status = BuildStatusPending 635 , duration = 636 { startedAt = Nothing 637 , finishedAt = Nothing 638 } 639 , reapTime = Nothing 640 } 641 ) 642 |> Tuple.first 643 |> Application.view 644 |> .title 645 |> Expect.equal "#1 - Concourse" 646 , test "with just the page name" <| 647 \_ -> 648 Application.init 649 flags 650 { protocol = Url.Http 651 , host = "" 652 , port_ = Nothing 653 , path = "/teams/t/pipelines/p/jobs/routejob/builds/1" 654 , query = Nothing 655 , fragment = Just "Lstepid:1" 656 } 657 |> Tuple.first 658 |> Application.view 659 |> .title 660 |> Expect.equal "routejob #1 - Concourse" 661 ] 662 , test "shows tombstone for reaped build with date in current zone" <| 663 \_ -> 664 let 665 buildTime = 666 Just <| 667 Time.millisToPosix 668 (12 * 60 * 60 * 1000) 669 in 670 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 671 |> Application.handleCallback 672 (Callback.BuildFetched <| 673 Ok 674 { id = 1 675 , name = "1" 676 , job = Just Data.shortJobId 677 , status = BuildStatusSucceeded 678 , duration = 679 { startedAt = buildTime 680 , finishedAt = buildTime 681 } 682 , reapTime = buildTime 683 } 684 ) 685 |> Tuple.first 686 |> Application.handleCallback 687 (Callback.GotCurrentTimeZone <| 688 Time.customZone 720 [] 689 ) 690 |> Tuple.first 691 |> Common.queryView 692 |> Query.find [ class "tombstone" ] 693 |> Query.has [ text "01/02/70" ] 694 , test "shows passport officer when build plan request gives 401" <| 695 \_ -> 696 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 697 |> Application.handleCallback 698 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusSucceeded)) 699 |> Tuple.first 700 |> Application.handleCallback 701 (Callback.PlanAndResourcesFetched 1 <| Data.httpUnauthorized) 702 |> Tuple.first 703 |> Common.queryView 704 |> Query.has [ class "not-authorized" ] 705 , test "shows 'build cancelled' in red when aborted build's plan request gives 404" <| 706 \_ -> 707 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 708 |> Application.handleCallback 709 (Callback.BuildFetched <| 710 Ok 711 { id = 1 712 , name = "1" 713 , job = Just Data.shortJobId 714 , status = BuildStatusAborted 715 , duration = 716 { startedAt = Nothing 717 , finishedAt = Just <| Time.millisToPosix 0 718 } 719 , reapTime = Nothing 720 } 721 ) 722 |> Tuple.first 723 |> Application.handleCallback 724 (Callback.PlanAndResourcesFetched 1 <| Data.httpNotFound) 725 |> Tuple.first 726 |> Common.queryView 727 |> Query.has 728 [ style "background-color" Colors.frame 729 , style "padding" "5px 10px" 730 , style "color" Colors.errorLog 731 , containing [ text "build cancelled" ] 732 ] 733 , test "shows passport officer when build prep request gives 401" <| 734 \_ -> 735 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 736 |> Application.handleCallback 737 (Callback.BuildFetched <| 738 Ok 739 { id = 1 740 , name = "1" 741 , job = Just Data.shortJobId 742 , status = BuildStatusPending 743 , duration = 744 { startedAt = Nothing 745 , finishedAt = Nothing 746 } 747 , reapTime = Nothing 748 } 749 ) 750 |> Tuple.first 751 |> Application.handleCallback 752 (Callback.BuildPrepFetched 1 <| Data.httpUnauthorized) 753 |> Tuple.first 754 |> Common.queryView 755 |> Query.has [ class "not-authorized" ] 756 , test "focuses build body when build is fetched" <| 757 \_ -> 758 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 759 |> Application.handleCallback 760 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusSucceeded)) 761 |> Tuple.second 762 |> Common.contains (Effects.Focus Build.bodyId) 763 , test "events from a different build are discarded" <| 764 \_ -> 765 Application.init 766 flags 767 { protocol = Url.Http 768 , host = "" 769 , port_ = Nothing 770 , path = "/builds/1" 771 , query = Nothing 772 , fragment = Just "Lstepid:1" 773 } 774 |> Tuple.first 775 |> Application.handleCallback 776 (Callback.BuildFetched <| 777 Ok 778 { id = 1 779 , name = "1" 780 , job = Nothing 781 , status = BuildStatusStarted 782 , duration = 783 { startedAt = Nothing 784 , finishedAt = Nothing 785 } 786 , reapTime = Nothing 787 } 788 ) 789 |> Tuple.first 790 |> Application.handleCallback 791 (Callback.PlanAndResourcesFetched 1 <| 792 Ok <| 793 ( { id = "stepid" 794 , step = 795 Concourse.BuildStepTask 796 "step" 797 } 798 , { inputs = [], outputs = [] } 799 ) 800 ) 801 |> Tuple.first 802 |> receiveEvent 803 { url = "http://localhost:8080/api/v1/builds/1/events" 804 , data = STModels.StartTask { id = "stepid", source = "" } (Time.millisToPosix 0) 805 } 806 |> Tuple.first 807 |> receiveEvent 808 { url = "http://localhost:8080/api/v1/builds/1/events" 809 , data = 810 STModels.Log 811 { id = "stepid" 812 , source = "stdout" 813 } 814 "log message" 815 Nothing 816 } 817 |> Tuple.first 818 |> receiveEvent 819 { url = "http://localhost:8080/api/v1/builds/2/events" 820 , data = 821 STModels.Log 822 { id = "stepid" 823 , source = "stdout" 824 } 825 "bad message" 826 Nothing 827 } 828 |> Tuple.first 829 |> Common.queryView 830 |> Query.hasNot [ text "bad message" ] 831 , test "output does not change when the wrong build is fetched" <| 832 \_ -> 833 Common.init "/teams/main/pipelines/pipeline/jobs/job/builds/1" 834 |> Application.handleCallback 835 (Callback.BuildFetched <| 836 Ok 837 { id = 1 838 , name = "1" 839 , job = Nothing 840 , status = BuildStatusStarted 841 , duration = 842 { startedAt = 843 Just <| Time.millisToPosix 0 844 , finishedAt = Nothing 845 } 846 , reapTime = Nothing 847 } 848 ) 849 |> Tuple.first 850 |> Application.handleCallback 851 (Callback.PlanAndResourcesFetched 1 <| 852 Ok <| 853 ( { id = "stepid" 854 , step = 855 Concourse.BuildStepTask 856 "my-step-name" 857 } 858 , { inputs = [], outputs = [] } 859 ) 860 ) 861 |> Tuple.first 862 |> Application.handleCallback 863 (Callback.BuildFetched <| 864 Ok 865 { id = 2 866 , name = "2" 867 , job = Nothing 868 , status = BuildStatusStarted 869 , duration = 870 { startedAt = 871 Just <| Time.millisToPosix 0 872 , finishedAt = Nothing 873 } 874 , reapTime = Nothing 875 } 876 ) 877 |> Tuple.first 878 |> Common.queryView 879 |> Query.has [ text "my-step-name" ] 880 , test "build name does not change when the wrong build is fetched" <| 881 \_ -> 882 Common.init "/teams/main/pipelines/pipeline/jobs/job/builds/1" 883 |> Application.handleCallback 884 (Callback.BuildFetched <| 885 Ok 886 { id = 1 887 , name = "1" 888 , job = Nothing 889 , status = BuildStatusStarted 890 , duration = 891 { startedAt = 892 Just <| Time.millisToPosix 0 893 , finishedAt = Nothing 894 } 895 , reapTime = Nothing 896 } 897 ) 898 |> Tuple.first 899 |> Application.handleCallback 900 (Callback.BuildFetched <| 901 Ok 902 { id = 2 903 , name = "2" 904 , job = Nothing 905 , status = BuildStatusStarted 906 , duration = 907 { startedAt = 908 Just <| Time.millisToPosix 0 909 , finishedAt = Nothing 910 } 911 , reapTime = Nothing 912 } 913 ) 914 |> Tuple.first 915 |> Common.queryView 916 |> Query.hasNot [ text "build #2" ] 917 , test "build prep does not appear when wrong prep is fetched" <| 918 \_ -> 919 Common.init "/teams/main/pipelines/pipeline/jobs/job/builds/1" 920 |> Application.handleCallback 921 (Callback.BuildFetched <| 922 Ok 923 { id = 1 924 , name = "1" 925 , job = Just Data.jobId 926 , status = BuildStatusStarted 927 , duration = 928 { startedAt = 929 Just <| Time.millisToPosix 0 930 , finishedAt = Nothing 931 } 932 , reapTime = Nothing 933 } 934 ) 935 |> Tuple.first 936 |> Application.handleCallback 937 (Callback.BuildPrepFetched 2 <| 938 Ok 939 { pausedPipeline = BuildPrepStatusUnknown 940 , pausedJob = BuildPrepStatusUnknown 941 , maxRunningBuilds = BuildPrepStatusUnknown 942 , inputs = Dict.empty 943 , inputsSatisfied = BuildPrepStatusUnknown 944 , missingInputReasons = Dict.empty 945 } 946 ) 947 |> Tuple.first 948 |> Common.queryView 949 |> Query.hasNot [ text "preparing" ] 950 , test "log lines have timestamps in current zone" <| 951 \_ -> 952 Common.init "/builds/1" 953 |> Application.handleCallback 954 (Callback.BuildFetched <| 955 Ok 956 { id = 1 957 , name = "1" 958 , job = Nothing 959 , status = BuildStatusStarted 960 , duration = 961 { startedAt = 962 Just <| Time.millisToPosix 0 963 , finishedAt = Nothing 964 } 965 , reapTime = Nothing 966 } 967 ) 968 |> Tuple.first 969 |> Application.handleCallback 970 (Callback.PlanAndResourcesFetched 1 <| 971 Ok <| 972 ( { id = "stepid" 973 , step = 974 Concourse.BuildStepTask 975 "step" 976 } 977 , { inputs = [], outputs = [] } 978 ) 979 ) 980 |> Tuple.first 981 |> receiveEvent 982 { url = "http://localhost:8080/api/v1/builds/1/events" 983 , data = 984 STModels.StartTask 985 { id = "stepid" 986 , source = "" 987 } 988 (Time.millisToPosix 0) 989 } 990 |> Tuple.first 991 |> receiveEvent 992 { url = "http://localhost:8080/api/v1/builds/1/events" 993 , data = 994 STModels.Log 995 { id = "stepid" 996 , source = "stdout" 997 } 998 "log message\n" 999 (Just <| Time.millisToPosix 0) 1000 } 1001 |> Tuple.first 1002 |> Application.handleCallback 1003 (Callback.GotCurrentTimeZone <| 1004 Time.customZone (5 * 60) [] 1005 ) 1006 |> Tuple.first 1007 |> Application.update 1008 (Msgs.Update <| 1009 Message.Message.Click <| 1010 Message.Message.StepHeader "stepid" 1011 ) 1012 |> Tuple.first 1013 |> Common.queryView 1014 |> Query.findAll [ class "timestamped-line" ] 1015 |> Query.first 1016 |> Query.has [ text "05:00:00" ] 1017 , test "when build is running it scrolls every build event" <| 1018 \_ -> 1019 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1020 |> Application.handleCallback 1021 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1022 |> Tuple.first 1023 |> receiveEvent 1024 { url = "http://localhost:8080/api/v1/builds/1/events" 1025 , data = STModels.StartTask { id = "stepid", source = "" } (Time.millisToPosix 0) 1026 } 1027 |> Tuple.second 1028 |> Common.contains (Effects.Scroll ScrollDirection.ToBottom "build-body") 1029 , test "when build is not running it does not scroll on build event" <| 1030 \_ -> 1031 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1032 |> Application.handleCallback 1033 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusSucceeded)) 1034 |> Tuple.first 1035 |> receiveEvent 1036 { url = "http://localhost:8080/api/v1/builds/1/events" 1037 , data = STModels.StartTask { id = "stepid", source = "" } (Time.millisToPosix 0) 1038 } 1039 |> Tuple.second 1040 |> Expect.equal [] 1041 , test "build body has scroll handler" <| 1042 \_ -> 1043 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1044 |> Application.handleCallback 1045 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusSucceeded)) 1046 |> Tuple.first 1047 |> Common.queryView 1048 |> Query.find [ id "build-body" ] 1049 |> Event.simulate 1050 (Event.custom "scroll" <| 1051 Encode.object 1052 [ ( "target" 1053 , Encode.object 1054 [ ( "clientHeight", Encode.int 0 ) 1055 , ( "scrollTop", Encode.int 0 ) 1056 , ( "scrollHeight", Encode.int 0 ) 1057 ] 1058 ) 1059 ] 1060 ) 1061 |> Event.expect 1062 (Msgs.Update <| 1063 Message.Message.Scrolled 1064 { scrollHeight = 0 1065 , scrollTop = 0 1066 , clientHeight = 0 1067 } 1068 ) 1069 , test "when build is running but the user is not scrolled to the bottom it does not scroll on build event" <| 1070 \_ -> 1071 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1072 |> Application.handleCallback 1073 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1074 |> Tuple.first 1075 |> Application.update 1076 (Msgs.Update <| 1077 Message.Message.Scrolled 1078 { scrollHeight = 2 1079 , scrollTop = 0 1080 , clientHeight = 1 1081 } 1082 ) 1083 |> Tuple.first 1084 |> receiveEvent 1085 { url = "http://localhost:8080/api/v1/builds/1/events" 1086 , data = STModels.StartTask { id = "stepid", source = "" } (Time.millisToPosix 0) 1087 } 1088 |> Tuple.second 1089 |> Expect.equal [] 1090 , test "when build is running but the user scrolls back to the bottom it scrolls on build event" <| 1091 \_ -> 1092 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1093 |> Application.handleCallback 1094 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1095 |> Tuple.first 1096 |> Application.update 1097 (Msgs.Update <| 1098 Message.Message.Scrolled 1099 { scrollHeight = 2 1100 , scrollTop = 0 1101 , clientHeight = 1 1102 } 1103 ) 1104 |> Tuple.first 1105 |> Application.update 1106 (Msgs.Update <| 1107 Message.Message.Scrolled 1108 { scrollHeight = 2 1109 , scrollTop = 1 1110 , clientHeight = 1 1111 } 1112 ) 1113 |> Tuple.first 1114 |> receiveEvent 1115 { url = "http://localhost:8080/api/v1/builds/1/events" 1116 , data = STModels.StartTask { id = "stepid", source = "" } (Time.millisToPosix 0) 1117 } 1118 |> Tuple.second 1119 |> Expect.equal [ Effects.Scroll ScrollDirection.ToBottom "build-body" ] 1120 , test "pressing 'T' twice triggers two builds" <| 1121 \_ -> 1122 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1123 |> Application.handleCallback 1124 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1125 |> Tuple.first 1126 |> Application.handleCallback 1127 (Callback.BuildJobDetailsFetched <| 1128 Ok 1129 { name = "" 1130 , pipelineName = "p" 1131 , teamName = "t" 1132 , nextBuild = Nothing 1133 , finishedBuild = Nothing 1134 , transitionBuild = Nothing 1135 , paused = False 1136 , disableManualTrigger = False 1137 , inputs = [] 1138 , outputs = [] 1139 , groups = [] 1140 } 1141 ) 1142 |> Tuple.first 1143 |> Application.update 1144 (Msgs.DeliveryReceived <| 1145 KeyDown <| 1146 { ctrlKey = False 1147 , shiftKey = True 1148 , metaKey = False 1149 , code = Keyboard.T 1150 } 1151 ) 1152 |> Tuple.first 1153 |> Application.update 1154 (Msgs.DeliveryReceived <| 1155 KeyUp <| 1156 { ctrlKey = False 1157 , shiftKey = False 1158 , metaKey = False 1159 , code = Keyboard.T 1160 } 1161 ) 1162 |> Tuple.first 1163 |> Application.update 1164 (Msgs.DeliveryReceived <| 1165 KeyDown <| 1166 { ctrlKey = False 1167 , shiftKey = True 1168 , metaKey = False 1169 , code = Keyboard.T 1170 } 1171 ) 1172 |> Tuple.second 1173 |> Expect.equal [ Effects.DoTriggerBuild Data.shortJobId ] 1174 , test "pressing 'R' reruns build" <| 1175 \_ -> 1176 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1177 |> Application.handleCallback 1178 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusSucceeded)) 1179 |> Tuple.first 1180 |> Application.handleCallback 1181 (Callback.BuildJobDetailsFetched <| 1182 Ok 1183 { name = "" 1184 , pipelineName = "p" 1185 , teamName = "t" 1186 , nextBuild = Nothing 1187 , finishedBuild = Nothing 1188 , transitionBuild = Nothing 1189 , paused = False 1190 , disableManualTrigger = False 1191 , inputs = [] 1192 , outputs = [] 1193 , groups = [] 1194 } 1195 ) 1196 |> Tuple.first 1197 |> Application.update 1198 (Msgs.DeliveryReceived <| 1199 KeyDown <| 1200 { ctrlKey = False 1201 , shiftKey = True 1202 , metaKey = False 1203 , code = Keyboard.R 1204 } 1205 ) 1206 |> Tuple.second 1207 |> Expect.equal [ Effects.RerunJobBuild Data.jobBuildId ] 1208 , test "pressing 'R' does nothing if there is a running build" <| 1209 \_ -> 1210 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1211 |> Application.handleCallback 1212 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1213 |> Tuple.first 1214 |> Application.handleCallback 1215 (Callback.BuildJobDetailsFetched <| 1216 Ok 1217 { name = "" 1218 , pipelineName = "p" 1219 , teamName = "t" 1220 , nextBuild = Nothing 1221 , finishedBuild = Nothing 1222 , transitionBuild = Nothing 1223 , paused = False 1224 , disableManualTrigger = False 1225 , inputs = [] 1226 , outputs = [] 1227 , groups = [] 1228 } 1229 ) 1230 |> Tuple.first 1231 |> Application.update 1232 (Msgs.DeliveryReceived <| 1233 KeyDown <| 1234 { ctrlKey = False 1235 , shiftKey = True 1236 , metaKey = False 1237 , code = Keyboard.R 1238 } 1239 ) 1240 |> Tuple.second 1241 |> Expect.equal [] 1242 , test "pressing 'gg' scrolls to the top" <| 1243 \_ -> 1244 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1245 |> Application.handleCallback 1246 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1247 |> Tuple.first 1248 |> Application.update 1249 (Msgs.DeliveryReceived <| 1250 KeyDown <| 1251 { ctrlKey = False 1252 , shiftKey = False 1253 , metaKey = False 1254 , code = Keyboard.G 1255 } 1256 ) 1257 |> Tuple.first 1258 |> Application.update 1259 (Msgs.DeliveryReceived <| 1260 KeyDown <| 1261 { ctrlKey = False 1262 , shiftKey = False 1263 , metaKey = False 1264 , code = Keyboard.G 1265 } 1266 ) 1267 |> Tuple.second 1268 |> Expect.equal [ Effects.Scroll ScrollDirection.ToTop "build-body" ] 1269 , test "pressing 'G' scrolls to the bottom" <| 1270 \_ -> 1271 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1272 |> Application.handleCallback 1273 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1274 |> Tuple.first 1275 |> Application.update 1276 (Msgs.DeliveryReceived <| 1277 KeyDown 1278 { ctrlKey = False 1279 , shiftKey = True 1280 , metaKey = False 1281 , code = Keyboard.G 1282 } 1283 ) 1284 |> Tuple.second 1285 |> Expect.equal [ Effects.Scroll ScrollDirection.ToBottom "build-body" ] 1286 , test "pressing 'g' once does nothing" <| 1287 \_ -> 1288 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1289 |> Application.handleCallback 1290 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1291 |> Tuple.first 1292 |> Application.update 1293 (Msgs.DeliveryReceived <| 1294 KeyDown <| 1295 { ctrlKey = False 1296 , shiftKey = False 1297 , metaKey = False 1298 , code = Keyboard.G 1299 } 1300 ) 1301 |> Tuple.second 1302 |> Expect.equal [] 1303 , test "pressing '?' shows the keyboard help" <| 1304 \_ -> 1305 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1306 |> Application.handleCallback 1307 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1308 |> Tuple.first 1309 |> Application.update 1310 (Msgs.DeliveryReceived <| 1311 KeyDown <| 1312 { ctrlKey = False 1313 , shiftKey = True 1314 , metaKey = False 1315 , code = Keyboard.Slash 1316 } 1317 ) 1318 |> Tuple.first 1319 |> Common.queryView 1320 |> Query.find [ class "keyboard-help" ] 1321 |> Query.hasNot [ class "hidden" ] 1322 , test "says 'loading' on page load" <| 1323 \_ -> 1324 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1325 |> Common.queryView 1326 |> Query.has [ text "loading" ] 1327 , test "fetches build on page load" <| 1328 \_ -> 1329 Application.init 1330 { turbulenceImgSrc = "" 1331 , notFoundImgSrc = "notfound.svg" 1332 , csrfToken = "csrf_token" 1333 , authToken = "" 1334 , pipelineRunningKeyframes = "pipeline-running" 1335 } 1336 { protocol = Url.Http 1337 , host = "" 1338 , port_ = Nothing 1339 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 1340 , query = Nothing 1341 , fragment = Nothing 1342 } 1343 |> Tuple.second 1344 |> Common.contains (Effects.FetchJobBuild Data.jobBuildId) 1345 , test "does not reload build when highlight is modified" <| 1346 \_ -> 1347 let 1348 buildParams = 1349 { id = Data.jobBuildId 1350 , highlight = Routes.HighlightNothing 1351 } 1352 in 1353 Common.init "/" 1354 |> Application.handleDelivery 1355 (RouteChanged <| 1356 Routes.Build 1357 buildParams 1358 ) 1359 |> Tuple.first 1360 |> Application.handleDelivery 1361 (RouteChanged <| 1362 Routes.Build 1363 { buildParams 1364 | highlight = Routes.HighlightLine "step" 1 1365 } 1366 ) 1367 |> Tuple.second 1368 |> Common.notContains 1369 (Effects.FetchJobBuild <| buildParams.id) 1370 , test "does not reload one off build page when highlight is modified" <| 1371 \_ -> 1372 let 1373 buildParams = 1374 { id = 1 1375 , highlight = Routes.HighlightNothing 1376 } 1377 in 1378 Common.init "/" 1379 |> Application.handleDelivery 1380 (RouteChanged <| 1381 Routes.OneOffBuild 1382 buildParams 1383 ) 1384 |> Tuple.first 1385 |> Application.handleDelivery 1386 (RouteChanged <| 1387 Routes.OneOffBuild 1388 { buildParams 1389 | highlight = Routes.HighlightLine "step" 1 1390 } 1391 ) 1392 |> Tuple.second 1393 |> Common.notContains 1394 (Effects.FetchBuild 0 buildParams.id) 1395 , test "gets current timezone on page load" <| 1396 \_ -> 1397 Application.init 1398 { turbulenceImgSrc = "" 1399 , notFoundImgSrc = "notfound.svg" 1400 , csrfToken = "csrf_token" 1401 , authToken = "" 1402 , pipelineRunningKeyframes = "pipeline-running" 1403 } 1404 { protocol = Url.Http 1405 , host = "" 1406 , port_ = Nothing 1407 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 1408 , query = Nothing 1409 , fragment = Nothing 1410 } 1411 |> Tuple.second 1412 |> Common.contains Effects.GetCurrentTimeZone 1413 , describe "top bar" <| 1414 [ test "has a top bar" <| 1415 \_ -> 1416 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1417 |> Common.queryView 1418 |> Query.has [ id "top-bar-app" ] 1419 , test "has a concourse icon" <| 1420 \_ -> 1421 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1422 |> Common.queryView 1423 |> Query.find [ id "top-bar-app" ] 1424 |> Query.has 1425 [ style "background-image" <| 1426 Assets.backgroundImage <| 1427 Just Assets.ConcourseLogoWhite 1428 ] 1429 , test "has the breadcrumbs" <| 1430 \_ -> 1431 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1432 |> Common.queryView 1433 |> Query.find [ id "top-bar-app" ] 1434 |> Expect.all 1435 [ Query.has [ id "breadcrumb-pipeline" ] 1436 , Query.has [ text "p" ] 1437 , Query.has [ id "breadcrumb-job" ] 1438 , Query.has [ text "j" ] 1439 ] 1440 , test "has the breadcrumbs after fetching build" <| 1441 \_ -> 1442 Common.init "/builds/1" 1443 |> fetchBuild BuildStatusSucceeded 1444 |> Tuple.first 1445 |> Common.queryView 1446 |> Query.find [ id "top-bar-app" ] 1447 |> Expect.all 1448 [ Query.has [ id "breadcrumb-pipeline" ] 1449 , Query.has [ text "p" ] 1450 , Query.has [ id "breadcrumb-job" ] 1451 , Query.has [ text "j" ] 1452 ] 1453 , test "has a user section" <| 1454 \_ -> 1455 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1456 |> Common.queryView 1457 |> Query.find [ id "top-bar-app" ] 1458 |> Query.has [ id "login-component" ] 1459 ] 1460 , test "page below top bar has padding to accomodate top bar" <| 1461 \_ -> 1462 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1463 |> Common.queryView 1464 |> Query.find [ id "page-below-top-bar" ] 1465 |> Query.has [ style "padding-top" "54px" ] 1466 , test "page below top bar fills vertically without scrolling" <| 1467 \_ -> 1468 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1469 |> Common.queryView 1470 |> Query.find [ id "page-below-top-bar" ] 1471 |> Query.has 1472 [ style "height" "100%" 1473 , style "box-sizing" "border-box" 1474 ] 1475 , describe "after build is fetched" <| 1476 let 1477 givenBuildFetched _ = 1478 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" |> fetchBuild BuildStatusSucceeded 1479 in 1480 [ test "has a header after the build is fetched" <| 1481 givenBuildFetched 1482 >> Tuple.first 1483 >> Common.queryView 1484 >> Query.has [ id "build-header" ] 1485 , test "build body scrolls independently of page frame" <| 1486 givenBuildFetched 1487 >> Tuple.first 1488 >> Common.queryView 1489 >> Query.find [ id "build-body" ] 1490 >> Query.has [ style "overflow-y" "auto" ] 1491 , test "build body has momentum based scroll enabled" <| 1492 givenBuildFetched 1493 >> Tuple.first 1494 >> Common.queryView 1495 >> Query.find [ id "build-body" ] 1496 >> Query.has [ style "-webkit-overflow-scrolling" "touch" ] 1497 , test "fetches build history and job details after build is fetched" <| 1498 givenBuildFetched 1499 >> Tuple.second 1500 >> Expect.all 1501 [ Common.contains (Effects.FetchBuildHistory Data.shortJobId Nothing) 1502 , Common.contains (Effects.FetchBuildJobDetails Data.shortJobId) 1503 ] 1504 , test "header is 60px tall" <| 1505 givenBuildFetched 1506 >> Tuple.first 1507 >> Common.queryView 1508 >> Query.find [ id "build-header" ] 1509 >> Query.has [ style "height" "60px" ] 1510 , test "header lays out horizontally" <| 1511 givenBuildFetched 1512 >> Tuple.first 1513 >> Common.queryView 1514 >> Query.find [ id "build-header" ] 1515 >> Query.has [ style "display" "flex" ] 1516 , test "when build finishes, shows finished timestamp" <| 1517 \_ -> 1518 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1519 |> Application.handleCallback (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1520 |> Tuple.first 1521 |> receiveEvent 1522 { url = "http://localhost:8080/api/v1/builds/1/events" 1523 , data = STModels.BuildStatus BuildStatusSucceeded (Time.millisToPosix 0) 1524 } 1525 |> Tuple.first 1526 |> Application.update 1527 (Msgs.DeliveryReceived <| 1528 ClockTicked 1529 OneSecond 1530 (Time.millisToPosix (2 * 1000)) 1531 ) 1532 |> Tuple.first 1533 |> Common.queryView 1534 |> Query.find [ class "build-duration" ] 1535 |> Query.find [ tag "tr", containing [ text "finished" ] ] 1536 |> Query.has [ text "2s ago" ] 1537 , test "when build finishes succesfully, header background is green" <| 1538 \_ -> 1539 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1540 |> Application.handleCallback (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusStarted)) 1541 |> Tuple.first 1542 |> receiveEvent 1543 { url = "http://localhost:8080/api/v1/builds/1/events" 1544 , data = STModels.BuildStatus BuildStatusSucceeded (Time.millisToPosix 0) 1545 } 1546 |> Tuple.first 1547 |> Application.update 1548 (Msgs.DeliveryReceived <| 1549 ClockTicked 1550 OneSecond 1551 (Time.millisToPosix (2 * 1000)) 1552 ) 1553 |> Tuple.first 1554 |> Common.queryView 1555 |> Query.find [ id "build-header" ] 1556 |> Query.has [ style "background-color" Colors.success ] 1557 , test "when less than 24h old, shows relative time since build" <| 1558 \_ -> 1559 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1560 |> Application.handleCallback (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusSucceeded)) 1561 |> Tuple.first 1562 |> Application.update 1563 (Msgs.DeliveryReceived <| 1564 ClockTicked 1565 OneSecond 1566 (Time.millisToPosix (2 * 1000)) 1567 ) 1568 |> Tuple.first 1569 |> Common.queryView 1570 |> Query.find [ id "build-header" ] 1571 |> Query.has [ text "2s ago" ] 1572 , test "when at least 24h old, shows absolute time of build" <| 1573 \_ -> 1574 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1575 |> Application.handleCallback 1576 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusSucceeded)) 1577 |> Tuple.first 1578 |> Application.update 1579 (Msgs.DeliveryReceived <| 1580 ClockTicked 1581 OneSecond 1582 (Time.millisToPosix (24 * 60 * 60 * 1000)) 1583 ) 1584 |> Tuple.first 1585 |> Common.queryView 1586 |> Query.find [ id "build-header" ] 1587 |> Query.has [ text "Jan 1 1970 12:00:00 AM" ] 1588 , test "when at least 24h old, absolute time is in current zone" <| 1589 \_ -> 1590 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1591 |> Application.handleCallback 1592 (Callback.GotCurrentTimeZone <| 1593 Time.customZone (5 * 60) [] 1594 ) 1595 |> Tuple.first 1596 |> Application.handleCallback 1597 (Callback.BuildFetched <| Ok (Data.jobBuild BuildStatusSucceeded)) 1598 |> Tuple.first 1599 |> Application.update 1600 (Msgs.DeliveryReceived <| 1601 ClockTicked 1602 OneSecond 1603 (Time.millisToPosix (24 * 60 * 60 * 1000)) 1604 ) 1605 |> Tuple.first 1606 |> Common.queryView 1607 |> Query.find [ id "build-header" ] 1608 |> Query.has [ text "Jan 1 1970 05:00:00 AM" ] 1609 , describe "build banner coloration" 1610 [ test "pending build has grey banner" <| 1611 \_ -> 1612 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1613 |> fetchBuildWithStatus BuildStatusPending 1614 |> Common.queryView 1615 |> Query.find [ id "build-header" ] 1616 |> Query.has [ style "background" "#9b9b9b" ] 1617 , test "started build has yellow banner" <| 1618 \_ -> 1619 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1620 |> fetchBuildWithStatus BuildStatusStarted 1621 |> Common.queryView 1622 |> Query.find [ id "build-header" ] 1623 |> Query.has [ style "background" "#f1c40f" ] 1624 , test "succeeded build has green banner" <| 1625 \_ -> 1626 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1627 |> fetchBuildWithStatus BuildStatusSucceeded 1628 |> Common.queryView 1629 |> Query.find [ id "build-header" ] 1630 |> Query.has [ style "background" "#11c560" ] 1631 , test "failed build has red banner" <| 1632 \_ -> 1633 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1634 |> fetchBuildWithStatus BuildStatusFailed 1635 |> Common.queryView 1636 |> Query.find [ id "build-header" ] 1637 |> Query.has [ style "background" "#ed4b35" ] 1638 , test "errored build has amber banner" <| 1639 \_ -> 1640 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1641 |> fetchBuildWithStatus BuildStatusErrored 1642 |> Common.queryView 1643 |> Query.find [ id "build-header" ] 1644 |> Query.has [ style "background" "#f5a623" ] 1645 , test "aborted build has brown banner" <| 1646 \_ -> 1647 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1648 |> fetchBuildWithStatus BuildStatusAborted 1649 |> Common.queryView 1650 |> Query.find [ id "build-header" ] 1651 |> Query.has [ style "background" "#8b572a" ] 1652 ] 1653 , describe "build history tab coloration" 1654 [ test "pending build has grey tab in build history" <| 1655 \_ -> 1656 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1657 |> fetchBuildWithStatus BuildStatusPending 1658 |> Common.queryView 1659 |> Query.find [ id "builds" ] 1660 |> Query.find [ tag "li" ] 1661 |> Query.has [ style "background" "#9b9b9b" ] 1662 , test "started build has animated striped yellow tab in build history" <| 1663 \_ -> 1664 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1665 |> fetchBuildWithStatus BuildStatusStarted 1666 |> Common.queryView 1667 |> Query.find [ id "builds" ] 1668 |> Query.find [ tag "li" ] 1669 |> isColorWithStripes { thick = "#f1c40f", thin = "#fad43b" } 1670 , test "succeeded build has green tab in build history" <| 1671 \_ -> 1672 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1673 |> fetchBuildWithStatus BuildStatusSucceeded 1674 |> Common.queryView 1675 |> Query.find [ id "builds" ] 1676 |> Query.find [ tag "li" ] 1677 |> Query.has [ style "background" "#11c560" ] 1678 , test "failed build has red tab in build history" <| 1679 \_ -> 1680 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1681 |> fetchBuildWithStatus BuildStatusFailed 1682 |> Common.queryView 1683 |> Query.find [ id "builds" ] 1684 |> Query.find [ tag "li" ] 1685 |> Query.has [ style "background" "#ed4b35" ] 1686 , test "errored build has amber tab in build history" <| 1687 \_ -> 1688 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1689 |> fetchBuildWithStatus BuildStatusErrored 1690 |> Common.queryView 1691 |> Query.find [ id "builds" ] 1692 |> Query.find [ tag "li" ] 1693 |> Query.has [ style "background" "#f5a623" ] 1694 , test "aborted build has brown tab in build history" <| 1695 \_ -> 1696 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 1697 |> fetchBuildWithStatus BuildStatusAborted 1698 |> Common.queryView 1699 |> Query.find [ id "builds" ] 1700 |> Query.find [ tag "li" ] 1701 |> Query.has [ style "background" "#8b572a" ] 1702 ] 1703 , test "header spreads out contents" <| 1704 givenBuildFetched 1705 >> Tuple.first 1706 >> Common.queryView 1707 >> Query.find [ id "build-header" ] 1708 >> Query.has [ style "justify-content" "space-between" ] 1709 , describe "after history and details get fetched" <| 1710 let 1711 givenHistoryAndDetailsFetched = 1712 givenBuildFetched 1713 >> Tuple.first 1714 >> fetchHistory 1715 >> Tuple.first 1716 >> fetchJobDetails 1717 in 1718 [ test "trigger build button on right side of header " <| 1719 givenHistoryAndDetailsFetched 1720 >> Tuple.first 1721 >> Common.queryView 1722 >> Query.find [ id "build-header" ] 1723 >> Query.children [] 1724 >> Query.index -1 1725 >> Query.has 1726 [ attribute <| 1727 Attr.attribute "aria-label" "Trigger Build" 1728 ] 1729 , test """page contents lay out vertically, filling available 1730 space without scrolling horizontally""" <| 1731 givenHistoryAndDetailsFetched 1732 >> Tuple.first 1733 >> Common.queryView 1734 >> Query.find [ id "page-below-top-bar" ] 1735 >> Query.children [] 1736 >> Query.index 1 1737 >> Query.has 1738 [ style "flex-grow" "1" 1739 , style "display" "flex" 1740 , style "flex-direction" "column" 1741 , style "overflow" "hidden" 1742 ] 1743 , test "pressing 'L' updates the URL" <| 1744 givenBuildFetched 1745 >> Tuple.first 1746 >> Application.handleCallback 1747 (Callback.BuildHistoryFetched 1748 (Ok 1749 { pagination = 1750 { previousPage = Nothing 1751 , nextPage = 1752 Just 1753 { direction = To 1 1754 , limit = 100 1755 } 1756 } 1757 , content = 1758 [ Data.jobBuild BuildStatusSucceeded 1759 , { id = 2 1760 , name = "2" 1761 , job = Just Data.shortJobId 1762 , status = BuildStatusSucceeded 1763 , duration = 1764 { startedAt = Just <| Time.millisToPosix 0 1765 , finishedAt = Just <| Time.millisToPosix 0 1766 } 1767 , reapTime = Nothing 1768 } 1769 ] 1770 } 1771 ) 1772 ) 1773 >> Tuple.first 1774 >> Application.handleDelivery 1775 (KeyDown 1776 { ctrlKey = False 1777 , shiftKey = False 1778 , metaKey = False 1779 , code = Keyboard.L 1780 } 1781 ) 1782 >> Tuple.second 1783 >> Expect.equal 1784 [ Effects.NavigateTo 1785 "/teams/t/pipelines/p/jobs/j/builds/2" 1786 ] 1787 , test "can use keyboard to switch builds after status change event" <| 1788 givenBuildFetched 1789 >> Tuple.first 1790 >> Application.handleCallback 1791 (Callback.BuildHistoryFetched 1792 (Ok 1793 { pagination = 1794 { previousPage = Nothing 1795 , nextPage = 1796 Just 1797 { direction = To 1 1798 , limit = 100 1799 } 1800 } 1801 , content = 1802 [ Data.jobBuild BuildStatusSucceeded 1803 , { id = 2 1804 , name = "2" 1805 , job = Just Data.shortJobId 1806 , status = BuildStatusSucceeded 1807 , duration = 1808 { startedAt = Just <| Time.millisToPosix 0 1809 , finishedAt = Just <| Time.millisToPosix 0 1810 } 1811 , reapTime = Nothing 1812 } 1813 ] 1814 } 1815 ) 1816 ) 1817 >> Tuple.first 1818 >> Application.handleDelivery 1819 (Subscription.EventsReceived <| 1820 Ok 1821 [ { data = 1822 STModels.BuildStatus BuildStatusSucceeded <| 1823 Time.millisToPosix 0 1824 , url = "http://localhost:8080/api/v1/builds/1/events" 1825 } 1826 ] 1827 ) 1828 >> Tuple.first 1829 >> Application.handleDelivery 1830 (Subscription.KeyDown 1831 { ctrlKey = False 1832 , shiftKey = False 1833 , metaKey = False 1834 , code = Keyboard.L 1835 } 1836 ) 1837 >> Tuple.second 1838 >> Common.contains 1839 (Effects.NavigateTo "/teams/t/pipelines/p/jobs/j/builds/2") 1840 , test "switching tabs updates the build name" <| 1841 givenBuildFetched 1842 >> Tuple.first 1843 >> Application.handleCallback 1844 (Callback.BuildHistoryFetched 1845 (Ok 1846 { pagination = 1847 { previousPage = Nothing 1848 , nextPage = 1849 Just 1850 { direction = To 1 1851 , limit = 100 1852 } 1853 } 1854 , content = 1855 [ Data.jobBuild BuildStatusSucceeded 1856 , { id = 2 1857 , name = "2" 1858 , job = Just Data.shortJobId 1859 , status = BuildStatusSucceeded 1860 , duration = 1861 { startedAt = Just <| Time.millisToPosix 0 1862 , finishedAt = Just <| Time.millisToPosix 0 1863 } 1864 , reapTime = Nothing 1865 } 1866 ] 1867 } 1868 ) 1869 ) 1870 >> Tuple.first 1871 >> Application.handleDelivery 1872 (RouteChanged <| 1873 Routes.Build 1874 { id = Data.jobBuildId |> Data.withBuildName "2" 1875 , highlight = Routes.HighlightNothing 1876 } 1877 ) 1878 >> Tuple.first 1879 >> Common.queryView 1880 >> Query.has [ text " #2" ] 1881 , test "pressing Command-L does nothing" <| 1882 givenBuildFetched 1883 >> Tuple.first 1884 >> Application.handleCallback 1885 (Callback.BuildHistoryFetched 1886 (Ok 1887 { pagination = 1888 { previousPage = Nothing 1889 , nextPage = 1890 Just 1891 { direction = To 1 1892 , limit = 100 1893 } 1894 } 1895 , content = 1896 [ Data.jobBuild BuildStatusSucceeded 1897 , { id = 2 1898 , name = "2" 1899 , job = Just Data.shortJobId 1900 , status = BuildStatusSucceeded 1901 , duration = 1902 { startedAt = Just <| Time.millisToPosix 0 1903 , finishedAt = Just <| Time.millisToPosix 0 1904 } 1905 , reapTime = Nothing 1906 } 1907 ] 1908 } 1909 ) 1910 ) 1911 >> Tuple.first 1912 >> Application.handleDelivery 1913 (KeyDown 1914 { ctrlKey = False 1915 , shiftKey = False 1916 , metaKey = True 1917 , code = Keyboard.L 1918 } 1919 ) 1920 >> Tuple.second 1921 >> Expect.equal [] 1922 , test "pressing Control-L does nothing" <| 1923 givenBuildFetched 1924 >> Tuple.first 1925 >> Application.handleCallback 1926 (Callback.BuildHistoryFetched 1927 (Ok 1928 { pagination = 1929 { previousPage = Nothing 1930 , nextPage = 1931 Just 1932 { direction = To 1 1933 , limit = 100 1934 } 1935 } 1936 , content = 1937 [ Data.jobBuild BuildStatusSucceeded 1938 , { id = 2 1939 , name = "2" 1940 , job = Just Data.shortJobId 1941 , status = BuildStatusSucceeded 1942 , duration = 1943 { startedAt = Just <| Time.millisToPosix 0 1944 , finishedAt = Just <| Time.millisToPosix 0 1945 } 1946 , reapTime = Nothing 1947 } 1948 ] 1949 } 1950 ) 1951 ) 1952 >> Tuple.first 1953 >> Application.handleDelivery 1954 (KeyDown 1955 { ctrlKey = True 1956 , shiftKey = False 1957 , metaKey = False 1958 , code = Keyboard.L 1959 } 1960 ) 1961 >> Tuple.second 1962 >> Expect.equal [] 1963 , describe "scrolling builds" 1964 [ test "checks if last build is visible" <| 1965 givenBuildFetched 1966 >> Tuple.first 1967 >> Application.handleCallback 1968 (Callback.BuildHistoryFetched 1969 (Ok 1970 { pagination = 1971 { previousPage = Nothing 1972 , nextPage = 1973 Just 1974 { direction = To 1 1975 , limit = 100 1976 } 1977 } 1978 , content = [ Data.jobBuild BuildStatusSucceeded ] 1979 } 1980 ) 1981 ) 1982 >> Tuple.first 1983 >> Application.update 1984 (Msgs.Update <| 1985 Message.Message.ScrollBuilds 1986 { deltaX = 0, deltaY = 0, deltaMode = DeltaModePixel } 1987 ) 1988 >> Tuple.second 1989 >> Common.contains (Effects.CheckIsVisible "1") 1990 , test "deltaX is negated" <| 1991 givenBuildFetched 1992 >> Tuple.first 1993 >> Application.update 1994 (Msgs.Update <| 1995 Message.Message.ScrollBuilds 1996 { deltaX = 5, deltaY = 0, deltaMode = DeltaModePixel } 1997 ) 1998 >> Tuple.second 1999 >> Common.contains (Effects.Scroll (ScrollDirection.Sideways -5) "builds") 2000 , test "deltaY is not negated" <| 2001 givenBuildFetched 2002 >> Tuple.first 2003 >> Application.update 2004 (Msgs.Update <| 2005 Message.Message.ScrollBuilds 2006 { deltaX = 0, deltaY = 5, deltaMode = DeltaModePixel } 2007 ) 2008 >> Tuple.second 2009 >> Common.contains (Effects.Scroll (ScrollDirection.Sideways 5) "builds") 2010 , test "deltaX is preferred" <| 2011 givenBuildFetched 2012 >> Tuple.first 2013 >> Application.update 2014 (Msgs.Update <| 2015 Message.Message.ScrollBuilds 2016 { deltaX = 5, deltaY = 4, deltaMode = DeltaModePixel } 2017 ) 2018 >> Tuple.second 2019 >> Common.contains (Effects.Scroll (ScrollDirection.Sideways -5) "builds") 2020 , test "DeltaModeLine" <| 2021 givenBuildFetched 2022 >> Tuple.first 2023 >> Application.update 2024 (Msgs.Update <| 2025 Message.Message.ScrollBuilds 2026 { deltaX = 5, deltaY = 0, deltaMode = DeltaModeLine } 2027 ) 2028 >> Tuple.second 2029 >> Common.contains (Effects.Scroll (ScrollDirection.Sideways -100) "builds") 2030 , test "DeltaModePage" <| 2031 givenBuildFetched 2032 >> Tuple.first 2033 >> Application.update 2034 (Msgs.Update <| 2035 Message.Message.ScrollBuilds 2036 { deltaX = 5, deltaY = 0, deltaMode = DeltaModePage } 2037 ) 2038 >> Tuple.second 2039 >> Common.contains (Effects.Scroll (ScrollDirection.Sideways -4000) "builds") 2040 ] 2041 , test "subscribes to element visibility" <| 2042 givenBuildFetched 2043 >> Tuple.first 2044 >> Application.subscriptions 2045 >> Common.contains Subscription.OnElementVisible 2046 , test "scrolling to last build fetches more if possible" <| 2047 givenBuildFetched 2048 >> Tuple.first 2049 >> Application.handleCallback 2050 (Callback.BuildHistoryFetched 2051 (Ok 2052 { pagination = 2053 { previousPage = Nothing 2054 , nextPage = 2055 Just 2056 { direction = To 1 2057 , limit = 100 2058 } 2059 } 2060 , content = [ Data.jobBuild BuildStatusSucceeded ] 2061 } 2062 ) 2063 ) 2064 >> Tuple.first 2065 >> Application.handleDelivery 2066 (Subscription.ElementVisible ( "1", True )) 2067 >> Tuple.second 2068 >> Expect.equal 2069 [ Effects.FetchBuildHistory Data.shortJobId 2070 (Just { direction = To 1, limit = 100 }) 2071 ] 2072 , test "scrolling to last build while fetching fetches no more" <| 2073 givenBuildFetched 2074 >> Tuple.first 2075 >> Application.handleCallback 2076 (Callback.BuildHistoryFetched 2077 (Ok 2078 { pagination = 2079 { previousPage = Nothing 2080 , nextPage = 2081 Just 2082 { direction = To 1 2083 , limit = 100 2084 } 2085 } 2086 , content = [ Data.jobBuild BuildStatusSucceeded ] 2087 } 2088 ) 2089 ) 2090 >> Tuple.first 2091 >> Application.handleDelivery 2092 (Subscription.ElementVisible ( "1", True )) 2093 >> Tuple.first 2094 >> Application.handleDelivery 2095 (Subscription.ElementVisible ( "1", True )) 2096 >> Tuple.second 2097 >> Expect.equal [] 2098 , test "scrolling to absolute last build fetches no more" <| 2099 givenHistoryAndDetailsFetched 2100 >> Tuple.first 2101 >> Application.handleDelivery 2102 (Subscription.ElementVisible ( "1", True )) 2103 >> Tuple.second 2104 >> Expect.equal [] 2105 , test "if build is present in history, fetches no more" <| 2106 givenBuildFetched 2107 >> Tuple.first 2108 >> Application.handleCallback 2109 (Callback.BuildHistoryFetched 2110 (Ok 2111 { pagination = 2112 { previousPage = Nothing 2113 , nextPage = 2114 Just 2115 { direction = To 1 2116 , limit = 100 2117 } 2118 } 2119 , content = [ Data.jobBuild BuildStatusSucceeded ] 2120 } 2121 ) 2122 ) 2123 >> Tuple.first 2124 >> Application.handleCallback 2125 (Callback.BuildHistoryFetched 2126 (Ok 2127 { pagination = 2128 { previousPage = Nothing 2129 , nextPage = 2130 Just 2131 { direction = To 2 2132 , limit = 100 2133 } 2134 } 2135 , content = 2136 [ { id = 2 2137 , name = "2" 2138 , job = Just Data.shortJobId 2139 , status = BuildStatusSucceeded 2140 , duration = 2141 { startedAt = Nothing 2142 , finishedAt = Nothing 2143 } 2144 , reapTime = Nothing 2145 } 2146 ] 2147 } 2148 ) 2149 ) 2150 >> Tuple.second 2151 >> Common.notContains 2152 (Effects.FetchBuildHistory Data.shortJobId 2153 (Just { direction = To 2, limit = 100 }) 2154 ) 2155 , test "if build is present in history, checks its visibility" <| 2156 givenBuildFetched 2157 >> Tuple.first 2158 >> Application.handleCallback 2159 (Callback.BuildHistoryFetched 2160 (Ok 2161 { pagination = 2162 { previousPage = Nothing 2163 , nextPage = 2164 Just 2165 { direction = To 1 2166 , limit = 100 2167 } 2168 } 2169 , content = [ Data.jobBuild BuildStatusSucceeded ] 2170 } 2171 ) 2172 ) 2173 >> Tuple.second 2174 >> Common.contains (Effects.CheckIsVisible "1") 2175 , test "if build is present and invisible, scrolls to it" <| 2176 givenBuildFetched 2177 >> Tuple.first 2178 >> Application.handleCallback 2179 (Callback.BuildHistoryFetched 2180 (Ok 2181 { pagination = 2182 { previousPage = Nothing 2183 , nextPage = 2184 Just 2185 { direction = To 1 2186 , limit = 100 2187 } 2188 } 2189 , content = [ Data.jobBuild BuildStatusSucceeded ] 2190 } 2191 ) 2192 ) 2193 >> Tuple.first 2194 >> Application.handleDelivery 2195 (Subscription.ElementVisible ( "1", False )) 2196 >> Tuple.second 2197 >> Expect.equal [ Effects.Scroll (ScrollDirection.ToId "1") "builds" ] 2198 , test "does not scroll to current build more than once" <| 2199 givenBuildFetched 2200 >> Tuple.first 2201 >> Application.handleCallback 2202 (Callback.BuildHistoryFetched 2203 (Ok 2204 { pagination = 2205 { previousPage = Nothing 2206 , nextPage = 2207 Just 2208 { direction = To 1 2209 , limit = 100 2210 } 2211 } 2212 , content = [ Data.jobBuild BuildStatusSucceeded ] 2213 } 2214 ) 2215 ) 2216 >> Tuple.first 2217 >> Application.handleDelivery 2218 (Subscription.ElementVisible ( "1", False )) 2219 >> Tuple.first 2220 >> Application.handleDelivery 2221 (Subscription.ElementVisible ( "1", False )) 2222 >> Tuple.second 2223 >> Expect.equal [] 2224 , test "if build is not present in history, fetches more" <| 2225 givenBuildFetched 2226 >> Tuple.first 2227 >> Application.handleCallback 2228 (Callback.BuildHistoryFetched 2229 (Ok 2230 { pagination = 2231 { previousPage = Nothing 2232 , nextPage = 2233 Just 2234 { direction = To 2 2235 , limit = 100 2236 } 2237 } 2238 , content = 2239 [ { id = 2 2240 , name = "2" 2241 , job = Just Data.shortJobId 2242 , status = BuildStatusSucceeded 2243 , duration = 2244 { startedAt = Nothing 2245 , finishedAt = Nothing 2246 } 2247 , reapTime = Nothing 2248 } 2249 ] 2250 } 2251 ) 2252 ) 2253 >> Tuple.second 2254 >> Expect.equal 2255 [ Effects.FetchBuildHistory Data.shortJobId 2256 (Just { direction = To 2, limit = 100 }) 2257 ] 2258 , test "trigger build button is styled as a box of the color of the build status" <| 2259 givenHistoryAndDetailsFetched 2260 >> Tuple.first 2261 >> Common.queryView 2262 >> Query.find 2263 [ attribute <| 2264 Attr.attribute "aria-label" "Trigger Build" 2265 ] 2266 >> Query.has 2267 [ style "padding" "10px" 2268 , style "background-color" brightGreen 2269 , style "outline" "none" 2270 , style "margin" "0" 2271 , style "border-width" "0 0 0 1px" 2272 , style "border-color" darkGreen 2273 , style "border-style" "solid" 2274 ] 2275 , test "hovered trigger build button is styled as a box of the secondary color of the build status" <| 2276 givenHistoryAndDetailsFetched 2277 >> Tuple.first 2278 >> Application.update 2279 (Msgs.Update <| 2280 Message.Message.Hover <| 2281 Just Message.Message.TriggerBuildButton 2282 ) 2283 >> Tuple.first 2284 >> Common.queryView 2285 >> Query.find 2286 [ attribute <| 2287 Attr.attribute "aria-label" "Trigger Build" 2288 ] 2289 >> Query.has 2290 [ style "padding" "10px" 2291 , style "background-color" darkGreen 2292 , style "outline" "none" 2293 , style "margin" "0" 2294 , style "border-width" "0 0 0 1px" 2295 , style "border-color" darkGreen 2296 , style "border-style" "solid" 2297 ] 2298 , test "trigger build button has pointer cursor" <| 2299 givenHistoryAndDetailsFetched 2300 >> Tuple.first 2301 >> Common.queryView 2302 >> Query.find 2303 [ attribute <| 2304 Attr.attribute "aria-label" "Trigger Build" 2305 ] 2306 >> Query.has [ style "cursor" "pointer" ] 2307 , test "trigger build button has 'plus' icon" <| 2308 givenHistoryAndDetailsFetched 2309 >> Tuple.first 2310 >> Common.queryView 2311 >> Query.find 2312 [ attribute <| 2313 Attr.attribute "aria-label" "Trigger Build" 2314 ] 2315 >> Query.children [] 2316 >> Query.first 2317 >> Query.has 2318 (iconSelector 2319 { size = "40px" 2320 , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon 2321 } 2322 ) 2323 ] 2324 , describe "when history and details fetched with manual triggering disabled" <| 2325 let 2326 givenHistoryAndDetailsFetched = 2327 givenBuildFetched 2328 >> Tuple.first 2329 >> fetchHistory 2330 >> Tuple.first 2331 >> fetchJobDetailsNoTrigger 2332 in 2333 [ test "when manual triggering is disabled, trigger build button has default cursor" <| 2334 givenHistoryAndDetailsFetched 2335 >> Tuple.first 2336 >> Common.queryView 2337 >> Query.find 2338 [ attribute <| 2339 Attr.attribute "aria-label" "Trigger Build" 2340 ] 2341 >> Query.has [ style "cursor" "default" ] 2342 , defineHoverBehaviour 2343 { name = "disabled trigger build button" 2344 , setup = 2345 givenHistoryAndDetailsFetched () |> Tuple.first 2346 , query = 2347 Common.queryView 2348 >> Query.find 2349 [ attribute <| 2350 Attr.attribute "aria-label" "Trigger Build" 2351 ] 2352 , unhoveredSelector = 2353 { description = "grey plus icon" 2354 , selector = 2355 iconSelector 2356 { size = "40px" 2357 , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon 2358 } 2359 } 2360 , hoveredSelector = 2361 { description = "grey plus icon with tooltip" 2362 , selector = 2363 [ style "position" "relative" 2364 , containing 2365 [ containing 2366 [ text "manual triggering disabled in job config" ] 2367 ] 2368 , containing <| 2369 iconSelector 2370 { size = "40px" 2371 , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon 2372 } 2373 ] 2374 } 2375 , hoverable = Message.Message.TriggerBuildButton 2376 } 2377 ] 2378 ] 2379 , describe "given build started and history and details fetched" <| 2380 let 2381 givenBuildStarted _ = 2382 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 2383 |> fetchBuildWithStatus BuildStatusStarted 2384 |> fetchHistory 2385 |> Tuple.first 2386 |> fetchJobDetails 2387 in 2388 [ test "build action section lays out horizontally" <| 2389 givenBuildStarted 2390 >> Tuple.first 2391 >> Common.queryView 2392 >> Query.find [ id "build-header" ] 2393 >> Query.children [] 2394 >> Query.index -1 2395 >> Query.has [ style "display" "flex" ] 2396 , test "abort build button is to the left of the trigger button" <| 2397 givenBuildStarted 2398 >> Tuple.first 2399 >> Common.queryView 2400 >> Query.find [ id "build-header" ] 2401 >> Query.children [] 2402 >> Query.index -1 2403 >> Query.children [] 2404 >> Query.first 2405 >> Query.has 2406 [ attribute <| 2407 Attr.attribute "aria-label" "Abort Build" 2408 ] 2409 , test "abort build button is styled as a bright red box" <| 2410 givenBuildStarted 2411 >> Tuple.first 2412 >> Common.queryView 2413 >> Query.find 2414 [ attribute <| 2415 Attr.attribute "aria-label" "Abort Build" 2416 ] 2417 >> Query.has 2418 [ style "padding" "10px" 2419 , style "background-color" brightRed 2420 , style "outline" "none" 2421 , style "margin" "0" 2422 , style "border-width" "0 0 0 1px" 2423 , style "border-color" darkRed 2424 , style "border-style" "solid" 2425 ] 2426 , test "hovered abort build button is styled as a dark red box" <| 2427 givenBuildStarted 2428 >> Tuple.first 2429 >> Application.update 2430 (Msgs.Update <| 2431 Message.Message.Hover <| 2432 Just Message.Message.AbortBuildButton 2433 ) 2434 >> Tuple.first 2435 >> Common.queryView 2436 >> Query.find 2437 [ attribute <| 2438 Attr.attribute "aria-label" "Abort Build" 2439 ] 2440 >> Query.has 2441 [ style "padding" "10px" 2442 , style "background-color" darkRed 2443 , style "outline" "none" 2444 , style "margin" "0" 2445 , style "border-width" "0 0 0 1px" 2446 , style "border-color" darkRed 2447 , style "border-style" "solid" 2448 ] 2449 , test "abort build button has pointer cursor" <| 2450 givenBuildStarted 2451 >> Tuple.first 2452 >> Common.queryView 2453 >> Query.find 2454 [ attribute <| 2455 Attr.attribute "aria-label" "Abort Build" 2456 ] 2457 >> Query.has [ style "cursor" "pointer" ] 2458 , test "abort build button has 'X' icon" <| 2459 givenBuildStarted 2460 >> Tuple.first 2461 >> Common.queryView 2462 >> Query.find 2463 [ attribute <| 2464 Attr.attribute "aria-label" "Abort Build" 2465 ] 2466 >> Query.children [] 2467 >> Query.first 2468 >> Query.has 2469 (iconSelector 2470 { size = "40px" 2471 , image = Assets.AbortCircleIcon |> Assets.CircleOutlineIcon 2472 } 2473 ) 2474 , describe "build prep section" 2475 [ test "when pipeline is not paused, shows a check" <| 2476 let 2477 prep = 2478 { pausedPipeline = BuildPrepStatusNotBlocking 2479 , pausedJob = BuildPrepStatusNotBlocking 2480 , maxRunningBuilds = BuildPrepStatusNotBlocking 2481 , inputs = Dict.empty 2482 , inputsSatisfied = BuildPrepStatusNotBlocking 2483 , missingInputReasons = Dict.empty 2484 } 2485 2486 icon = 2487 Assets.backgroundImage <| Just Assets.NotBlockingCheckIcon 2488 in 2489 givenBuildStarted 2490 >> Tuple.first 2491 >> Application.handleCallback 2492 (Callback.BuildPrepFetched 1 <| Ok prep) 2493 >> Tuple.first 2494 >> Common.queryView 2495 >> Query.find [ class "prep-status-list" ] 2496 >> Expect.all 2497 [ Query.children [] 2498 >> Query.each 2499 (Query.children [] 2500 >> Query.first 2501 >> Query.has 2502 [ style "display" "flex" 2503 , style "align-items" "center" 2504 ] 2505 ) 2506 , Query.has 2507 [ style "background-image" icon 2508 , style "background-position" "50% 50%" 2509 , style "background-repeat" "no-repeat" 2510 , style "background-size" "contain" 2511 , style "width" "12px" 2512 , style "height" "12px" 2513 , style "margin-right" "8px" 2514 , attribute <| Attr.title "not blocking" 2515 ] 2516 ] 2517 , test "when pipeline is paused, shows a spinner" <| 2518 let 2519 prep = 2520 { pausedPipeline = BuildPrepStatusBlocking 2521 , pausedJob = BuildPrepStatusNotBlocking 2522 , maxRunningBuilds = BuildPrepStatusNotBlocking 2523 , inputs = Dict.empty 2524 , inputsSatisfied = BuildPrepStatusNotBlocking 2525 , missingInputReasons = Dict.empty 2526 } 2527 in 2528 givenBuildStarted 2529 >> Tuple.first 2530 >> Application.handleCallback 2531 (Callback.BuildPrepFetched 1 <| Ok prep) 2532 >> Tuple.first 2533 >> Common.queryView 2534 >> Query.find [ class "prep-status-list" ] 2535 >> Expect.all 2536 [ Query.children [] 2537 >> Query.each 2538 (Query.children [] 2539 >> Query.first 2540 >> Query.has 2541 [ style "display" "flex" 2542 , style "align-items" "center" 2543 ] 2544 ) 2545 , Query.has 2546 [ style "animation" 2547 "container-rotate 1568ms linear infinite" 2548 , style "height" "12px" 2549 , style "width" "12px" 2550 , style "margin" "0 8px 0 0" 2551 ] 2552 , Query.has [ attribute <| Attr.title "blocking" ] 2553 ] 2554 , test "when paused state is unknown, shows a spinner" <| 2555 let 2556 prep = 2557 { pausedPipeline = BuildPrepStatusUnknown 2558 , pausedJob = BuildPrepStatusNotBlocking 2559 , maxRunningBuilds = BuildPrepStatusNotBlocking 2560 , inputs = Dict.empty 2561 , inputsSatisfied = BuildPrepStatusNotBlocking 2562 , missingInputReasons = Dict.empty 2563 } 2564 in 2565 givenBuildStarted 2566 >> Tuple.first 2567 >> Application.handleCallback 2568 (Callback.BuildPrepFetched 1 <| Ok prep) 2569 >> Tuple.first 2570 >> Common.queryView 2571 >> Query.find [ class "prep-status-list" ] 2572 >> Expect.all 2573 [ Query.children [] 2574 >> Query.each 2575 (Query.children [] 2576 >> Query.first 2577 >> Query.has 2578 [ style "display" "flex" 2579 , style "align-items" "center" 2580 ] 2581 ) 2582 , Query.has 2583 [ style "animation" 2584 "container-rotate 1568ms linear infinite" 2585 , style "height" "12px" 2586 , style "width" "12px" 2587 , style "margin" "0 8px 0 0" 2588 ] 2589 , Query.has [ attribute <| Attr.title "thinking..." ] 2590 ] 2591 ] 2592 , describe "build events subscription" <| 2593 let 2594 preBuildPlanReceived _ = 2595 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 2596 |> fetchBuild BuildStatusStarted 2597 |> Tuple.first 2598 |> fetchHistory 2599 |> Tuple.first 2600 |> fetchJobDetails 2601 |> Tuple.first 2602 in 2603 [ test "after build plan is received, opens event stream" <| 2604 preBuildPlanReceived 2605 >> Application.handleCallback 2606 (Callback.PlanAndResourcesFetched 1 <| 2607 Ok <| 2608 ( { id = "plan" 2609 , step = 2610 Concourse.BuildStepGet 2611 "step" 2612 Nothing 2613 } 2614 , { inputs = [], outputs = [] } 2615 ) 2616 ) 2617 >> Expect.all 2618 [ Tuple.second 2619 >> Common.contains 2620 (Effects.OpenBuildEventStream 2621 { url = "/api/v1/builds/1/events" 2622 , eventTypes = [ "end", "event" ] 2623 } 2624 ) 2625 , Tuple.first 2626 >> Application.subscriptions 2627 >> Common.contains 2628 (Subscription.FromEventSource 2629 ( "/api/v1/builds/1/events" 2630 , [ "end", "event" ] 2631 ) 2632 ) 2633 ] 2634 , test "if build plan request fails, no event stream" <| 2635 preBuildPlanReceived 2636 >> Application.handleCallback 2637 (Callback.PlanAndResourcesFetched 1 <| Data.httpUnauthorized) 2638 >> Expect.all 2639 [ Tuple.second >> Expect.equal [] 2640 , Tuple.first 2641 >> Application.subscriptions 2642 >> Common.notContains 2643 (Subscription.FromEventSource 2644 ( "/api/v1/builds/1/events" 2645 , [ "end", "event" ] 2646 ) 2647 ) 2648 ] 2649 ] 2650 , describe "sync sticky build log headers" <| 2651 let 2652 setup _ = 2653 Common.init "/teams/t/pipelines/p/jobs/j/builds/1" 2654 |> fetchBuild BuildStatusStarted 2655 |> Tuple.first 2656 |> fetchHistory 2657 |> Tuple.first 2658 |> fetchJobDetails 2659 |> Tuple.first 2660 in 2661 [ test "on plan received" <| 2662 setup 2663 >> Application.handleCallback 2664 (Callback.PlanAndResourcesFetched 1 <| 2665 Ok <| 2666 ( { id = "plan" 2667 , step = 2668 Concourse.BuildStepGet 2669 "step" 2670 Nothing 2671 } 2672 , { inputs = [], outputs = [] } 2673 ) 2674 ) 2675 >> Tuple.second 2676 >> Common.contains Effects.SyncStickyBuildLogHeaders 2677 , test "on header clicked" <| 2678 setup 2679 >> Application.update 2680 (Msgs.Update <| 2681 Message.Message.Click <| 2682 Message.Message.StepHeader "plan" 2683 ) 2684 >> Tuple.second 2685 >> Common.contains Effects.SyncStickyBuildLogHeaders 2686 , test "on sub header clicked" <| 2687 setup 2688 >> Application.update 2689 (Msgs.Update <| 2690 Message.Message.Click <| 2691 Message.Message.StepSubHeader "plan" 0 2692 ) 2693 >> Tuple.second 2694 >> Common.contains Effects.SyncStickyBuildLogHeaders 2695 , test "on window resized" <| 2696 setup 2697 >> Application.handleDelivery (WindowResized 1 2) 2698 >> Tuple.second 2699 >> Common.contains Effects.SyncStickyBuildLogHeaders 2700 ] 2701 , describe "step header" <| 2702 let 2703 fetchPlanWithGetStep : () -> Application.Model 2704 fetchPlanWithGetStep = 2705 givenBuildStarted 2706 >> Tuple.first 2707 >> Application.handleCallback 2708 (Callback.PlanAndResourcesFetched 1 <| 2709 Ok <| 2710 ( { id = "plan" 2711 , step = 2712 Concourse.BuildStepGet 2713 "step" 2714 Nothing 2715 } 2716 , { inputs = [], outputs = [] } 2717 ) 2718 ) 2719 >> Tuple.first 2720 2721 fetchPlanWithArtifactInputStep : () -> Application.Model 2722 fetchPlanWithArtifactInputStep = 2723 givenBuildStarted 2724 >> Tuple.first 2725 >> Application.handleCallback 2726 (Callback.PlanAndResourcesFetched 1 <| 2727 Ok <| 2728 ( { id = "plan" 2729 , step = 2730 Concourse.BuildStepArtifactInput 2731 "step" 2732 } 2733 , { inputs = [], outputs = [] } 2734 ) 2735 ) 2736 >> Tuple.first 2737 2738 fetchPlanWithEnsureArtifactOutputStep : () -> Application.Model 2739 fetchPlanWithEnsureArtifactOutputStep = 2740 givenBuildStarted 2741 >> Tuple.first 2742 >> Application.handleCallback 2743 (Callback.PlanAndResourcesFetched 1 <| 2744 Ok <| 2745 ( { id = "plan" 2746 , step = 2747 Concourse.BuildStepEnsure 2748 { hook = 2749 { id = "plan1" 2750 , step = 2751 Concourse.BuildStepArtifactOutput 2752 "step" 2753 } 2754 , step = 2755 { id = "plan2" 2756 , step = 2757 Concourse.BuildStepGet 2758 "step" 2759 Nothing 2760 } 2761 } 2762 } 2763 , { inputs = [], outputs = [] } 2764 ) 2765 ) 2766 >> Tuple.first 2767 2768 fetchPlanWithTaskStep : () -> Application.Model 2769 fetchPlanWithTaskStep = 2770 givenBuildStarted 2771 >> Tuple.first 2772 >> Application.handleCallback 2773 (Callback.PlanAndResourcesFetched 1 <| 2774 Ok <| 2775 ( { id = "plan" 2776 , step = 2777 Concourse.BuildStepTask 2778 "step" 2779 } 2780 , { inputs = [], outputs = [] } 2781 ) 2782 ) 2783 >> Tuple.first 2784 2785 fetchPlanWithCheckStep : () -> Application.Model 2786 fetchPlanWithCheckStep = 2787 givenBuildStarted 2788 >> Tuple.first 2789 >> Application.handleCallback 2790 (Callback.PlanAndResourcesFetched 307 <| 2791 Ok <| 2792 ( { id = "plan" 2793 , step = 2794 Concourse.BuildStepCheck 2795 "step" 2796 } 2797 , { inputs = [], outputs = [] } 2798 ) 2799 ) 2800 >> Tuple.first 2801 2802 fetchPlanWithSetPipelineStep : () -> Application.Model 2803 fetchPlanWithSetPipelineStep = 2804 givenBuildStarted 2805 >> Tuple.first 2806 >> Application.handleCallback 2807 (Callback.PlanAndResourcesFetched 1 <| 2808 Ok <| 2809 ( { id = "plan" 2810 , step = 2811 Concourse.BuildStepSetPipeline 2812 "step" 2813 } 2814 , { inputs = [], outputs = [] } 2815 ) 2816 ) 2817 >> Tuple.first 2818 2819 fetchPlanWithLoadVarStep : () -> Application.Model 2820 fetchPlanWithLoadVarStep = 2821 givenBuildStarted 2822 >> Tuple.first 2823 >> Application.handleCallback 2824 (Callback.PlanAndResourcesFetched 307 <| 2825 Ok <| 2826 ( { id = "plan" 2827 , step = 2828 Concourse.BuildStepLoadVar 2829 "step" 2830 } 2831 , { inputs = [], outputs = [] } 2832 ) 2833 ) 2834 >> Tuple.first 2835 2836 fetchPlanWithPutStep : () -> Application.Model 2837 fetchPlanWithPutStep = 2838 givenBuildStarted 2839 >> Tuple.first 2840 >> Application.handleCallback 2841 (Callback.PlanAndResourcesFetched 1 <| 2842 Ok <| 2843 ( { id = "plan" 2844 , step = 2845 Concourse.BuildStepPut 2846 "step" 2847 } 2848 , { inputs = [], outputs = [] } 2849 ) 2850 ) 2851 >> Tuple.first 2852 2853 fetchPlanWithGetStepWithFirstOccurrence : 2854 () 2855 -> Application.Model 2856 fetchPlanWithGetStepWithFirstOccurrence = 2857 let 2858 version = 2859 Dict.fromList 2860 [ ( "ref", "abc123" ) ] 2861 2862 step = 2863 Concourse.BuildStepDo <| 2864 Array.fromList 2865 [ { id = "foo" 2866 , step = 2867 Concourse.BuildStepGet "step" 2868 (Just version) 2869 } 2870 , { id = "bar" 2871 , step = 2872 Concourse.BuildStepGet "step2" 2873 (Just version) 2874 } 2875 , { id = "baz" 2876 , step = 2877 Concourse.BuildStepGet "step3" 2878 (Just version) 2879 } 2880 ] 2881 in 2882 givenBuildStarted 2883 >> Tuple.first 2884 >> Application.handleCallback 2885 (Callback.PlanAndResourcesFetched 1 <| 2886 Ok <| 2887 ( { id = "plan", step = step } 2888 , { inputs = 2889 [ { name = "step" 2890 , version = version 2891 , firstOccurrence = True 2892 } 2893 , { name = "step2" 2894 , version = version 2895 , firstOccurrence = True 2896 } 2897 , { name = "step3" 2898 , version = version 2899 , firstOccurrence = False 2900 } 2901 ] 2902 , outputs = [] 2903 } 2904 ) 2905 ) 2906 >> Tuple.first 2907 in 2908 [ test "step is collapsed by default" <| 2909 fetchPlanWithGetStep 2910 >> Application.handleDelivery 2911 (EventsReceived <| 2912 Ok <| 2913 [ { url = 2914 eventsUrl 2915 , data = 2916 STModels.InitializeGet 2917 { source = "" 2918 , id = "plan" 2919 } 2920 (Time.millisToPosix 0) 2921 } 2922 ] 2923 ) 2924 >> Tuple.first 2925 >> Common.queryView 2926 >> Query.hasNot [ class "step-body" ] 2927 , test "step expands on click" <| 2928 fetchPlanWithGetStep 2929 >> Application.handleDelivery 2930 (EventsReceived <| 2931 Ok <| 2932 [ { url = 2933 eventsUrl 2934 , data = 2935 STModels.InitializeGet 2936 { source = "" 2937 , id = "plan" 2938 } 2939 (Time.millisToPosix 0) 2940 } 2941 ] 2942 ) 2943 >> Tuple.first 2944 >> Application.update 2945 (Msgs.Update <| 2946 Message.Message.Click <| 2947 Message.Message.StepHeader "plan" 2948 ) 2949 >> Tuple.first 2950 >> Common.queryView 2951 >> Query.has [ class "step-body" ] 2952 , test "expanded step collapses on click" <| 2953 fetchPlanWithGetStep 2954 >> Application.handleDelivery 2955 (EventsReceived <| 2956 Ok <| 2957 [ { url = 2958 eventsUrl 2959 , data = 2960 STModels.InitializeGet 2961 { source = "" 2962 , id = "plan" 2963 } 2964 (Time.millisToPosix 0) 2965 } 2966 ] 2967 ) 2968 >> Tuple.first 2969 >> Application.update 2970 (Msgs.Update <| 2971 Message.Message.Click <| 2972 Message.Message.StepHeader "plan" 2973 ) 2974 >> Tuple.first 2975 >> Application.update 2976 (Msgs.Update <| 2977 Message.Message.Click <| 2978 Message.Message.StepHeader "plan" 2979 ) 2980 >> Tuple.first 2981 >> Common.queryView 2982 >> Query.hasNot [ class "step-body" ] 2983 , test "build step header lays out horizontally" <| 2984 fetchPlanWithGetStep 2985 >> Common.queryView 2986 >> Query.find [ class "header" ] 2987 >> Query.has [ style "display" "flex" ] 2988 , test "has two children spread apart" <| 2989 fetchPlanWithGetStep 2990 >> Common.queryView 2991 >> Query.find [ class "header" ] 2992 >> Expect.all 2993 [ Query.has 2994 [ style "justify-content" "space-between" ] 2995 , Query.children [] >> Query.count (Expect.equal 2) 2996 ] 2997 , test "both children lay out horizontally" <| 2998 fetchPlanWithGetStep 2999 >> Common.queryView 3000 >> Query.find [ class "header" ] 3001 >> Query.children [] 3002 >> Query.each 3003 (Query.has [ style "display" "flex" ]) 3004 , test "resource get step shows get label" <| 3005 fetchPlanWithGetStep 3006 >> Common.queryView 3007 >> Query.has getStepLabel 3008 , test "artifact input step shows get label" <| 3009 fetchPlanWithArtifactInputStep 3010 >> Common.queryView 3011 >> Query.has getStepLabel 3012 , test "task step shows task label" <| 3013 fetchPlanWithTaskStep 3014 >> Common.queryView 3015 >> Query.has taskStepLabel 3016 , test "check step shows check label" <| 3017 fetchPlanWithCheckStep 3018 >> Common.queryView 3019 >> Query.has checkStepLabel 3020 , test "set_pipeline step shows set_pipeline label" <| 3021 fetchPlanWithSetPipelineStep 3022 >> Common.queryView 3023 >> Query.has setPipelineStepLabel 3024 , test "load_var step shows load_var label" <| 3025 fetchPlanWithLoadVarStep 3026 >> Common.queryView 3027 >> Query.has loadVarStepLabel 3028 , test "artifact output step shows put label" <| 3029 fetchPlanWithEnsureArtifactOutputStep 3030 >> Common.queryView 3031 >> Query.has putStepLabel 3032 , test "put step shows upward arrow" <| 3033 fetchPlanWithPutStep 3034 >> Common.queryView 3035 >> Query.has putStepLabel 3036 , test "get step on first occurrence shows yellow downward arrow" <| 3037 fetchPlanWithGetStepWithFirstOccurrence 3038 >> Common.queryView 3039 >> Query.has firstOccurrenceGetStepLabel 3040 , test "hovering over a normal get step label does nothing" <| 3041 fetchPlanWithGetStep 3042 >> Common.queryView 3043 >> Query.find getStepLabel 3044 >> Event.simulate Event.mouseEnter 3045 >> Event.toResult 3046 >> Expect.err 3047 , test "hovering over a normal set_pipeline step label does nothing" <| 3048 fetchPlanWithSetPipelineStep 3049 >> Common.queryView 3050 >> Query.find setPipelineStepLabel 3051 >> Event.simulate Event.mouseEnter 3052 >> Event.toResult 3053 >> Expect.err 3054 , describe "first-occurrence get step label hover behaviour" 3055 [ test "first-occurrence get step label has no tooltip" <| 3056 fetchPlanWithGetStepWithFirstOccurrence 3057 >> Common.queryView 3058 >> Query.findAll [ text "new version" ] 3059 >> Query.count (Expect.equal 0) 3060 , test "hovering over yellow label triggers Hover message" <| 3061 fetchPlanWithGetStepWithFirstOccurrence 3062 >> Common.queryView 3063 >> Query.findAll firstOccurrenceGetStepLabel 3064 >> Query.first 3065 >> Event.simulate Event.mouseEnter 3066 >> Event.expect 3067 (Msgs.Update <| 3068 Message.Message.Hover <| 3069 Just firstOccurrenceLabelID 3070 ) 3071 , test "no tooltip before 1 second has passed" <| 3072 fetchPlanWithGetStepWithFirstOccurrence 3073 >> hoverFirstOccurrenceLabel 3074 >> Common.queryView 3075 >> Query.findAll [ text "new version" ] 3076 >> Query.count (Expect.equal 0) 3077 , test "1 second after hovering, tooltip appears" <| 3078 fetchPlanWithGetStepWithFirstOccurrence 3079 >> Application.handleDelivery 3080 (ClockTicked OneSecond <| 3081 Time.millisToPosix 0 3082 ) 3083 >> Tuple.first 3084 >> hoverFirstOccurrenceLabel 3085 >> Application.handleDelivery 3086 (ClockTicked OneSecond <| 3087 Time.millisToPosix 1 3088 ) 3089 >> Tuple.second 3090 >> Common.contains 3091 (Effects.GetViewportOf firstOccurrenceLabelID) 3092 , test "mousing off yellow label triggers Hover message" <| 3093 fetchPlanWithGetStepWithFirstOccurrence 3094 >> hoverFirstOccurrenceLabel 3095 >> Common.queryView 3096 >> Query.findAll firstOccurrenceGetStepLabel 3097 >> Query.first 3098 >> Event.simulate Event.mouseLeave 3099 >> Event.expect 3100 (Msgs.Update <| Message.Message.Hover Nothing) 3101 , test "unhovering after tooltip appears dismisses" <| 3102 fetchPlanWithGetStepWithFirstOccurrence 3103 >> Application.handleDelivery 3104 (ClockTicked OneSecond <| 3105 Time.millisToPosix 0 3106 ) 3107 >> Tuple.first 3108 >> hoverFirstOccurrenceLabel 3109 >> Application.handleDelivery 3110 (ClockTicked OneSecond <| 3111 Time.millisToPosix 1 3112 ) 3113 >> Tuple.first 3114 >> Application.update 3115 (Msgs.Update <| 3116 Message.Message.Hover Nothing 3117 ) 3118 >> Tuple.first 3119 >> Common.queryView 3120 >> Query.findAll [ text "new version" ] 3121 >> Query.count (Expect.equal 0) 3122 ] 3123 , test "hovering one resource of several produces only a single tooltip" <| 3124 fetchPlanWithGetStepWithFirstOccurrence 3125 >> Application.handleDelivery 3126 (ClockTicked OneSecond <| 3127 Time.millisToPosix 0 3128 ) 3129 >> Tuple.first 3130 >> hoverFirstOccurrenceLabel 3131 >> Application.handleDelivery 3132 (ClockTicked OneSecond <| 3133 Time.millisToPosix 1 3134 ) 3135 >> Tuple.first 3136 >> Application.handleCallback 3137 (Callback.GotViewport firstOccurrenceLabelID <| 3138 Ok 3139 { scene = 3140 { width = 1 3141 , height = 0 3142 } 3143 , viewport = 3144 { width = 1 3145 , height = 0 3146 , x = 0 3147 , y = 0 3148 } 3149 } 3150 ) 3151 >> Tuple.first 3152 >> Application.handleCallback 3153 (Callback.GotElement <| 3154 Ok 3155 { scene = 3156 { width = 0 3157 , height = 0 3158 } 3159 , viewport = 3160 { width = 0 3161 , height = 0 3162 , x = 0 3163 , y = 0 3164 } 3165 , element = 3166 { x = 0 3167 , y = 0 3168 , width = 1 3169 , height = 1 3170 } 3171 } 3172 ) 3173 >> Tuple.first 3174 >> Common.queryView 3175 >> Query.findAll [ text "new version" ] 3176 >> Query.count (Expect.equal 1) 3177 , test "successful step has a checkmark at the far right" <| 3178 fetchPlanWithGetStep 3179 >> Application.handleDelivery 3180 (EventsReceived <| 3181 Ok <| 3182 [ { url = 3183 eventsUrl 3184 , data = 3185 STModels.FinishGet 3186 { source = "stdout" 3187 , id = "plan" 3188 } 3189 0 3190 Dict.empty 3191 [] 3192 Nothing 3193 } 3194 ] 3195 ) 3196 >> Tuple.first 3197 >> Common.queryView 3198 >> Query.find [ class "header" ] 3199 >> Query.children [] 3200 >> Query.index -1 3201 >> Query.has 3202 (iconSelector 3203 { size = "28px" 3204 , image = Assets.SuccessCheckIcon 3205 } 3206 ++ [ style "background-size" "14px 14px" ] 3207 ) 3208 , test "get step lists resource version on the right" <| 3209 fetchPlanWithGetStep 3210 >> Application.handleDelivery 3211 (EventsReceived <| 3212 Ok <| 3213 [ { url = eventsUrl 3214 , data = 3215 STModels.FinishGet 3216 { source = "stdout", id = "plan" } 3217 0 3218 (Dict.fromList [ ( "version", "v3.1.4" ) ]) 3219 [] 3220 Nothing 3221 } 3222 ] 3223 ) 3224 >> Tuple.first 3225 >> Common.queryView 3226 >> Query.find [ class "header" ] 3227 >> Query.children [] 3228 >> Query.index -1 3229 >> Query.has [ text "v3.1.4" ] 3230 , test "one tick after hovering step state, GetElement fires" <| 3231 fetchPlanWithGetStep 3232 >> Application.handleDelivery 3233 (EventsReceived <| 3234 Ok <| 3235 [ { url = eventsUrl 3236 , data = 3237 STModels.InitializeTask 3238 { source = "stdout", id = "plan" } 3239 (Time.millisToPosix 0) 3240 } 3241 , { url = eventsUrl 3242 , data = 3243 STModels.StartTask 3244 { source = "stdout", id = "plan" } 3245 (Time.millisToPosix 10000) 3246 } 3247 , { url = eventsUrl 3248 , data = 3249 STModels.FinishTask 3250 { source = "stdout", id = "plan" } 3251 0 3252 (Time.millisToPosix 30000) 3253 } 3254 ] 3255 ) 3256 >> Tuple.first 3257 >> Application.update 3258 (Msgs.Update <| 3259 Message.Message.Hover <| 3260 Just <| 3261 Message.Message.StepState 3262 "plan" 3263 ) 3264 >> Tuple.first 3265 >> Application.handleDelivery 3266 (ClockTicked OneSecond <| 3267 Time.millisToPosix 1 3268 ) 3269 >> Tuple.second 3270 >> Common.contains 3271 (Effects.GetViewportOf <| Message.Message.StepState "plan") 3272 , test "finished task lists initialization duration in tooltip" <| 3273 fetchPlanWithTaskStep 3274 >> Application.handleDelivery 3275 (EventsReceived <| 3276 Ok <| 3277 [ { url = eventsUrl 3278 , data = 3279 STModels.InitializeTask 3280 { source = "stdout", id = "plan" } 3281 (Time.millisToPosix 0) 3282 } 3283 , { url = eventsUrl 3284 , data = 3285 STModels.StartTask 3286 { source = "stdout", id = "plan" } 3287 (Time.millisToPosix 10000) 3288 } 3289 , { url = eventsUrl 3290 , data = 3291 STModels.FinishTask 3292 { source = "stdout", id = "plan" } 3293 0 3294 (Time.millisToPosix 30000) 3295 } 3296 ] 3297 ) 3298 >> Tuple.first 3299 >> hoverOver (Message.Message.StepState "plan") 3300 >> Tuple.first 3301 >> Common.queryView 3302 >> Query.find [ id "tooltips" ] 3303 >> Query.children [] 3304 >> Query.index -1 3305 >> Query.findAll [ tag "tr" ] 3306 >> Query.index 0 3307 >> Query.has [ text "initialization", text "10s" ] 3308 , test "finished task lists step duration in tooltip" <| 3309 fetchPlanWithTaskStep 3310 >> Application.handleDelivery 3311 (EventsReceived <| 3312 Ok <| 3313 [ { url = eventsUrl 3314 , data = 3315 STModels.InitializeTask 3316 { source = "stdout", id = "plan" } 3317 (Time.millisToPosix 0) 3318 } 3319 , { url = eventsUrl 3320 , data = 3321 STModels.StartTask 3322 { source = "stdout", id = "plan" } 3323 (Time.millisToPosix 10000) 3324 } 3325 , { url = eventsUrl 3326 , data = 3327 STModels.FinishTask 3328 { source = "stdout", id = "plan" } 3329 0 3330 (Time.millisToPosix 30000) 3331 } 3332 ] 3333 ) 3334 >> Tuple.first 3335 >> hoverOver (Message.Message.StepState "plan") 3336 >> Tuple.first 3337 >> Common.queryView 3338 >> Query.find [ id "tooltips" ] 3339 >> Query.children [] 3340 >> Query.index -1 3341 >> Query.findAll [ tag "tr" ] 3342 >> Query.index 1 3343 >> Query.has [ text "step", text "20s" ] 3344 , test "running step has loading spinner at the right" <| 3345 fetchPlanWithTaskStep 3346 >> Application.handleDelivery 3347 (EventsReceived <| 3348 Ok <| 3349 [ { url = eventsUrl 3350 , data = 3351 STModels.StartTask 3352 { source = "stdout" 3353 , id = "plan" 3354 } 3355 (Time.millisToPosix 0) 3356 } 3357 ] 3358 ) 3359 >> Tuple.first 3360 >> Common.queryView 3361 >> Query.find [ class "header" ] 3362 >> Query.children [] 3363 >> Query.index -1 3364 >> Query.has 3365 [ style "animation" 3366 "container-rotate 1568ms linear infinite" 3367 ] 3368 , test "running step shows running in tooltip" <| 3369 fetchPlanWithTaskStep 3370 >> Application.handleDelivery 3371 (EventsReceived <| 3372 Ok <| 3373 [ { url = eventsUrl 3374 , data = 3375 STModels.StartTask 3376 { source = "stdout" 3377 , id = "plan" 3378 } 3379 (Time.millisToPosix 0) 3380 } 3381 ] 3382 ) 3383 >> Tuple.first 3384 >> hoverOver (Message.Message.StepState "plan") 3385 >> Tuple.first 3386 >> Common.queryView 3387 >> Query.find [ id "tooltips" ] 3388 >> Query.has [ text "running" ] 3389 , test "pending step has dashed circle at the right" <| 3390 fetchPlanWithTaskStep 3391 >> Common.queryView 3392 >> Query.find [ class "header" ] 3393 >> Query.children [] 3394 >> Query.index -1 3395 >> Query.has 3396 (iconSelector 3397 { size = "28px" 3398 , image = Assets.PendingIcon 3399 } 3400 ++ [ style "background-size" "14px 14px" ] 3401 ) 3402 , test "pending step shows pending in tooltip" <| 3403 fetchPlanWithTaskStep 3404 >> hoverOver (Message.Message.StepState "plan") 3405 >> Tuple.first 3406 >> Common.queryView 3407 >> Query.find [ id "tooltips" ] 3408 >> Query.has [ text "pending" ] 3409 , test "interrupted step has no-entry circle at the right" <| 3410 fetchPlanWithTaskStep 3411 >> Application.handleDelivery 3412 (EventsReceived <| 3413 Ok <| 3414 [ { url = eventsUrl 3415 , data = 3416 STModels.InitializeTask 3417 { source = "stdout" 3418 , id = "plan" 3419 } 3420 (Time.millisToPosix 0) 3421 } 3422 , { url = eventsUrl 3423 , data = 3424 STModels.BuildStatus 3425 BuildStatusAborted 3426 (Time.millisToPosix 0) 3427 } 3428 ] 3429 ) 3430 >> Tuple.first 3431 >> Common.queryView 3432 >> Query.find [ class "header" ] 3433 >> Query.children [] 3434 >> Query.index -1 3435 >> Query.has 3436 (iconSelector 3437 { size = "28px" 3438 , image = Assets.InterruptedIcon 3439 } 3440 ++ [ style "background-size" "14px 14px" ] 3441 ) 3442 , test "interrupted step shows interrupted in tooltip" <| 3443 fetchPlanWithTaskStep 3444 >> Application.handleDelivery 3445 (EventsReceived <| 3446 Ok <| 3447 [ { url = eventsUrl 3448 , data = 3449 STModels.InitializeTask 3450 { source = "stdout" 3451 , id = "plan" 3452 } 3453 (Time.millisToPosix 0) 3454 } 3455 , { url = eventsUrl 3456 , data = 3457 STModels.BuildStatus 3458 BuildStatusAborted 3459 (Time.millisToPosix 0) 3460 } 3461 ] 3462 ) 3463 >> Tuple.first 3464 >> hoverOver (Message.Message.StepState "plan") 3465 >> Tuple.first 3466 >> Common.queryView 3467 >> Query.find [ id "tooltips" ] 3468 >> Query.has [ text "interrupted" ] 3469 , test "cancelled step has dashed circle with dot at the right" <| 3470 fetchPlanWithTaskStep 3471 >> Application.handleDelivery 3472 (EventsReceived <| 3473 Ok <| 3474 [ { url = eventsUrl 3475 , data = 3476 STModels.BuildStatus 3477 BuildStatusAborted 3478 (Time.millisToPosix 0) 3479 } 3480 ] 3481 ) 3482 >> Tuple.first 3483 >> Common.queryView 3484 >> Query.find [ class "header" ] 3485 >> Query.children [] 3486 >> Query.index -1 3487 >> Query.has 3488 (iconSelector 3489 { size = "28px" 3490 , image = Assets.CancelledIcon 3491 } 3492 ++ [ style "background-size" "14px 14px" ] 3493 ) 3494 , test "cancelled step shows cancelled in tooltip" <| 3495 fetchPlanWithTaskStep 3496 >> Application.handleDelivery 3497 (EventsReceived <| 3498 Ok <| 3499 [ { url = eventsUrl 3500 , data = 3501 STModels.BuildStatus 3502 BuildStatusAborted 3503 (Time.millisToPosix 0) 3504 } 3505 ] 3506 ) 3507 >> Tuple.first 3508 >> hoverOver (Message.Message.StepState "plan") 3509 >> Tuple.first 3510 >> Common.queryView 3511 >> Query.find [ id "tooltips" ] 3512 >> Query.has [ text "cancelled" ] 3513 , test "failing step has an X at the far right" <| 3514 fetchPlanWithGetStep 3515 >> Application.handleDelivery 3516 (EventsReceived <| 3517 Ok <| 3518 [ { url = eventsUrl 3519 , data = 3520 STModels.FinishGet 3521 { source = "stdout", id = "plan" } 3522 1 3523 Dict.empty 3524 [] 3525 Nothing 3526 } 3527 ] 3528 ) 3529 >> Tuple.first 3530 >> Common.queryView 3531 >> Query.find [ class "header" ] 3532 >> Query.children [] 3533 >> Query.index -1 3534 >> Query.has 3535 (iconSelector 3536 { size = "28px" 3537 , image = Assets.FailureTimesIcon 3538 } 3539 ++ [ style "background-size" "14px 14px" ] 3540 ) 3541 , test "erroring step has orange exclamation triangle at right" <| 3542 fetchPlanWithGetStep 3543 >> Application.handleDelivery 3544 (EventsReceived <| 3545 Ok <| 3546 [ { url = eventsUrl 3547 , data = 3548 STModels.Error 3549 { source = "stderr", id = "plan" } 3550 "error message" 3551 (Time.millisToPosix 0) 3552 } 3553 ] 3554 ) 3555 >> Tuple.first 3556 >> Common.queryView 3557 >> Query.find [ class "header" ] 3558 >> Query.children [] 3559 >> Query.index -1 3560 >> Query.has 3561 (iconSelector 3562 { size = "28px" 3563 , image = Assets.ExclamationTriangleIcon 3564 } 3565 ++ [ style "background-size" "14px 14px" ] 3566 ) 3567 , test "successful step has no border" <| 3568 fetchPlanWithGetStep 3569 >> Application.handleDelivery 3570 (EventsReceived <| 3571 Ok <| 3572 [ { url = 3573 eventsUrl 3574 , data = 3575 STModels.FinishGet 3576 { source = "stdout" 3577 , id = "plan" 3578 } 3579 0 3580 Dict.empty 3581 [] 3582 Nothing 3583 } 3584 ] 3585 ) 3586 >> Tuple.first 3587 >> Common.queryView 3588 >> Query.find [ class "header" ] 3589 >> Query.has [ style "border-color" "transparent" ] 3590 , test "failing step has a red border" <| 3591 fetchPlanWithGetStep 3592 >> Application.handleDelivery 3593 (EventsReceived <| 3594 Ok <| 3595 [ { url = eventsUrl 3596 , data = 3597 STModels.FinishGet 3598 { source = "stdout", id = "plan" } 3599 1 3600 Dict.empty 3601 [] 3602 Nothing 3603 } 3604 ] 3605 ) 3606 >> Tuple.first 3607 >> Common.queryView 3608 >> Query.find [ class "header" ] 3609 >> Query.has 3610 [ style "border-color" <| Colors.failure ] 3611 , test "started step has a yellow border" <| 3612 fetchPlanWithTaskStep 3613 >> Application.handleDelivery 3614 (EventsReceived <| 3615 Ok <| 3616 [ { url = eventsUrl 3617 , data = 3618 STModels.StartTask 3619 { source = "stdout" 3620 , id = "plan" 3621 } 3622 (Time.millisToPosix 0) 3623 } 3624 ] 3625 ) 3626 >> Tuple.first 3627 >> Common.queryView 3628 >> Query.find [ class "header" ] 3629 >> Query.has 3630 [ style "border-color" <| Colors.started ] 3631 , test "set_pipeline step that changed something has a yellow text" <| 3632 fetchPlanWithSetPipelineStep 3633 >> Application.handleDelivery 3634 (EventsReceived <| 3635 Ok <| 3636 [ { url = eventsUrl 3637 , data = 3638 STModels.SetPipelineChanged 3639 { source = "stdout", id = "plan" } 3640 True 3641 } 3642 ] 3643 ) 3644 >> Tuple.first 3645 >> Common.queryView 3646 >> Query.has changedSetPipelineStepLabel 3647 , test "set_pipeline step that changed something tooltip appears after 1 second" <| 3648 fetchPlanWithSetPipelineStep 3649 >> Application.handleDelivery 3650 (EventsReceived <| 3651 Ok <| 3652 [ { url = eventsUrl 3653 , data = 3654 STModels.SetPipelineChanged 3655 { source = "stdout", id = "plan" } 3656 True 3657 } 3658 ] 3659 ) 3660 >> Tuple.first 3661 >> Application.handleDelivery 3662 (ClockTicked OneSecond <| 3663 Time.millisToPosix 0 3664 ) 3665 >> Tuple.first 3666 >> hoverSetPipelineChangedLabel 3667 >> Application.handleDelivery 3668 (ClockTicked OneSecond <| 3669 Time.millisToPosix 1 3670 ) 3671 >> Tuple.second 3672 >> Common.contains 3673 (Effects.GetViewportOf setPipelineChangedLabelID) 3674 , test "network error on first event shows passport officer" <| 3675 let 3676 imgUrl = 3677 Assets.toString Assets.PassportOfficerIcon 3678 in 3679 fetchPlanWithGetStep 3680 >> Application.handleDelivery 3681 (EventsReceived <| 3682 Ok 3683 [ { data = STModels.NetworkError 3684 , url = eventsUrl 3685 } 3686 ] 3687 ) 3688 >> Tuple.first 3689 >> Common.queryView 3690 >> Query.find [ class "not-authorized" ] 3691 >> Query.find [ tag "img" ] 3692 >> Query.has [ attribute <| Attr.src imgUrl ] 3693 , test """network error after first event fails silently 3694 (EventSource browser API will retry connection)""" <| 3695 fetchPlanWithGetStep 3696 >> Application.handleDelivery 3697 (EventsReceived <| 3698 Ok 3699 [ { url = eventsUrl 3700 , data = STModels.Opened 3701 } 3702 ] 3703 ) 3704 >> Tuple.first 3705 >> Application.handleDelivery 3706 (EventsReceived <| 3707 Ok 3708 [ { data = STModels.NetworkError 3709 , url = eventsUrl 3710 } 3711 ] 3712 ) 3713 >> Tuple.first 3714 >> Common.queryView 3715 >> Query.findAll [ class "not-authorized" ] 3716 >> Query.count (Expect.equal 0) 3717 ] 3718 , describe "get step with metadata" <| 3719 let 3720 httpURLText = 3721 "http://some-url" 3722 3723 httpsURLText = 3724 "https://some-url" 3725 3726 plainText = 3727 "plain-text" 3728 3729 invalidURLText = 3730 "https:// is secure!" 3731 3732 metadataView = 3733 Application.init 3734 flags 3735 { protocol = Url.Http 3736 , host = "" 3737 , port_ = Nothing 3738 , path = "/teams/t/pipelines/p/jobs/j/builds/1" 3739 , query = Nothing 3740 , fragment = Just "Lstepid:1" 3741 } 3742 |> Tuple.first 3743 |> Application.handleCallback 3744 (Callback.BuildFetched <| 3745 Ok 3746 { id = 1 3747 , name = "1" 3748 , job = Just Data.shortJobId 3749 , status = BuildStatusStarted 3750 , duration = 3751 { startedAt = Nothing 3752 , finishedAt = Nothing 3753 } 3754 , reapTime = Nothing 3755 } 3756 ) 3757 |> Tuple.first 3758 |> Application.handleCallback 3759 (Callback.PlanAndResourcesFetched 1 <| 3760 Ok <| 3761 ( { id = "stepid" 3762 , step = 3763 Concourse.BuildStepGet 3764 "step" 3765 (Just <| Dict.fromList [ ( "version", "1" ) ]) 3766 } 3767 , { inputs = [], outputs = [] } 3768 ) 3769 ) 3770 |> Tuple.first 3771 |> Application.update 3772 (Msgs.DeliveryReceived <| 3773 EventsReceived <| 3774 Ok <| 3775 [ { url = eventsUrl 3776 , data = 3777 STModels.FinishGet 3778 { source = "stdout" 3779 , id = "stepid" 3780 } 3781 1 3782 (Dict.fromList [ ( "version", "1" ) ]) 3783 [ { name = "http-url" 3784 , value = httpURLText 3785 } 3786 , { name = "https-url" 3787 , value = httpsURLText 3788 } 3789 , { name = "plain-text" 3790 , value = plainText 3791 } 3792 ] 3793 Nothing 3794 } 3795 ] 3796 ) 3797 |> Tuple.first 3798 |> Common.queryView 3799 in 3800 [ test "should show hyperlink if metadata starts with 'http://'" <| 3801 \_ -> 3802 metadataView 3803 |> Query.find 3804 [ containing [ text httpURLText ] 3805 ] 3806 |> Query.has 3807 [ tag "a" 3808 , style "text-decoration-line" "underline" 3809 , attribute <| Attr.target "_blank" 3810 , attribute <| Attr.href httpURLText 3811 ] 3812 , test "should show hyperlink if metadata starts with 'https://'" <| 3813 \_ -> 3814 metadataView 3815 |> Query.find 3816 [ containing [ text httpsURLText ] 3817 ] 3818 |> Query.has 3819 [ tag "a" 3820 , style "text-decoration-line" "underline" 3821 , attribute <| Attr.target "_blank" 3822 , attribute <| Attr.href httpsURLText 3823 ] 3824 , test "should not show hyperlink if metadata is plain text" <| 3825 \_ -> 3826 metadataView 3827 |> Query.find 3828 [ containing [ text plainText ] 3829 ] 3830 |> Query.hasNot 3831 [ tag "a" 3832 , style "text-decoration-line" "underline" 3833 , attribute <| Attr.target "_blank" 3834 , attribute <| Attr.href plainText 3835 ] 3836 , test "should not show hyperlink if metadata is malformed URL" <| 3837 \_ -> 3838 metadataView 3839 |> Query.find 3840 [ containing [ text invalidURLText ] 3841 ] 3842 |> Query.hasNot 3843 [ tag "a" 3844 , style "text-decoration-line" "underline" 3845 , attribute <| Attr.target "_blank" 3846 , attribute <| Attr.href invalidURLText 3847 ] 3848 ] 3849 ] 3850 ] 3851 3852 3853 getStepLabel = 3854 [ style "color" Colors.pending 3855 , style "line-height" "28px" 3856 , style "padding-left" "6px" 3857 , containing [ text "get:" ] 3858 ] 3859 3860 3861 firstOccurrenceGetStepLabel = 3862 [ style "color" Colors.started 3863 , style "line-height" "28px" 3864 , style "padding-left" "6px" 3865 , containing [ text "get:" ] 3866 ] 3867 3868 3869 putStepLabel = 3870 [ style "color" Colors.pending 3871 , style "line-height" "28px" 3872 , style "padding-left" "6px" 3873 , containing [ text "put:" ] 3874 ] 3875 3876 3877 taskStepLabel = 3878 [ style "color" Colors.pending 3879 , style "line-height" "28px" 3880 , style "padding-left" "6px" 3881 , containing [ text "task:" ] 3882 ] 3883 3884 3885 setPipelineStepLabel = 3886 [ style "color" Colors.pending 3887 , style "line-height" "28px" 3888 , style "padding-left" "6px" 3889 , containing [ text "set_pipeline:" ] 3890 ] 3891 3892 3893 changedSetPipelineStepLabel = 3894 [ style "color" Colors.started 3895 , style "line-height" "28px" 3896 , style "padding-left" "6px" 3897 , containing [ text "set_pipeline:" ] 3898 ] 3899 3900 3901 checkStepLabel = 3902 [ style "color" Colors.pending 3903 , style "line-height" "28px" 3904 , style "padding-left" "6px" 3905 , containing [ text "check:" ] 3906 ] 3907 3908 3909 loadVarStepLabel = 3910 [ style "color" Colors.pending 3911 , style "line-height" "28px" 3912 , style "padding-left" "6px" 3913 , containing [ text "load_var:" ] 3914 ] 3915 3916 3917 firstOccurrenceLabelID = 3918 Message.Message.ChangedStepLabel 3919 "foo" 3920 "new version" 3921 3922 3923 hoverFirstOccurrenceLabel = 3924 Application.update 3925 (Msgs.Update <| Message.Message.Hover <| Just firstOccurrenceLabelID) 3926 >> Tuple.first 3927 3928 3929 setPipelineChangedLabelID = 3930 Message.Message.ChangedStepLabel 3931 "foo" 3932 "pipeline config changed" 3933 3934 3935 hoverSetPipelineChangedLabel = 3936 Application.update 3937 (Msgs.Update <| Message.Message.Hover <| Just setPipelineChangedLabelID) 3938 >> Tuple.first 3939 3940 3941 tooltipGreyHex : String 3942 tooltipGreyHex = 3943 "#9b9b9b" 3944 3945 3946 darkRed : String 3947 darkRed = 3948 "#bd3826" 3949 3950 3951 brightRed : String 3952 brightRed = 3953 "#ed4b35" 3954 3955 3956 darkGreen : String 3957 darkGreen = 3958 "#419867" 3959 3960 3961 brightGreen : String 3962 brightGreen = 3963 "#11c560" 3964 3965 3966 darkGrey : String 3967 darkGrey = 3968 "#3d3c3c" 3969 3970 3971 receiveEvent : 3972 STModels.BuildEventEnvelope 3973 -> Application.Model 3974 -> ( Application.Model, List Effects.Effect ) 3975 receiveEvent envelope = 3976 Application.update (Msgs.DeliveryReceived <| EventsReceived <| Ok [ envelope ]) 3977 3978 3979 hoverOver domID = 3980 Application.update 3981 (Msgs.Update (Message.Message.Hover (Just domID))) 3982 >> Tuple.first 3983 >> Application.handleDelivery 3984 (ClockTicked OneSecond <| 3985 Time.millisToPosix 1 3986 ) 3987 >> Tuple.first 3988 >> Application.handleCallback 3989 (Callback.GotViewport domID <| 3990 Ok 3991 { scene = 3992 { width = 1 3993 , height = 0 3994 } 3995 , viewport = 3996 { width = 1 3997 , height = 0 3998 , x = 0 3999 , y = 0 4000 } 4001 } 4002 ) 4003 >> Tuple.first 4004 >> Application.handleCallback 4005 (Callback.GotElement <| 4006 Ok 4007 { scene = 4008 { width = 0 4009 , height = 0 4010 } 4011 , viewport = 4012 { width = 0 4013 , height = 0 4014 , x = 0 4015 , y = 0 4016 } 4017 , element = 4018 { x = 0 4019 , y = 0 4020 , width = 1 4021 , height = 1 4022 } 4023 } 4024 )