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          ]