github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/tests/TopBarTests.elm (about) 1 module TopBarTests exposing (all) 2 3 import Application.Application as Application 4 import Assets 5 import Char 6 import ColorValues 7 import Common exposing (defineHoverBehaviour, queryView) 8 import Concourse 9 import Dashboard.SearchBar as SearchBar 10 import DashboardTests exposing (iconSelector) 11 import Data 12 import Dict 13 import Expect exposing (..) 14 import Html.Attributes as Attr 15 import Http 16 import Keyboard 17 import Login.Login as Login 18 import Message.Callback as Callback exposing (Callback(..)) 19 import Message.Effects as Effects 20 import Message.Message as Msgs 21 import Message.Subscription exposing (Delivery(..)) 22 import Message.TopLevelMessage as ApplicationMsgs 23 import Routes 24 import Test exposing (..) 25 import Test.Html.Event as Event 26 import Test.Html.Query as Query 27 import Test.Html.Selector as Selector 28 exposing 29 ( attribute 30 , class 31 , containing 32 , id 33 , style 34 , tag 35 , text 36 ) 37 import Time 38 import Url 39 import Views.Styles 40 41 42 rspecStyleDescribe : String -> subject -> List (subject -> Test) -> Test 43 rspecStyleDescribe description beforeEach subTests = 44 Test.describe description 45 (subTests |> List.map ((|>) beforeEach)) 46 47 48 context : String -> (setup -> subject) -> List (subject -> Test) -> (setup -> Test) 49 context description beforeEach subTests setup = 50 Test.describe description 51 (subTests |> List.map ((|>) (beforeEach setup))) 52 53 54 it : String -> (subject -> Expectation) -> subject -> Test 55 it desc expectationFunc subject = 56 Test.test desc <| 57 \_ -> expectationFunc subject 58 59 60 update : Msgs.Message -> Login.Model {} -> ( Login.Model {}, List Effects.Effect ) 61 update msg = 62 (\a -> ( a, [] )) >> Login.update msg 63 64 65 lineHeight : String 66 lineHeight = 67 "54px" 68 69 70 borderGrey : String 71 borderGrey = 72 "#3d3c3c" 73 74 75 backgroundGrey : String 76 backgroundGrey = 77 "#1e1d1d" 78 79 80 pausedBlue : String 81 pausedBlue = 82 "#3498db" 83 84 85 topBarHeight : String 86 topBarHeight = 87 "54px" 88 89 90 searchBarBorder : String -> String 91 searchBarBorder color = 92 "1px solid " ++ color 93 94 95 searchBarHeight : String 96 searchBarHeight = 97 "30px" 98 99 100 searchBarWidth : String 101 searchBarWidth = 102 "251px" 103 104 105 searchBarPadding : String 106 searchBarPadding = 107 "0 42px" 108 109 110 flags : Application.Flags 111 flags = 112 { turbulenceImgSrc = "" 113 , notFoundImgSrc = "" 114 , csrfToken = "" 115 , authToken = "" 116 , pipelineRunningKeyframes = "" 117 } 118 119 120 all : Test 121 all = 122 describe "TopBar" 123 [ rspecStyleDescribe "when on pipeline page" 124 (Common.init "/teams/team/pipelines/pipeline") 125 [ context "when login state unknown" 126 queryView 127 [ it "shows concourse logo" <| 128 Query.has 129 [ style "background-image" <| 130 Assets.backgroundImage <| 131 Just Assets.ConcourseLogoWhite 132 , style "background-position" "50% 50%" 133 , style "background-repeat" "no-repeat" 134 , style "background-size" "42px 42px" 135 , style "width" topBarHeight 136 , style "height" topBarHeight 137 ] 138 , it "shows pipeline breadcrumb" <| 139 Query.has [ id "breadcrumb-pipeline" ] 140 , context "pipeline breadcrumb" 141 (Query.find [ id "breadcrumb-pipeline" ]) 142 [ it "renders icon first" <| 143 Query.children [] 144 >> Query.first 145 >> Query.has pipelineBreadcrumbSelector 146 , it "renders pipeline name second" <| 147 Query.children [] 148 >> Query.index 1 149 >> Query.has 150 [ text "pipeline" ] 151 , it "has pointer cursor" <| 152 Query.has [ style "cursor" "pointer" ] 153 , it "is a link to the relevant pipeline page" <| 154 Query.has 155 [ tag "a" 156 , attribute <| 157 Attr.href 158 "/teams/team/pipelines/pipeline" 159 ] 160 ] 161 , it "has dark grey background" <| 162 Query.has [ style "background-color" ColorValues.grey100 ] 163 , it "lays out contents horizontally" <| 164 Query.has [ style "display" "flex" ] 165 , it "maximizes spacing between the left and right navs" <| 166 Query.has [ style "justify-content" "space-between" ] 167 , it "renders the login component last" <| 168 Query.children [] 169 >> Query.index -1 170 >> Query.has [ id "login-component" ] 171 ] 172 , context "when logged out" 173 (Application.handleCallback 174 (Callback.UserFetched <| Data.httpUnauthorized) 175 >> Tuple.first 176 >> queryView 177 ) 178 [ it "renders the login component last" <| 179 Query.children [] 180 >> Query.index -1 181 >> Query.has [ id "login-component" ] 182 , it "has a link to login" <| 183 Query.children [] 184 >> Query.index -1 185 >> Query.find [ id "login-item" ] 186 >> Query.has [ tag "a", attribute <| Attr.href "/sky/login" ] 187 ] 188 , context "when logged in" 189 (Application.handleCallback 190 (Callback.UserFetched <| Ok sampleUser) 191 >> Tuple.first 192 >> queryView 193 ) 194 [ it "renders the login component last" <| 195 Query.children [] 196 >> Query.index -1 197 >> Query.has [ id "login-component" ] 198 , it "renders login component with a maximum width" <| 199 Query.find [ id "login-component" ] 200 >> Query.has [ style "max-width" "20%" ] 201 , it "renders login container with relative position" <| 202 Query.children [] 203 >> Query.index -1 204 >> Query.find [ id "login-container" ] 205 >> Query.has 206 [ style "position" "relative" ] 207 , it "lays out login container contents vertically" <| 208 Query.children [] 209 >> Query.index -1 210 >> Query.find [ id "login-container" ] 211 >> Query.has 212 [ style "display" "flex" 213 , style "flex-direction" "column" 214 ] 215 , it "draws lighter grey line to the left of login container" <| 216 Query.children [] 217 >> Query.index -1 218 >> Query.find [ id "login-container" ] 219 >> Query.has 220 [ style "border-left" <| "1px solid " ++ borderGrey ] 221 , it "renders login container tall enough" <| 222 Query.children [] 223 >> Query.index -1 224 >> Query.find [ id "login-container" ] 225 >> Query.has 226 [ style "line-height" lineHeight ] 227 , it "has the login username styles" <| 228 Query.children [] 229 >> Query.index -1 230 >> Query.find [ id "user-id" ] 231 >> Expect.all 232 [ Query.has 233 [ style "padding" "0 30px" 234 , style "cursor" "pointer" 235 , style "display" "flex" 236 , style "align-items" "center" 237 , style "justify-content" "center" 238 , style "flex-grow" "1" 239 ] 240 , Query.children [] 241 >> Query.index 0 242 >> Query.has 243 [ style "overflow" "hidden" 244 , style "text-overflow" "ellipsis" 245 ] 246 ] 247 , it "shows the logged in username when the user is logged in" <| 248 Query.children [] 249 >> Query.index -1 250 >> Query.find [ id "user-id" ] 251 >> Query.has [ text "test" ] 252 , it "Click UserMenu message is received when login menu is clicked" <| 253 Query.find [ id "login-container" ] 254 >> Event.simulate Event.click 255 >> Event.expect 256 (ApplicationMsgs.Update <| Msgs.Click Msgs.UserMenu) 257 , it "does not render the logout button" <| 258 Query.children [] 259 >> Query.index -1 260 >> Query.find [ id "user-id" ] 261 >> Query.hasNot [ id "logout-button" ] 262 , it "renders pause pipeline button" <| 263 Query.find [ id "top-bar-pause-toggle" ] 264 >> Query.children [] 265 >> Query.first 266 >> Query.has 267 [ style "background-image" <| 268 Assets.backgroundImage <| 269 Just Assets.PauseIcon 270 ] 271 , it "draws lighter grey line to the left of pause pipeline button" <| 272 Query.find [ id "top-bar-pause-toggle" ] 273 >> Query.has 274 [ style "border-left" <| "1px solid " ++ borderGrey ] 275 ] 276 , it "clicking a pinned resource navigates to the pinned resource page" <| 277 Application.update 278 (ApplicationMsgs.Update <| 279 Msgs.GoToRoute 280 (Routes.Resource 281 { id = Data.shortResourceId 282 , page = Nothing 283 } 284 ) 285 ) 286 >> Tuple.second 287 >> Expect.equal 288 [ Effects.NavigateTo <| 289 Routes.toString <| 290 Routes.Resource 291 { id = Data.shortResourceId 292 , page = Nothing 293 } 294 ] 295 , context "when pipeline is paused" 296 (Application.handleCallback 297 (Callback.PipelineFetched <| 298 Ok <| 299 (Data.pipeline "t" 0 300 |> Data.withName "p" 301 |> Data.withPaused True 302 ) 303 ) 304 >> Tuple.first 305 >> Application.handleCallback 306 (Callback.UserFetched <| Ok sampleUser) 307 >> Tuple.first 308 >> queryView 309 ) 310 [ it "has blue background" <| 311 Query.has [ style "background-color" pausedBlue ] 312 ] 313 , context "when pipeline is archived" 314 (Application.handleCallback 315 (Callback.PipelineFetched <| 316 Ok <| 317 (Data.pipeline "t" 0 318 |> Data.withName "p" 319 |> Data.withPaused True 320 |> Data.withArchived True 321 ) 322 ) 323 >> Tuple.first 324 >> Application.handleCallback 325 (Callback.UserFetched <| Ok sampleUser) 326 >> Tuple.first 327 >> queryView 328 ) 329 [ it "does not render pause toggle" <| 330 Query.hasNot [ id "top-bar-pause-toggle" ] 331 , it "draws uses the normal border colour for the login container" <| 332 Query.find [ id "login-container" ] 333 >> Query.has 334 [ style "border-left" <| "1px solid " ++ borderGrey ] 335 ] 336 ] 337 , rspecStyleDescribe "rendering user menus on clicks" 338 (Common.init "/teams/team/pipelines/pipeline") 339 [ it "shows user menu when ToggleUserMenu msg is received" <| 340 Application.handleCallback 341 (Callback.UserFetched <| Ok sampleUser) 342 >> Tuple.first 343 >> Application.update 344 (ApplicationMsgs.Update <| Msgs.Click Msgs.UserMenu) 345 >> Tuple.first 346 >> queryView 347 >> Query.has [ id "logout-button" ] 348 , it "renders user menu content when click UserMenu msg is received and logged in" <| 349 Application.handleCallback 350 (Callback.UserFetched <| Ok sampleUser) 351 >> Tuple.first 352 >> Application.update 353 (ApplicationMsgs.Update <| Msgs.Click Msgs.UserMenu) 354 >> Tuple.first 355 >> queryView 356 >> Expect.all 357 [ Query.has [ id "logout-button" ] 358 , Query.find [ id "logout-button" ] 359 >> Query.has [ text "logout" ] 360 , Query.find [ id "logout-button" ] 361 >> Query.has 362 [ style "position" "absolute" 363 , style "top" "55px" 364 , style "background-color" ColorValues.grey100 365 , style "height" topBarHeight 366 , style "width" "100%" 367 , style "border-top" <| "1px solid " ++ borderGrey 368 , style "cursor" "pointer" 369 , style "display" "flex" 370 , style "align-items" "center" 371 , style "justify-content" "center" 372 , style "flex-grow" "1" 373 ] 374 ] 375 , it "when logout is clicked, a Click LogoutButton msg is sent" <| 376 Application.handleCallback 377 (Callback.UserFetched <| Ok sampleUser) 378 >> Tuple.first 379 >> Application.update 380 (ApplicationMsgs.Update <| Msgs.Click Msgs.UserMenu) 381 >> Tuple.first 382 >> queryView 383 >> Query.find [ id "logout-button" ] 384 >> Event.simulate Event.click 385 >> Event.expect 386 (ApplicationMsgs.Update <| Msgs.Click Msgs.LogoutButton) 387 , it "shows 'login' when LoggedOut TopLevelMessage is successful" <| 388 Application.handleCallback 389 (Callback.LoggedOut <| Ok ()) 390 >> Tuple.first 391 >> queryView 392 >> Query.find [ id "login-item" ] 393 >> Query.has [ text "login" ] 394 ] 395 , rspecStyleDescribe "login component when user is logged out" 396 (Common.init "/teams/team/pipelines/pipeline" 397 |> Application.handleCallback 398 (Callback.LoggedOut (Ok ())) 399 |> Tuple.first 400 |> queryView 401 ) 402 [ it "has a link to login" <| 403 Query.children [] 404 >> Query.index -1 405 >> Query.find [ id "login-item" ] 406 >> Query.has [ tag "a", attribute <| Attr.href "/sky/login" ] 407 , it "has the login container styles" <| 408 Query.children [] 409 >> Query.index -1 410 >> Query.find [ id "login-container" ] 411 >> Query.has 412 [ style "position" "relative" 413 , style "display" "flex" 414 , style "flex-direction" "column" 415 , style "border-left" <| "1px solid " ++ borderGrey 416 , style "line-height" lineHeight 417 ] 418 , it "has the login username styles" <| 419 Query.children [] 420 >> Query.index -1 421 >> Query.find [ id "login-item" ] 422 >> Query.has 423 [ style "padding" "0 30px" 424 , style "cursor" "pointer" 425 , style "display" "flex" 426 , style "align-items" "center" 427 , style "justify-content" "center" 428 , style "flex-grow" "1" 429 ] 430 ] 431 , rspecStyleDescribe "when triggering a log in message" 432 (Common.init "/" 433 |> Application.handleCallback 434 (Callback.LoggedOut (Ok ())) 435 ) 436 [ it "redirects to login page when you click login" <| 437 Tuple.first 438 >> Application.update 439 (ApplicationMsgs.Update <| Msgs.Click Msgs.LoginButton) 440 >> Tuple.second 441 >> Expect.equal [ Effects.RedirectToLogin ] 442 ] 443 , rspecStyleDescribe "rendering top bar on build page" 444 (Common.init "/teams/team/pipelines/pipeline/jobs/job/builds/1" 445 |> queryView 446 ) 447 [ it "should pad the breadcrumbs to max size so they can be left-aligned" <| 448 Query.find 449 [ id "breadcrumbs" ] 450 >> Query.has [ style "flex-grow" "1" ] 451 , it "pipeline breadcrumb should have a link to the pipeline page when viewing build details" <| 452 Query.find [ id "breadcrumb-pipeline" ] 453 >> Query.has 454 [ tag "a" 455 , attribute <| 456 Attr.href 457 "/teams/team/pipelines/pipeline" 458 ] 459 , context "job breadcrumb" 460 (Query.find [ id "breadcrumb-job" ]) 461 [ it "is laid out horizontally with appropriate spacing" <| 462 Query.has 463 [ style "display" "inline-block" 464 , style "padding" "0 10px" 465 ] 466 , it "has job icon rendered first" <| 467 Query.has jobBreadcrumbSelector 468 , it "has build name after job icon" <| 469 Query.has [ text "job" ] 470 , it "does not appear clickable" <| 471 Query.hasNot [ style "cursor" "pointer" ] 472 ] 473 ] 474 , rspecStyleDescribe "rendering top bar on resource page" 475 (Common.init "/teams/team/pipelines/pipeline/resources/resource" 476 |> queryView 477 ) 478 [ it "should pad the breadcrumbs to max size so they can be left-aligned" <| 479 Query.find 480 [ id "breadcrumbs" ] 481 >> Query.has [ style "flex-grow" "1" ] 482 , it "pipeline breadcrumb should have a link to the pipeline page when viewing resource details" <| 483 Query.find [ id "breadcrumb-pipeline" ] 484 >> Query.has 485 [ tag "a" 486 , attribute <| 487 Attr.href 488 "/teams/team/pipelines/pipeline" 489 ] 490 , it "there is a / between pipeline and resource in breadcrumb" <| 491 Query.find [ id "breadcrumbs" ] 492 >> Query.children [] 493 >> Expect.all 494 [ Query.index 1 495 >> Query.has [ class "breadcrumb-separator" ] 496 , Query.index 1 >> Query.has [ text "/" ] 497 , Query.index 2 >> Query.has [ id "breadcrumb-resource" ] 498 ] 499 , it "resource breadcrumb is laid out horizontally with appropriate spacing" <| 500 Query.find [ id "breadcrumb-resource" ] 501 >> Query.has 502 [ style "display" "inline-block" 503 , style "padding" "0 10px" 504 ] 505 , it "top bar has resource breadcrumb with resource icon rendered first" <| 506 Query.find [ id "breadcrumb-resource" ] 507 >> Query.children [] 508 >> Query.index 0 509 >> Query.has resourceBreadcrumbSelector 510 , it "top bar has resource name after resource icon" <| 511 Query.find [ id "breadcrumb-resource" ] 512 >> Query.children [] 513 >> Query.index 1 514 >> Query.has 515 [ text "resource" ] 516 ] 517 , rspecStyleDescribe "rendering top bar on job page" 518 (Common.init "/teams/team/pipelines/pipeline/jobs/job" 519 |> queryView 520 ) 521 [ it "should pad the breadcrumbs to max size so they can be left-aligned" <| 522 Query.find 523 [ id "breadcrumbs" ] 524 >> Query.has [ style "flex-grow" "1" ] 525 , it "pipeline breadcrumb should have a link to the pipeline page when viewing job details" <| 526 Query.find [ id "breadcrumb-pipeline" ] 527 >> Query.has 528 [ tag "a" 529 , attribute <| 530 Attr.href 531 "/teams/team/pipelines/pipeline" 532 ] 533 , it "there is a / between pipeline and job in breadcrumb" <| 534 Query.find [ id "breadcrumbs" ] 535 >> Query.children [] 536 >> Expect.all 537 [ Query.index 1 538 >> Query.has [ class "breadcrumb-separator" ] 539 , Query.index 0 >> Query.has [ id "breadcrumb-pipeline" ] 540 , Query.index 2 >> Query.has [ id "breadcrumb-job" ] 541 ] 542 ] 543 , rspecStyleDescribe "when checking search bar values" 544 (Application.init 545 flags 546 { protocol = Url.Http 547 , host = "" 548 , port_ = Nothing 549 , path = "/" 550 , query = Just "search=test" 551 , fragment = Nothing 552 } 553 |> Tuple.first 554 |> Application.handleCallback 555 (Callback.AllTeamsFetched <| 556 Ok 557 [ Concourse.Team 1 "team1" 558 , Concourse.Team 2 "team2" 559 ] 560 ) 561 |> Tuple.first 562 |> Application.handleCallback 563 (Callback.AllPipelinesFetched <| 564 Ok 565 [ Data.pipeline "team1" 0 |> Data.withName "pipeline" ] 566 ) 567 |> Tuple.first 568 ) 569 [ it "renders the search bar with the text in the search query" <| 570 queryView 571 >> Query.find [ id SearchBar.searchInputId ] 572 >> Query.has [ tag "input", attribute <| Attr.value "test" ] 573 , it "sends a click msg when the clear search button is clicked" <| 574 queryView 575 >> Query.find [ id "search-container" ] 576 >> Query.find [ id "search-clear" ] 577 >> Event.simulate Event.click 578 >> Event.expect 579 (ApplicationMsgs.Update <| 580 Msgs.Click Msgs.ClearSearchButton 581 ) 582 , it "click msg clears the search input" <| 583 Application.update 584 (ApplicationMsgs.Update <| 585 Msgs.Click Msgs.ClearSearchButton 586 ) 587 >> Tuple.first 588 >> queryView 589 >> Query.find [ id "search-input-field" ] 590 >> Query.has [ attribute <| Attr.value "" ] 591 , it "clear search button shows up when there is a query" <| 592 queryView 593 >> Query.has [ id "search-clear" ] 594 ] 595 , rspecStyleDescribe "rendering search bar on dashboard page" 596 (Common.init "/" 597 |> Application.handleCallback 598 (Callback.AllTeamsFetched <| 599 Ok 600 [ Concourse.Team 1 "team1" 601 , Concourse.Team 2 "team2" 602 ] 603 ) 604 |> Tuple.first 605 |> Application.handleCallback 606 (Callback.AllPipelinesFetched <| 607 Ok 608 [ Data.pipeline "team1" 0 |> Data.withName "pipeline" ] 609 ) 610 |> Tuple.first 611 ) 612 [ context "when desktop sized" 613 (Application.handleCallback 614 (ScreenResized 615 { scene = { width = 0, height = 0 } 616 , viewport = { x = 0, y = 0, width = 1500, height = 900 } 617 } 618 ) 619 >> Tuple.first 620 >> queryView 621 ) 622 [ it "renders search bar" <| 623 Query.has [ id SearchBar.searchInputId ] 624 , it "search bar is an input field" <| 625 Query.find [ id SearchBar.searchInputId ] 626 >> Query.has [ tag "input" ] 627 , it "renders search bar with transparent background to remove white of search bar" <| 628 Query.find [ id SearchBar.searchInputId ] 629 >> Query.has 630 [ style "background-color" ColorValues.grey90 ] 631 , it "search bar does not use browser's built-in autocomplete" <| 632 Query.find [ id SearchBar.searchInputId ] 633 >> Query.has 634 [ attribute <| Attr.attribute "autocomplete" "off" ] 635 , it "sets magnifying glass on search bar in correct position" <| 636 Query.find [ id SearchBar.searchInputId ] 637 >> Query.has 638 [ style "background-image" <| 639 Assets.backgroundImage <| 640 Just Assets.SearchIconGrey 641 , style "background-position" "12px 8px" 642 , style "background-repeat" "no-repeat" 643 ] 644 , it "styles search border and input text colour" <| 645 Query.find [ id SearchBar.searchInputId ] 646 >> Query.has 647 [ style "border" <| searchBarBorder ColorValues.grey60 648 , style "color" ColorValues.white 649 , style "font-size" "12px" 650 , style "font-family" Views.Styles.fontFamilyDefault 651 ] 652 , it "renders search with appropriate size and padding" <| 653 Query.find [ id SearchBar.searchInputId ] 654 >> Query.has 655 [ style "height" searchBarHeight 656 , style "width" searchBarWidth 657 , style "padding" searchBarPadding 658 ] 659 , it "does not have an outline when focused" <| 660 Query.find [ id SearchBar.searchInputId ] 661 >> Query.has [ style "outline" "0" ] 662 , it "has placeholder text" <| 663 Query.find [ id SearchBar.searchInputId ] 664 >> Query.has 665 [ tag "input" 666 , attribute <| 667 Attr.placeholder 668 "filter pipelines by name, status, or team" 669 ] 670 , it "has a wrapper for top bar content" <| 671 Query.has 672 [ id "top-bar-content" 673 , containing [ id "search-container" ] 674 ] 675 , it "top bar content wrapper fills available space" <| 676 Query.find [ id "top-bar-content" ] 677 >> Query.has [ style "flex-grow" "1" ] 678 , it "top bar content wrapper centers its content" <| 679 Query.find [ id "top-bar-content" ] 680 >> Query.has 681 [ style "display" "flex" 682 , style "justify-content" "center" 683 ] 684 , it "search container is positioned appropriately" <| 685 Query.find [ id "search-container" ] 686 >> Expect.all 687 [ Query.has 688 [ style "position" "relative" 689 , style "display" "flex" 690 , style "flex-direction" "column" 691 , style "align-items" "stretch" 692 ] 693 , Query.hasNot [ style "flex-grow" "1" ] 694 ] 695 , it "search container is sized correctly" <| 696 Query.find [ id "search-container" ] 697 >> Expect.all 698 [ Query.has [ style "margin" "12px" ] 699 , Query.hasNot [ style "height" "56px" ] 700 ] 701 , it "does not show clear search when there's no search query" <| 702 Query.find [ id "search-container" ] 703 >> Query.hasNot [ id "search-clear" ] 704 ] 705 , context "when mobile sized" 706 (Application.handleCallback 707 (ScreenResized 708 { scene = { width = 0, height = 0 } 709 , viewport = { x = 0, y = 0, width = 400, height = 900 } 710 } 711 ) 712 >> Tuple.first 713 ) 714 [ it "should not have a search bar" <| 715 queryView 716 >> Query.hasNot 717 [ id SearchBar.searchInputId ] 718 , it "should have a magnifying glass icon" <| 719 queryView 720 >> Query.find [ id "show-search-button" ] 721 >> Query.has 722 [ style "background-image" <| 723 Assets.backgroundImage <| 724 Just Assets.SearchIconGrey 725 , style "background-position" "12px 8px" 726 , style "background-repeat" "no-repeat" 727 ] 728 , it "shows the login component" <| 729 queryView 730 >> Query.has [ id "login-component" ] 731 , context "after clicking the search icon" 732 (Application.update 733 (ApplicationMsgs.Update <| 734 Msgs.Click Msgs.ShowSearchButton 735 ) 736 ) 737 [ it "tells the ui to focus on the search bar" <| 738 Tuple.second 739 >> Expect.equal 740 [ Effects.Focus SearchBar.searchInputId ] 741 , context "the ui" 742 (Tuple.first 743 >> queryView 744 ) 745 [ it "renders search bar" <| 746 Query.has [ id SearchBar.searchInputId ] 747 , it "search bar is an input field" <| 748 Query.find [ id SearchBar.searchInputId ] 749 >> Query.has [ tag "input" ] 750 , it "has placeholder text" <| 751 Query.find [ id SearchBar.searchInputId ] 752 >> Query.has 753 [ tag "input" 754 , attribute <| 755 Attr.placeholder 756 "filter pipelines by name, status, or team" 757 ] 758 , it "has a search container" <| 759 Query.has [ id "search-container" ] 760 , it "positions the search container appropriately" <| 761 Query.find [ id "search-container" ] 762 >> Query.has 763 [ style "position" "relative" 764 , style "display" "flex" 765 , style "flex-direction" "column" 766 , style "align-items" "stretch" 767 , style "flex-grow" "1" 768 ] 769 , it "search container is sized correctly" <| 770 Query.find [ id "search-container" ] 771 >> Expect.all 772 [ Query.has [ style "margin" "12px" ] 773 , Query.hasNot [ style "height" "56px" ] 774 ] 775 , it "does not show clear search when there's no search query" <| 776 Query.find [ id "search-container" ] 777 >> Query.hasNot [ id "search-clear" ] 778 , it "hides the login component" <| 779 Query.hasNot [ id "login-component" ] 780 ] 781 , context "after the focus returns" 782 (Tuple.first 783 >> Application.update 784 (ApplicationMsgs.Update Msgs.FocusMsg) 785 >> Tuple.first 786 ) 787 [ it "should display a dropdown of options" <| 788 queryView 789 >> Query.find [ id "search-dropdown" ] 790 >> Query.findAll [ tag "li" ] 791 >> Expect.all 792 [ Query.count (Expect.equal 2) 793 , Query.index 0 >> Query.has [ text "status: " ] 794 , Query.index 1 >> Query.has [ text "team: " ] 795 ] 796 , it "the search dropdown is positioned below the search bar" <| 797 queryView 798 >> Query.find [ id "search-dropdown" ] 799 >> Expect.all 800 [ Query.has 801 [ style "top" "100%" 802 , style "margin" "0" 803 ] 804 , Query.hasNot [ style "position" "absolute" ] 805 ] 806 , it "the search dropdown is the same width as search bar" <| 807 queryView 808 >> Query.find [ id "search-dropdown" ] 809 >> Query.has [ style "width" "100%" ] 810 , context "after the search is blurred" 811 (Application.update 812 (ApplicationMsgs.Update Msgs.BlurMsg) 813 >> Tuple.first 814 >> queryView 815 ) 816 [ it "should not have a search bar" <| 817 Query.hasNot 818 [ id SearchBar.searchInputId ] 819 , it "should have a magnifying glass icon" <| 820 Query.find [ id "show-search-button" ] 821 >> Query.has 822 [ style "background-image" <| 823 Assets.backgroundImage <| 824 Just Assets.SearchIconGrey 825 , style "background-position" "12px 8px" 826 , style "background-repeat" "no-repeat" 827 ] 828 , it "shows the login component" <| 829 Query.has [ id "login-component" ] 830 ] 831 , context "after the search is blurred with a search query" 832 (Application.update 833 (ApplicationMsgs.Update <| 834 Msgs.FilterMsg "query" 835 ) 836 >> Tuple.first 837 >> Application.update 838 (ApplicationMsgs.Update <| Msgs.BlurMsg) 839 >> Tuple.first 840 >> queryView 841 ) 842 [ it "should have a search bar" <| 843 Query.has [ id SearchBar.searchInputId ] 844 , it "should not have a magnifying glass icon" <| 845 Query.hasNot [ id "show-search-button" ] 846 , it "should not show the login component" <| 847 Query.hasNot [ id "login-component" ] 848 , it "should not display a dropdown of options" <| 849 Query.hasNot [ id "search-dropdown" ] 850 , it "has a clear search button container" <| 851 Query.has [ id "search-clear" ] 852 ] 853 ] 854 ] 855 ] 856 ] 857 , rspecStyleDescribe "when search query is updated" 858 (Common.init "/" 859 |> Application.handleCallback 860 (Callback.AllTeamsFetched <| 861 Ok 862 [ Concourse.Team 1 "team1" 863 , Concourse.Team 2 "team2" 864 ] 865 ) 866 |> Tuple.first 867 |> Application.handleCallback 868 (Callback.AllPipelinesFetched <| 869 Ok 870 [ Data.pipeline "team1" 0 |> Data.withName "pipeline" ] 871 ) 872 |> Tuple.first 873 ) 874 [ context "the search clear button" 875 (Application.update 876 (ApplicationMsgs.Update Msgs.FocusMsg) 877 >> Tuple.first 878 >> Application.update 879 (ApplicationMsgs.Update <| 880 Msgs.FilterMsg "status:" 881 ) 882 >> Tuple.first 883 ) 884 [ it "clear search button has no border and renders text appropriately" <| 885 queryView 886 >> Query.has [ id "search-clear" ] 887 , it "styles search border and input text colour" <| 888 queryView 889 >> Query.find [ id SearchBar.searchInputId ] 890 >> Query.has 891 [ style "border" <| searchBarBorder ColorValues.grey30 892 , style "color" ColorValues.white 893 , style "font-family" Views.Styles.fontFamilyDefault 894 ] 895 , it "has a clear search button container" <| 896 queryView 897 >> Query.find [ id "search-clear" ] 898 >> Query.has 899 [ style "border" "0" 900 , style "color" "transparent" 901 ] 902 , it "clear search button is positioned appropriately" <| 903 queryView 904 >> Query.find [ id "search-clear" ] 905 >> Query.has 906 [ style "position" "absolute" 907 , style "right" "0" 908 , style "padding" "17px" 909 ] 910 ] 911 , context "when focusing the search bar" 912 (Application.update 913 (ApplicationMsgs.Update Msgs.FocusMsg) 914 >> Tuple.first 915 >> Application.update 916 (ApplicationMsgs.Update <| Msgs.FilterMsg "status:") 917 >> Tuple.first 918 ) 919 [ it 920 ("shows the list of statuses when " 921 ++ "`status:` is typed in the search bar" 922 ) 923 <| 924 queryView 925 >> Query.find [ id "search-dropdown" ] 926 >> Query.findAll [ tag "li" ] 927 >> Expect.all 928 [ Query.count (Expect.equal 7) 929 , Query.index 0 >> Query.has [ text "status: paused" ] 930 , Query.index 1 >> Query.has [ text "status: pending" ] 931 , Query.index 2 >> Query.has [ text "status: failed" ] 932 , Query.index 3 >> Query.has [ text "status: errored" ] 933 , Query.index 4 >> Query.has [ text "status: aborted" ] 934 , Query.index 5 >> Query.has [ text "status: running" ] 935 , Query.index 6 >> Query.has [ text "status: succeeded" ] 936 ] 937 , it "after typing `status: pending` the dropdown is empty" <| 938 Application.update 939 (ApplicationMsgs.Update <| 940 Msgs.FilterMsg "status: pending" 941 ) 942 >> Tuple.first 943 >> queryView 944 >> Query.findAll [ id "search-dropdown" ] 945 >> Query.first 946 >> Query.children [] 947 >> Query.count (Expect.equal 0) 948 ] 949 ] 950 , rspecStyleDescribe "when search query is `status:`" 951 (Application.init 952 flags 953 { protocol = Url.Http 954 , host = "" 955 , port_ = Nothing 956 , path = "/" 957 , query = Just "search=status:" 958 , fragment = Nothing 959 } 960 |> Tuple.first 961 |> Application.handleCallback 962 (Callback.AllTeamsFetched <| 963 Ok 964 [ Concourse.Team 1 "team1" 965 , Concourse.Team 2 "team2" 966 ] 967 ) 968 |> Tuple.first 969 |> Application.handleCallback 970 (Callback.AllPipelinesFetched <| 971 Ok 972 [ Data.pipeline "team1" 0 |> Data.withName "pipeline" ] 973 ) 974 |> Tuple.first 975 ) 976 [ it "should display a dropdown of status options when the search bar is focused" <| 977 Application.update 978 (ApplicationMsgs.Update Msgs.FocusMsg) 979 >> Tuple.first 980 >> queryView 981 >> Query.find [ id "search-dropdown" ] 982 >> Query.findAll [ tag "li" ] 983 >> Expect.all 984 [ Query.count (Expect.equal 7) 985 , Query.index 0 >> Query.has [ text "status: paused" ] 986 , Query.index 1 >> Query.has [ text "status: pending" ] 987 , Query.index 2 >> Query.has [ text "status: failed" ] 988 , Query.index 3 >> Query.has [ text "status: errored" ] 989 , Query.index 4 >> Query.has [ text "status: aborted" ] 990 , Query.index 5 >> Query.has [ text "status: running" ] 991 , Query.index 6 >> Query.has [ text "status: succeeded" ] 992 ] 993 ] 994 , rspecStyleDescribe "when the search query is `team:`" 995 (Application.init 996 flags 997 { protocol = Url.Http 998 , host = "" 999 , port_ = Nothing 1000 , path = "/" 1001 , query = Just "search=team:" 1002 , fragment = Nothing 1003 } 1004 |> Tuple.first 1005 ) 1006 [ it "when there are teams the dropdown displays them" <| 1007 Application.handleCallback 1008 (Callback.AllTeamsFetched <| 1009 Ok 1010 [ Concourse.Team 1 "team1", Concourse.Team 2 "team2" ] 1011 ) 1012 >> Tuple.first 1013 >> Application.handleCallback 1014 (Callback.AllPipelinesFetched <| 1015 Ok 1016 [ Data.pipeline "team1" 0 |> Data.withName "pipeline" ] 1017 ) 1018 >> Tuple.first 1019 >> Application.update 1020 (ApplicationMsgs.Update Msgs.FocusMsg) 1021 >> Tuple.first 1022 >> queryView 1023 >> Query.find [ id "search-dropdown" ] 1024 >> Query.children [] 1025 >> Expect.all 1026 [ Query.count (Expect.equal 2) 1027 , Query.first >> Query.has [ tag "li", text "team1" ] 1028 , Query.index 1 >> Query.has [ tag "li", text "team2" ] 1029 ] 1030 , it "when there are many teams, the dropdown only displays the first 10" <| 1031 Application.handleCallback 1032 (Callback.AllTeamsFetched <| 1033 Ok 1034 [ Concourse.Team 1 "team1" 1035 , Concourse.Team 2 "team2" 1036 , Concourse.Team 3 "team3" 1037 , Concourse.Team 4 "team4" 1038 , Concourse.Team 5 "team5" 1039 , Concourse.Team 6 "team6" 1040 , Concourse.Team 7 "team7" 1041 , Concourse.Team 8 "team8" 1042 , Concourse.Team 9 "team9" 1043 , Concourse.Team 10 "team10" 1044 , Concourse.Team 11 "team11" 1045 ] 1046 ) 1047 >> Tuple.first 1048 >> Application.handleCallback 1049 (Callback.AllPipelinesFetched <| 1050 Ok 1051 [ Data.pipeline "team1" 0 |> Data.withName "pipeline" ] 1052 ) 1053 >> Tuple.first 1054 >> Application.update 1055 (ApplicationMsgs.Update Msgs.FocusMsg) 1056 >> Tuple.first 1057 >> queryView 1058 >> Query.find [ id "search-dropdown" ] 1059 >> Query.children [] 1060 >> Query.count (Expect.equal 10) 1061 ] 1062 , rspecStyleDescribe "dropdown stuff" 1063 (Common.init "/" 1064 |> Application.handleCallback 1065 (Callback.AllTeamsFetched <| 1066 Ok 1067 [ { id = 0, name = "team" } ] 1068 ) 1069 |> Tuple.first 1070 |> Application.handleCallback 1071 (Callback.AllPipelinesFetched <| 1072 Ok 1073 [ Data.pipeline "team" 0 |> Data.withName "pipeline" ] 1074 ) 1075 |> Tuple.first 1076 ) 1077 [ context "before receiving FocusMsg" 1078 queryView 1079 [ it "has no dropdown" <| 1080 Query.findAll [ id "search-dropdown" ] 1081 >> Query.count (Expect.equal 0) 1082 , it "sends FocusMsg when focusing on search bar" <| 1083 Query.find [ id SearchBar.searchInputId ] 1084 >> Event.simulate Event.focus 1085 >> Event.expect (ApplicationMsgs.Update Msgs.FocusMsg) 1086 ] 1087 , it "hitting '/' focuses search input" <| 1088 Application.update 1089 (ApplicationMsgs.DeliveryReceived <| 1090 KeyDown 1091 { ctrlKey = False 1092 , shiftKey = False 1093 , metaKey = False 1094 , code = Keyboard.Slash 1095 } 1096 ) 1097 >> Tuple.second 1098 >> Expect.equal [ Effects.Focus SearchBar.searchInputId ] 1099 , it "hitting shift + '/' (= '?') does not focus search input" <| 1100 Application.update 1101 (ApplicationMsgs.DeliveryReceived <| 1102 KeyDown 1103 { ctrlKey = False 1104 , shiftKey = True 1105 , metaKey = False 1106 , code = Keyboard.Slash 1107 } 1108 ) 1109 >> Tuple.second 1110 >> Expect.equal [] 1111 , it "hitting other keys does not cause dropdown to expand" <| 1112 Application.update 1113 (ApplicationMsgs.DeliveryReceived <| 1114 KeyDown 1115 { ctrlKey = False 1116 , shiftKey = False 1117 , metaKey = False 1118 , code = Keyboard.A 1119 } 1120 ) 1121 >> Tuple.first 1122 >> queryView 1123 >> Query.findAll [ id "search-dropdown" ] 1124 >> Query.count (Expect.equal 0) 1125 , context "after receiving FocusMsg" 1126 (Application.update (ApplicationMsgs.Update Msgs.FocusMsg)) 1127 ([ testDropdown [] [ 0, 1 ] ] 1128 ++ [ context "after down arrow keypress" 1129 (Tuple.first 1130 >> Application.update 1131 (ApplicationMsgs.DeliveryReceived <| 1132 KeyDown 1133 { ctrlKey = False 1134 , shiftKey = False 1135 , metaKey = False 1136 , code = Keyboard.ArrowDown 1137 } 1138 ) 1139 ) 1140 ([ testDropdown [ 0 ] [ 1 ] ] 1141 ++ [ context "after second down arrow keypress" 1142 (Tuple.first 1143 >> Application.update 1144 (ApplicationMsgs.DeliveryReceived <| 1145 KeyDown 1146 { ctrlKey = False 1147 , shiftKey = False 1148 , metaKey = False 1149 , code = Keyboard.ArrowDown 1150 } 1151 ) 1152 ) 1153 ([ testDropdown [ 1 ] [ 0 ] ] 1154 ++ [ context "after loop around down arrow keypress" 1155 (Tuple.first 1156 >> Application.update 1157 (ApplicationMsgs.DeliveryReceived <| 1158 KeyDown 1159 { ctrlKey = False 1160 , shiftKey = False 1161 , metaKey = False 1162 , code = Keyboard.ArrowDown 1163 } 1164 ) 1165 ) 1166 [ testDropdown [ 0 ] [ 1 ] ] 1167 , context "after hitting enter" 1168 (Tuple.first 1169 >> Application.update 1170 (ApplicationMsgs.DeliveryReceived <| 1171 KeyDown 1172 { ctrlKey = False 1173 , shiftKey = False 1174 , metaKey = False 1175 , code = Keyboard.Enter 1176 } 1177 ) 1178 >> viewNormally 1179 ) 1180 [ it "updates the query" <| 1181 Query.find [ id SearchBar.searchInputId ] 1182 >> Query.has [ attribute <| Attr.value "team: " ] 1183 ] 1184 ] 1185 ) 1186 , context "after hitting enter" 1187 (Tuple.first 1188 >> Application.update 1189 (ApplicationMsgs.DeliveryReceived <| 1190 KeyDown 1191 { ctrlKey = False 1192 , shiftKey = False 1193 , metaKey = False 1194 , code = Keyboard.Enter 1195 } 1196 ) 1197 ) 1198 [ it "updates the query" <| 1199 Tuple.first 1200 >> queryView 1201 >> Query.find 1202 [ id SearchBar.searchInputId ] 1203 >> Query.has 1204 [ attribute <| 1205 Attr.value "status: " 1206 ] 1207 , it "updates the URL" <| 1208 Tuple.second 1209 >> Expect.equal 1210 [ Effects.ModifyUrl 1211 "/?search=status%3A%20" 1212 ] 1213 ] 1214 ] 1215 ) 1216 , context "after up arrow keypress" 1217 (Tuple.first 1218 >> Application.update 1219 (ApplicationMsgs.DeliveryReceived <| 1220 KeyDown 1221 { ctrlKey = False 1222 , shiftKey = False 1223 , metaKey = False 1224 , code = Keyboard.ArrowUp 1225 } 1226 ) 1227 ) 1228 ([ testDropdown [ 1 ] [ 0 ] ] 1229 ++ [ context "after second up arrow keypress" 1230 (Tuple.first 1231 >> Application.update 1232 (ApplicationMsgs.DeliveryReceived <| 1233 KeyDown 1234 { ctrlKey = False 1235 , shiftKey = False 1236 , metaKey = False 1237 , code = Keyboard.ArrowUp 1238 } 1239 ) 1240 ) 1241 ([ testDropdown [ 0 ] [ 1 ] ] 1242 ++ [ context "after loop around up arrow keypress" 1243 (Tuple.first 1244 >> Application.update 1245 (ApplicationMsgs.DeliveryReceived <| 1246 KeyDown 1247 { ctrlKey = False 1248 , shiftKey = False 1249 , metaKey = False 1250 , code = Keyboard.ArrowUp 1251 } 1252 ) 1253 ) 1254 [ testDropdown [ 1 ] [ 0 ] ] 1255 ] 1256 ) 1257 ] 1258 ) 1259 ] 1260 ++ [ context "on ESC keypress" 1261 (Tuple.first 1262 >> Application.update 1263 (ApplicationMsgs.DeliveryReceived <| 1264 KeyDown 1265 { ctrlKey = False 1266 , shiftKey = False 1267 , metaKey = False 1268 , code = Keyboard.Escape 1269 } 1270 ) 1271 ) 1272 [ it "search input is blurred" <| 1273 Tuple.second 1274 >> Expect.equal [ Effects.Blur SearchBar.searchInputId ] 1275 ] 1276 ] 1277 ) 1278 , context "after receiving FocusMsg and then BlurMsg" 1279 (Application.update (ApplicationMsgs.Update Msgs.FocusMsg) 1280 >> Tuple.first 1281 >> Application.update 1282 (ApplicationMsgs.Update Msgs.BlurMsg) 1283 >> viewNormally 1284 ) 1285 [ it "hides the dropdown" <| 1286 Query.findAll [ id "search-dropdown" ] 1287 >> Query.count (Expect.equal 0) 1288 ] 1289 ] 1290 , rspecStyleDescribe "HD dashboard view" 1291 (Common.init "/hd" 1292 |> Application.handleCallback 1293 (Callback.AllPipelinesFetched <| 1294 Ok 1295 [ Data.pipeline "team1" 0 |> Data.withName "pipeline" ] 1296 ) 1297 |> Tuple.first 1298 ) 1299 [ it "renders an empty top bar content that fills width" <| 1300 queryView 1301 >> Query.has 1302 [ id "top-bar-content" 1303 , style "flex-grow" "1" 1304 ] 1305 ] 1306 , describe "pause toggle" <| 1307 let 1308 givenPipelinePaused = 1309 Common.init "/teams/t/pipelines/p" 1310 |> Application.handleCallback 1311 (Callback.PipelineFetched <| 1312 Ok 1313 (Data.pipeline "t" 0 1314 |> Data.withName "p" 1315 |> Data.withPaused True 1316 ) 1317 ) 1318 |> Tuple.first 1319 1320 givenUserAuthorized = 1321 Application.handleCallback 1322 (Callback.UserFetched <| 1323 Ok 1324 { id = "test" 1325 , userName = "test" 1326 , name = "test" 1327 , email = "test" 1328 , isAdmin = False 1329 , teams = 1330 Dict.fromList 1331 [ ( "t", [ "member" ] ) ] 1332 } 1333 ) 1334 >> Tuple.first 1335 1336 givenUserUnauthorized = 1337 Application.handleCallback 1338 (Callback.UserFetched <| 1339 Ok 1340 { id = "test" 1341 , userName = "test" 1342 , name = "test" 1343 , email = "test" 1344 , isAdmin = False 1345 , teams = 1346 Dict.fromList 1347 [ ( "s", [ "member" ] ) ] 1348 } 1349 ) 1350 >> Tuple.first 1351 1352 pipelineIdentifier = 1353 Data.shortPipelineId 1354 1355 toggleMsg = 1356 ApplicationMsgs.Update <| 1357 Msgs.Click <| 1358 Msgs.TopBarPauseToggle 1359 pipelineIdentifier 1360 in 1361 [ defineHoverBehaviour 1362 { name = "play pipeline icon when authorized" 1363 , setup = givenPipelinePaused |> givenUserAuthorized 1364 , query = 1365 queryView 1366 >> Query.find [ id "top-bar-pause-toggle" ] 1367 >> Query.children [] 1368 >> Query.first 1369 , unhoveredSelector = 1370 { description = "faded play button with light border" 1371 , selector = 1372 [ style "opacity" "0.5" 1373 , style "margin" "17px" 1374 , style "cursor" "pointer" 1375 ] 1376 ++ iconSelector 1377 { size = "20px" 1378 , image = Assets.PlayIcon 1379 } 1380 } 1381 , hoveredSelector = 1382 { description = "white play button with light border" 1383 , selector = 1384 [ style "opacity" "1" 1385 , style "margin" "17px" 1386 , style "cursor" "pointer" 1387 ] 1388 ++ iconSelector 1389 { size = "20px" 1390 , image = Assets.PlayIcon 1391 } 1392 } 1393 , hoverable = 1394 Msgs.TopBarPauseToggle pipelineIdentifier 1395 } 1396 , defineHoverBehaviour 1397 { name = "play pipeline icon when unauthenticated" 1398 , setup = givenPipelinePaused 1399 , query = 1400 queryView 1401 >> Query.find [ id "top-bar-pause-toggle" ] 1402 >> Query.children [] 1403 >> Query.first 1404 , unhoveredSelector = 1405 { description = "faded play button with light border" 1406 , selector = 1407 [ style "opacity" "0.5" 1408 , style "margin" "17px" 1409 , style "cursor" "pointer" 1410 ] 1411 ++ iconSelector 1412 { size = "20px" 1413 , image = Assets.PlayIcon 1414 } 1415 } 1416 , hoveredSelector = 1417 { description = "white play button with light border" 1418 , selector = 1419 [ style "opacity" "1" 1420 , style "margin" "17px" 1421 , style "cursor" "pointer" 1422 ] 1423 ++ iconSelector 1424 { size = "20px" 1425 , image = Assets.PlayIcon 1426 } 1427 } 1428 , hoverable = 1429 Msgs.TopBarPauseToggle pipelineIdentifier 1430 } 1431 , defineHoverBehaviour 1432 { name = "play pipeline icon when unauthorized" 1433 , setup = givenPipelinePaused |> givenUserUnauthorized 1434 , query = 1435 queryView 1436 >> Query.find [ id "top-bar-pause-toggle" ] 1437 >> Query.children [] 1438 >> Query.first 1439 , unhoveredSelector = 1440 { description = "faded play button with light border" 1441 , selector = 1442 [ style "opacity" "0.2" 1443 , style "margin" "17px" 1444 , style "cursor" "default" 1445 ] 1446 ++ iconSelector 1447 { size = "20px" 1448 , image = Assets.PlayIcon 1449 } 1450 } 1451 , hoveredSelector = 1452 { description = "faded play button with tooltip below" 1453 , selector = 1454 [ containing 1455 ([ style "cursor" "default" 1456 , style "opacity" "0.2" 1457 ] 1458 ++ iconSelector 1459 { size = "20px" 1460 , image = Assets.PlayIcon 1461 } 1462 ) 1463 , containing 1464 [ style "position" "absolute" 1465 , style "top" "100%" 1466 ] 1467 , style "position" "relative" 1468 , style "margin" "17px" 1469 ] 1470 } 1471 , hoverable = 1472 Msgs.TopBarPauseToggle pipelineIdentifier 1473 } 1474 , test "clicking play button sends TogglePipelinePaused msg" <| 1475 \_ -> 1476 givenPipelinePaused 1477 |> queryView 1478 |> Query.find [ id "top-bar-pause-toggle" ] 1479 |> Query.children [] 1480 |> Query.first 1481 |> Event.simulate Event.click 1482 |> Event.expect toggleMsg 1483 , test "play button unclickable for non-members" <| 1484 \_ -> 1485 givenPipelinePaused 1486 |> givenUserUnauthorized 1487 |> queryView 1488 |> Query.find [ id "top-bar-pause-toggle" ] 1489 |> Query.children [] 1490 |> Query.first 1491 |> Event.simulate Event.click 1492 |> Event.toResult 1493 |> Expect.err 1494 , test "play button click msg sends api call" <| 1495 \_ -> 1496 givenPipelinePaused 1497 |> Application.update toggleMsg 1498 |> Tuple.second 1499 |> Expect.equal 1500 [ Effects.SendTogglePipelineRequest 1501 pipelineIdentifier 1502 True 1503 ] 1504 , test "play button click msg turns icon into spinner" <| 1505 \_ -> 1506 givenPipelinePaused 1507 |> Application.update toggleMsg 1508 |> Tuple.first 1509 |> queryView 1510 |> Query.find [ id "top-bar-pause-toggle" ] 1511 |> Query.children [] 1512 |> Query.first 1513 |> Query.has 1514 [ style "animation" 1515 "container-rotate 1568ms linear infinite" 1516 , style "height" "20px" 1517 , style "width" "20px" 1518 ] 1519 , test "successful PipelineToggled callback turns topbar dark" <| 1520 \_ -> 1521 givenPipelinePaused 1522 |> Application.update toggleMsg 1523 |> Tuple.first 1524 |> Application.handleCallback 1525 (Callback.PipelineToggled pipelineIdentifier <| Ok ()) 1526 |> Tuple.first 1527 |> queryView 1528 |> Query.find [ id "top-bar-app" ] 1529 |> Query.has 1530 [ style "background-color" ColorValues.grey100 ] 1531 , test "successful callback turns spinner into pause button" <| 1532 \_ -> 1533 givenPipelinePaused 1534 |> Application.update toggleMsg 1535 |> Tuple.first 1536 |> Application.handleCallback 1537 (Callback.PipelineToggled pipelineIdentifier <| Ok ()) 1538 |> Tuple.first 1539 |> queryView 1540 |> Query.find [ id "top-bar-pause-toggle" ] 1541 |> Query.children [] 1542 |> Query.first 1543 |> Query.has 1544 (iconSelector 1545 { size = "20px" 1546 , image = Assets.PauseIcon 1547 } 1548 ) 1549 , test "Unauthorized PipelineToggled callback redirects to login" <| 1550 \_ -> 1551 givenPipelinePaused 1552 |> Application.handleCallback 1553 (Callback.PipelineToggled pipelineIdentifier <| 1554 Data.httpUnauthorized 1555 ) 1556 |> Tuple.second 1557 |> Expect.equal 1558 [ Effects.RedirectToLogin ] 1559 , test "erroring PipelineToggled callback leaves topbar blue" <| 1560 \_ -> 1561 givenPipelinePaused 1562 |> Application.handleCallback 1563 (Callback.PipelineToggled pipelineIdentifier <| 1564 Data.httpInternalServerError 1565 ) 1566 |> Tuple.first 1567 |> queryView 1568 |> Query.find [ id "top-bar-app" ] 1569 |> Query.has 1570 [ style "background-color" pausedBlue ] 1571 ] 1572 ] 1573 1574 1575 eachHasStyle : String -> String -> Query.Multiple msg -> Expectation 1576 eachHasStyle property value = 1577 Query.each <| Query.has [ style property value ] 1578 1579 1580 sampleUser : Concourse.User 1581 sampleUser = 1582 { id = "1", userName = "test", name = "Bob", isAdmin = False, email = "bob@bob.com", teams = Dict.empty } 1583 1584 1585 pipelineBreadcrumbSelector : List Selector.Selector 1586 pipelineBreadcrumbSelector = 1587 [ style "background-image" <| 1588 Assets.backgroundImage <| 1589 Just (Assets.BreadcrumbIcon Assets.PipelineComponent) 1590 , style "background-repeat" "no-repeat" 1591 ] 1592 1593 1594 jobBreadcrumbSelector : List Selector.Selector 1595 jobBreadcrumbSelector = 1596 [ style "background-image" <| 1597 Assets.backgroundImage <| 1598 Just (Assets.BreadcrumbIcon Assets.JobComponent) 1599 , style "background-repeat" "no-repeat" 1600 ] 1601 1602 1603 resourceBreadcrumbSelector : List Selector.Selector 1604 resourceBreadcrumbSelector = 1605 [ style "background-image" <| 1606 Assets.backgroundImage <| 1607 Just (Assets.BreadcrumbIcon Assets.ResourceComponent) 1608 , style "background-repeat" "no-repeat" 1609 ] 1610 1611 1612 viewNormally : 1613 ( Application.Model, List Effects.Effect ) 1614 -> Query.Single ApplicationMsgs.TopLevelMessage 1615 viewNormally = 1616 Tuple.first >> queryView 1617 1618 1619 testDropdown : 1620 List Int 1621 -> List Int 1622 -> ( Application.Model, List Effects.Effect ) 1623 -> Test 1624 testDropdown selecteds notSelecteds = 1625 context "ui" 1626 viewNormally 1627 [ it "has a dropdown when search bar is focused" <| 1628 Query.find [ id "search-container" ] 1629 >> Query.has [ id "search-dropdown" ] 1630 , it "should trigger a FilterMsg when typing in the search bar" <| 1631 Query.find [ id SearchBar.searchInputId ] 1632 >> Event.simulate (Event.input "test") 1633 >> Event.expect 1634 (ApplicationMsgs.Update <| Msgs.FilterMsg "test") 1635 , context "dropdown elements" 1636 (Query.findAll [ tag "li" ]) 1637 [ it "have the same width and padding as search bar" <| 1638 eachHasStyle "padding" searchBarPadding 1639 , it "have the same height as the search bar" <| 1640 eachHasStyle "line-height" searchBarHeight 1641 , it "have no bullet points" <| 1642 eachHasStyle "list-style-type" "none" 1643 , it "have the same border style as the search bar" <| 1644 eachHasStyle "border" <| 1645 searchBarBorder ColorValues.grey60 1646 , it "are vertically aligned flush to each other" <| 1647 eachHasStyle "margin-top" "-1px" 1648 , it "have slightly larger font" <| 1649 eachHasStyle "font-size" "1.15em" 1650 , it "have a pointer cursor" <| 1651 eachHasStyle "cursor" "pointer" 1652 ] 1653 , it "the search dropdown is positioned below the search bar" <| 1654 Query.find [ id "search-dropdown" ] 1655 >> Query.has 1656 [ style "position" "absolute" 1657 , style "top" "100%" 1658 , style "margin" "0" 1659 ] 1660 , it "the search dropdown is the same width as search bar" <| 1661 Query.find [ id "search-dropdown" ] 1662 >> Query.has [ style "width" "100%" ] 1663 , it "the search dropdown has 2 elements" <| 1664 Query.find [ id "search-dropdown" ] 1665 >> Expect.all 1666 [ Query.findAll [ tag "li" ] >> Query.count (Expect.equal 2) 1667 , Query.has [ text "status: " ] 1668 , Query.has [ text "team: " ] 1669 ] 1670 , it "when team is clicked, it should trigger a FilterMsg for team" <| 1671 Query.find [ id "search-dropdown" ] 1672 >> Query.find [ tag "li", containing [ text "team: " ] ] 1673 >> Event.simulate Event.mouseDown 1674 >> Event.expect 1675 (ApplicationMsgs.Update <| Msgs.FilterMsg "team: ") 1676 , it "when status is clicked, it should trigger a FilterMsg for status" <| 1677 Query.find [ id "search-dropdown" ] 1678 >> Query.find [ tag "li", containing [ text "status: " ] ] 1679 >> Event.simulate Event.mouseDown 1680 >> Event.expect 1681 (ApplicationMsgs.Update <| Msgs.FilterMsg "status: ") 1682 , it "sends BlurMsg when blurring the search bar" <| 1683 Query.find [ id SearchBar.searchInputId ] 1684 >> Event.simulate Event.blur 1685 >> Event.expect 1686 (ApplicationMsgs.Update Msgs.BlurMsg) 1687 , context "selected highlighting" 1688 (Query.findAll [ tag "li" ]) 1689 (List.concat 1690 (List.map 1691 (\idx -> 1692 [ it ("has the first element highlighted " ++ String.fromInt idx) <| 1693 Query.index idx 1694 >> Query.has [ style "background-color" ColorValues.grey90 ] 1695 , it ("has white text " ++ String.fromInt idx) <| 1696 Query.index idx 1697 >> Query.has [ style "color" ColorValues.grey30 ] 1698 ] 1699 ) 1700 selecteds 1701 ) 1702 ++ [ it "always has at least one test" <| \_ -> Expect.equal 0 0 ] 1703 ) 1704 , context "other highlighting" 1705 (Query.findAll [ tag "li" ]) 1706 (List.concat 1707 (List.map 1708 (\idx -> 1709 [ it ("has the other elements not highlighted " ++ String.fromInt idx) <| 1710 Query.index idx 1711 >> Query.has [ style "background-color" ColorValues.grey80 ] 1712 , it ("have light grey text " ++ String.fromInt idx) <| 1713 Query.index idx 1714 >> Query.has [ style "color" ColorValues.grey40 ] 1715 ] 1716 ) 1717 notSelecteds 1718 ) 1719 ) 1720 ]