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