github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/tests/PipelineCardTests.elm (about) 1 module PipelineCardTests exposing (all) 2 3 import Application.Application as Application 4 import Assets 5 import ColorValues 6 import Colors 7 import Common exposing (defineHoverBehaviour, isColorWithStripes) 8 import Concourse 9 import Concourse.BuildStatus exposing (BuildStatus(..)) 10 import Concourse.PipelineStatus exposing (PipelineStatus(..), StatusDetails(..)) 11 import DashboardTests 12 exposing 13 ( afterSeconds 14 , amber 15 , apiData 16 , blue 17 , brown 18 , circularJobs 19 , darkGrey 20 , fadedGreen 21 , givenDataAndUser 22 , givenDataUnauthenticated 23 , green 24 , iconSelector 25 , job 26 , jobWithNameTransitionedAt 27 , lightGrey 28 , middleGrey 29 , orange 30 , otherJob 31 , red 32 , running 33 , userWithRoles 34 , whenOnDashboard 35 , whenOnDashboardViewingAllPipelines 36 , white 37 ) 38 import Data 39 import Expect exposing (Expectation) 40 import Html.Attributes as Attr 41 import Message.Callback as Callback 42 import Message.Effects as Effects 43 import Message.Message as Msgs exposing (DomID(..), PipelinesSection(..)) 44 import Message.Subscription exposing (Delivery(..), Interval(..)) 45 import Message.TopLevelMessage as ApplicationMsgs 46 import Set 47 import Test exposing (Test, describe, test) 48 import Test.Html.Event as Event 49 import Test.Html.Query as Query 50 import Test.Html.Selector exposing (attribute, class, containing, style, tag, text) 51 import Time 52 53 54 all : Test 55 all = 56 describe "pipeline cards" <| 57 let 58 findHeader : 59 Query.Single ApplicationMsgs.TopLevelMessage 60 -> Query.Single ApplicationMsgs.TopLevelMessage 61 findHeader = 62 Query.find [ class "card-header" ] 63 64 findBody : 65 Query.Single ApplicationMsgs.TopLevelMessage 66 -> Query.Single ApplicationMsgs.TopLevelMessage 67 findBody = 68 Query.find [ class "card-body" ] 69 70 pipelineWithStatus : 71 BuildStatus 72 -> Bool 73 -> Application.Model 74 -> Query.Single ApplicationMsgs.TopLevelMessage 75 pipelineWithStatus status isRunning = 76 let 77 jobFunc = 78 if isRunning then 79 job >> running 80 81 else 82 job 83 in 84 Application.handleCallback 85 (Callback.AllJobsFetched <| Ok [ jobFunc status ]) 86 >> Tuple.first 87 >> givenDataUnauthenticated [ { id = 0, name = "team" } ] 88 >> Tuple.first 89 >> Application.handleCallback 90 (Callback.AllPipelinesFetched <| 91 Ok 92 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 93 ) 94 >> Tuple.first 95 >> Application.handleDelivery 96 (ClockTicked OneSecond <| Time.millisToPosix 0) 97 >> Tuple.first 98 >> Common.queryView 99 in 100 [ describe "when team has no visible pipelines" <| 101 let 102 noPipelinesCard : () -> Query.Single ApplicationMsgs.TopLevelMessage 103 noPipelinesCard _ = 104 whenOnDashboard { highDensity = False } 105 |> givenDataUnauthenticated 106 (apiData 107 [ ( "some-team", [] ) 108 , ( "other-team", [] ) 109 ] 110 ) 111 |> Tuple.first 112 |> Application.handleCallback 113 (Callback.AllPipelinesFetched <| 114 Ok 115 [ Data.pipeline "other-team" 0 |> Data.withName "pipeline" ] 116 ) 117 |> Tuple.first 118 |> Common.queryView 119 |> Query.find 120 [ class "dashboard-team-group" 121 , attribute <| 122 Attr.attribute "data-team-name" 123 "some-team" 124 ] 125 |> Query.find [ class "card" ] 126 in 127 [ describe "card" <| 128 [ test "card has display flex with direction column" <| 129 noPipelinesCard 130 >> Query.has 131 [ style "display" "flex" 132 , style "flex-direction" "column" 133 ] 134 , test "card has width 272px and height 268px" <| 135 noPipelinesCard 136 >> Query.has 137 [ style "width" "272px" 138 , style "height" "268px" 139 ] 140 , test "card has a left margin of 25px" <| 141 noPipelinesCard 142 >> Query.has 143 [ style "margin-left" "25px" ] 144 ] 145 , describe "header" <| 146 let 147 header : () -> Query.Single ApplicationMsgs.TopLevelMessage 148 header = 149 noPipelinesCard 150 >> findHeader 151 in 152 [ test "says 'no pipeline set' in white font" <| 153 header 154 >> Expect.all 155 [ Query.has [ text "no pipeline set" ] 156 , Query.has [ style "color" ColorValues.grey20 ] 157 ] 158 , test "has dark grey background and 12.5px padding" <| 159 header 160 >> Query.has 161 [ style "background-color" ColorValues.grey90 162 , style "padding" "12.5px" 163 ] 164 , test "text is larger and wider spaced" <| 165 header 166 >> Query.has 167 [ style "font-size" "1.5em" 168 , style "letter-spacing" "0.1em" 169 ] 170 , test "text is centered" <| 171 header 172 >> Query.has [ style "text-align" "center" ] 173 ] 174 , describe "body" <| 175 let 176 body : () -> Query.Single ApplicationMsgs.TopLevelMessage 177 body = 178 noPipelinesCard 179 >> Query.find [ class "card-body" ] 180 in 181 [ test "has 20px 36px padding" <| 182 body 183 >> Query.has 184 [ style "padding" "20px 36px" ] 185 , test "fills available height" <| 186 body 187 >> Query.has [ style "flex-grow" "1" ] 188 , test "has dark grey background" <| 189 body 190 >> Query.has [ style "background-color" ColorValues.grey90 ] 191 , test "has 2px margins above and below" <| 192 body 193 >> Query.has [ style "margin" "2px 0" ] 194 , test "has lighter grey placeholder box that fills" <| 195 body 196 >> Expect.all 197 [ Query.has [ style "display" "flex" ] 198 , Query.children [] 199 >> Query.first 200 >> Query.has 201 [ style "background-color" ColorValues.grey80 202 , style "flex-grow" "1" 203 ] 204 ] 205 ] 206 , test "footer is dark grey and 47 pixels tall" <| 207 noPipelinesCard 208 >> Query.find [ class "card-footer" ] 209 >> Query.has 210 [ style "background-color" ColorValues.grey90 211 , style "height" "47px" 212 ] 213 ] 214 , test "is draggable when in all pipelines section and result is not stale" <| 215 \_ -> 216 whenOnDashboard { highDensity = False } 217 |> givenDataUnauthenticated (apiData [ ( "team", [] ) ]) 218 |> Tuple.first 219 |> Application.handleCallback 220 (Callback.AllPipelinesFetched <| 221 Ok 222 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 223 ) 224 |> Tuple.first 225 |> Common.queryView 226 |> Query.find 227 [ class "card" 228 , containing [ text "pipeline" ] 229 ] 230 |> Expect.all 231 [ Query.has [ attribute <| Attr.attribute "draggable" "true" ] 232 , Query.has [ style "cursor" "move" ] 233 ] 234 , test "is not draggable when in favorites section" <| 235 \_ -> 236 whenOnDashboard { highDensity = False } 237 |> givenDataUnauthenticated (apiData [ ( "team", [] ) ]) 238 |> Tuple.first 239 |> Application.handleCallback 240 (Callback.AllPipelinesFetched <| 241 Ok 242 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 243 ) 244 |> Tuple.first 245 |> Application.handleDelivery 246 (FavoritedPipelinesReceived <| Ok <| Set.singleton 0) 247 |> Tuple.first 248 |> Common.queryView 249 |> Query.findAll 250 [ class "card" 251 , containing [ text "pipeline" ] 252 ] 253 |> Query.first 254 |> Expect.all 255 [ Query.hasNot [ attribute <| Attr.attribute "draggable" "true" ] 256 , Query.hasNot [ style "cursor" "move" ] 257 ] 258 , test "is not draggable when rendering based on the cache" <| 259 \_ -> 260 whenOnDashboard { highDensity = False } 261 |> givenDataUnauthenticated (apiData [ ( "team", [] ) ]) 262 |> Tuple.first 263 |> Application.handleDelivery 264 (CachedPipelinesReceived <| 265 Ok 266 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 267 ) 268 |> Tuple.first 269 |> Common.queryView 270 |> Query.find 271 [ class "card" 272 , containing [ text "pipeline" ] 273 ] 274 |> Expect.all 275 [ Query.hasNot [ attribute <| Attr.attribute "draggable" "true" ] 276 , Query.hasNot [ style "cursor" "move" ] 277 ] 278 , test "fills available space" <| 279 \_ -> 280 whenOnDashboard { highDensity = False } 281 |> givenDataUnauthenticated (apiData [ ( "team", [] ) ]) 282 |> Tuple.first 283 |> Application.handleCallback 284 (Callback.AllPipelinesFetched <| 285 Ok 286 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 287 ) 288 |> Tuple.first 289 |> Common.queryView 290 |> Query.find 291 [ class "card" 292 , containing [ text "pipeline" ] 293 ] 294 |> Query.has 295 [ style "width" "100%" 296 , style "height" "100%" 297 , style "display" "flex" 298 , style "flex-direction" "column" 299 ] 300 , describe "header" <| 301 let 302 header : () -> Query.Single ApplicationMsgs.TopLevelMessage 303 header _ = 304 whenOnDashboard { highDensity = False } 305 |> givenDataUnauthenticated 306 (apiData [ ( "team", [] ) ]) 307 |> Tuple.first 308 |> Application.handleCallback 309 (Callback.AllPipelinesFetched <| 310 Ok 311 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 312 ) 313 |> Tuple.first 314 |> Common.queryView 315 |> Query.find 316 [ class "card" 317 , containing [ text "pipeline" ] 318 ] 319 |> findHeader 320 in 321 [ test "has dark grey background" <| 322 header 323 >> Query.has [ style "background-color" ColorValues.grey90 ] 324 , test "has larger, spaced-out white text" <| 325 header 326 >> Query.has 327 [ style "font-size" "1.5em" 328 , style "letter-spacing" "0.1em" 329 , style "color" ColorValues.grey20 330 ] 331 , test "has 12.5px padding" <| 332 header 333 >> Query.has [ style "padding" "12.5px" ] 334 , test "text does not overflow or wrap" <| 335 header 336 >> Query.children [] 337 >> Query.first 338 >> Query.has 339 [ style "width" "245px" 340 , style "white-space" "nowrap" 341 , style "overflow" "hidden" 342 , style "text-overflow" "ellipsis" 343 ] 344 ] 345 , describe "colored banner" <| 346 let 347 findBanner = 348 Query.find 349 [ class "card" 350 , containing [ text "pipeline" ] 351 ] 352 >> Query.find 353 [ class "banner" ] 354 355 isSolid : String -> Query.Single ApplicationMsgs.TopLevelMessage -> Expectation 356 isSolid color = 357 Query.has [ style "background-color" color ] 358 in 359 [ describe "non-HD view" 360 [ test "is 7px tall" <| 361 \_ -> 362 whenOnDashboard { highDensity = False } 363 |> givenDataUnauthenticated 364 (apiData [ ( "team", [] ) ]) 365 |> Tuple.first 366 |> Application.handleCallback 367 (Callback.AllPipelinesFetched <| 368 Ok 369 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 370 ) 371 |> Tuple.first 372 |> Common.queryView 373 |> findBanner 374 |> Query.has [ style "height" "7px" ] 375 , test "is grey when pipeline is cached" <| 376 \_ -> 377 whenOnDashboard { highDensity = False } 378 |> givenDataUnauthenticated [ { id = 0, name = "team" } ] 379 |> Tuple.first 380 |> Application.handleDelivery 381 (CachedPipelinesReceived <| 382 Ok 383 [ Data.pipeline "team" 0 384 |> Data.withName "pipeline" 385 |> Data.withPaused True 386 ] 387 ) 388 |> Tuple.first 389 |> Common.queryView 390 |> findBanner 391 |> isSolid lightGrey 392 , test "is dark grey when pipeline is archived" <| 393 \_ -> 394 whenOnDashboardViewingAllPipelines { highDensity = False } 395 |> givenDataUnauthenticated [ { id = 0, name = "team" } ] 396 |> Tuple.first 397 |> Application.handleCallback 398 (Callback.AllPipelinesFetched <| 399 Ok 400 [ Data.pipeline "team" 0 401 |> Data.withName "pipeline" 402 |> Data.withArchived True 403 |> Data.withPaused True 404 ] 405 ) 406 |> Tuple.first 407 |> Common.queryView 408 |> findBanner 409 |> isSolid Colors.backgroundDark 410 , test "is blue when pipeline is paused" <| 411 \_ -> 412 whenOnDashboard { highDensity = False } 413 |> givenDataUnauthenticated [ { id = 0, name = "team" } ] 414 |> Tuple.first 415 |> Application.handleCallback 416 (Callback.AllPipelinesFetched <| 417 Ok 418 [ Data.pipeline "team" 0 419 |> Data.withName "pipeline" 420 |> Data.withPaused True 421 ] 422 ) 423 |> Tuple.first 424 |> Common.queryView 425 |> findBanner 426 |> isSolid blue 427 , test "is green when pipeline is succeeding" <| 428 \_ -> 429 whenOnDashboard { highDensity = False } 430 |> pipelineWithStatus 431 BuildStatusSucceeded 432 False 433 |> findBanner 434 |> isSolid green 435 , test "is green with black stripes when pipeline is succeeding and running" <| 436 \_ -> 437 whenOnDashboard { highDensity = False } 438 |> pipelineWithStatus 439 BuildStatusSucceeded 440 True 441 |> findBanner 442 |> isColorWithStripes { thin = green, thick = ColorValues.grey90 } 443 , test "is grey when pipeline is pending" <| 444 \_ -> 445 whenOnDashboard { highDensity = False } 446 |> givenDataUnauthenticated 447 (apiData [ ( "team", [] ) ]) 448 |> Tuple.first 449 |> Application.handleCallback 450 (Callback.AllPipelinesFetched <| 451 Ok 452 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 453 ) 454 |> Tuple.first 455 |> Common.queryView 456 |> findBanner 457 |> isSolid lightGrey 458 , test "is grey with black stripes when pipeline is pending and running" <| 459 \_ -> 460 whenOnDashboard { highDensity = False } 461 |> pipelineWithStatus 462 BuildStatusStarted 463 True 464 |> findBanner 465 |> isColorWithStripes { thin = lightGrey, thick = ColorValues.grey90 } 466 , test "is red when pipeline is failing" <| 467 \_ -> 468 whenOnDashboard { highDensity = False } 469 |> pipelineWithStatus 470 BuildStatusFailed 471 False 472 |> findBanner 473 |> isSolid red 474 , test "is red with black stripes when pipeline is failing and running" <| 475 \_ -> 476 whenOnDashboard { highDensity = False } 477 |> pipelineWithStatus 478 BuildStatusFailed 479 True 480 |> findBanner 481 |> isColorWithStripes { thin = red, thick = ColorValues.grey90 } 482 , test "is amber when pipeline is erroring" <| 483 \_ -> 484 whenOnDashboard { highDensity = False } 485 |> pipelineWithStatus 486 BuildStatusErrored 487 False 488 |> findBanner 489 |> isSolid amber 490 , test "is amber with black stripes when pipeline is erroring and running" <| 491 \_ -> 492 whenOnDashboard { highDensity = False } 493 |> pipelineWithStatus 494 BuildStatusErrored 495 True 496 |> findBanner 497 |> isColorWithStripes { thin = amber, thick = ColorValues.grey90 } 498 , test "is brown when pipeline is aborted" <| 499 \_ -> 500 whenOnDashboard { highDensity = False } 501 |> pipelineWithStatus 502 BuildStatusAborted 503 False 504 |> findBanner 505 |> isSolid brown 506 , test "is brown with black stripes when pipeline is aborted and running" <| 507 \_ -> 508 whenOnDashboard { highDensity = False } 509 |> pipelineWithStatus 510 BuildStatusAborted 511 True 512 |> findBanner 513 |> isColorWithStripes { thin = brown, thick = ColorValues.grey90 } 514 , describe "status priorities" <| 515 let 516 givenTwoJobsWithModifiers : 517 BuildStatus 518 -> (Concourse.Job -> Concourse.Job) 519 -> BuildStatus 520 -> (Concourse.Job -> Concourse.Job) 521 -> Query.Single ApplicationMsgs.TopLevelMessage 522 givenTwoJobsWithModifiers firstStatus firstModifier secondStatus secondModifier = 523 whenOnDashboard { highDensity = False } 524 |> Application.handleCallback 525 (Callback.AllJobsFetched <| 526 Ok 527 [ job firstStatus |> firstModifier 528 , otherJob secondStatus |> secondModifier 529 ] 530 ) 531 |> Tuple.first 532 |> givenDataUnauthenticated [ { id = 0, name = "team" } ] 533 |> Tuple.first 534 |> Application.handleCallback 535 (Callback.AllPipelinesFetched <| 536 Ok 537 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 538 ) 539 |> Tuple.first 540 |> Common.queryView 541 542 givenTwoJobs : 543 BuildStatus 544 -> BuildStatus 545 -> Query.Single ApplicationMsgs.TopLevelMessage 546 givenTwoJobs firstStatus secondStatus = 547 givenTwoJobsWithModifiers firstStatus identity secondStatus identity 548 in 549 [ test "failed is more important than errored" <| 550 \_ -> 551 givenTwoJobs 552 BuildStatusFailed 553 BuildStatusErrored 554 |> findBanner 555 |> isSolid red 556 , test "errored is more important than aborted" <| 557 \_ -> 558 givenTwoJobs 559 BuildStatusErrored 560 BuildStatusAborted 561 |> findBanner 562 |> isSolid amber 563 , test "aborted is more important than succeeding" <| 564 \_ -> 565 givenTwoJobs 566 BuildStatusAborted 567 BuildStatusSucceeded 568 |> findBanner 569 |> isSolid brown 570 , test "succeeding is more important than pending" <| 571 \_ -> 572 givenTwoJobs 573 BuildStatusSucceeded 574 BuildStatusPending 575 |> findBanner 576 |> isSolid green 577 , test "paused jobs are ignored" <| 578 \_ -> 579 givenTwoJobsWithModifiers 580 BuildStatusSucceeded 581 identity 582 BuildStatusFailed 583 (Data.withPaused True) 584 |> findBanner 585 |> isSolid green 586 ] 587 , test "does not crash with a circular pipeline" <| 588 \_ -> 589 whenOnDashboard { highDensity = False } 590 |> Application.handleCallback (Callback.AllJobsFetched <| Ok circularJobs) 591 |> Tuple.first 592 |> givenDataUnauthenticated [ { id = 0, name = "team" } ] 593 |> Tuple.first 594 |> Application.handleCallback 595 (Callback.AllPipelinesFetched <| 596 Ok 597 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 598 ) 599 |> Tuple.first 600 |> Common.queryView 601 |> findBanner 602 |> isSolid green 603 , describe "HD view" 604 [ test "is 8px wide" <| 605 \_ -> 606 whenOnDashboard { highDensity = True } 607 |> givenDataUnauthenticated 608 (apiData [ ( "team", [] ) ]) 609 |> Tuple.first 610 |> Application.handleCallback 611 (Callback.AllPipelinesFetched <| 612 Ok 613 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 614 ) 615 |> Tuple.first 616 |> Common.queryView 617 |> findBanner 618 |> Query.has [ style "width" "8px" ] 619 , test "is grey when pipeline is cached" <| 620 \_ -> 621 whenOnDashboard { highDensity = True } 622 |> givenDataUnauthenticated [ { id = 0, name = "team" } ] 623 |> Tuple.first 624 |> Application.handleDelivery 625 (CachedPipelinesReceived <| 626 Ok 627 [ Data.pipeline "team" 0 628 |> Data.withName "pipeline" 629 |> Data.withPaused True 630 ] 631 ) 632 |> Tuple.first 633 |> Common.queryView 634 |> findBanner 635 |> isSolid lightGrey 636 , test "is dark grey when pipeline is archived" <| 637 \_ -> 638 whenOnDashboardViewingAllPipelines { highDensity = False } 639 |> givenDataUnauthenticated [ { id = 0, name = "team" } ] 640 |> Tuple.first 641 |> Application.handleCallback 642 (Callback.AllPipelinesFetched <| 643 Ok 644 [ Data.pipeline "team" 0 645 |> Data.withName "pipeline" 646 |> Data.withArchived True 647 |> Data.withPaused True 648 ] 649 ) 650 |> Tuple.first 651 |> Common.queryView 652 |> findBanner 653 |> isSolid Colors.backgroundDark 654 , test "is blue when pipeline is paused" <| 655 \_ -> 656 whenOnDashboard { highDensity = True } 657 |> givenDataUnauthenticated [ { id = 0, name = "team" } ] 658 |> Tuple.first 659 |> Application.handleCallback 660 (Callback.AllPipelinesFetched <| 661 Ok 662 [ Data.pipeline "team" 0 663 |> Data.withName "pipeline" 664 |> Data.withPaused True 665 ] 666 ) 667 |> Tuple.first 668 |> Common.queryView 669 |> findBanner 670 |> isSolid blue 671 , test "is green when pipeline is succeeding" <| 672 \_ -> 673 whenOnDashboard { highDensity = True } 674 |> pipelineWithStatus 675 BuildStatusSucceeded 676 False 677 |> findBanner 678 |> isSolid green 679 , test "is green with black stripes when pipeline is succeeding and running" <| 680 \_ -> 681 whenOnDashboard { highDensity = True } 682 |> pipelineWithStatus 683 BuildStatusSucceeded 684 True 685 |> findBanner 686 |> isColorWithStripes { thin = green, thick = ColorValues.grey90 } 687 , test "is grey when pipeline is pending" <| 688 \_ -> 689 whenOnDashboard { highDensity = True } 690 |> givenDataUnauthenticated 691 (apiData [ ( "team", [] ) ]) 692 |> Tuple.first 693 |> Application.handleCallback 694 (Callback.AllPipelinesFetched <| 695 Ok 696 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 697 ) 698 |> Tuple.first 699 |> Common.queryView 700 |> findBanner 701 |> isSolid lightGrey 702 , test "is grey with black stripes when pipeline is pending and running" <| 703 \_ -> 704 whenOnDashboard { highDensity = True } 705 |> pipelineWithStatus 706 BuildStatusStarted 707 True 708 |> findBanner 709 |> isColorWithStripes { thin = lightGrey, thick = ColorValues.grey90 } 710 , test "is red when pipeline is failing" <| 711 \_ -> 712 whenOnDashboard { highDensity = True } 713 |> pipelineWithStatus 714 BuildStatusFailed 715 False 716 |> findBanner 717 |> isSolid red 718 , test "is red with black stripes when pipeline is failing and running" <| 719 \_ -> 720 whenOnDashboard { highDensity = True } 721 |> pipelineWithStatus 722 BuildStatusFailed 723 True 724 |> findBanner 725 |> isColorWithStripes { thin = red, thick = ColorValues.grey90 } 726 , test "is amber when pipeline is erroring" <| 727 \_ -> 728 whenOnDashboard { highDensity = True } 729 |> pipelineWithStatus 730 BuildStatusErrored 731 False 732 |> findBanner 733 |> isSolid amber 734 , test "is amber with black stripes when pipeline is erroring and running" <| 735 \_ -> 736 whenOnDashboard { highDensity = True } 737 |> pipelineWithStatus 738 BuildStatusErrored 739 True 740 |> findBanner 741 |> isColorWithStripes { thin = amber, thick = ColorValues.grey90 } 742 , test "is brown when pipeline is aborted" <| 743 \_ -> 744 whenOnDashboard { highDensity = True } 745 |> pipelineWithStatus 746 BuildStatusAborted 747 False 748 |> findBanner 749 |> isSolid brown 750 , test "is brown with black stripes when pipeline is aborted and running" <| 751 \_ -> 752 whenOnDashboard { highDensity = True } 753 |> pipelineWithStatus 754 BuildStatusAborted 755 True 756 |> findBanner 757 |> isColorWithStripes { thin = brown, thick = ColorValues.grey90 } 758 , describe "status priorities" <| 759 let 760 givenTwoJobsWithModifiers : 761 BuildStatus 762 -> (Concourse.Job -> Concourse.Job) 763 -> BuildStatus 764 -> (Concourse.Job -> Concourse.Job) 765 -> Query.Single ApplicationMsgs.TopLevelMessage 766 givenTwoJobsWithModifiers firstStatus firstModifier secondStatus secondModifier = 767 whenOnDashboard { highDensity = True } 768 |> Application.handleCallback 769 (Callback.AllJobsFetched <| 770 Ok 771 [ job firstStatus |> firstModifier 772 , otherJob secondStatus |> secondModifier 773 ] 774 ) 775 |> Tuple.first 776 |> givenDataUnauthenticated [ { id = 0, name = "team" } ] 777 |> Tuple.first 778 |> Application.handleCallback 779 (Callback.AllPipelinesFetched <| 780 Ok 781 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 782 ) 783 |> Tuple.first 784 |> Common.queryView 785 786 givenTwoJobs : 787 BuildStatus 788 -> BuildStatus 789 -> Query.Single ApplicationMsgs.TopLevelMessage 790 givenTwoJobs firstStatus secondStatus = 791 givenTwoJobsWithModifiers firstStatus identity secondStatus identity 792 in 793 [ test "failed is more important than errored" <| 794 \_ -> 795 givenTwoJobs 796 BuildStatusFailed 797 BuildStatusErrored 798 |> findBanner 799 |> isSolid red 800 , test "errored is more important than aborted" <| 801 \_ -> 802 givenTwoJobs 803 BuildStatusErrored 804 BuildStatusAborted 805 |> findBanner 806 |> isSolid amber 807 , test "aborted is more important than succeeding" <| 808 \_ -> 809 givenTwoJobs 810 BuildStatusAborted 811 BuildStatusSucceeded 812 |> findBanner 813 |> isSolid brown 814 , test "succeeding is more important than pending" <| 815 \_ -> 816 givenTwoJobs 817 BuildStatusSucceeded 818 BuildStatusPending 819 |> findBanner 820 |> isSolid green 821 , test "paused jobs are ignored" <| 822 \_ -> 823 givenTwoJobsWithModifiers 824 BuildStatusSucceeded 825 identity 826 BuildStatusFailed 827 (Data.withPaused True) 828 |> findBanner 829 |> isSolid green 830 ] 831 ] 832 ] 833 ] 834 , describe "on HD view" <| 835 let 836 setup : () -> Query.Single ApplicationMsgs.TopLevelMessage 837 setup _ = 838 whenOnDashboard { highDensity = True } 839 |> givenDataUnauthenticated 840 (apiData [ ( "team", [] ) ]) 841 |> Tuple.first 842 |> Application.handleCallback 843 (Callback.AllPipelinesFetched <| 844 Ok 845 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 846 ) 847 |> Tuple.first 848 |> Common.queryView 849 850 noPipelines : () -> Query.Single ApplicationMsgs.TopLevelMessage 851 noPipelines _ = 852 whenOnDashboard { highDensity = True } 853 |> givenDataUnauthenticated 854 (apiData 855 [ ( "some-team", [] ) 856 , ( "other-team", [] ) 857 ] 858 ) 859 |> Tuple.first 860 |> Application.handleCallback 861 (Callback.AllPipelinesFetched <| 862 Ok 863 [ Data.pipeline "other-team" 0 |> Data.withName "pipeline" ] 864 ) 865 |> Tuple.first 866 |> Common.queryView 867 868 card : Query.Single ApplicationMsgs.TopLevelMessage -> Query.Single ApplicationMsgs.TopLevelMessage 869 card = 870 Query.find 871 [ class "card" 872 , containing [ text "pipeline" ] 873 ] 874 875 cardText : Query.Single ApplicationMsgs.TopLevelMessage -> Query.Single ApplicationMsgs.TopLevelMessage 876 cardText = 877 card 878 >> Query.children [] 879 >> Query.index 1 880 881 noPipelinesCard = 882 Query.find 883 [ class "card" 884 , containing [ text "no pipeline" ] 885 ] 886 in 887 [ test "no pipelines card has 14px font and 1px spacing" <| 888 noPipelines 889 >> noPipelinesCard 890 >> Query.has 891 [ style "font-size" "14px" 892 , style "letter-spacing" "1px" 893 ] 894 , test "no pipelines card text is vertically centered" <| 895 noPipelines 896 >> noPipelinesCard 897 >> Query.has 898 [ style "display" "flex" 899 , style "align-items" "center" 900 ] 901 , test "no pipelines card is 60px tall" <| 902 noPipelines 903 >> noPipelinesCard 904 >> Query.has [ style "height" "60px" ] 905 , test "no pipelines card has 60px right margin" <| 906 noPipelines 907 >> noPipelinesCard 908 >> Query.has [ style "margin-right" "60px" ] 909 , test "no pipelines card text has 10px padding" <| 910 noPipelines 911 >> noPipelinesCard 912 >> Query.children [] 913 >> Query.first 914 >> Query.has [ style "padding" "10px" ] 915 , test "no pipelines card is 200px wide" <| 916 noPipelines 917 >> noPipelinesCard 918 >> Query.has [ style "width" "200px" ] 919 , test "no pipelines card has dark grey background" <| 920 noPipelines 921 >> noPipelinesCard 922 >> Query.has [ style "background-color" ColorValues.grey90 ] 923 , test "card has larger tighter font" <| 924 setup 925 >> card 926 >> Query.has 927 [ style "font-size" "19px" 928 , style "letter-spacing" "1px" 929 ] 930 , test "card text does not overflow or wrap" <| 931 setup 932 >> cardText 933 >> Query.has 934 [ style "width" "180px" 935 , style "white-space" "nowrap" 936 , style "overflow" "hidden" 937 , style "text-overflow" "ellipsis" 938 ] 939 , test "card text is vertically centered" <| 940 setup 941 >> cardText 942 >> Query.has 943 [ style "align-self" "center" ] 944 , test "card text has 10px padding" <| 945 setup 946 >> cardText 947 >> Query.has 948 [ style "padding" "10px" ] 949 , test "card lays out contents horizontally" <| 950 setup 951 >> card 952 >> Query.has 953 [ style "display" "flex" ] 954 , test "card is 60px tall" <| 955 setup 956 >> card 957 >> Query.has [ style "height" "60px" ] 958 , test "card is 200px wide" <| 959 setup 960 >> card 961 >> Query.has [ style "width" "200px" ] 962 , test "no triangle when there is no resource error" <| 963 setup 964 >> card 965 >> Query.children [] 966 >> Query.count (Expect.equal 2) 967 , describe "resource error triangle" <| 968 let 969 givenResourceFailingToCheck : () -> Query.Single ApplicationMsgs.TopLevelMessage 970 givenResourceFailingToCheck _ = 971 whenOnDashboard { highDensity = True } 972 |> Application.handleCallback 973 (Callback.AllResourcesFetched <| 974 Ok 975 [ { teamName = "team" 976 , pipelineName = "pipeline" 977 , name = "resource" 978 , lastChecked = Nothing 979 , pinnedVersion = Nothing 980 , pinnedInConfig = False 981 , pinComment = Nothing 982 , icon = Nothing 983 , build = Just (Data.build Concourse.BuildStatus.BuildStatusFailed) 984 } 985 ] 986 ) 987 |> Tuple.first 988 |> givenDataUnauthenticated [ { id = 0, name = "team" } ] 989 |> Tuple.first 990 |> Application.handleCallback 991 (Callback.AllPipelinesFetched <| 992 Ok 993 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 994 ) 995 |> Tuple.first 996 |> Common.queryView 997 998 resourceErrorTriangle = 999 Query.children [] 1000 >> Query.index -1 1001 in 1002 [ test "exists" <| 1003 givenResourceFailingToCheck 1004 >> card 1005 >> Query.children [] 1006 >> Query.count (Expect.equal 3) 1007 , test "is at the top right of card" <| 1008 givenResourceFailingToCheck 1009 >> card 1010 >> Expect.all 1011 [ Query.has [ style "position" "relative" ] 1012 , resourceErrorTriangle 1013 >> Query.has 1014 [ style "position" "absolute" 1015 , style "top" "0" 1016 , style "right" "0" 1017 ] 1018 ] 1019 , test "is an orange 'top right' triangle" <| 1020 givenResourceFailingToCheck 1021 >> card 1022 >> resourceErrorTriangle 1023 >> Query.has 1024 [ style "width" "0" 1025 , style "height" "0" 1026 , style "border-top" <| "30px solid " ++ orange 1027 , style "border-left" "30px solid transparent" 1028 ] 1029 ] 1030 , test 1031 ("cards are spaced 4px apart vertically and " 1032 ++ "60px apart horizontally" 1033 ) 1034 <| 1035 setup 1036 >> card 1037 >> Query.has 1038 [ style "margin" "0 60px 4px 0" ] 1039 , test "card is faded green when pipeline is succeeding" <| 1040 \_ -> 1041 whenOnDashboard { highDensity = True } 1042 |> pipelineWithStatus 1043 BuildStatusSucceeded 1044 False 1045 |> card 1046 |> Query.has [ style "background-color" fadedGreen ] 1047 , test "card is red when pipeline is failing" <| 1048 \_ -> 1049 whenOnDashboard { highDensity = True } 1050 |> pipelineWithStatus 1051 BuildStatusFailed 1052 False 1053 |> card 1054 |> Query.has [ style "background-color" red ] 1055 , test "card is amber when pipeline is erroring" <| 1056 \_ -> 1057 whenOnDashboard { highDensity = True } 1058 |> pipelineWithStatus 1059 BuildStatusErrored 1060 False 1061 |> card 1062 |> Query.has [ style "background-color" amber ] 1063 , test "card is not affected by jobs from other pipelines" <| 1064 \_ -> 1065 whenOnDashboard { highDensity = True } 1066 |> Application.handleCallback 1067 (Callback.AllPipelinesFetched <| 1068 Ok 1069 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 1070 ) 1071 |> Tuple.first 1072 |> Application.handleCallback 1073 (Callback.AllJobsFetched <| 1074 let 1075 baseJob = 1076 job BuildStatusErrored 1077 in 1078 Ok 1079 [ { baseJob | pipelineName = "other-pipeline" } ] 1080 ) 1081 |> Tuple.first 1082 |> Common.queryView 1083 |> card 1084 |> Query.hasNot [ style "background-color" amber ] 1085 ] 1086 , describe "body" <| 1087 let 1088 setup : () -> Query.Single ApplicationMsgs.TopLevelMessage 1089 setup _ = 1090 whenOnDashboard { highDensity = False } 1091 |> givenDataUnauthenticated 1092 (apiData [ ( "team", [] ) ]) 1093 |> Tuple.first 1094 |> Application.handleCallback 1095 (Callback.AllPipelinesFetched <| 1096 Ok 1097 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 1098 ) 1099 |> Tuple.first 1100 |> Common.queryView 1101 |> Query.find 1102 [ class "card" 1103 , containing [ text "pipeline" ] 1104 ] 1105 in 1106 [ test "has dark grey background" <| 1107 setup 1108 >> findBody 1109 >> Query.has [ style "background-color" ColorValues.grey90 ] 1110 , test "has 2px margin above and below" <| 1111 setup 1112 >> findBody 1113 >> Query.has [ style "margin" "2px 0" ] 1114 , test "fills available height" <| 1115 setup 1116 >> findBody 1117 >> Query.has [ style "flex-grow" "1" ] 1118 , test "allows pipeline-grid to fill available height" <| 1119 setup 1120 >> findBody 1121 >> Query.has [ style "display" "flex" ] 1122 , test "pipeline-grid fills available width" <| 1123 setup 1124 >> findBody 1125 >> Query.find [ class "pipeline-grid" ] 1126 >> Query.has 1127 [ style "box-sizing" "border-box" 1128 , style "width" "100%" 1129 ] 1130 ] 1131 , describe "footer" <| 1132 let 1133 hasStyle : String -> String -> Expectation 1134 hasStyle property value = 1135 whenOnDashboard { highDensity = False } 1136 |> givenDataAndUser 1137 (apiData [ ( "team", [] ) ]) 1138 (userWithRoles [ ( "team", [ "owner" ] ) ]) 1139 |> Tuple.first 1140 |> Application.handleCallback 1141 (Callback.AllPipelinesFetched <| 1142 Ok 1143 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 1144 ) 1145 |> Tuple.first 1146 |> Common.queryView 1147 |> Query.find [ class "card-footer" ] 1148 |> Query.has [ style property value ] 1149 in 1150 [ test "has dark grey background" <| 1151 \_ -> 1152 hasStyle "background-color" ColorValues.grey90 1153 , test "has medium padding" <| 1154 \_ -> 1155 hasStyle "padding" "13.5px" 1156 , test "lays out contents horizontally" <| 1157 \_ -> 1158 hasStyle "display" "flex" 1159 , test "is divided into a left and right section, spread apart" <| 1160 \_ -> 1161 whenOnDashboard { highDensity = False } 1162 |> givenDataAndUser 1163 (apiData [ ( "team", [] ) ]) 1164 (userWithRoles [ ( "team", [ "owner" ] ) ]) 1165 |> Tuple.first 1166 |> Application.handleCallback 1167 (Callback.AllPipelinesFetched <| 1168 Ok 1169 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 1170 ) 1171 |> Tuple.first 1172 |> Common.queryView 1173 |> Query.find [ class "card-footer" ] 1174 |> Expect.all 1175 [ Query.children [] 1176 >> Query.count (Expect.equal 2) 1177 , Query.has 1178 [ style "justify-content" "space-between" ] 1179 ] 1180 , test "both sections lay out contents horizontally" <| 1181 \_ -> 1182 whenOnDashboard { highDensity = False } 1183 |> givenDataAndUser 1184 (apiData [ ( "team", [] ) ]) 1185 (userWithRoles [ ( "team", [ "owner" ] ) ]) 1186 |> Tuple.first 1187 |> Application.handleCallback 1188 (Callback.AllPipelinesFetched <| 1189 Ok 1190 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 1191 ) 1192 |> Tuple.first 1193 |> Common.queryView 1194 |> Query.find [ class "card-footer" ] 1195 |> Query.children [] 1196 |> Query.each (Query.has [ style "display" "flex" ]) 1197 , describe "left-hand section" <| 1198 let 1199 findStatusIcon = 1200 Query.find [ class "pipeline-status" ] 1201 >> Query.children [] 1202 >> Query.first 1203 1204 findStatusText = 1205 Query.find [ class "pipeline-status" ] 1206 >> Query.children [] 1207 >> Query.index -1 1208 in 1209 [ describe "when pipeline is paused" <| 1210 let 1211 setup = 1212 whenOnDashboard { highDensity = False } 1213 |> givenDataUnauthenticated 1214 [ { id = 0, name = "team" } ] 1215 |> Tuple.first 1216 |> Application.handleCallback 1217 (Callback.AllPipelinesFetched <| 1218 Ok 1219 [ Data.pipeline "team" 0 1220 |> Data.withName "pipeline" 1221 |> Data.withPaused True 1222 ] 1223 ) 1224 |> Tuple.first 1225 |> Common.queryView 1226 in 1227 [ test "status icon is blue pause" <| 1228 \_ -> 1229 setup 1230 |> findStatusIcon 1231 |> Query.has 1232 (iconSelector 1233 { size = "20px" 1234 , image = PipelineStatusPaused |> Assets.PipelineStatusIcon 1235 } 1236 ++ [ style "background-size" "contain" ] 1237 ) 1238 , test "status text is blue" <| 1239 \_ -> 1240 setup 1241 |> findStatusText 1242 |> Query.has [ style "color" blue ] 1243 , test "status text is larger and spaced more widely" <| 1244 \_ -> 1245 setup 1246 |> findStatusText 1247 |> Query.has 1248 [ style "font-size" "18px" 1249 , style "line-height" "20px" 1250 , style "letter-spacing" "0.05em" 1251 ] 1252 , test "status text is offset to the right of the icon" <| 1253 \_ -> 1254 setup 1255 |> findStatusText 1256 |> Query.has [ style "margin-left" "8px" ] 1257 , test "status text says 'paused'" <| 1258 \_ -> 1259 setup 1260 |> findStatusText 1261 |> Query.has [ text "paused" ] 1262 ] 1263 , describe "when a pipeline is based on cache" <| 1264 let 1265 setup = 1266 whenOnDashboard { highDensity = False } 1267 |> givenDataUnauthenticated 1268 [ { id = 0, name = "team" } ] 1269 |> Tuple.first 1270 |> Application.handleDelivery 1271 (CachedPipelinesReceived <| 1272 Ok 1273 [ Data.pipeline "team" 0 1274 |> Data.withName "pipeline" 1275 |> Data.withPaused True 1276 ] 1277 ) 1278 |> Tuple.first 1279 |> Common.queryView 1280 in 1281 [ test "status icon is grey pending" <| 1282 \_ -> 1283 setup 1284 |> findStatusIcon 1285 |> Query.has 1286 (iconSelector 1287 { size = "20px" 1288 , image = Assets.PipelineStatusIconStale 1289 } 1290 ++ [ style "background-size" "contain" ] 1291 ) 1292 , test "status text is grey" <| 1293 \_ -> 1294 setup 1295 |> findStatusText 1296 |> Query.has [ style "color" lightGrey ] 1297 , test "status text says 'loading...'" <| 1298 \_ -> 1299 setup 1300 |> findStatusText 1301 |> Query.has [ text "loading..." ] 1302 , test "pipeline card is faded" <| 1303 \_ -> 1304 setup 1305 |> Query.find [ class "card" ] 1306 |> Query.has [ style "opacity" "0.45" ] 1307 ] 1308 , describe "when pipeline has no jobs due to a disabled endpoint" <| 1309 let 1310 setup = 1311 whenOnDashboard { highDensity = False } 1312 |> givenDataUnauthenticated 1313 [ { id = 0, name = "team" } ] 1314 |> Tuple.first 1315 |> Application.handleDelivery 1316 (CachedJobsReceived <| Ok [ Data.job 0 ]) 1317 |> Tuple.first 1318 |> Application.handleCallback 1319 (Callback.AllPipelinesFetched <| 1320 Ok 1321 [ Data.pipeline "team" 0 ] 1322 ) 1323 |> Tuple.first 1324 |> Application.handleCallback 1325 (Callback.AllJobsFetched <| 1326 Data.httpNotImplemented 1327 ) 1328 1329 domID = 1330 Msgs.PipelineStatusIcon AllPipelinesSection 1331 (Data.pipelineId 1332 |> Data.withPipelineName "pipeline-0" 1333 ) 1334 in 1335 [ test "status icon is faded sync" <| 1336 \_ -> 1337 setup 1338 |> Tuple.first 1339 |> Common.queryView 1340 |> findStatusIcon 1341 |> Query.has 1342 (iconSelector 1343 { size = "20px" 1344 , image = Assets.PipelineStatusIconJobsDisabled 1345 } 1346 ++ [ style "background-size" "contain" 1347 , style "opacity" "0.5" 1348 ] 1349 ) 1350 , test "status icon is hoverable" <| 1351 \_ -> 1352 setup 1353 |> Tuple.first 1354 |> Common.queryView 1355 |> findStatusIcon 1356 |> Event.simulate Event.mouseEnter 1357 |> Event.expect 1358 (ApplicationMsgs.Update <| 1359 Msgs.Hover <| 1360 Just domID 1361 ) 1362 , test "hovering status icon sends location request" <| 1363 \_ -> 1364 setup 1365 |> Tuple.first 1366 |> Application.update 1367 (ApplicationMsgs.Update <| 1368 Msgs.Hover <| 1369 Just domID 1370 ) 1371 |> Tuple.second 1372 |> Common.contains 1373 (Effects.GetViewportOf domID) 1374 , test "hovering status icon shows tooltip" <| 1375 \_ -> 1376 setup 1377 |> Tuple.first 1378 |> Application.update 1379 (ApplicationMsgs.Update <| 1380 Msgs.Hover <| 1381 Just domID 1382 ) 1383 |> Tuple.first 1384 |> Application.handleCallback 1385 (Callback.GotViewport domID 1386 (Ok 1387 { scene = 1388 { width = 1 1389 , height = 0 1390 } 1391 , viewport = 1392 { width = 1 1393 , height = 0 1394 , x = 0 1395 , y = 0 1396 } 1397 } 1398 ) 1399 ) 1400 |> Tuple.first 1401 |> Application.handleCallback 1402 (Callback.GotElement <| 1403 Ok 1404 { scene = 1405 { width = 0 1406 , height = 0 1407 } 1408 , viewport = 1409 { width = 0 1410 , height = 0 1411 , x = 0 1412 , y = 0 1413 } 1414 , element = 1415 { x = 0 1416 , y = 0 1417 , width = 1 1418 , height = 1 1419 } 1420 } 1421 ) 1422 |> Tuple.first 1423 |> Common.queryView 1424 |> Query.has 1425 [ style "position" "fixed" 1426 , containing [ text "automatic job monitoring disabled" ] 1427 ] 1428 , test "status text says 'no data'" <| 1429 \_ -> 1430 setup 1431 |> Tuple.first 1432 |> Common.queryView 1433 |> findStatusText 1434 |> Query.has [ text "no data" ] 1435 , test "job preview is empty placeholder" <| 1436 \_ -> 1437 setup 1438 |> Tuple.first 1439 |> Common.queryView 1440 |> Query.find [ class "card-body" ] 1441 |> Query.has [ style "background-color" ColorValues.grey90 ] 1442 , test "job data is cleared" <| 1443 \_ -> 1444 setup 1445 |> Tuple.first 1446 |> Common.queryView 1447 |> Query.find [ class "parallel-grid" ] 1448 |> Query.hasNot [ tag "a" ] 1449 , test "job data is cleared from local cache" <| 1450 \_ -> 1451 setup 1452 |> Tuple.second 1453 |> Common.contains Effects.DeleteCachedJobs 1454 ] 1455 , describe "when pipeline is archived" <| 1456 let 1457 setup = 1458 whenOnDashboardViewingAllPipelines { highDensity = False } 1459 |> givenDataUnauthenticated 1460 [ { id = 0, name = "team" } ] 1461 |> Tuple.first 1462 |> Application.handleCallback 1463 (Callback.AllPipelinesFetched <| 1464 Ok 1465 [ Data.pipeline "team" 0 1466 |> Data.withArchived True 1467 ] 1468 ) 1469 |> Tuple.first 1470 |> Application.handleCallback 1471 (Callback.AllJobsFetched <| 1472 Ok 1473 [ Data.job 0 ] 1474 ) 1475 in 1476 [ test "status section is empty" <| 1477 \_ -> 1478 setup 1479 |> Tuple.first 1480 |> Common.queryView 1481 |> Query.find [ class "pipeline-status" ] 1482 |> Query.children [] 1483 |> Query.count (Expect.equal 0) 1484 , test "job preview is empty placeholder" <| 1485 \_ -> 1486 setup 1487 |> Tuple.first 1488 |> Common.queryView 1489 |> Query.find [ class "card-body" ] 1490 |> Query.has [ style "background-color" ColorValues.grey90 ] 1491 , test "there is no pause button" <| 1492 \_ -> 1493 setup 1494 |> Tuple.first 1495 |> Common.queryView 1496 |> Query.hasNot [ class "pause-toggle" ] 1497 ] 1498 , describe "when pipeline is pending" <| 1499 [ test "status icon is grey" <| 1500 \_ -> 1501 whenOnDashboard { highDensity = False } 1502 |> pipelineWithStatus 1503 BuildStatusPending 1504 False 1505 |> findStatusIcon 1506 |> Query.has 1507 (iconSelector 1508 { size = "20px" 1509 , image = PipelineStatusPending True |> Assets.PipelineStatusIcon 1510 } 1511 ++ [ style "background-size" "contain" ] 1512 ) 1513 , test "status text is grey" <| 1514 \_ -> 1515 whenOnDashboard { highDensity = False } 1516 |> pipelineWithStatus 1517 BuildStatusPending 1518 False 1519 |> findStatusText 1520 |> Query.has [ style "color" lightGrey ] 1521 , test "status text says 'pending'" <| 1522 \_ -> 1523 whenOnDashboard { highDensity = False } 1524 |> pipelineWithStatus 1525 BuildStatusPending 1526 False 1527 |> findStatusText 1528 |> Query.has 1529 [ text "pending" ] 1530 , test "when running, status text says 'running'" <| 1531 \_ -> 1532 whenOnDashboard { highDensity = False } 1533 |> pipelineWithStatus 1534 BuildStatusPending 1535 True 1536 |> findStatusText 1537 |> Query.has 1538 [ text "running" ] 1539 ] 1540 , describe "when pipeline is succeeding" 1541 [ test "status icon is a green check" <| 1542 \_ -> 1543 whenOnDashboard { highDensity = False } 1544 |> pipelineWithStatus 1545 BuildStatusSucceeded 1546 False 1547 |> findStatusIcon 1548 |> Query.has 1549 (iconSelector 1550 { size = "20px" 1551 , image = PipelineStatusSucceeded Running |> Assets.PipelineStatusIcon 1552 } 1553 ++ [ style "background-size" "contain" ] 1554 ) 1555 , test "status text is green" <| 1556 \_ -> 1557 whenOnDashboard { highDensity = False } 1558 |> pipelineWithStatus 1559 BuildStatusSucceeded 1560 False 1561 |> findStatusText 1562 |> Query.has [ style "color" green ] 1563 , test "when running, status text says 'running'" <| 1564 \_ -> 1565 whenOnDashboard { highDensity = False } 1566 |> pipelineWithStatus 1567 BuildStatusSucceeded 1568 True 1569 |> findStatusText 1570 |> Query.has 1571 [ text "running" ] 1572 , test "when not running, status text shows age" <| 1573 \_ -> 1574 whenOnDashboard { highDensity = False } 1575 |> Application.handleCallback 1576 (Callback.AllJobsFetched <| 1577 Ok 1578 [ jobWithNameTransitionedAt 1579 "job" 1580 (Just <| Time.millisToPosix 0) 1581 BuildStatusSucceeded 1582 ] 1583 ) 1584 |> Tuple.first 1585 |> givenDataUnauthenticated 1586 [ { id = 0, name = "team" } ] 1587 |> Tuple.first 1588 |> Application.handleCallback 1589 (Callback.AllPipelinesFetched <| 1590 Ok 1591 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 1592 ) 1593 |> Tuple.first 1594 |> afterSeconds 1 1595 |> Common.queryView 1596 |> findStatusText 1597 |> Query.has 1598 [ text "1s" ] 1599 ] 1600 , describe "when pipeline is failing" 1601 [ test "status icon is a red !" <| 1602 \_ -> 1603 whenOnDashboard { highDensity = False } 1604 |> pipelineWithStatus 1605 BuildStatusFailed 1606 False 1607 |> findStatusIcon 1608 |> Query.has 1609 (iconSelector 1610 { size = "20px" 1611 , image = PipelineStatusFailed Running |> Assets.PipelineStatusIcon 1612 } 1613 ++ [ style "background-size" "contain" ] 1614 ) 1615 , test "status text is red" <| 1616 \_ -> 1617 whenOnDashboard { highDensity = False } 1618 |> pipelineWithStatus 1619 BuildStatusFailed 1620 False 1621 |> findStatusText 1622 |> Query.has [ style "color" red ] 1623 ] 1624 , test "when pipeline is aborted, status icon is a brown x" <| 1625 \_ -> 1626 whenOnDashboard { highDensity = False } 1627 |> pipelineWithStatus 1628 BuildStatusAborted 1629 False 1630 |> findStatusIcon 1631 |> Query.has 1632 (iconSelector 1633 { size = "20px" 1634 , image = PipelineStatusAborted Running |> Assets.PipelineStatusIcon 1635 } 1636 ++ [ style "background-size" "contain" ] 1637 ) 1638 , test "when pipeline is errored, status icon is an amber triangle" <| 1639 \_ -> 1640 whenOnDashboard { highDensity = False } 1641 |> pipelineWithStatus 1642 BuildStatusErrored 1643 False 1644 |> findStatusIcon 1645 |> Query.has 1646 (iconSelector 1647 { size = "20px" 1648 , image = PipelineStatusErrored Running |> Assets.PipelineStatusIcon 1649 } 1650 ++ [ style "background-size" "contain" ] 1651 ) 1652 ] 1653 , describe "right-hand section" 1654 [ describe "visibility toggle" <| 1655 let 1656 pipelineId = 1657 Data.pipelineId 1658 1659 visibilityToggle = 1660 Common.queryView 1661 >> Query.findAll [ class "card-footer" ] 1662 >> Query.first 1663 >> Query.children [] 1664 >> Query.index -1 1665 >> Query.children [] 1666 >> Query.index 2 1667 1668 openEye = 1669 iconSelector 1670 { size = "20px" 1671 , image = Assets.VisibilityToggleIcon True 1672 } 1673 ++ [ style "background-size" "contain" ] 1674 1675 slashedOutEye = 1676 iconSelector 1677 { size = "20px" 1678 , image = Assets.VisibilityToggleIcon False 1679 } 1680 ++ [ style "background-size" "contain" ] 1681 1682 openEyeClickable setup = 1683 [ defineHoverBehaviour 1684 { name = "open eye toggle" 1685 , setup = 1686 whenOnDashboard { highDensity = False } 1687 |> setup 1688 |> Tuple.first 1689 , query = visibilityToggle 1690 , unhoveredSelector = 1691 { description = "faded 20px square" 1692 , selector = 1693 openEye 1694 ++ [ style "opacity" "0.5" 1695 , style "cursor" "pointer" 1696 ] 1697 } 1698 , hoverable = 1699 Msgs.VisibilityButton AllPipelinesSection pipelineId 1700 , hoveredSelector = 1701 { description = "bright 20px square" 1702 , selector = 1703 openEye 1704 ++ [ style "opacity" "1" 1705 , style "cursor" "pointer" 1706 ] 1707 } 1708 } 1709 , test "has click handler" <| 1710 \_ -> 1711 whenOnDashboard { highDensity = False } 1712 |> setup 1713 |> Tuple.first 1714 |> visibilityToggle 1715 |> Event.simulate Event.click 1716 |> Event.expect 1717 (ApplicationMsgs.Update <| 1718 Msgs.Click <| 1719 Msgs.VisibilityButton AllPipelinesSection 1720 pipelineId 1721 ) 1722 , test "click has HidePipeline effect" <| 1723 \_ -> 1724 whenOnDashboard { highDensity = False } 1725 |> setup 1726 |> Tuple.first 1727 |> Application.update 1728 (ApplicationMsgs.Update <| 1729 Msgs.Click <| 1730 Msgs.VisibilityButton AllPipelinesSection 1731 pipelineId 1732 ) 1733 |> Tuple.second 1734 |> Expect.equal 1735 [ Effects.ChangeVisibility 1736 Msgs.Hide 1737 pipelineId 1738 ] 1739 , defineHoverBehaviour 1740 { name = "open eye toggle in favorites section" 1741 , setup = 1742 whenOnDashboard { highDensity = False } 1743 |> Application.handleDelivery 1744 (Message.Subscription.FavoritedPipelinesReceived <| Ok <| Set.singleton 0) 1745 |> Tuple.first 1746 |> setup 1747 |> Tuple.first 1748 , query = visibilityToggle 1749 , unhoveredSelector = 1750 { description = "faded 20px square" 1751 , selector = 1752 openEye 1753 ++ [ style "opacity" "0.5" 1754 , style "cursor" "pointer" 1755 ] 1756 } 1757 , hoverable = 1758 Msgs.VisibilityButton FavoritesSection pipelineId 1759 , hoveredSelector = 1760 { description = "bright 20px square" 1761 , selector = 1762 openEye 1763 ++ [ style "opacity" "1" 1764 , style "cursor" "pointer" 1765 ] 1766 } 1767 } 1768 , defineHoverBehaviour 1769 { name = "visibility spinner" 1770 , setup = 1771 whenOnDashboard { highDensity = False } 1772 |> setup 1773 |> Tuple.first 1774 |> Application.update 1775 (ApplicationMsgs.Update <| 1776 Msgs.Click <| 1777 Msgs.VisibilityButton AllPipelinesSection 1778 pipelineId 1779 ) 1780 |> Tuple.first 1781 , query = visibilityToggle 1782 , unhoveredSelector = 1783 { description = "20px spinner" 1784 , selector = 1785 [ style "animation" 1786 "container-rotate 1568ms linear infinite" 1787 , style "height" "20px" 1788 , style "width" "20px" 1789 ] 1790 } 1791 , hoverable = 1792 Msgs.VisibilityButton AllPipelinesSection pipelineId 1793 , hoveredSelector = 1794 { description = "20px spinner" 1795 , selector = 1796 [ style "animation" 1797 "container-rotate 1568ms linear infinite" 1798 , style "height" "20px" 1799 , style "width" "20px" 1800 ] 1801 } 1802 } 1803 , test "success resolves spinner to slashed-out eye" <| 1804 \_ -> 1805 whenOnDashboard { highDensity = False } 1806 |> setup 1807 |> Tuple.first 1808 |> Application.update 1809 (ApplicationMsgs.Update <| 1810 Msgs.Click <| 1811 Msgs.VisibilityButton AllPipelinesSection 1812 pipelineId 1813 ) 1814 |> Tuple.first 1815 |> Application.handleCallback 1816 (Callback.VisibilityChanged 1817 Msgs.Hide 1818 pipelineId 1819 (Ok ()) 1820 ) 1821 |> Tuple.first 1822 |> visibilityToggle 1823 |> Query.has slashedOutEye 1824 , test "error resolves spinner to open eye" <| 1825 \_ -> 1826 whenOnDashboard { highDensity = False } 1827 |> setup 1828 |> Tuple.first 1829 |> Application.update 1830 (ApplicationMsgs.Update <| 1831 Msgs.Click <| 1832 Msgs.VisibilityButton AllPipelinesSection 1833 pipelineId 1834 ) 1835 |> Tuple.first 1836 |> Application.handleCallback 1837 (Callback.VisibilityChanged 1838 Msgs.Hide 1839 pipelineId 1840 Data.httpInternalServerError 1841 ) 1842 |> Tuple.first 1843 |> visibilityToggle 1844 |> Query.has openEye 1845 , test "401 redirects to login" <| 1846 \_ -> 1847 whenOnDashboard { highDensity = False } 1848 |> setup 1849 |> Tuple.first 1850 |> Application.update 1851 (ApplicationMsgs.Update <| 1852 Msgs.Click <| 1853 Msgs.VisibilityButton AllPipelinesSection 1854 pipelineId 1855 ) 1856 |> Tuple.first 1857 |> Application.handleCallback 1858 (Callback.VisibilityChanged 1859 Msgs.Hide 1860 pipelineId 1861 Data.httpUnauthorized 1862 ) 1863 |> Tuple.second 1864 |> Expect.equal 1865 [ Effects.RedirectToLogin ] 1866 ] 1867 1868 openEyeUnclickable setup = 1869 [ defineHoverBehaviour 1870 { name = "open eye toggle" 1871 , setup = 1872 whenOnDashboard { highDensity = False } 1873 |> setup 1874 |> Tuple.first 1875 , query = visibilityToggle 1876 , unhoveredSelector = 1877 { description = "faded 20px square" 1878 , selector = 1879 openEye 1880 ++ [ style "opacity" "0.5" 1881 , style "cursor" "default" 1882 ] 1883 } 1884 , hoverable = 1885 Msgs.VisibilityButton AllPipelinesSection pipelineId 1886 , hoveredSelector = 1887 { description = "faded 20px square" 1888 , selector = 1889 openEye 1890 ++ [ style "opacity" "0.5" 1891 , style "cursor" "default" 1892 ] 1893 } 1894 } 1895 , test "has no click handler" <| 1896 \_ -> 1897 whenOnDashboard { highDensity = False } 1898 |> setup 1899 |> Tuple.first 1900 |> visibilityToggle 1901 |> Event.simulate Event.click 1902 |> Event.toResult 1903 |> Expect.err 1904 ] 1905 1906 slashedOutEyeClickable setup = 1907 [ defineHoverBehaviour 1908 { name = "slashed-out eye toggle" 1909 , setup = 1910 whenOnDashboard { highDensity = False } 1911 |> setup 1912 |> Tuple.first 1913 , query = visibilityToggle 1914 , unhoveredSelector = 1915 { description = "faded 20px square" 1916 , selector = 1917 slashedOutEye 1918 ++ [ style "opacity" "0.5" 1919 , style "cursor" "pointer" 1920 ] 1921 } 1922 , hoverable = 1923 Msgs.VisibilityButton AllPipelinesSection pipelineId 1924 , hoveredSelector = 1925 { description = "bright 20px square" 1926 , selector = 1927 slashedOutEye 1928 ++ [ style "opacity" "1" 1929 , style "cursor" "pointer" 1930 ] 1931 } 1932 } 1933 , test "has click handler" <| 1934 \_ -> 1935 whenOnDashboard { highDensity = False } 1936 |> setup 1937 |> Tuple.first 1938 |> visibilityToggle 1939 |> Event.simulate Event.click 1940 |> Event.expect 1941 (ApplicationMsgs.Update <| 1942 Msgs.Click <| 1943 Msgs.VisibilityButton AllPipelinesSection 1944 pipelineId 1945 ) 1946 , test "click has ExposePipeline effect" <| 1947 \_ -> 1948 whenOnDashboard { highDensity = False } 1949 |> setup 1950 |> Tuple.first 1951 |> Application.update 1952 (ApplicationMsgs.Update <| 1953 Msgs.Click <| 1954 Msgs.VisibilityButton AllPipelinesSection 1955 pipelineId 1956 ) 1957 |> Tuple.second 1958 |> Expect.equal 1959 [ Effects.ChangeVisibility 1960 Msgs.Expose 1961 pipelineId 1962 ] 1963 , defineHoverBehaviour 1964 { name = "visibility spinner" 1965 , setup = 1966 whenOnDashboard { highDensity = False } 1967 |> setup 1968 |> Tuple.first 1969 |> Application.update 1970 (ApplicationMsgs.Update <| 1971 Msgs.Click <| 1972 Msgs.VisibilityButton AllPipelinesSection 1973 pipelineId 1974 ) 1975 |> Tuple.first 1976 , query = visibilityToggle 1977 , unhoveredSelector = 1978 { description = "20px spinner" 1979 , selector = 1980 [ style "animation" 1981 "container-rotate 1568ms linear infinite" 1982 , style "height" "20px" 1983 , style "width" "20px" 1984 ] 1985 } 1986 , hoverable = 1987 Msgs.VisibilityButton AllPipelinesSection pipelineId 1988 , hoveredSelector = 1989 { description = "20px spinner" 1990 , selector = 1991 [ style "animation" 1992 "container-rotate 1568ms linear infinite" 1993 , style "height" "20px" 1994 , style "width" "20px" 1995 ] 1996 } 1997 } 1998 , test "success resolves spinner to open eye" <| 1999 \_ -> 2000 whenOnDashboard { highDensity = False } 2001 |> setup 2002 |> Tuple.first 2003 |> Application.update 2004 (ApplicationMsgs.Update <| 2005 Msgs.Click <| 2006 Msgs.VisibilityButton AllPipelinesSection 2007 pipelineId 2008 ) 2009 |> Tuple.first 2010 |> Application.handleCallback 2011 (Callback.VisibilityChanged 2012 Msgs.Expose 2013 pipelineId 2014 (Ok ()) 2015 ) 2016 |> Tuple.first 2017 |> visibilityToggle 2018 |> Query.has openEye 2019 , test "error resolves spinner to slashed-out eye" <| 2020 \_ -> 2021 whenOnDashboard { highDensity = False } 2022 |> setup 2023 |> Tuple.first 2024 |> Application.update 2025 (ApplicationMsgs.Update <| 2026 Msgs.Click <| 2027 Msgs.VisibilityButton AllPipelinesSection 2028 pipelineId 2029 ) 2030 |> Tuple.first 2031 |> Application.handleCallback 2032 (Callback.VisibilityChanged 2033 Msgs.Expose 2034 pipelineId 2035 Data.httpInternalServerError 2036 ) 2037 |> Tuple.first 2038 |> visibilityToggle 2039 |> Query.has slashedOutEye 2040 ] 2041 2042 slashedOutEyeUnclickable setup = 2043 [ defineHoverBehaviour 2044 { name = "slashed-out eye toggle" 2045 , setup = 2046 whenOnDashboard { highDensity = False } 2047 |> setup 2048 |> Tuple.first 2049 , query = visibilityToggle 2050 , unhoveredSelector = 2051 { description = "faded 20px square" 2052 , selector = 2053 slashedOutEye 2054 ++ [ style "opacity" "0.5" 2055 , style "cursor" "default" 2056 ] 2057 } 2058 , hoverable = 2059 Msgs.VisibilityButton AllPipelinesSection pipelineId 2060 , hoveredSelector = 2061 { description = "faded 20px square" 2062 , selector = 2063 slashedOutEye 2064 ++ [ style "opacity" "0.5" 2065 , style "cursor" "default" 2066 ] 2067 } 2068 } 2069 , test "has no click handler" <| 2070 \_ -> 2071 whenOnDashboard { highDensity = False } 2072 |> setup 2073 |> Tuple.first 2074 |> visibilityToggle 2075 |> Event.simulate Event.click 2076 |> Event.toResult 2077 |> Expect.err 2078 ] 2079 in 2080 [ describe "when authorized" <| 2081 let 2082 whenAuthorizedPublic = 2083 givenDataAndUser 2084 (apiData [ ( "team", [] ) ]) 2085 (userWithRoles 2086 [ ( "team", [ "owner" ] ) ] 2087 ) 2088 >> Tuple.first 2089 >> Application.handleCallback 2090 (Callback.AllPipelinesFetched <| 2091 Ok 2092 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2093 ) 2094 2095 whenAuthorizedNonPublic = 2096 givenDataAndUser 2097 (apiData [ ( "team", [] ) ]) 2098 (userWithRoles 2099 [ ( "team", [ "owner" ] ) ] 2100 ) 2101 >> Tuple.first 2102 >> Application.handleCallback 2103 (Callback.AllPipelinesFetched <| 2104 Ok 2105 [ Data.pipeline "team" 0 2106 |> Data.withName "pipeline" 2107 |> Data.withPublic False 2108 ] 2109 ) 2110 in 2111 [ describe "on public pipeline" <| 2112 openEyeClickable whenAuthorizedPublic 2113 , describe "on a non-public pipeline" <| 2114 slashedOutEyeClickable whenAuthorizedNonPublic 2115 ] 2116 , describe "when unauthorized" <| 2117 let 2118 whenUnauthorizedPublic = 2119 givenDataAndUser 2120 (apiData [ ( "team", [] ) ]) 2121 (userWithRoles 2122 [ ( "team", [ "viewer" ] ) ] 2123 ) 2124 >> Tuple.first 2125 >> Application.handleCallback 2126 (Callback.AllPipelinesFetched <| 2127 Ok 2128 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2129 ) 2130 2131 whenUnauthorizedNonPublic = 2132 givenDataAndUser 2133 (apiData [ ( "team", [] ) ]) 2134 (userWithRoles 2135 [ ( "team", [ "viewer" ] ) ] 2136 ) 2137 >> Tuple.first 2138 >> Application.handleCallback 2139 (Callback.AllPipelinesFetched <| 2140 Ok 2141 [ Data.pipeline "team" 0 2142 |> Data.withName "pipeline" 2143 |> Data.withPublic False 2144 ] 2145 ) 2146 in 2147 [ describe "on public pipeline" <| 2148 openEyeUnclickable whenUnauthorizedPublic 2149 , describe "on a non-public pipeline" <| 2150 slashedOutEyeUnclickable 2151 whenUnauthorizedNonPublic 2152 ] 2153 , describe "when unauthenticated" <| 2154 let 2155 whenUnauthenticated = 2156 givenDataUnauthenticated 2157 (apiData [ ( "team", [] ) ]) 2158 >> Tuple.first 2159 >> Application.handleCallback 2160 (Callback.AllPipelinesFetched <| 2161 Ok 2162 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2163 ) 2164 in 2165 [ describe "on public pipeline" <| 2166 openEyeClickable whenUnauthenticated 2167 ] 2168 ] 2169 , test "there is medium spacing between the eye and the play/pause button" <| 2170 \_ -> 2171 whenOnDashboard { highDensity = False } 2172 |> givenDataAndUser 2173 (apiData [ ( "team", [] ) ]) 2174 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2175 |> Tuple.first 2176 |> Application.handleCallback 2177 (Callback.AllPipelinesFetched <| 2178 Ok 2179 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2180 ) 2181 |> Tuple.first 2182 |> Common.queryView 2183 |> Query.find [ class "card-footer" ] 2184 |> Query.children [] 2185 |> Query.index -1 2186 |> Query.children [] 2187 |> Expect.all 2188 [ Query.count (Expect.equal 5) 2189 , Query.index 1 >> Query.has [ style "width" "12px" ] 2190 ] 2191 , test "there is medium spacing between the eye and the favorited icon" <| 2192 \_ -> 2193 whenOnDashboard { highDensity = False } 2194 |> givenDataAndUser 2195 (apiData [ ( "team", [] ) ]) 2196 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2197 |> Tuple.first 2198 |> Application.handleCallback 2199 (Callback.AllPipelinesFetched <| 2200 Ok 2201 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2202 ) 2203 |> Tuple.first 2204 |> Common.queryView 2205 |> Query.find [ class "card-footer" ] 2206 |> Query.children [] 2207 |> Query.index -1 2208 |> Query.children [] 2209 |> Expect.all 2210 [ Query.count (Expect.equal 5) 2211 , Query.index 3 >> Query.has [ style "width" "12px" ] 2212 ] 2213 , describe "pause toggle" 2214 [ test "the right section has a 20px square pause button on the left" <| 2215 \_ -> 2216 whenOnDashboard { highDensity = False } 2217 |> givenDataAndUser 2218 (apiData [ ( "team", [] ) ]) 2219 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2220 |> Tuple.first 2221 |> Application.handleCallback 2222 (Callback.AllPipelinesFetched <| 2223 Ok 2224 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2225 ) 2226 |> Tuple.first 2227 |> Common.queryView 2228 |> Query.find [ class "card-footer" ] 2229 |> Query.children [] 2230 |> Query.index -1 2231 |> Query.children [] 2232 |> Query.index 0 2233 |> Query.has 2234 (iconSelector 2235 { size = "20px" 2236 , image = Assets.PauseIcon 2237 } 2238 ) 2239 , test "pause button has pointer cursor when authorized" <| 2240 \_ -> 2241 whenOnDashboard { highDensity = False } 2242 |> givenDataAndUser 2243 (apiData [ ( "team", [] ) ]) 2244 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2245 |> Tuple.first 2246 |> Application.handleCallback 2247 (Callback.AllPipelinesFetched <| 2248 Ok 2249 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2250 ) 2251 |> Tuple.first 2252 |> Common.queryView 2253 |> Query.find [ class "card-footer" ] 2254 |> Query.find 2255 (iconSelector 2256 { size = "20px" 2257 , image = Assets.PauseIcon 2258 } 2259 ) 2260 |> Query.has [ style "cursor" "pointer" ] 2261 , test "pause button is transparent" <| 2262 \_ -> 2263 whenOnDashboard { highDensity = False } 2264 |> givenDataAndUser 2265 (apiData [ ( "team", [] ) ]) 2266 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2267 |> Tuple.first 2268 |> Application.handleCallback 2269 (Callback.AllPipelinesFetched <| 2270 Ok 2271 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2272 ) 2273 |> Tuple.first 2274 |> Common.queryView 2275 |> Query.find [ class "card-footer" ] 2276 |> Query.find 2277 (iconSelector 2278 { size = "20px" 2279 , image = Assets.PauseIcon 2280 } 2281 ) 2282 |> Query.has [ style "opacity" "0.5" ] 2283 , defineHoverBehaviour 2284 { name = "pause button" 2285 , setup = 2286 whenOnDashboard { highDensity = False } 2287 |> givenDataAndUser 2288 (apiData [ ( "team", [] ) ]) 2289 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2290 |> Tuple.first 2291 |> Application.handleCallback 2292 (Callback.AllPipelinesFetched <| 2293 Ok 2294 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2295 ) 2296 |> Tuple.first 2297 , query = 2298 Common.queryView 2299 >> Query.find [ class "card-footer" ] 2300 >> Query.children [] 2301 >> Query.index -1 2302 >> Query.children [] 2303 >> Query.index 0 2304 , unhoveredSelector = 2305 { description = "a faded 20px square pause button with pointer cursor" 2306 , selector = 2307 iconSelector 2308 { size = "20px" 2309 , image = Assets.PauseIcon 2310 } 2311 ++ [ style "cursor" "pointer" 2312 , style "opacity" "0.5" 2313 ] 2314 } 2315 , hoverable = Msgs.PipelineCardPauseToggle AllPipelinesSection Data.pipelineId 2316 , hoveredSelector = 2317 { description = "a bright 20px square pause button with pointer cursor" 2318 , selector = 2319 iconSelector 2320 { size = "20px" 2321 , image = Assets.PauseIcon 2322 } 2323 ++ [ style "cursor" "pointer" 2324 , style "opacity" "1" 2325 ] 2326 } 2327 } 2328 , defineHoverBehaviour 2329 { name = "pause button in favorites section" 2330 , setup = 2331 whenOnDashboard { highDensity = False } 2332 |> givenDataAndUser 2333 (apiData [ ( "team", [] ) ]) 2334 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2335 |> Tuple.first 2336 |> Application.handleCallback 2337 (Callback.AllPipelinesFetched <| 2338 Ok 2339 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2340 ) 2341 |> Tuple.first 2342 |> Application.handleDelivery 2343 (Message.Subscription.FavoritedPipelinesReceived <| Ok <| Set.singleton 0) 2344 |> Tuple.first 2345 , query = 2346 Common.queryView 2347 >> Query.findAll [ class "card-footer" ] 2348 >> Query.first 2349 >> Query.children [] 2350 >> Query.index -1 2351 >> Query.children [] 2352 >> Query.index 0 2353 , unhoveredSelector = 2354 { description = "a faded 20px square pause button with pointer cursor" 2355 , selector = 2356 iconSelector 2357 { size = "20px" 2358 , image = Assets.PauseIcon 2359 } 2360 ++ [ style "cursor" "pointer" 2361 , style "opacity" "0.5" 2362 ] 2363 } 2364 , hoverable = 2365 Msgs.PipelineCardPauseToggle FavoritesSection Data.pipelineId 2366 , hoveredSelector = 2367 { description = "a bright 20px square pause button with pointer cursor" 2368 , selector = 2369 iconSelector 2370 { size = "20px" 2371 , image = Assets.PauseIcon 2372 } 2373 ++ [ style "cursor" "pointer" 2374 , style "opacity" "1" 2375 ] 2376 } 2377 } 2378 , defineHoverBehaviour 2379 { name = "play button" 2380 , setup = 2381 whenOnDashboard { highDensity = False } 2382 |> givenDataAndUser 2383 (apiData [ ( "team", [] ) ]) 2384 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2385 |> Tuple.first 2386 |> Application.handleCallback 2387 (Callback.AllPipelinesFetched <| 2388 Ok 2389 [ Data.pipeline "team" 0 2390 |> Data.withName "pipeline" 2391 |> Data.withPaused True 2392 ] 2393 ) 2394 |> Tuple.first 2395 , query = 2396 Common.queryView 2397 >> Query.find [ class "card-footer" ] 2398 >> Query.children [] 2399 >> Query.index -1 2400 >> Query.children [] 2401 >> Query.index 0 2402 , unhoveredSelector = 2403 { description = "a transparent 20px square play button with pointer cursor" 2404 , selector = 2405 iconSelector 2406 { size = "20px" 2407 , image = Assets.PlayIcon 2408 } 2409 ++ [ style "cursor" "pointer" 2410 , style "opacity" "0.5" 2411 ] 2412 } 2413 , hoverable = Msgs.PipelineCardPauseToggle AllPipelinesSection Data.pipelineId 2414 , hoveredSelector = 2415 { description = "an opaque 20px square play button with pointer cursor" 2416 , selector = 2417 iconSelector 2418 { size = "20px" 2419 , image = Assets.PlayIcon 2420 } 2421 ++ [ style "cursor" "pointer" 2422 , style "opacity" "1" 2423 ] 2424 } 2425 } 2426 , test "clicking pause button sends TogglePipeline msg" <| 2427 \_ -> 2428 whenOnDashboard { highDensity = False } 2429 |> givenDataAndUser 2430 (apiData [ ( "team", [] ) ]) 2431 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2432 |> Tuple.first 2433 |> Application.handleCallback 2434 (Callback.AllPipelinesFetched <| 2435 Ok 2436 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2437 ) 2438 |> Tuple.first 2439 |> Common.queryView 2440 |> Query.find [ class "card-footer" ] 2441 |> Query.find [ class "pause-toggle" ] 2442 |> Event.simulate Event.click 2443 |> Event.expect 2444 (ApplicationMsgs.Update <| 2445 Msgs.Click <| 2446 Msgs.PipelineCardPauseToggle AllPipelinesSection Data.pipelineId 2447 ) 2448 , test "pause button turns into spinner on click" <| 2449 \_ -> 2450 let 2451 animation = 2452 "container-rotate 1568ms linear infinite" 2453 in 2454 whenOnDashboard { highDensity = False } 2455 |> givenDataAndUser 2456 (apiData [ ( "team", [] ) ]) 2457 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2458 |> Tuple.first 2459 |> Application.handleCallback 2460 (Callback.AllPipelinesFetched <| 2461 Ok 2462 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2463 ) 2464 |> Tuple.first 2465 |> Application.update 2466 (ApplicationMsgs.Update <| 2467 Msgs.Click <| 2468 Msgs.PipelineCardPauseToggle AllPipelinesSection Data.pipelineId 2469 ) 2470 |> Tuple.first 2471 |> Common.queryView 2472 |> Query.find [ class "card-footer" ] 2473 |> Query.has [ style "animation" animation ] 2474 , test "clicking pause button sends toggle api call" <| 2475 \_ -> 2476 whenOnDashboard { highDensity = False } 2477 |> givenDataAndUser 2478 (apiData [ ( "team", [] ) ]) 2479 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2480 |> Tuple.first 2481 |> Application.handleCallback 2482 (Callback.AllPipelinesFetched <| 2483 Ok 2484 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2485 ) 2486 |> Tuple.first 2487 |> Application.update 2488 (ApplicationMsgs.Update <| 2489 Msgs.Click <| 2490 Msgs.PipelineCardPauseToggle AllPipelinesSection Data.pipelineId 2491 ) 2492 |> Tuple.second 2493 |> Expect.equal [ Effects.SendTogglePipelineRequest Data.pipelineId False ] 2494 , test "all pipelines are refetched after ok toggle call" <| 2495 \_ -> 2496 whenOnDashboard { highDensity = False } 2497 |> givenDataAndUser 2498 (apiData [ ( "team", [] ) ]) 2499 (userWithRoles [ ( "team", [ "owner" ] ) ]) 2500 |> Tuple.first 2501 |> Application.handleCallback 2502 (Callback.AllPipelinesFetched <| 2503 Ok 2504 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2505 ) 2506 |> Tuple.first 2507 |> Application.update 2508 (ApplicationMsgs.Update <| 2509 Msgs.Click <| 2510 Msgs.PipelineCardPauseToggle AllPipelinesSection Data.pipelineId 2511 ) 2512 |> Tuple.first 2513 |> Application.handleCallback 2514 (Callback.PipelineToggled Data.pipelineId (Ok ())) 2515 |> Tuple.second 2516 |> Expect.equal [ Effects.FetchAllPipelines ] 2517 , test "401 toggle call redirects to login" <| 2518 \_ -> 2519 whenOnDashboard { highDensity = False } 2520 |> givenDataUnauthenticated 2521 (apiData [ ( "team", [] ) ]) 2522 |> Tuple.first 2523 |> Application.handleCallback 2524 (Callback.AllPipelinesFetched <| 2525 Ok 2526 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2527 ) 2528 |> Tuple.first 2529 |> Application.update 2530 (ApplicationMsgs.Update <| 2531 Msgs.Click <| 2532 Msgs.PipelineCardPauseToggle AllPipelinesSection Data.pipelineId 2533 ) 2534 |> Tuple.first 2535 |> Application.handleCallback 2536 (Callback.PipelineToggled Data.pipelineId Data.httpUnauthorized) 2537 |> Tuple.second 2538 |> Expect.equal [ Effects.RedirectToLogin ] 2539 ] 2540 , describe "favorited icon" <| 2541 let 2542 pipelineId = 2543 0 2544 2545 unfilledFavoritedIcon = 2546 iconSelector 2547 { size = "20px" 2548 , image = Assets.FavoritedToggleIcon { isFavorited = False, isHovered = False, isSideBar = False } 2549 } 2550 ++ [ style "background-size" "contain" ] 2551 2552 unfilledBrightFavoritedIcon = 2553 iconSelector 2554 { size = "20px" 2555 , image = Assets.FavoritedToggleIcon { isFavorited = False, isHovered = True, isSideBar = False } 2556 } 2557 ++ [ style "background-size" "contain" ] 2558 2559 filledFavoritedIcon = 2560 iconSelector 2561 { size = "20px" 2562 , image = Assets.FavoritedToggleIcon { isFavorited = True, isHovered = False, isSideBar = False } 2563 } 2564 ++ [ style "background-size" "contain" ] 2565 2566 favoritedToggle = 2567 Common.queryView 2568 >> Query.findAll [ class "card-footer" ] 2569 >> Query.first 2570 >> Query.children [] 2571 >> Query.index -1 2572 >> Query.children [] 2573 >> Query.index -1 2574 2575 favoritedIconClickable setup = 2576 [ defineHoverBehaviour 2577 { name = "favorited icon toggle" 2578 , setup = 2579 whenOnDashboard { highDensity = False } 2580 |> setup 2581 |> Tuple.first 2582 , query = favoritedToggle 2583 , unhoveredSelector = 2584 { description = "faded 20px square" 2585 , selector = 2586 unfilledFavoritedIcon 2587 ++ [ style "cursor" "pointer" ] 2588 } 2589 , hoverable = 2590 Msgs.PipelineCardFavoritedIcon AllPipelinesSection pipelineId 2591 , hoveredSelector = 2592 { description = "bright 20px square" 2593 , selector = 2594 unfilledBrightFavoritedIcon 2595 ++ [ style "cursor" "pointer" ] 2596 } 2597 } 2598 , test "has click handler" <| 2599 \_ -> 2600 whenOnDashboard { highDensity = False } 2601 |> setup 2602 |> Tuple.first 2603 |> favoritedToggle 2604 |> Event.simulate Event.click 2605 |> Event.expect 2606 (ApplicationMsgs.Update <| 2607 Msgs.Click <| 2608 Msgs.PipelineCardFavoritedIcon 2609 AllPipelinesSection 2610 pipelineId 2611 ) 2612 , test "click has FavoritedPipeline effect" <| 2613 \_ -> 2614 whenOnDashboard { highDensity = False } 2615 |> setup 2616 |> Tuple.first 2617 |> Application.update 2618 (ApplicationMsgs.Update <| 2619 Msgs.Click <| 2620 Msgs.PipelineCardFavoritedIcon 2621 AllPipelinesSection 2622 pipelineId 2623 ) 2624 |> Tuple.second 2625 |> Expect.equal 2626 [ Effects.SaveFavoritedPipelines <| 2627 Set.singleton pipelineId 2628 ] 2629 , test "favorited pipeline card has a bright filled star icon" <| 2630 \_ -> 2631 whenOnDashboard { highDensity = False } 2632 |> setup 2633 |> Tuple.first 2634 |> Application.handleDelivery 2635 (FavoritedPipelinesReceived <| 2636 Ok <| 2637 Set.singleton pipelineId 2638 ) 2639 |> Tuple.first 2640 |> favoritedToggle 2641 |> Expect.all 2642 [ Query.has filledFavoritedIcon 2643 ] 2644 ] 2645 in 2646 favoritedIconClickable 2647 (givenDataAndUser 2648 (apiData [ ( "team", [] ) ]) 2649 (userWithRoles 2650 [ ( "team", [ "owner" ] ) ] 2651 ) 2652 >> Tuple.first 2653 >> Application.handleCallback 2654 (Callback.AllPipelinesFetched <| 2655 Ok 2656 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 2657 ) 2658 ) 2659 ] 2660 ] 2661 ]