github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/tests/JobTests.elm (about)

     1  module JobTests exposing (all)
     2  
     3  import Application.Application as Application
     4  import Assets
     5  import Common exposing (defineHoverBehaviour, queryView)
     6  import Concourse exposing (Build)
     7  import Concourse.BuildStatus exposing (BuildStatus(..))
     8  import Concourse.Pagination exposing (Direction(..), Paginated)
     9  import DashboardTests exposing (darkGrey, iconSelector, middleGrey)
    10  import Data
    11  import Dict
    12  import Expect exposing (..)
    13  import Html.Attributes as Attr
    14  import Http
    15  import Job.Job as Job exposing (update)
    16  import Message.Callback as Callback exposing (Callback(..))
    17  import Message.Effects as Effects
    18  import Message.Message exposing (DomID(..), Message(..))
    19  import Message.Subscription as Subscription
    20      exposing
    21          ( Delivery(..)
    22          , Interval(..)
    23          )
    24  import Message.TopLevelMessage as Msgs
    25  import RemoteData
    26  import Test exposing (..)
    27  import Test.Html.Query as Query
    28  import Test.Html.Selector as Selector
    29      exposing
    30          ( attribute
    31          , class
    32          , containing
    33          , id
    34          , style
    35          , text
    36          )
    37  import Time
    38  import Url
    39  import Views.Styles
    40  
    41  
    42  all : Test
    43  all =
    44      describe "Job"
    45          [ describe "update" <|
    46              [ describe "while page is loading"
    47                  [ test "title includes job name" <|
    48                      \_ ->
    49                          Common.init "/teams/team/pipelines/pipeline/jobs/job"
    50                              |> Application.view
    51                              |> .title
    52                              |> Expect.equal "job - Concourse"
    53                  , test "gets current timezone" <|
    54                      \_ ->
    55                          Application.init
    56                              { turbulenceImgSrc = ""
    57                              , notFoundImgSrc = "notfound.svg"
    58                              , csrfToken = "csrf_token"
    59                              , authToken = ""
    60                              , pipelineRunningKeyframes = "pipeline-running"
    61                              }
    62                              { protocol = Url.Http
    63                              , host = ""
    64                              , port_ = Nothing
    65                              , path = "/teams/team/pipelines/pipeline/jobs/job"
    66                              , query = Nothing
    67                              , fragment = Nothing
    68                              }
    69                              |> Tuple.second
    70                              |> Common.contains Effects.GetCurrentTimeZone
    71                  , test "fetches pipelines" <|
    72                      \_ ->
    73                          Application.init
    74                              { turbulenceImgSrc = ""
    75                              , notFoundImgSrc = ""
    76                              , csrfToken = ""
    77                              , authToken = ""
    78                              , pipelineRunningKeyframes = ""
    79                              }
    80                              { protocol = Url.Http
    81                              , host = ""
    82                              , port_ = Nothing
    83                              , path = "/teams/team/pipelines/pipeline/jobs/job"
    84                              , query = Nothing
    85                              , fragment = Nothing
    86                              }
    87                              |> Tuple.second
    88                              |> Common.contains Effects.FetchAllPipelines
    89                  , test "shows two spinners before anything has loaded" <|
    90                      \_ ->
    91                          Common.init "/teams/team/pipelines/pipeline/jobs/job"
    92                              |> queryView
    93                              |> Query.findAll loadingIndicatorSelector
    94                              |> Query.count (Expect.equal 2)
    95                  , test "loading build has spinners for inputs and outputs" <|
    96                      init { disabled = False, paused = False }
    97                          >> Application.handleCallback
    98                              (JobBuildsFetched <|
    99                                  Ok ( Job.startingPage, buildsWithEmptyPagination )
   100                              )
   101                          >> Tuple.first
   102                          >> queryView
   103                          >> Expect.all
   104                              [ Query.find [ class "inputs" ]
   105                                  >> Query.has loadingIndicatorSelector
   106                              , Query.find [ class "outputs" ]
   107                                  >> Query.has loadingIndicatorSelector
   108                              ]
   109                  ]
   110              , test "build header lays out contents horizontally" <|
   111                  init { disabled = False, paused = False }
   112                      >> queryView
   113                      >> Query.find [ class "build-header" ]
   114                      >> Query.has
   115                          [ style "display" "flex"
   116                          , style "justify-content" "space-between"
   117                          ]
   118              , test "header has play/pause button at the left" <|
   119                  init { disabled = False, paused = False }
   120                      >> queryView
   121                      >> Query.find [ class "build-header" ]
   122                      >> Query.has [ id "pause-toggle" ]
   123              , test "play/pause has background of the header color, faded" <|
   124                  init { disabled = False, paused = False }
   125                      >> queryView
   126                      >> Query.find [ id "pause-toggle" ]
   127                      >> Query.has
   128                          [ style "padding" "10px"
   129                          , style "border" "none"
   130                          , style "background-color" darkGreen
   131                          , style "outline" "none"
   132                          ]
   133              , test "hover play/pause has background of the header color" <|
   134                  init { disabled = False, paused = False }
   135                      >> Application.update
   136                          (Msgs.Update <|
   137                              Message.Message.Hover <|
   138                                  Just Message.Message.ToggleJobButton
   139                          )
   140                      >> Tuple.first
   141                      >> queryView
   142                      >> Query.find [ id "pause-toggle" ]
   143                      >> Query.has
   144                          [ style "padding" "10px"
   145                          , style "border" "none"
   146                          , style "background-color" brightGreen
   147                          , style "outline" "none"
   148                          ]
   149              , defineHoverBehaviour
   150                  { name = "play/pause button when job is unpaused"
   151                  , setup =
   152                      init { disabled = False, paused = False } ()
   153                  , query =
   154                      queryView >> Query.find [ id "pause-toggle" ]
   155                  , unhoveredSelector =
   156                      { description = "grey pause icon"
   157                      , selector =
   158                          [ style "opacity" "0.5" ]
   159                              ++ iconSelector
   160                                  { size = "40px"
   161                                  , image = Assets.PauseCircleIcon |> Assets.CircleOutlineIcon
   162                                  }
   163                      }
   164                  , hoveredSelector =
   165                      { description = "white pause icon"
   166                      , selector =
   167                          [ style "opacity" "1" ]
   168                              ++ iconSelector
   169                                  { size = "40px"
   170                                  , image = Assets.PauseCircleIcon |> Assets.CircleOutlineIcon
   171                                  }
   172                      }
   173                  , hoverable = Message.Message.ToggleJobButton
   174                  }
   175              , defineHoverBehaviour
   176                  { name = "play/pause button when job is paused"
   177                  , setup =
   178                      init { disabled = False, paused = True } ()
   179                  , query =
   180                      queryView >> Query.find [ id "pause-toggle" ]
   181                  , unhoveredSelector =
   182                      { description = "grey play icon"
   183                      , selector =
   184                          [ style "opacity" "0.5" ]
   185                              ++ iconSelector
   186                                  { size = "40px"
   187                                  , image = Assets.PlayCircleIcon |> Assets.CircleOutlineIcon
   188                                  }
   189                      }
   190                  , hoveredSelector =
   191                      { description = "white play icon"
   192                      , selector =
   193                          [ style "opacity" "1" ]
   194                              ++ iconSelector
   195                                  { size = "40px"
   196                                  , image = Assets.PlayCircleIcon |> Assets.CircleOutlineIcon
   197                                  }
   198                      }
   199                  , hoverable = Message.Message.ToggleJobButton
   200                  }
   201              , test "trigger build button has background of the header color, faded" <|
   202                  init { disabled = False, paused = False }
   203                      >> queryView
   204                      >> Query.find
   205                          [ attribute <|
   206                              Attr.attribute "aria-label" "Trigger Build"
   207                          ]
   208                      >> Query.has
   209                          [ style "padding" "10px"
   210                          , style "border" "none"
   211                          , style "background-color" darkGreen
   212                          , style "outline" "none"
   213                          ]
   214              , test "hovered trigger build button has background of the header color" <|
   215                  init { disabled = False, paused = False }
   216                      >> Application.update
   217                          (Msgs.Update <|
   218                              Message.Message.Hover <|
   219                                  Just Message.Message.TriggerBuildButton
   220                          )
   221                      >> Tuple.first
   222                      >> queryView
   223                      >> Query.find
   224                          [ attribute <|
   225                              Attr.attribute "aria-label" "Trigger Build"
   226                          ]
   227                      >> Query.has
   228                          [ style "padding" "10px"
   229                          , style "border" "none"
   230                          , style "background-color" brightGreen
   231                          , style "outline" "none"
   232                          ]
   233              , test "trigger build button has 'plus' icon" <|
   234                  init { disabled = False, paused = False }
   235                      >> queryView
   236                      >> Query.find
   237                          [ attribute <|
   238                              Attr.attribute "aria-label" "Trigger Build"
   239                          ]
   240                      >> Query.children []
   241                      >> Query.first
   242                      >> Query.has
   243                          (iconSelector
   244                              { size = "40px"
   245                              , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon
   246                              }
   247                          )
   248              , defineHoverBehaviour
   249                  { name = "trigger build button"
   250                  , setup =
   251                      init { disabled = False, paused = False } ()
   252                  , query =
   253                      queryView
   254                          >> Query.find
   255                              [ attribute <|
   256                                  Attr.attribute "aria-label" "Trigger Build"
   257                              ]
   258                  , unhoveredSelector =
   259                      { description = "grey plus icon"
   260                      , selector =
   261                          [ style "opacity" "0.5" ]
   262                              ++ iconSelector
   263                                  { size = "40px"
   264                                  , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon
   265                                  }
   266                      }
   267                  , hoveredSelector =
   268                      { description = "white plus icon"
   269                      , selector =
   270                          [ style "opacity" "1" ]
   271                              ++ iconSelector
   272                                  { size = "40px"
   273                                  , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon
   274                                  }
   275                      }
   276                  , hoverable = Message.Message.TriggerBuildButton
   277                  }
   278              , defineHoverBehaviour
   279                  { name = "disabled trigger build button"
   280                  , setup =
   281                      init { disabled = True, paused = False } ()
   282                  , query =
   283                      queryView
   284                          >> Query.find
   285                              [ attribute <|
   286                                  Attr.attribute "aria-label" "Trigger Build"
   287                              ]
   288                  , unhoveredSelector =
   289                      { description = "grey plus icon"
   290                      , selector =
   291                          [ style "opacity" "0.5" ]
   292                              ++ iconSelector
   293                                  { size = "40px"
   294                                  , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon
   295                                  }
   296                      }
   297                  , hoveredSelector =
   298                      { description = "grey plus icon with tooltip"
   299                      , selector =
   300                          [ style "position" "relative"
   301                          , containing
   302                              [ containing
   303                                  [ text "manual triggering disabled in job config" ]
   304                              , style "position" "absolute"
   305                              , style "right" "100%"
   306                              , style "top" "15px"
   307                              , style "width" "300px"
   308                              , style "color" "#ecf0f1"
   309                              , style "font-size" "12px"
   310                              , style "font-family" Views.Styles.fontFamilyDefault
   311                              , style "padding" "10px"
   312                              , style "text-align" "right"
   313                              ]
   314                          , containing <|
   315                              [ style "opacity" "0.5" ]
   316                                  ++ iconSelector
   317                                      { size = "40px"
   318                                      , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon
   319                                      }
   320                          ]
   321                      }
   322                  , hoverable = Message.Message.TriggerBuildButton
   323                  }
   324              , describe "archived pipelines" <|
   325                  let
   326                      initWithArchivedPipeline =
   327                          init { paused = False, disabled = False }
   328                              >> Application.handleCallback
   329                                  (Callback.AllPipelinesFetched <|
   330                                      Ok
   331                                          [ Data.pipeline "team" 0
   332                                              |> Data.withName "pipeline"
   333                                              |> Data.withArchived True
   334                                          ]
   335                                  )
   336                              >> Tuple.first
   337                  in
   338                  [ test "play/pause button not displayed" <|
   339                      initWithArchivedPipeline
   340                          >> queryView
   341                          >> Query.find [ class "build-header" ]
   342                          >> Query.hasNot [ id "pause-toggle" ]
   343                  , test "header still includes job name" <|
   344                      initWithArchivedPipeline
   345                          >> queryView
   346                          >> Query.find [ class "build-header" ]
   347                          >> Query.has [ text "job" ]
   348                  , test "trigger build button not displayed" <|
   349                      initWithArchivedPipeline
   350                          >> queryView
   351                          >> Query.find [ class "build-header" ]
   352                          >> Query.hasNot [ class "trigger-build" ]
   353                  ]
   354              , test "page below top bar fills height without scrolling" <|
   355                  init { disabled = False, paused = False }
   356                      >> queryView
   357                      >> Query.find [ id "page-below-top-bar" ]
   358                      >> Query.has
   359                          [ style "box-sizing" "border-box"
   360                          , style "height" "100%"
   361                          , style "display" "flex"
   362                          ]
   363              , test "page contents fill available space and align vertically" <|
   364                  init { disabled = False, paused = False }
   365                      >> queryView
   366                      >> Query.find [ id "page-below-top-bar" ]
   367                      >> Query.has
   368                          [ style "flex-grow" "1"
   369                          , style "display" "flex"
   370                          , style "flex-direction" "column"
   371                          ]
   372              , test "body scrolls independently" <|
   373                  init { disabled = False, paused = False }
   374                      >> Application.handleCallback
   375                          (JobBuildsFetched <|
   376                              Ok ( Job.startingPage, buildsWithEmptyPagination )
   377                          )
   378                      >> Tuple.first
   379                      >> queryView
   380                      >> Query.find [ class "job-body" ]
   381                      >> Query.has [ style "overflow-y" "auto" ]
   382              , test "inputs icon on build" <|
   383                  init { disabled = False, paused = False }
   384                      >> Application.handleCallback
   385                          (JobBuildsFetched <|
   386                              Ok ( Job.startingPage, buildsWithEmptyPagination )
   387                          )
   388                      >> Tuple.first
   389                      >> queryView
   390                      >> Query.find [ class "inputs" ]
   391                      >> Query.children []
   392                      >> Query.first
   393                      >> Expect.all
   394                          [ Query.has
   395                              [ style "display" "flex"
   396                              , style "align-items" "center"
   397                              , style "padding-bottom" "5px"
   398                              ]
   399                          , Query.children []
   400                              >> Query.first
   401                              >> Query.has
   402                                  (iconSelector
   403                                      { size = "12px"
   404                                      , image = Assets.DownArrow
   405                                      }
   406                                      ++ [ style "background-size" "contain"
   407                                         , style "margin-right" "5px"
   408                                         ]
   409                                  )
   410                          ]
   411              , test "outputs icon on build" <|
   412                  init { disabled = False, paused = False }
   413                      >> Application.handleCallback
   414                          (JobBuildsFetched <|
   415                              Ok ( Job.startingPage, buildsWithEmptyPagination )
   416                          )
   417                      >> Tuple.first
   418                      >> queryView
   419                      >> Query.find [ class "outputs" ]
   420                      >> Query.children []
   421                      >> Query.first
   422                      >> Expect.all
   423                          [ Query.has
   424                              [ style "display" "flex"
   425                              , style "align-items" "center"
   426                              , style "padding-bottom" "5px"
   427                              ]
   428                          , Query.children []
   429                              >> Query.first
   430                              >> Query.has
   431                                  (iconSelector
   432                                      { size = "12px"
   433                                      , image = Assets.UpArrow
   434                                      }
   435                                      ++ [ style "background-size" "contain"
   436                                         , style "margin-right" "5px"
   437                                         ]
   438                                  )
   439                          ]
   440              , test "pagination header lays out horizontally" <|
   441                  init { disabled = False, paused = False }
   442                      >> queryView
   443                      >> Query.find [ id "pagination-header" ]
   444                      >> Query.has
   445                          [ style "display" "flex"
   446                          , style "justify-content" "space-between"
   447                          , style "align-items" "stretch"
   448                          , style "background-color" darkGrey
   449                          , style "height" "60px"
   450                          ]
   451              , test "the word 'builds' is indented" <|
   452                  init { disabled = False, paused = False }
   453                      >> queryView
   454                      >> Query.find [ id "pagination-header" ]
   455                      >> Query.children []
   456                      >> Query.first
   457                      >> Query.has
   458                          [ containing [ text "builds" ]
   459                          , style "margin" "0 18px"
   460                          ]
   461              , test "pagination lays out horizontally" <|
   462                  init { disabled = False, paused = False }
   463                      >> queryView
   464                      >> Query.find [ id "pagination" ]
   465                      >> Query.has
   466                          [ style "display" "flex"
   467                          , style "align-items" "stretch"
   468                          ]
   469              , test "pagination chevrons with no pages" <|
   470                  init { disabled = False, paused = False }
   471                      >> Application.handleCallback
   472                          (JobBuildsFetched <|
   473                              Ok ( Job.startingPage, buildsWithEmptyPagination )
   474                          )
   475                      >> Tuple.first
   476                      >> queryView
   477                      >> Query.find [ id "pagination" ]
   478                      >> Query.children []
   479                      >> Expect.all
   480                          [ Query.index 0
   481                              >> Query.has
   482                                  [ style "padding" "5px"
   483                                  , style "display" "flex"
   484                                  , style "align-items" "center"
   485                                  , style "border-left" <|
   486                                      "1px solid "
   487                                          ++ middleGrey
   488                                  , containing
   489                                      (iconSelector
   490                                          { image = Assets.ChevronLeft
   491                                          , size = "24px"
   492                                          }
   493                                          ++ [ style "padding" "5px"
   494                                             , style "opacity" "0.5"
   495                                             ]
   496                                      )
   497                                  ]
   498                          , Query.index 1
   499                              >> Query.has
   500                                  [ style "padding" "5px"
   501                                  , style "display" "flex"
   502                                  , style "align-items" "center"
   503                                  , style "border-left" <|
   504                                      "1px solid "
   505                                          ++ middleGrey
   506                                  , containing
   507                                      (iconSelector
   508                                          { image = Assets.ChevronRight
   509                                          , size = "24px"
   510                                          }
   511                                          ++ [ style "padding" "5px"
   512                                             , style "opacity" "0.5"
   513                                             ]
   514                                      )
   515                                  ]
   516                          ]
   517              , defineHoverBehaviour <|
   518                  let
   519                      urlPath =
   520                          "/teams/team/pipelines/pipeline/jobs/job?from=1&limit=1"
   521                  in
   522                  { name = "left pagination chevron with previous page"
   523                  , setup =
   524                      let
   525                          prevPage =
   526                              { direction = From 1
   527                              , limit = 1
   528                              }
   529                      in
   530                      init { disabled = False, paused = False } ()
   531                          |> Application.handleCallback
   532                              (JobBuildsFetched <|
   533                                  Ok
   534                                      ( Job.startingPage
   535                                      , { pagination =
   536                                              { previousPage = Just prevPage
   537                                              , nextPage = Nothing
   538                                              }
   539                                        , content = builds
   540                                        }
   541                                      )
   542                              )
   543                          |> Tuple.first
   544                  , query =
   545                      queryView
   546                          >> Query.find [ id "pagination" ]
   547                          >> Query.children []
   548                          >> Query.index 0
   549                  , unhoveredSelector =
   550                      { description = "white left chevron"
   551                      , selector =
   552                          [ style "padding" "5px"
   553                          , style "display" "flex"
   554                          , style "align-items" "center"
   555                          , style "border-left" <|
   556                              "1px solid "
   557                                  ++ middleGrey
   558                          , containing
   559                              (iconSelector
   560                                  { image = Assets.ChevronLeft
   561                                  , size = "24px"
   562                                  }
   563                                  ++ [ style "padding" "5px"
   564                                     , style "opacity" "1"
   565                                     , attribute <| Attr.href urlPath
   566                                     ]
   567                              )
   568                          ]
   569                      }
   570                  , hoveredSelector =
   571                      { description =
   572                          "left chevron with light grey circular bg"
   573                      , selector =
   574                          [ style "padding" "5px"
   575                          , style "display" "flex"
   576                          , style "align-items" "center"
   577                          , style "border-left" <|
   578                              "1px solid "
   579                                  ++ middleGrey
   580                          , containing
   581                              (iconSelector
   582                                  { image = Assets.ChevronLeft
   583                                  , size = "24px"
   584                                  }
   585                                  ++ [ style "padding" "5px"
   586                                     , style "opacity" "1"
   587                                     , style "border-radius" "50%"
   588                                     , style "background-color" <|
   589                                          "#504b4b"
   590                                     , attribute <| Attr.href urlPath
   591                                     ]
   592                              )
   593                          ]
   594                      }
   595                  , hoverable = Message.Message.PreviousPageButton
   596                  }
   597              , test "pagination previous page loads most recent page if less than 100 entries" <|
   598                  \_ ->
   599                      let
   600                          previousPage =
   601                              { direction = From 1, limit = 100 }
   602                      in
   603                      init { disabled = False, paused = False } ()
   604                          |> Application.handleCallback
   605                              (JobBuildsFetched <|
   606                                  Ok
   607                                      ( previousPage
   608                                      , { pagination =
   609                                              { previousPage = Nothing
   610                                              , nextPage = Nothing
   611                                              }
   612                                        , content = []
   613                                        }
   614                                      )
   615                              )
   616                          |> Tuple.second
   617                          |> Common.contains
   618                              (Effects.FetchJobBuilds
   619                                  { teamName = "team"
   620                                  , pipelineName = "pipeline"
   621                                  , jobName = "job"
   622                                  }
   623                                  Job.startingPage
   624                              )
   625              , describe "When fetching builds"
   626                  [ test "says no builds" <|
   627                      \_ ->
   628                          init { disabled = False, paused = False } ()
   629                              |> Application.handleCallback
   630                                  (JobBuildsFetched <|
   631                                      Ok
   632                                          ( Job.startingPage
   633                                          , { pagination =
   634                                                  { previousPage = Nothing
   635                                                  , nextPage = Nothing
   636                                                  }
   637                                            , content = []
   638                                            }
   639                                          )
   640                                  )
   641                              |> Tuple.first
   642                              |> queryView
   643                              |> Query.has [ text "no builds for job “job”" ]
   644                  ]
   645              , test "JobBuildsFetched" <|
   646                  \_ ->
   647                      Expect.equal
   648                          { defaultModel
   649                              | currentPage =
   650                                  { direction = Concourse.Pagination.To 123
   651                                  , limit = 1
   652                                  }
   653                              , buildsWithResources =
   654                                  RemoteData.Success
   655                                      { content =
   656                                          [ { build = someBuild
   657                                            , resources = Nothing
   658                                            }
   659                                          ]
   660                                      , pagination =
   661                                          { previousPage = Nothing
   662                                          , nextPage = Nothing
   663                                          }
   664                                      }
   665                          }
   666                      <|
   667                          Tuple.first <|
   668                              Job.handleCallback
   669                                  (JobBuildsFetched <|
   670                                      Ok
   671                                          ( Job.startingPage
   672                                          , { content = [ someBuild ]
   673                                            , pagination =
   674                                                  { previousPage = Nothing
   675                                                  , nextPage = Nothing
   676                                                  }
   677                                            }
   678                                          )
   679                                  )
   680                                  ( defaultModel, [] )
   681              , test "JobBuildsFetched error" <|
   682                  \_ ->
   683                      Expect.equal
   684                          defaultModel
   685                      <|
   686                          Tuple.first <|
   687                              Job.handleCallback
   688                                  (JobBuildsFetched <| Err Http.NetworkError)
   689                                  ( defaultModel, [] )
   690              , test "JobFetched" <|
   691                  \_ ->
   692                      Expect.equal
   693                          { defaultModel
   694                              | job = RemoteData.Success someJob
   695                          }
   696                      <|
   697                          Tuple.first <|
   698                              Job.handleCallback (JobFetched <| Ok someJob) ( defaultModel, [] )
   699              , test "JobFetched error" <|
   700                  \_ ->
   701                      Expect.equal
   702                          defaultModel
   703                      <|
   704                          Tuple.first <|
   705                              Job.handleCallback
   706                                  (JobFetched <| Err Http.NetworkError)
   707                                  ( defaultModel, [] )
   708              , test "BuildResourcesFetched" <|
   709                  \_ ->
   710                      let
   711                          buildInput =
   712                              { name = "some-input"
   713                              , version = Dict.fromList [ ( "version", "v1" ) ]
   714                              , firstOccurrence = True
   715                              }
   716  
   717                          buildOutput =
   718                              { name = "some-resource"
   719                              , version = Dict.fromList [ ( "version", "v2" ) ]
   720                              }
   721                      in
   722                      let
   723                          buildResources =
   724                              { inputs = [ buildInput ]
   725                              , outputs = [ buildOutput ]
   726                              }
   727                      in
   728                      Expect.equal
   729                          defaultModel
   730                      <|
   731                          Tuple.first <|
   732                              Job.handleCallback (BuildResourcesFetched (Ok ( 1, buildResources )))
   733                                  ( defaultModel, [] )
   734              , test "BuildResourcesFetched error" <|
   735                  \_ ->
   736                      Expect.equal
   737                          defaultModel
   738                      <|
   739                          Tuple.first <|
   740                              Job.handleCallback
   741                                  (BuildResourcesFetched (Err Http.NetworkError))
   742                                  ( defaultModel, [] )
   743              , test "TogglePaused" <|
   744                  \_ ->
   745                      Expect.equal
   746                          { defaultModel
   747                              | job = RemoteData.Success { someJob | paused = True }
   748                              , pausedChanging = True
   749                          }
   750                      <|
   751                          Tuple.first <|
   752                              update
   753                                  (Click ToggleJobButton)
   754                                  ( { defaultModel | job = RemoteData.Success someJob }, [] )
   755              , test "PausedToggled" <|
   756                  \_ ->
   757                      Expect.equal
   758                          { defaultModel
   759                              | job = RemoteData.Success someJob
   760                              , pausedChanging = False
   761                          }
   762                      <|
   763                          Tuple.first <|
   764                              Job.handleCallback
   765                                  (PausedToggled <| Ok ())
   766                                  ( { defaultModel | job = RemoteData.Success someJob }, [] )
   767              , test "PausedToggled error" <|
   768                  \_ ->
   769                      Expect.equal
   770                          { defaultModel | job = RemoteData.Success someJob }
   771                      <|
   772                          Tuple.first <|
   773                              Job.handleCallback
   774                                  (PausedToggled <| Err Http.NetworkError)
   775                                  ( { defaultModel | job = RemoteData.Success someJob }, [] )
   776              , test "PausedToggled unauthorized" <|
   777                  \_ ->
   778                      Expect.equal
   779                          { defaultModel | job = RemoteData.Success someJob }
   780                      <|
   781                          Tuple.first <|
   782                              Job.handleCallback (PausedToggled <| Data.httpUnauthorized)
   783                                  ( { defaultModel | job = RemoteData.Success someJob }, [] )
   784              , test "page is subscribed to one and five second timers" <|
   785                  init { disabled = False, paused = False }
   786                      >> Application.subscriptions
   787                      >> Expect.all
   788                          [ Common.contains (Subscription.OnClockTick OneSecond)
   789                          , Common.contains (Subscription.OnClockTick FiveSeconds)
   790                          ]
   791              , test "on five-second timer, refreshes job and builds" <|
   792                  init { disabled = False, paused = False }
   793                      >> Application.update
   794                          (Msgs.DeliveryReceived <|
   795                              ClockTicked FiveSeconds <|
   796                                  Time.millisToPosix 0
   797                          )
   798                      >> Tuple.second
   799                      >> Expect.all
   800                          [ Common.contains (Effects.FetchJobBuilds jobInfo { direction = ToMostRecent, limit = 100 })
   801                          , Common.contains (Effects.FetchJob jobInfo)
   802                          ]
   803              , test "on one-second timer, updates build timestamps" <|
   804                  init { disabled = False, paused = False }
   805                      >> Application.handleCallback
   806                          (Callback.JobBuildsFetched <|
   807                              Ok
   808                                  ( Job.startingPage
   809                                  , { content = [ someBuild ]
   810                                    , pagination =
   811                                          { nextPage = Nothing
   812                                          , previousPage = Nothing
   813                                          }
   814                                    }
   815                                  )
   816                          )
   817                      >> Tuple.first
   818                      >> Application.update
   819                          (Msgs.DeliveryReceived <|
   820                              ClockTicked OneSecond <|
   821                                  Time.millisToPosix (2 * 1000)
   822                          )
   823                      >> Tuple.first
   824                      >> queryView
   825                      >> Query.find [ class "js-build" ]
   826                      >> Query.has [ text "2s ago" ]
   827              , test "shows build timestamps in current timezone" <|
   828                  init { disabled = False, paused = False }
   829                      >> Application.handleCallback
   830                          (Callback.GotCurrentTimeZone <|
   831                              Time.customZone (5 * 60) []
   832                          )
   833                      >> Tuple.first
   834                      >> Application.handleCallback
   835                          (Callback.JobBuildsFetched <|
   836                              Ok
   837                                  ( Job.startingPage
   838                                  , { content = [ someBuild ]
   839                                    , pagination =
   840                                          { nextPage = Nothing
   841                                          , previousPage = Nothing
   842                                          }
   843                                    }
   844                                  )
   845                          )
   846                      >> Tuple.first
   847                      >> Application.update
   848                          (Msgs.DeliveryReceived <|
   849                              ClockTicked OneSecond <|
   850                                  Time.millisToPosix (24 * 60 * 60 * 1000)
   851                          )
   852                      >> Tuple.first
   853                      >> queryView
   854                      >> Query.find [ class "js-build" ]
   855                      >> Query.has [ text "Jan 1 1970 05:00:00 AM" ]
   856              ]
   857          ]
   858  
   859  
   860  darkGreen : String
   861  darkGreen =
   862      "#419867"
   863  
   864  
   865  brightGreen : String
   866  brightGreen =
   867      "#11c560"
   868  
   869  
   870  someJobInfo : Concourse.JobIdentifier
   871  someJobInfo =
   872      Data.jobId
   873          |> Data.withJobName "some-job"
   874          |> Data.withPipelineName "some-pipeline"
   875          |> Data.withTeamName "some-team"
   876  
   877  
   878  someBuild : Build
   879  someBuild =
   880      { id = 123
   881      , name = "45"
   882      , job = Just someJobInfo
   883      , status = BuildStatusSucceeded
   884      , duration =
   885          { startedAt = Just <| Time.millisToPosix 0
   886          , finishedAt = Just <| Time.millisToPosix 0
   887          }
   888      , reapTime = Just <| Time.millisToPosix 0
   889      }
   890  
   891  
   892  jobInfo : Concourse.JobIdentifier
   893  jobInfo =
   894      Data.jobId
   895  
   896  
   897  builds : List Build
   898  builds =
   899      [ { id = 0
   900        , name = "0"
   901        , job = Just jobInfo
   902        , status = BuildStatusSucceeded
   903        , duration =
   904              { startedAt = Nothing
   905              , finishedAt = Nothing
   906              }
   907        , reapTime = Nothing
   908        }
   909      ]
   910  
   911  
   912  buildsWithEmptyPagination : Paginated Build
   913  buildsWithEmptyPagination =
   914      { content = builds
   915      , pagination =
   916          { previousPage = Nothing
   917          , nextPage = Nothing
   918          }
   919      }
   920  
   921  
   922  someJob : Concourse.Job
   923  someJob =
   924      { name = "some-job"
   925      , pipelineName = "some-pipeline"
   926      , teamName = "some-team"
   927      , nextBuild = Nothing
   928      , finishedBuild = Just someBuild
   929      , transitionBuild = Nothing
   930      , paused = False
   931      , disableManualTrigger = False
   932      , inputs = []
   933      , outputs = []
   934      , groups = []
   935      }
   936  
   937  
   938  defaultModel : Job.Model
   939  defaultModel =
   940      Job.init
   941          { jobId = someJobInfo
   942          , paging = Nothing
   943          }
   944          |> Tuple.first
   945  
   946  
   947  init : { disabled : Bool, paused : Bool } -> () -> Application.Model
   948  init { disabled, paused } _ =
   949      Common.init "/teams/team/pipelines/pipeline/jobs/job"
   950          |> Application.handleCallback
   951              (JobFetched <|
   952                  Ok
   953                      { name = "job"
   954                      , pipelineName = "pipeline"
   955                      , teamName = "team"
   956                      , nextBuild = Nothing
   957                      , finishedBuild = Just someBuild
   958                      , transitionBuild = Nothing
   959                      , paused = paused
   960                      , disableManualTrigger = disabled
   961                      , inputs = []
   962                      , outputs = []
   963                      , groups = []
   964                      }
   965              )
   966          |> Tuple.first
   967  
   968  
   969  loadingIndicatorSelector : List Selector.Selector
   970  loadingIndicatorSelector =
   971      [ style "animation"
   972          "container-rotate 1568ms linear infinite"
   973      , style "height" "14px"
   974      , style "width" "14px"
   975      , style "margin" "7px"
   976      ]