diff --git a/src/ProgramTest.elm b/src/ProgramTest.elm index bea2279..69ae2c8 100644 --- a/src/ProgramTest.elm +++ b/src/ProgramTest.elm @@ -210,6 +210,7 @@ import ProgramTest.ComplexQuery as ComplexQuery exposing (ComplexQuery) import ProgramTest.EffectSimulation as EffectSimulation exposing (EffectSimulation) import ProgramTest.Failure as Failure exposing (Failure(..)) import ProgramTest.Program as Program exposing (Program) +import Result.Extra import SimulatedEffect exposing (SimulatedEffect, SimulatedSub, SimulatedTask) import String.Extra import Test.Html.Event @@ -760,10 +761,18 @@ simulateLabeledInputHelper functionDescription fieldId label allowTextArea addit ) -{-| TODO: have other internal functions use this to have more consistent error message. --} -simulateComplexQuery : String -> (ComplexQuery (Query.Single msg) -> ComplexQuery msg) -> ProgramTest model msg effect -> ProgramTest model msg effect -simulateComplexQuery functionName complexQuery = +runComplexQuery : + String + -> (ComplexQuery (Query.Single msg) -> ComplexQuery a) + -> + (a + -> Program model msg effect (SimulatedSub msg) + -> TestState model msg effect + -> Result Failure (TestState model msg effect) + ) + -> ProgramTest model msg effect + -> ProgramTest model msg effect +runComplexQuery functionName complexQuery fn = andThen <| \program state -> let @@ -771,27 +780,23 @@ simulateComplexQuery functionName complexQuery = Program.renderView program state.currentModel in case ComplexQuery.run (complexQuery (ComplexQuery.succeed view)) of - ( _, Ok msg ) -> - TestState.update msg program state + ( _, Ok a ) -> + fn a program state ( highlight, Err queryFailure ) -> Err (ViewAssertionFailed ("ProgramTest." ++ functionName) (Html.map (\_ -> ()) (program.view state.currentModel)) highlight queryFailure) +{-| TODO: have other internal functions use this to have more consistent error message. +-} +simulateComplexQuery : String -> (ComplexQuery (Query.Single msg) -> ComplexQuery msg) -> ProgramTest model msg effect -> ProgramTest model msg effect +simulateComplexQuery functionName complexQuery = + runComplexQuery functionName complexQuery TestState.update + + assertComplexQuery : String -> (ComplexQuery (Query.Single msg) -> ComplexQuery ignored) -> ProgramTest model msg effect -> ProgramTest model msg effect assertComplexQuery functionName complexQuery = - andThen <| - \program state -> - let - view = - Program.renderView program state.currentModel - in - case ComplexQuery.run (complexQuery (ComplexQuery.succeed view)) of - ( _, Ok _ ) -> - Ok state - - ( highlight, Err queryFailure ) -> - Err (ViewAssertionFailed ("ProgramTest." ++ functionName) (Html.map (\_ -> ()) (program.view state.currentModel)) highlight queryFailure) + runComplexQuery functionName complexQuery (\_ _ state -> Ok state) {-| Simulates a custom DOM event. @@ -1004,16 +1009,41 @@ sets `preventDefault`, but this will be done in the future after -} clickLink : String -> String -> ProgramTest model msg effect -> ProgramTest model msg effect -clickLink linkText href programTest = +clickLink linkText href = let functionDescription = "clickLink " ++ String.Extra.escape linkText - findLinkTag = - [ Selector.tag "a" - , Selector.attribute (Html.Attributes.href href) - , Selector.containing [ Selector.text linkText ] - ] + findLink = + ComplexQuery.exactlyOneOf "Expected one of the following to exist" + [ ( " with text" + , ComplexQuery.find (Just "find link") + [ "a" ] + [ Selector.tag "a" + , Selector.attribute (Html.Attributes.href href) + , Selector.containing [ Selector.text linkText ] + ] + ) + , ( " with aria-label" + , ComplexQuery.find (Just "find link") + [ "a" ] + [ Selector.tag "a" + , Selector.attribute (Html.Attributes.href href) + , Selector.attribute (Html.Attributes.attribute "aria-label" linkText) + ] + ) + , ( " with with alt text" + , ComplexQuery.find (Just "find link") + [ "a" ] + [ Selector.tag "a" + , Selector.attribute (Html.Attributes.href href) + , Selector.containing + [ Selector.tag "img" + , Selector.attribute (Html.Attributes.alt linkText) + ] + ] + ) + ] normalClick = ( "click" @@ -1039,63 +1069,53 @@ clickLink linkText href programTest = ] ) + respondsTo event single = + single + |> Test.Html.Event.simulate event + |> Test.Html.Event.toResult + |> Result.Extra.isOk + tryClicking : { otherwise : Program model msg effect (SimulatedSub msg) -> TestState model msg effect -> Result Failure (TestState model msg effect) } - -> ProgramTest model msg effect - -> ProgramTest model msg effect - tryClicking { otherwise } = - andThen <| - \program state -> - let - link = - Program.renderView program state.currentModel - |> Query.find findLinkTag - in - if respondsTo normalClick link then - -- there is a click handler - -- first make sure the handler properly respects "Open in new tab", etc - if respondsTo ctrlClick link || respondsTo metaClick link then - Err - (CustomFailure functionDescription - (String.concat - [ "Found an `` tag has an onClick handler, " - , "but the handler is overriding ctrl-click and meta-click.\n\n" - , "A properly behaved single-page app should not override ctrl- and meta-clicks on `` tags " - , "because this prevents users from opening links in new tabs/windows.\n\n" - , "Use `onClickPreventDefaultForLinkWithHref` defined at instead of `onClick` to fix this problem.\n\n" - , "See discussion of this issue at ." - ] - ) - ) - - else - -- everything looks good, so simulate that event and ignore the `href` - simulateHelper functionDescription (Query.find findLinkTag) normalClick program state - - else - -- the link doesn't have a click handler - otherwise program state + -> Query.Single msg + -> Program model msg effect (SimulatedSub msg) + -> TestState model msg effect + -> Result Failure (TestState model msg effect) + tryClicking { otherwise } single program state = + if respondsTo normalClick single then + -- there is a click handler + -- first make sure the handler properly respects "Open in new tab", etc + if respondsTo ctrlClick single || respondsTo metaClick single then + Err + (CustomFailure functionDescription + (String.concat + [ "Found an `` tag has an onClick handler, " + , "but the handler is overriding ctrl-click and meta-click.\n\n" + , "A properly behaved single-page app should not override ctrl- and meta-clicks on `` tags " + , "because this prevents users from opening links in new tabs/windows.\n\n" + , "Use `onClickPreventDefaultForLinkWithHref` defined at instead of `onClick` to fix this problem.\n\n" + , "See discussion of this issue at ." + ] + ) + ) - respondsTo event single = - case - single - |> Test.Html.Event.simulate event - |> Test.Html.Event.toResult - of - Err _ -> - False + else + -- everything looks good, so simulate that event and ignore the `href` + single + |> Test.Html.Event.simulate normalClick + |> Test.Html.Event.toResult + |> Result.mapError (SimulateFailed functionDescription) + |> Result.andThen (\msg -> TestState.update msg program state) - Ok _ -> - True + else + -- the link doesn't have a click handler + otherwise program state in - programTest - |> assertComplexQuery functionDescription - (ComplexQuery.find Nothing [ "a" ] findLinkTag) - |> tryClicking { otherwise = TestState.urlRequestHelper functionDescription href } + runComplexQuery functionDescription findLink (tryClicking { otherwise = TestState.urlRequestHelper functionDescription href }) {-| Simulates replacing the text in an input field labeled with the given label. diff --git a/src/ProgramTest/ComplexQuery.elm b/src/ProgramTest/ComplexQuery.elm index fed8d67..8defc05 100644 --- a/src/ProgramTest/ComplexQuery.elm +++ b/src/ProgramTest/ComplexQuery.elm @@ -3,6 +3,7 @@ module ProgramTest.ComplexQuery exposing (ComplexQuery, Failure(..), FailureCont import Json.Encode as Json import ProgramTest.TestHtmlHacks as TestHtmlHacks import ProgramTest.TestHtmlParser as TestHtmlParser +import Result.Extra import Set exposing (Set) import Test.Html.Event import Test.Html.Query as Query @@ -356,14 +357,4 @@ firstErrorOf source choices = countSuccesses : List (Result String String) -> Int countSuccesses results = - List.length (List.filter isOk results) - - -isOk : Result x a -> Bool -isOk result = - case result of - Ok _ -> - True - - Err _ -> - False + List.length (List.filter Result.Extra.isOk results) diff --git a/src/Result/Extra.elm b/src/Result/Extra.elm new file mode 100644 index 0000000..d36599c --- /dev/null +++ b/src/Result/Extra.elm @@ -0,0 +1,11 @@ +module Result.Extra exposing (isOk) + + +isOk : Result x a -> Bool +isOk result = + case result of + Ok _ -> + True + + Err _ -> + False diff --git a/tests/ProgramTestTests/UserInput/ClickLinkTest.elm b/tests/ProgramTestTests/UserInput/ClickLinkTest.elm index 52bcd11..93e4a46 100644 --- a/tests/ProgramTestTests/UserInput/ClickLinkTest.elm +++ b/tests/ProgramTestTests/UserInput/ClickLinkTest.elm @@ -34,6 +34,12 @@ linkProgram = Html.div [] [ Html.a [ href "https://example.com/link" ] [ Html.text "External" ] , Html.a [ href "/settings" ] [ Html.text "Relative" ] + , Html.a + [ href "https://example.com/link" + , Html.Attributes.attribute "aria-label" "Aria" + ] + [] + , Html.a [ href "https://example.com/link" ] [ Html.img [ Html.Attributes.alt "Alt Text" ] [] ] ] } |> ProgramTest.withBaseUrl "http://localhost:3000/Main.elm" @@ -48,6 +54,16 @@ all = linkProgram |> ProgramTest.clickLink "External" "https://example.com/link" |> ProgramTest.expectPageChange "https://example.com/link" + , test "can verify a link with aria-label" <| + \() -> + linkProgram + |> ProgramTest.clickLink "Aria" "https://example.com/link" + |> ProgramTest.expectPageChange "https://example.com/link" + , test "can verify a link with img and alt text" <| + \() -> + linkProgram + |> ProgramTest.clickLink "Alt Text" "https://example.com/link" + |> ProgramTest.expectPageChange "https://example.com/link" , test "can verify a relative link" <| \() -> linkProgram