diff --git a/Main.elm b/Main.elm index a743889..d1e2df6 100644 --- a/Main.elm +++ b/Main.elm @@ -4,9 +4,9 @@ module Main exposing (..) have everything set up properly. -} +import Auth import Html exposing (..) import Html.Attributes exposing (..) -import Auth import Http import Json.Decode exposing (Decoder) @@ -37,9 +37,9 @@ searchFeed = url = "https://api.github.com/search/repositories?q=test&access_token=" ++ Auth.token in - Json.Decode.succeed () - |> Http.get url - |> Http.send Response + Json.Decode.succeed () + |> Http.get url + |> Http.send Response view : Model -> Html Msg @@ -90,8 +90,8 @@ update msg model = _ -> "GitHub's Search API returned an error: " - ++ (toString status.code) + ++ toString status.code ++ " " ++ status.message in - ( { status = status }, Cmd.none ) + ( { status = status }, Cmd.none ) diff --git a/part10/ElmHub.elm b/part10/ElmHub.elm index 6759761..ba53731 100644 --- a/part10/ElmHub.elm +++ b/part10/ElmHub.elm @@ -1,9 +1,9 @@ port module ElmHub exposing (..) -import Html exposing (..) -import Html.Attributes exposing (class, target, href, defaultValue, type_, checked, placeholder, value) -import Html.Events exposing (..) import Auth +import Html exposing (..) +import Html.Attributes exposing (checked, class, defaultValue, href, placeholder, target, type_, value) +import Html.Events exposing (..) import Json.Decode exposing (Decoder) import Json.Decode.Pipeline exposing (..) import String @@ -116,7 +116,7 @@ update msg model = newModel = { model | results = newResults } in - ( newModel, Cmd.none ) + ( newModel, Cmd.none ) DoNothing -> ( model, Cmd.none ) @@ -270,16 +270,17 @@ getQueryString : Model -> String Try identifying patterns and writing helper functions which are responsible for handling those patterns. Then have this function call them. Things to consider: -* There's pattern of adding "+foo:bar" - could we write a helper function for this? -* In one case, if the "bar" in "+foo:bar" is empty, we want to return "" instead - of "+foo:" - is this always true? Should our helper function always do that? -* We also join query parameters together with "=" and "&" a lot. Can we give - that pattern a similar treatment? Should we also take "?" into account? + - There's pattern of adding "+foo:bar" - could we write a helper function for this? + - In one case, if the "bar" in "+foo:bar" is empty, we want to return "" instead + of "+foo:" - is this always true? Should our helper function always do that? + - We also join query parameters together with "=" and "&" a lot. Can we give + that pattern a similar treatment? Should we also take "?" into account? If you have time, give this refactor a shot and see how it turns out! Writing something out the long way like this, and then refactoring to something nicer, is generally the preferred way to go about building things in Elm. + -} getQueryString : Model -> String getQueryString model = @@ -291,7 +292,7 @@ getQueryString model = ++ "+in:" ++ model.options.searchIn ++ "+stars:>=" - ++ (toString model.options.minStars) + ++ toString model.options.minStars ++ "+language:elm" ++ (if String.isEmpty model.options.userFilter then "" diff --git a/part10/tests/Tests.elm b/part10/tests/Tests.elm index 1dedfc6..9e9fe8e 100644 --- a/part10/tests/Tests.elm +++ b/part10/tests/Tests.elm @@ -1,11 +1,11 @@ module Tests exposing (..) -import Test exposing (..) -import Fuzz exposing (..) -import Expect exposing (Expectation) import ElmHub exposing (responseDecoder) -import Json.Decode exposing (decodeString, Value) +import Expect exposing (Expectation) +import Fuzz exposing (..) +import Json.Decode exposing (Value, decodeString) import String +import Test exposing (..) all : Test @@ -25,10 +25,10 @@ all = Ok _ -> False in - json - |> decodeString responseDecoder - |> isErrorResult - |> Expect.true "Expected decoding an invalid response to return an Err." + json + |> decodeString responseDecoder + |> isErrorResult + |> Expect.true "Expected decoding an invalid response to return an Err." , test "it successfully decodes a valid response" <| \() -> """{ "items": [ @@ -54,11 +54,11 @@ all = json = """{ "items": [""" ++ jsonItems ++ """] }""" in - case decodeString responseDecoder json of - Ok results -> - List.length results - |> Expect.equal (List.length ids) + case decodeString responseDecoder json of + Ok results -> + List.length results + |> Expect.equal (List.length ids) - Err err -> - Expect.fail ("JSON decoding failed unexpectedly: " ++ err) + Err err -> + Expect.fail ("JSON decoding failed unexpectedly: " ++ err) ] diff --git a/part11/ElmHub.elm b/part11/ElmHub.elm index 3899719..a4aea59 100644 --- a/part11/ElmHub.elm +++ b/part11/ElmHub.elm @@ -1,9 +1,9 @@ port module ElmHub exposing (..) -import Html exposing (..) -import Html.Attributes exposing (class, target, href, defaultValue, type_, checked, placeholder, value) -import Html.Events exposing (..) import Auth +import Html exposing (..) +import Html.Attributes exposing (checked, class, defaultValue, href, placeholder, target, type_, value) +import Html.Events exposing (..) import Json.Decode exposing (Decoder) import Json.Decode.Pipeline exposing (..) import String @@ -120,7 +120,7 @@ update msg model = newModel = { model | results = newResults } in - ( newModel, Cmd.none ) + ( newModel, Cmd.none ) -- TODO add a new branch for SetTableState -- which records the new tableState in the Model. @@ -139,7 +139,7 @@ tableConfig = { toId = .id >> toString , toMsg = -- TODO have the table use SetTableState for its toMsg instead of: - (\tableState -> DoNothing) + \tableState -> DoNothing , columns = [ starsColumn, nameColumn ] } @@ -191,22 +191,23 @@ view model = -- TODO have this use the actual current table state Table.initialSort "Stars" in - div [ class "content" ] - [ header [] - [ h1 [] [ text "ElmHub" ] - , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] - ] - , div [ class "search" ] - [ Html.map Options (viewOptions model.options) - , div [ class "search-input" ] - [ input [ class "search-query", onInput SetQuery, defaultValue model.query ] [] - , button [ class "search-button", onClick Search ] [ text "Search" ] - ] - ] - , viewErrorMessage model.errorMessage - -- TODO have this use model.results instead of [] - , Table.view tableConfig currentTableState [] + div [ class "content" ] + [ header [] + [ h1 [] [ text "ElmHub" ] + , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] ] + , div [ class "search" ] + [ Html.map Options (viewOptions model.options) + , div [ class "search-input" ] + [ input [ class "search-query", onInput SetQuery, defaultValue model.query ] [] + , button [ class "search-button", onClick Search ] [ text "Search" ] + ] + ] + , viewErrorMessage model.errorMessage + + -- TODO have this use model.results instead of [] + , Table.view tableConfig currentTableState [] + ] viewErrorMessage : Maybe String -> Html msg @@ -317,16 +318,17 @@ getQueryString : Model -> String Try identifying patterns and writing helper functions which are responsible for handling those patterns. Then have this function call them. Things to consider: -* There's pattern of adding "+foo:bar" - could we write a helper function for this? -* In one case, if the "bar" in "+foo:bar" is empty, we want to return "" instead - of "+foo:" - is this always true? Should our helper function always do that? -* We also join query parameters together with "=" and "&" a lot. Can we give - that pattern a similar treatment? Should we also take "?" into account? + - There's pattern of adding "+foo:bar" - could we write a helper function for this? + - In one case, if the "bar" in "+foo:bar" is empty, we want to return "" instead + of "+foo:" - is this always true? Should our helper function always do that? + - We also join query parameters together with "=" and "&" a lot. Can we give + that pattern a similar treatment? Should we also take "?" into account? If you have time, give this refactor a shot and see how it turns out! Writing something out the long way like this, and then refactoring to something nicer, is generally the preferred way to go about building things in Elm. + -} getQueryString : Model -> String getQueryString model = @@ -338,7 +340,7 @@ getQueryString model = ++ "+in:" ++ model.options.searchIn ++ "+stars:>=" - ++ (toString model.options.minStars) + ++ toString model.options.minStars ++ "+language:elm" ++ (if String.isEmpty model.options.userFilter then "" diff --git a/part12/ElmHub.elm b/part12/ElmHub.elm index aaeba59..ba24d7c 100644 --- a/part12/ElmHub.elm +++ b/part12/ElmHub.elm @@ -1,10 +1,10 @@ port module ElmHub exposing (..) +import Auth import Html exposing (..) -import Html.Attributes exposing (class, target, href, defaultValue, type_, checked, placeholder, value) +import Html.Attributes exposing (checked, class, defaultValue, href, placeholder, target, type_, value) import Html.Events exposing (..) import Html.Lazy exposing (lazy, lazy3) -import Auth import Json.Decode exposing (Decoder) import Json.Decode.Pipeline exposing (..) import String @@ -89,11 +89,12 @@ viewOptions opts = , input [ type_ "text" , placeholder "Enter a username" - -- TODO replace opts.userFilter with the following: - -- - -- (Debug.log "username" opts.userFilter) - -- - -- This way, we'll see console output whenever this gets run! + + -- TODO replace opts.userFilter with the following: + -- + -- (Debug.log "username" opts.userFilter) + -- + -- This way, we'll see console output whenever this gets run! , defaultValue opts.userFilter , onInput SetUserFilter ] @@ -160,7 +161,7 @@ update msg model = newModel = { model | results = newResults } in - ( newModel, Cmd.none ) + ( newModel, Cmd.none ) SetTableState tableState -> ( { model | tableState = tableState }, Cmd.none ) @@ -243,8 +244,9 @@ view model = ] ] , viewErrorMessage model.errorMessage - -- TODO add a lazy3 to wrap Table.view. - -- (We have no Debug.log for verification this time.) + + -- TODO add a lazy3 to wrap Table.view. + -- (We have no Debug.log for verification this time.) , Table.view tableConfig model.tableState model.results ] @@ -322,7 +324,7 @@ getQueryString model = ++ "+in:" ++ model.options.searchIn ++ "+stars:>=" - ++ (toString model.options.minStars) + ++ toString model.options.minStars ++ "+language:elm" ++ (if String.isEmpty model.options.userFilter then "" diff --git a/part13/ElmHub.elm b/part13/ElmHub.elm index c149578..da23ed0 100644 --- a/part13/ElmHub.elm +++ b/part13/ElmHub.elm @@ -1,10 +1,10 @@ port module ElmHub exposing (..) +import Auth import Html exposing (..) -import Html.Attributes exposing (class, target, href, defaultValue, type_, checked, placeholder, value) +import Html.Attributes exposing (checked, class, defaultValue, href, placeholder, target, type_, value) import Html.Events exposing (..) import Html.Lazy exposing (lazy, lazy3) -import Auth import Json.Decode exposing (Decoder) import Json.Decode.Pipeline exposing (..) import String @@ -155,7 +155,7 @@ update msg model = newModel = { model | results = newResults } in - ( newModel, Cmd.none ) + ( newModel, Cmd.none ) SetTableState tableState -> ( { model | tableState = tableState }, Cmd.none ) @@ -309,7 +309,7 @@ getQueryString model = ++ "+in:" ++ model.options.searchIn ++ "+stars:>=" - ++ (toString model.options.minStars) + ++ toString model.options.minStars ++ "+language:elm" ++ (if String.isEmpty model.options.userFilter then "" diff --git a/part13/ElmHubCss.elm b/part13/ElmHubCss.elm index ad5ded6..f08afe7 100644 --- a/part13/ElmHubCss.elm +++ b/part13/ElmHubCss.elm @@ -28,7 +28,7 @@ css = -- .hide-result:hover { -- color: rgb(96, 181, 204); -- } - ((.) "content") + (.) "content" [ width (px 960) , margin2 zero auto , padding (px 30) diff --git a/part13/Stylesheets.elm b/part13/Stylesheets.elm index 5d3091e..2ce17d4 100644 --- a/part13/Stylesheets.elm +++ b/part13/Stylesheets.elm @@ -17,7 +17,7 @@ main : Program Never Model Msg main = Html.program { init = ( (), files cssFiles ) - , view = \_ -> (div [] []) + , view = \_ -> div [] [] , update = \_ _ -> ( (), Cmd.none ) , subscriptions = \_ -> Sub.none } diff --git a/part14/ElmHub.elm b/part14/ElmHub.elm index c149578..da23ed0 100644 --- a/part14/ElmHub.elm +++ b/part14/ElmHub.elm @@ -1,10 +1,10 @@ port module ElmHub exposing (..) +import Auth import Html exposing (..) -import Html.Attributes exposing (class, target, href, defaultValue, type_, checked, placeholder, value) +import Html.Attributes exposing (checked, class, defaultValue, href, placeholder, target, type_, value) import Html.Events exposing (..) import Html.Lazy exposing (lazy, lazy3) -import Auth import Json.Decode exposing (Decoder) import Json.Decode.Pipeline exposing (..) import String @@ -155,7 +155,7 @@ update msg model = newModel = { model | results = newResults } in - ( newModel, Cmd.none ) + ( newModel, Cmd.none ) SetTableState tableState -> ( { model | tableState = tableState }, Cmd.none ) @@ -309,7 +309,7 @@ getQueryString model = ++ "+in:" ++ model.options.searchIn ++ "+stars:>=" - ++ (toString model.options.minStars) + ++ toString model.options.minStars ++ "+language:elm" ++ (if String.isEmpty model.options.userFilter then "" diff --git a/part14/Stylesheets.elm b/part14/Stylesheets.elm index 5d3091e..2ce17d4 100644 --- a/part14/Stylesheets.elm +++ b/part14/Stylesheets.elm @@ -17,7 +17,7 @@ main : Program Never Model Msg main = Html.program { init = ( (), files cssFiles ) - , view = \_ -> (div [] []) + , view = \_ -> div [] [] , update = \_ _ -> ( (), Cmd.none ) , subscriptions = \_ -> Sub.none } diff --git a/part2/Main.elm b/part2/Main.elm index cc79dd0..6893bde 100644 --- a/part2/Main.elm +++ b/part2/Main.elm @@ -21,18 +21,19 @@ main = , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] ] in - div [ class "content" ] - [ text "TODO put the contents of elmHubHeader here instead of this text!" - , ul [ class "results" ] - [ li [] - [ span [ class "star-count" ] - [-- TODO display the number of stars here. - -- - -- HINT: You'll need some parentheses to do this! - ] - -- TODO use the model to put a link here that points to - -- https://github.com/TheSeamau5/elm-checkerboardgrid-tutorial - -- by prepending the "https://github.com/" part. + div [ class "content" ] + [ text "TODO put the contents of elmHubHeader here instead of this text!" + , ul [ class "results" ] + [ li [] + [ span [ class "star-count" ] + [-- TODO display the number of stars here. + -- + -- HINT: You'll need some parentheses to do this! ] + + -- TODO use the model to put a link here that points to + -- https://github.com/TheSeamau5/elm-checkerboardgrid-tutorial + -- by prepending the "https://github.com/" part. ] ] + ] diff --git a/part5/Main.elm b/part5/Main.elm index db48086..47b6a0e 100644 --- a/part5/Main.elm +++ b/part5/Main.elm @@ -68,7 +68,8 @@ view model = ] , input [ class "search-query" - -- TODO onInput, set the query in the model + + -- TODO onInput, set the query in the model , defaultValue model.query ] [] diff --git a/part6/Main.elm b/part6/Main.elm index 619b687..221962d 100644 --- a/part6/Main.elm +++ b/part6/Main.elm @@ -1,7 +1,7 @@ module Main exposing (..) import Html exposing (..) -import Html.Attributes exposing (class, target, href, property, defaultValue) +import Html.Attributes exposing (class, defaultValue, href, property, target) import Html.Events exposing (..) import Json.Decode exposing (..) import Json.Decode.Pipeline exposing (..) @@ -116,4 +116,4 @@ update msg model = newResults = List.filter (\{ id } -> id /= idToHide) model.results in - { model | results = newResults } + { model | results = newResults } diff --git a/part7/Main.elm b/part7/Main.elm index 061132b..d39bea4 100644 --- a/part7/Main.elm +++ b/part7/Main.elm @@ -2,7 +2,7 @@ module Main exposing (..) import Auth import Html exposing (..) -import Html.Attributes exposing (class, target, href, property, defaultValue) +import Html.Attributes exposing (class, defaultValue, href, property, target) import Html.Events exposing (..) import Http import Json.Decode exposing (Decoder) @@ -33,11 +33,11 @@ searchFeed query = request = "TODO replace this String with a Request built using http://package.elm-lang.org/packages/elm-lang/http/latest/Http#get" in - -- TODO replace this Cmd.none with a call to Http.send - -- http://package.elm-lang.org/packages/elm-lang/http/latest/Http#send - -- - -- HINT: request and HandleSearchResponse may be useful here. - Cmd.none + -- TODO replace this Cmd.none with a call to Http.send + -- http://package.elm-lang.org/packages/elm-lang/http/latest/Http#send + -- + -- HINT: request and HandleSearchResponse may be useful here. + Cmd.none responseDecoder : Decoder (List SearchResult) @@ -153,4 +153,4 @@ update msg model = newModel = { model | results = newResults } in - ( newModel, Cmd.none ) + ( newModel, Cmd.none ) diff --git a/part8/Main.elm b/part8/Main.elm index db134ee..58e77a1 100644 --- a/part8/Main.elm +++ b/part8/Main.elm @@ -1,11 +1,10 @@ port module Main exposing (..) -import Json.Decode exposing (..) -import Html exposing (..) -import Html.Attributes exposing (class, target, href, property, defaultValue) -import Html.Events exposing (..) import Auth -import Json.Decode exposing (Decoder) +import Html exposing (..) +import Html.Attributes exposing (class, defaultValue, href, property, target) +import Html.Events exposing (..) +import Json.Decode exposing (..) import Json.Decode.Pipeline exposing (..) @@ -123,7 +122,7 @@ update msg model = newModel = { model | results = newResults } in - ( newModel, Cmd.none ) + ( newModel, Cmd.none ) type Msg diff --git a/part9/ElmHub.elm b/part9/ElmHub.elm index 87f3b17..a51b09a 100644 --- a/part9/ElmHub.elm +++ b/part9/ElmHub.elm @@ -1,9 +1,9 @@ port module ElmHub exposing (..) -import Html exposing (..) -import Html.Attributes exposing (class, target, href, property, defaultValue) -import Html.Events exposing (..) import Auth +import Html exposing (..) +import Html.Attributes exposing (class, defaultValue, href, property, target) +import Html.Events exposing (..) import Json.Decode exposing (Decoder) import Json.Decode.Pipeline exposing (..) @@ -131,7 +131,7 @@ update msg model = newModel = { model | results = newResults } in - ( newModel, Cmd.none ) + ( newModel, Cmd.none ) DoNothing -> ( model, Cmd.none ) diff --git a/part9/tests/Tests.elm b/part9/tests/Tests.elm index 561513f..f60c8ee 100644 --- a/part9/tests/Tests.elm +++ b/part9/tests/Tests.elm @@ -1,11 +1,11 @@ module Tests exposing (..) -import Test exposing (..) -import Fuzz exposing (..) -import Expect exposing (Expectation) import ElmHub exposing (responseDecoder) -import Json.Decode exposing (decodeString, Value) +import Expect exposing (Expectation) +import Fuzz exposing (..) +import Json.Decode exposing (Value, decodeString) import String +import Test exposing (..) all : Test @@ -24,10 +24,10 @@ all = -- Result docs: http://package.elm-lang.org/packages/elm-lang/core/latest/Result False in - json - |> decodeString responseDecoder - |> isErrorResult - |> Expect.true "Expected decoding an invalid response to return an Err." + json + |> decodeString responseDecoder + |> isErrorResult + |> Expect.true "Expected decoding an invalid response to return an Err." , test "it successfully decodes a valid response" <| \() -> """{ "items": [ @@ -60,11 +60,11 @@ all = json = """{ "items": [""" ++ jsonItems ++ """] }""" in - case decodeString responseDecoder json of - Ok results -> - List.length results - |> Expect.equal (List.length ids) + case decodeString responseDecoder json of + Ok results -> + List.length results + |> Expect.equal (List.length ids) - Err err -> - Expect.fail ("JSON decoding failed unexpectedly: " ++ err) + Err err -> + Expect.fail ("JSON decoding failed unexpectedly: " ++ err) ] diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/.gitignore b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/.gitignore new file mode 100644 index 0000000..3ce2c7d --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/.gitignore @@ -0,0 +1,5 @@ +*~ +node_modules/ +elm-stuff/ +docs/ +*.html diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/.travis.yml b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/.travis.yml new file mode 100644 index 0000000..267ad1c --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/.travis.yml @@ -0,0 +1,46 @@ +sudo: false + +cache: + directories: + - test/elm-stuff/build-artifacts + - sysconfcpus + +os: + - osx + - linux + +env: + matrix: + - ELM_VERSION=0.18.0-beta TARGET_NODE_VERSION=node + - ELM_VERSION=0.18.0-beta TARGET_NODE_VERSION=4.0 + +before_install: + - if [ ${TRAVIS_OS_NAME} == "osx" ]; + then brew update; brew install nvm; mkdir ~/.nvm; export NVM_DIR=~/.nvm; source $(brew --prefix nvm)/nvm.sh; + fi + - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config + - | # epic build time improvement - see https://github.com/elm-lang/elm-compiler/issues/1473#issuecomment-245704142 + if [ ! -d sysconfcpus/bin ]; + then + git clone https://github.com/obmarg/libsysconfcpus.git; + cd libsysconfcpus; + ./configure --prefix=$TRAVIS_BUILD_DIR/sysconfcpus; + make && make install; + cd ..; + fi + +install: + - nvm install $TARGET_NODE_VERSION + - nvm use $TARGET_NODE_VERSION + - node --version + - npm --version + - cd tests + - npm install -g elm@$ELM_VERSION elm-test + - mv $(npm config get prefix)/bin/elm-make $(npm config get prefix)/bin/elm-make-old + - printf '%s\n\n' '#!/bin/bash' 'echo "Running elm-make with sysconfcpus -n 2"' '$TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-make-old "$@"' > $(npm config get prefix)/bin/elm-make + - chmod +x $(npm config get prefix)/bin/elm-make + - npm install + - elm package install --yes + +script: + - npm test diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/LICENSE b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/LICENSE new file mode 100644 index 0000000..9a5e944 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2016 Richard Feldman and Max Goldstein +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of elm-test nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/README.md b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/README.md new file mode 100644 index 0000000..f52cdb4 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/README.md @@ -0,0 +1,107 @@ +# elm-test [![Travis build Status](https://travis-ci.org/elm-community/elm-test.svg?branch=master)](http://travis-ci.org/elm-community/elm-test) + +Write unit and fuzz tests for your Elm code, in Elm. + +## Quick Start + +Here are three example tests: + +```elm +suite : Test +suite = + describe "The String module" + [ describe "String.reverse" -- Nest as many descriptions as you like. + [ test "has no effect on a palindrome" <| + \() -> + let + palindrome = + "hannah" + in + Expect.equal palindrome (String.reverse palindrome) + + -- Expect.equal is designed to be used in pipeline style, like this. + , test "reverses a known string" <| + \() -> + "ABCDEFG" + |> String.reverse + |> Expect.equal "GFEDCBA" + + -- fuzz runs the test 100 times with randomly-generated inputs! + , fuzz string "restores the original string if you run it again" <| + \randomlyGeneratedString -> + randomlyGeneratedString + |> String.reverse + |> String.reverse + |> Expect.equal randomlyGeneratedString + ] + ] +``` + +This code uses a few common functions: + +* [`describe`](http://package.elm-lang.org/packages/elm-community/elm-test/latest/Test#test) to add a description string to a list of tests +* [`test`](http://package.elm-lang.org/packages/elm-community/elm-test/latest/Test#test) to write a unit test +* [`Expect`](http://package.elm-lang.org/packages/elm-community/elm-test/latest/Expect) to determine if a test should pass or fail +* [`fuzz`](http://package.elm-lang.org/packages/elm-community/elm-test/latest/Test#fuzz) to run a function that produces a test several times with randomly-generated inputs + +Check out [a large real-world test suite](https://github.com/rtfeldman/elm-css/tree/master/tests) for more. + +### Running tests locally + +There are several ways you can run tests locally: + +* [from your terminal](https://github.com/rtfeldman/node-test-runner) via `npm install -g elm-test` +* [from your browser](https://github.com/rtfeldman/html-test-runner) + +Here's how to set up and run your tests using the CLI test runner: + +1. Run `npm install -g elm-test` if you haven't already. +2. `cd` into the project's root directory that has your `elm-package.json`. +3. Run `elm-test init`. It will create a `tests` directory inside this one, + with some files in it. +4. Copy all the dependencies from `elm-package.json` into + `tests/elm-package.json`. These dependencies need to stay in sync, so make + sure whenever you change your dependencies in your current + `elm-package.json`, you make the same change to `tests/elm-package.json`. +5. Run `elm-test`. +6. Edit `tests/Tests.elm` to introduce new tests. + +### Running tests on CI + +Here are some examples of running tests on CI servers: + +* [`travis.yml`](https://github.com/rtfeldman/elm-css/blob/6ba8404f53269bc110c2e08ab24c9caf850da515/.travis.yml) +* [`appveyor.yml`](https://github.com/rtfeldman/elm-css/blob/6ba8404f53269bc110c2e08ab24c9caf850da515/appveyor.yml) + +## Strategies for effective testing + +* [Make impossible states unrepresentable](https://www.youtube.com/watch?v=IcgmSRJHu_8) so that you don't have to test that they can't occur. +* When doing TDD, treat compiler errors as a red test. So feel free to write the test you wish you had even if it means calling functions that don't exist yet! +* How do you know when to stop testing? This is an engineering tradeoff without a perfect answer. If you don't feel confident in the correctness of your code, write more tests. If you feel you are wasting time testing better spent writing your application, stop writing tests for now. +* Prefer fuzz tests to unit tests, when possible. But don't be afraid to supplement them with unit tests for tricky cases and regressions. +* For simple functions, it's okay to copy the implementation to the test; this is a useful regression check. But if the implementation isn't obviously right, try to write tests that don't duplicate the suspect logic. The great thing about fuzz tests is that you don't have to arrive at the exact same value as the code under test, just state something that will be true of that value. +* If you're writing a library that wraps an existing standard or protocol, use examples from the specification or docs as unit tests. Anything in your README should be backed by a unit test (sadly there's no easy way to keep them in sync). +* Not even your test modules can import unexposed functions, so test them only as the exposed interface uses them. Don't expose a function just to test it. Every exposed function should have tests. (If you practice TDD, this happens automatically!) +* `elm-test` is designed to test functions, not effects. To test them, use [elm-testable](http://package.elm-lang.org/packages/avh4/elm-testable/latest). For integration or end-to-end testing, use your favorite PhantomJS or Selenium webdriver, such as Capybara. + +## Upgrading +### From 0.17 +You will need to delete `elm-stuff` and `tests/elm-stuff`. + +If you are using the Node runner, you will need to pull down the new `Main.elm`: `curl -o tests/Main.elm https://raw.githubusercontent.com/rtfeldman/node-test-runner/master/templates/Main.elm` + +### From the old elm-test +[`legacy-elm-test`](http://package.elm-lang.org/packages/rtfeldman/legacy-elm-test/latest) provides a +drop-in replacement for the `ElmTest 1.0` API, except implemented in terms of +the current `elm-test`. It also includes support for `elm-check` tests. + +This lets you use the latest test runners right now, and upgrade incrementally. + +## Releases +| Version | Notes | +| ------- | ----- | +| [**3.1.0**](https://github.com/elm-community/elm-test/tree/3.1.0) | Add Expect.all +| [**3.0.0**](https://github.com/elm-community/elm-test/tree/3.0.0) | Update for Elm 0.18; switch the argument order of `Fuzz.andMap`. +| [**2.1.0**](https://github.com/elm-community/elm-test/tree/2.1.0) | Switch to rose trees for `Fuzz.andThen`, other API additions. +| [**2.0.0**](https://github.com/elm-community/elm-test/tree/2.0.0) | Scratch-rewrite to project-fuzzball +| [**1.0.0**](https://github.com/elm-community/elm-test/tree/1.0.0) | ElmTest initial release diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/elm-package.json b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/elm-package.json new file mode 100644 index 0000000..9b1cc00 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/elm-package.json @@ -0,0 +1,22 @@ +{ + "version": "3.1.0", + "summary": "Unit and Fuzz testing support with Console/Html/String outputs.", + "repository": "https://github.com/elm-community/elm-test.git", + "license": "BSD-3-Clause", + "source-directories": [ + "src" + ], + "exposed-modules": [ + "Test", + "Test.Runner", + "Expect", + "Fuzz" + ], + "dependencies": { + "elm-community/lazy-list": "1.0.0 <= v < 2.0.0", + "elm-community/shrink": "2.0.0 <= v < 3.0.0", + "elm-lang/core": "5.0.0 <= v < 6.0.0", + "mgold/elm-random-pcg": "4.0.2 <= v < 5.0.0" + }, + "elm-version": "0.18.0 <= v < 0.19.0" +} diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Expect.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Expect.elm new file mode 100644 index 0000000..61916f8 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Expect.elm @@ -0,0 +1,649 @@ +module Expect + exposing + ( Expectation + , pass + , fail + , getFailure + , equal + , notEqual + , atMost + , lessThan + , greaterThan + , atLeast + , true + , false + , equalLists + , equalDicts + , equalSets + , onFail + , all + ) + +{-| A library to create `Expectation`s, which describe a claim to be tested. + +## Quick Reference + +* [`equal`](#equal) `(arg2 == arg1)` +* [`notEqual`](#notEqual) `(arg2 /= arg1)` +* [`lessThan`](#lessThan) `(arg2 < arg1)` +* [`atMost`](#atMost) `(arg2 <= arg1)` +* [`greaterThan`](#greaterThan) `(arg2 > arg1)` +* [`atLeast`](#atLeast) `(arg2 >= arg1)` +* [`true`](#true) `(arg == True)` +* [`false`](#false) `(arg == False)` + +## Basic Expectations + +@docs Expectation, equal, notEqual, all + +## Comparisons + +@docs lessThan, atMost, greaterThan, atLeast + +## Booleans + +@docs true, false + +## Collections + +@docs equalLists, equalDicts, equalSets + +## Customizing + +@docs pass, fail, onFail, getFailure +-} + +import Test.Expectation +import Dict exposing (Dict) +import Set exposing (Set) +import String + + +{-| The result of a single test run: either a [`pass`](#pass) or a +[`fail`](#fail). +-} +type alias Expectation = + Test.Expectation.Expectation + + +{-| Passes if the arguments are equal. + + Expect.equal 0 (List.length []) + + -- Passes because (0 == 0) is True + +Failures resemble code written in pipeline style, so you can tell +which argument is which: + + -- Fails because the expected value didn't split the space in "Betty Botter" + String.split " " "Betty Botter bought some butter" + |> Expect.equal [ "Betty Botter", "bought", "some", "butter" ] + + {- + + [ "Betty", "Botter", "bought", "some", "butter" ] + ╷ + │ Expect.equal + ╵ + [ "Betty Botter", "bought", "some", "butter" ] + + -} +-} +equal : a -> a -> Expectation +equal = + compareWith "Expect.equal" (==) + + +{-| Passes if the arguments are not equal. + + -- Passes because (11 /= 100) is True + 90 + 10 + |> Expect.notEqual 11 + + + -- Fails because (100 /= 100) is False + 90 + 10 + |> Expect.notEqual 100 + + {- + + 100 + ╷ + │ Expect.notEqual + ╵ + 100 + + -} +-} +notEqual : a -> a -> Expectation +notEqual = + compareWith "Expect.notEqual" (/=) + + +{-| Passes if the second argument is less than the first. + + Expect.lessThan 1 (List.length []) + + -- Passes because (0 < 1) is True + +Failures resemble code written in pipeline style, so you can tell +which argument is which: + + -- Fails because (0 < -1) is False + List.length [] + |> Expect.lessThan -1 + + + {- + + 0 + ╷ + │ Expect.lessThan + ╵ + -1 + + -} +-} +lessThan : comparable -> comparable -> Expectation +lessThan = + compareWith "Expect.lessThan" (<) + + +{-| Passes if the second argument is less than or equal to the first. + + Expect.atMost 1 (List.length []) + + -- Passes because (0 <= 1) is True + +Failures resemble code written in pipeline style, so you can tell +which argument is which: + + -- Fails because (0 <= -3) is False + List.length [] + |> Expect.atMost -3 + + {- + + 0 + ╷ + │ Expect.atMost + ╵ + -3 + + -} +-} +atMost : comparable -> comparable -> Expectation +atMost = + compareWith "Expect.atMost" (<=) + + +{-| Passes if the second argument is greater than the first. + + Expect.greaterThan -2 List.length [] + + -- Passes because (0 > -2) is True + +Failures resemble code written in pipeline style, so you can tell +which argument is which: + + -- Fails because (0 > 1) is False + List.length [] + |> Expect.greaterThan 1 + + {- + + 0 + ╷ + │ Expect.greaterThan + ╵ + 1 + + -} +-} +greaterThan : comparable -> comparable -> Expectation +greaterThan = + compareWith "Expect.greaterThan" (>) + + +{-| Passes if the second argument is greater than or equal to the first. + + Expect.atLeast -2 (List.length []) + + -- Passes because (0 >= -2) is True + +Failures resemble code written in pipeline style, so you can tell +which argument is which: + + -- Fails because (0 >= 3) is False + List.length [] + |> Expect.atLeast 3 + + {- + + 0 + ╷ + │ Expect.atLeast + ╵ + 3 + + -} +-} +atLeast : comparable -> comparable -> Expectation +atLeast = + compareWith "Expect.atLeast" (>=) + + +{-| Passes if the argument is 'True', and otherwise fails with the given message. + + Expect.true "Expected the list to be empty." (List.isEmpty []) + + -- Passes because (List.isEmpty []) is True + +Failures resemble code written in pipeline style, so you can tell +which argument is which: + + -- Fails because List.isEmpty returns False, but we expect True. + List.isEmpty [ 42 ] + |> Expect.true "Expected the list to be empty." + + {- + + Expected the list to be empty. + + -} +-} +true : String -> Bool -> Expectation +true message bool = + if bool then + pass + else + fail message + + +{-| Passes if the argument is 'False', and otherwise fails with the given message. + + Expect.false "Expected the list not to be empty." (List.isEmpty [ 42 ]) + + -- Passes because (List.isEmpty [ 42 ]) is False + +Failures resemble code written in pipeline style, so you can tell +which argument is which: + + -- Fails because (List.isEmpty []) is True + List.isEmpty [] + |> Expect.false "Expected the list not to be empty." + + {- + + Expected the list not to be empty. + + -} +-} +false : String -> Bool -> Expectation +false message bool = + if bool then + fail message + else + pass + + +{-| Passes if the arguments are equal lists. + + -- Passes + [1, 2, 3] + |> Expect.equalLists [1, 2, 3] + +Failures resemble code written in pipeline style, so you can tell +which argument is which, and reports which index the lists first +differed at or which list was longer: + + -- Fails + [ 1, 2, 4, 6 ] + |> Expect.equalLists [ 1, 2, 5 ] + + {- + + [1,2,4,6] + first diff at index index 2: +`4`, -`5` + ╷ + │ Expect.equalLists + ╵ + first diff at index index 2: +`5`, -`4` + [1,2,5] + + -} +-} +equalLists : List a -> List a -> Expectation +equalLists expected actual = + if expected == actual then + pass + else + let + result = + List.map2 (,) actual expected + |> List.indexedMap (,) + |> List.filterMap + (\( index, ( a, e ) ) -> + if e == a then + Nothing + else + Just ( index, a, e ) + ) + |> List.head + |> Maybe.map + (\( index, a, e ) -> + [ toString actual + , "first diff at index index " ++ toString index ++ ": +`" ++ toString a ++ "`, -`" ++ toString e ++ "`" + , "╷" + , "│ Expect.equalLists" + , "╵" + , "first diff at index index " ++ toString index ++ ": +`" ++ toString e ++ "`, -`" ++ toString a ++ "`" + , toString expected + ] + |> String.join "\n" + |> fail + ) + in + case result of + Just failure -> + failure + + Nothing -> + case compare (List.length actual) (List.length expected) of + GT -> + reportFailure "Expect.equalLists was longer than" (toString expected) (toString actual) + |> fail + + LT -> + reportFailure "Expect.equalLists was shorter than" (toString expected) (toString actual) + |> fail + + _ -> + pass + + +{-| Passes if the arguments are equal dicts. + + -- Passes + (Dict.fromList [ ( 1, "one" ), ( 2, "two" ) ]) + |> Expect.equalDicts (Dict.fromList [ ( 1, "one" ), ( 2, "two" ) ]) + +Failures resemble code written in pipeline style, so you can tell +which argument is which, and reports which keys were missing from +or added to each dict: + + -- Fails + (Dict.fromList [ ( 1, "one" ), ( 2, "too" ) ]) + |> Expect.equalDicts (Dict.fromList [ ( 1, "one" ), ( 2, "two" ), ( 3, "three" ) ]) + + {- + + Dict.fromList [(1,"one"),(2,"too")] + diff: -[ (2,"two"), (3,"three") ] +[ (2,"too") ] + ╷ + │ Expect.equalDicts + ╵ + diff: +[ (2,"two"), (3,"three") ] -[ (2,"too") ] + Dict.fromList [(1,"one"),(2,"two"),(3,"three")] + + -} +-} +equalDicts : Dict comparable a -> Dict comparable a -> Expectation +equalDicts expected actual = + if Dict.toList expected == Dict.toList actual then + pass + else + let + differ dict k v diffs = + if Dict.get k dict == Just v then + diffs + else + ( k, v ) :: diffs + + missingKeys = + Dict.foldr (differ actual) [] expected + + extraKeys = + Dict.foldr (differ expected) [] actual + in + fail (reportCollectionFailure "Expect.equalDicts" expected actual missingKeys extraKeys) + + +{-| Passes if the arguments are equal sets. + + -- Passes + (Set.fromList [1, 2]) + |> Expect.equalSets (Set.fromList [1, 2]) + +Failures resemble code written in pipeline style, so you can tell +which argument is which, and reports which keys were missing from +or added to each set: + + -- Fails + (Set.fromList [ 1, 2, 4, 6 ]) + |> Expect.equalSets (Set.fromList [ 1, 2, 5 ]) + + {- + + Set.fromList [1,2,4,6] + diff: -[ 5 ] +[ 4, 6 ] + ╷ + │ Expect.equalSets + ╵ + diff: +[ 5 ] -[ 4, 6 ] + Set.fromList [1,2,5] + + -} +-} +equalSets : Set comparable -> Set comparable -> Expectation +equalSets expected actual = + if Set.toList expected == Set.toList actual then + pass + else + let + missingKeys = + Set.diff expected actual + |> Set.toList + + extraKeys = + Set.diff actual expected + |> Set.toList + in + fail (reportCollectionFailure "Expect.equalSets" expected actual missingKeys extraKeys) + + +{-| Always passes. + + import Json.Decode exposing (decodeString, int) + import Test exposing (test) + import Expect + + + test "Json.Decode.int can decode the number 42." <| + \() -> + case decodeString int "42" of + Ok _ -> + Expect.pass + + Err err -> + Expect.fail err +-} +pass : Expectation +pass = + Test.Expectation.Pass + + +{-| Fails with the given message. + + import Json.Decode exposing (decodeString, int) + import Test exposing (test) + import Expect + + + test "Json.Decode.int can decode the number 42." <| + \() -> + case decodeString int "42" of + Ok _ -> + Expect.pass + + Err err -> + Expect.fail err +-} +fail : String -> Expectation +fail = + Test.Expectation.Fail "" + + +{-| Return `Nothing` if the given [`Expectation`](#Expectation) is a [`pass`](#pass). + +If it is a [`fail`](#fail), return a record containing the failure message, +along with the given inputs if it was a fuzz test. (If no inputs were involved, +the record's `given` field will be `""`). + +For example, if a fuzz test generates random integers, this might return +`{ message = "it was supposed to be positive", given = "-1" }` + + getFailure (Expect.fail "this failed") + -- Just { message = "this failed", given = "" } + + getFailure (Expect.pass) + -- Nothing +-} +getFailure : Expectation -> Maybe { given : String, message : String } +getFailure expectation = + case expectation of + Test.Expectation.Pass -> + Nothing + + Test.Expectation.Fail given message -> + Just { given = given, message = message } + + +{-| If the given expectation fails, replace its failure message with a custom one. + + "something" + |> Expect.equal "something else" + |> Expect.onFail "thought those two strings would be the same" +-} +onFail : String -> Expectation -> Expectation +onFail str expectation = + case expectation of + Test.Expectation.Pass -> + expectation + + Test.Expectation.Fail given _ -> + Test.Expectation.Fail given str + + +reportFailure : String -> String -> String -> String +reportFailure comparison expected actual = + [ actual + , "╷" + , "│ " ++ comparison + , "╵" + , expected + ] + |> String.join "\n" + + +reportCollectionFailure : String -> a -> b -> List c -> List d -> String +reportCollectionFailure comparison expected actual missingKeys extraKeys = + [ toString actual + , "diff:" ++ formatDiffs Missing missingKeys ++ formatDiffs Extra extraKeys + , "╷" + , "│ " ++ comparison + , "╵" + , "diff:" ++ formatDiffs Extra missingKeys ++ formatDiffs Missing extraKeys + , toString expected + ] + |> String.join "\n" + + +type Diff + = Extra + | Missing + + +formatDiffs : Diff -> List a -> String +formatDiffs diffType diffs = + if List.isEmpty diffs then + "" + else + let + modifier = + case diffType of + Extra -> + "+" + + Missing -> + "-" + in + diffs + |> List.map toString + |> String.join ", " + |> (\d -> " " ++ modifier ++ "[ " ++ d ++ " ]") + + +compareWith : String -> (a -> b -> Bool) -> b -> a -> Expectation +compareWith label compare expected actual = + if compare actual expected then + pass + else + fail (reportFailure label (toString expected) (toString actual)) + + +{-| Passes if each of the given functions passes when applied to the subject. + +**NOTE:** Passing an empty list is assumed to be a mistake, so `Expect.all []` +will always return a failed expectation no matter what else it is passed. + + Expect.all + [ Expect.greaterThan -2 + , Expect.lessThan 5 + ] + (List.length []) + + -- Passes because (0 > -2) is True and (0 < 5) is also True + +Failures resemble code written in pipeline style, so you can tell +which argument is which: + + -- Fails because (0 > -10) is False + List.length [] + |> Expect.all + [ Expect.greaterThan -2 + , Expect.lessThan -10 + , Expect.equal 0 + ] + + {- + + 0 + ╷ + │ Expect.lessThan + ╵ + -10 + + -} +-} +all : List (subject -> Expectation) -> subject -> Expectation +all list query = + if List.isEmpty list then + fail "Expect.all received an empty list. I assume this was due to a mistake somewhere, so I'm failing this test!" + else + allHelp list query + + +allHelp : List (subject -> Expectation) -> subject -> Expectation +allHelp list query = + case list of + [] -> + pass + + check :: rest -> + case check query of + Test.Expectation.Pass -> + allHelp rest query + + outcome -> + outcome diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Fuzz.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Fuzz.elm new file mode 100644 index 0000000..d271f90 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Fuzz.elm @@ -0,0 +1,802 @@ +module Fuzz exposing (Fuzzer, custom, constant, unit, bool, order, char, float, floatRange, int, tuple, tuple3, tuple4, tuple5, result, string, percentage, map, map2, map3, map4, map5, andMap, andThen, maybe, intRange, list, array, frequency, frequencyOrCrash) + +{-| This is a library of *fuzzers* you can use to supply values to your fuzz +tests. You can typically pick out which ones you need according to their types. + +A `Fuzzer a` knows how to create values of type `a` in two different ways. It +can create them randomly, so that your test's expectations are run against many +values. Fuzzers will often generate edge cases likely to find bugs. If the +fuzzer can make your test fail, it also knows how to "shrink" that failing input +into more minimal examples, some of which might also cause the tests to fail. In +this way, fuzzers can usually find the smallest or simplest input that +reproduces a bug. + +## Common Fuzzers +@docs bool, int, intRange, float, floatRange, percentage, string, maybe, result, list, array + +## Working with Fuzzers +@docs Fuzzer, constant, map, map2, map3,map4, map5, andMap, andThen, frequency, frequencyOrCrash + +## Tuple Fuzzers +Instead of using a tuple, consider using `fuzzN`. +@docs tuple, tuple3, tuple4, tuple5 + +## Uncommon Fuzzers +@docs custom, char, unit, order + +-} + +import Array exposing (Array) +import Char +import Util exposing (..) +import Lazy.List exposing (LazyList) +import Shrink exposing (Shrinker) +import RoseTree exposing (RoseTree(..)) +import Random.Pcg as Random exposing (Generator) +import Fuzz.Internal as Internal exposing (Fuzz(..)) + + +{-| The representation of fuzzers is opaque. Conceptually, a `Fuzzer a` +consists of a way to randomly generate values of type `a`, and a way to shrink +those values. +-} +type alias Fuzzer a = + Internal.Fuzzer a + + +{-| Build a custom `Fuzzer a` by providing a `Generator a` and a `Shrinker a`. +Generators are defined in [`mgold/elm-random-pcg`](http://package.elm-lang.org/packages/mgold/elm-random-pcg/latest), +which is not core's Random module but has a compatible interface. Shrinkers are +defined in [`elm-community/shrink`](http://package.elm-lang.org/packages/elm-community/shrink/latest/). + +Here is an example for a record: + + import Random.Pcg as Random + import Shrink + + type alias Position = + { x : Int, y : Int } + + position : Fuzzer Position + position = + Fuzz.custom + (Random.map2 Position (Random.int -100 100) (Random.int -100 100)) + (\{ x, y } -> Shrink.map Position (Shrink.int x) |> Shrink.andMap (Shrink.int y)) + +Here is an example for a custom union type, assuming there is already a `genName : Generator String` defined: + + type Question + = Name String + | Age Int + + question = + let + generator = + Random.bool |> Random.andThen (\b -> + if b then + Random.map Name genName + else + Random.map Age (Random.int 0 120) + ) + + shrinker question = + case question of + Name n -> + Shrink.string n |> Shrink.map Name + Age i -> + Shrink.int i |> Shrink.map Age + in + Fuzz.custom generator shrinker + +It is not possible to extract the generator and shrinker from an existing fuzzer. +-} +custom : Generator a -> Shrinker a -> Fuzzer a +custom generator shrinker = + let + shrinkTree a = + Rose a (Lazy.List.map shrinkTree (shrinker a)) + in + Internal.Fuzzer + (\noShrink -> + if noShrink then + Gen generator + else + Shrink <| Random.map shrinkTree generator + ) + + +{-| A fuzzer for the unit value. Unit is a type with only one value, commonly +used as a placeholder. +-} +unit : Fuzzer () +unit = + Internal.Fuzzer + (\noShrink -> + if noShrink then + Gen <| Random.constant () + else + Shrink <| Random.constant (RoseTree.singleton ()) + ) + + +{-| A fuzzer for bool values. +-} +bool : Fuzzer Bool +bool = + custom Random.bool Shrink.bool + + +{-| A fuzzer for order values. +-} +order : Fuzzer Order +order = + let + intToOrder i = + if i == 0 then + LT + else if i == 1 then + EQ + else + GT + in + custom (Random.map intToOrder (Random.int 0 2)) Shrink.order + + +{-| A fuzzer for int values. It will never produce `NaN`, `Infinity`, or `-Infinity`. + +It's possible for this fuzzer to generate any 32-bit integer, but it favors +numbers between -50 and 50 and especially zero. +-} +int : Fuzzer Int +int = + let + generator = + Random.frequency + [ ( 3, Random.int -50 50 ) + , ( 0.2, Random.constant 0 ) + , ( 1, Random.int 0 (Random.maxInt - Random.minInt) ) + , ( 1, Random.int (Random.minInt - Random.maxInt) 0 ) + ] + in + custom generator Shrink.int + + +{-| A fuzzer for int values within between a given minimum and maximum value, +inclusive. Shrunken values will also be within the range. + +Remember that [Random.maxInt](http://package.elm-lang.org/packages/elm-lang/core/latest/Random#maxInt) +is the maximum possible int value, so you can do `intRange x Random.maxInt` to get all +the ints x or bigger. +-} +intRange : Int -> Int -> Fuzzer Int +intRange lo hi = + custom + (Random.frequency + [ ( 8, Random.int lo hi ) + , ( 1, Random.constant lo ) + , ( 1, Random.constant hi ) + ] + ) + (Shrink.keepIf (\i -> i >= lo && i <= hi) Shrink.int) + + +{-| A fuzzer for float values. It will never produce `NaN`, `Infinity`, or `-Infinity`. + + +It's possible for this fuzzer to generate any other floating-point value, but it +favors numbers between -50 and 50, numbers between -1 and 1, and especially zero. +-} +float : Fuzzer Float +float = + let + generator = + Random.frequency + [ ( 3, Random.float -50 50 ) + , ( 0.5, Random.constant 0 ) + , ( 1, Random.float -1 1 ) + , ( 1, Random.float 0 (toFloat <| Random.maxInt - Random.minInt) ) + , ( 1, Random.float (toFloat <| Random.minInt - Random.maxInt) 0 ) + ] + in + custom generator Shrink.float + + +{-| A fuzzer for float values within between a given minimum and maximum +value, inclusive. Shrunken values will also be within the range. +-} +floatRange : Float -> Float -> Fuzzer Float +floatRange lo hi = + custom + (Random.frequency + [ ( 8, Random.float lo hi ) + , ( 1, Random.constant lo ) + , ( 1, Random.constant hi ) + ] + ) + (Shrink.keepIf (\i -> i >= lo && i <= hi) Shrink.float) + + +{-| A fuzzer for percentage values. Generates random floats between `0.0` and +`1.0`. It will test zero and one about 10% of the time each. +-} +percentage : Fuzzer Float +percentage = + let + generator = + Random.frequency + [ ( 8, Random.float 0 1 ) + , ( 1, Random.constant 0 ) + , ( 1, Random.constant 1 ) + ] + in + custom generator Shrink.float + + +{-| A fuzzer for char values. Generates random ascii chars disregarding the control +characters. +-} +char : Fuzzer Char +char = + custom charGenerator Shrink.character + + +charGenerator : Generator Char +charGenerator = + (Random.map Char.fromCode (Random.int 32 126)) + + +{-| Generates random printable ASCII strings of up to 1000 characters. + +Shorter strings are more common, especially the empty string. +-} +string : Fuzzer String +string = + let + generator : Generator String + generator = + Random.frequency + [ ( 3, Random.int 1 10 ) + , ( 0.2, Random.constant 0 ) + , ( 1, Random.int 11 50 ) + , ( 1, Random.int 50 1000 ) + ] + |> Random.andThen (lengthString charGenerator) + in + custom generator Shrink.string + + +{-| Given a fuzzer of a type, create a fuzzer of a maybe for that type. +-} +maybe : Fuzzer a -> Fuzzer (Maybe a) +maybe (Internal.Fuzzer baseFuzzer) = + Internal.Fuzzer <| + \noShrink -> + case baseFuzzer noShrink of + Gen gen -> + Gen <| + Random.map2 + (\useNothing val -> + if useNothing then + Nothing + else + Just val + ) + (Random.oneIn 4) + gen + + Shrink genTree -> + Shrink <| + Random.map2 + (\useNothing tree -> + if useNothing then + RoseTree.singleton Nothing + else + RoseTree.map Just tree |> RoseTree.addChild (RoseTree.singleton Nothing) + ) + (Random.oneIn 4) + genTree + + +{-| Given fuzzers for an error type and a success type, create a fuzzer for +a result. +-} +result : Fuzzer error -> Fuzzer value -> Fuzzer (Result error value) +result (Internal.Fuzzer baseFuzzerError) (Internal.Fuzzer baseFuzzerValue) = + Internal.Fuzzer <| + \noShrink -> + case ( baseFuzzerError noShrink, baseFuzzerValue noShrink ) of + ( Gen genErr, Gen genVal ) -> + Gen <| + Random.map3 + (\useError err val -> + if useError then + Err err + else + Ok val + ) + (Random.oneIn 4) + genErr + genVal + + ( Shrink genTreeErr, Shrink genTreeVal ) -> + Shrink <| + Random.map3 + (\useError errorTree valueTree -> + if useError then + RoseTree.map Err errorTree + else + RoseTree.map Ok valueTree + ) + (Random.oneIn 4) + genTreeErr + genTreeVal + + err -> + Debug.crash "This shouldn't happen: Fuzz.result" err + + +{-| Given a fuzzer of a type, create a fuzzer of a list of that type. +Generates random lists of varying length, favoring shorter lists. +-} +list : Fuzzer a -> Fuzzer (List a) +list (Internal.Fuzzer baseFuzzer) = + let + genLength = + Random.frequency + [ ( 1, Random.constant 0 ) + , ( 1, Random.constant 1 ) + , ( 3, Random.int 2 10 ) + , ( 2, Random.int 10 100 ) + , ( 0.5, Random.int 100 400 ) + ] + in + Internal.Fuzzer + (\noShrink -> + case baseFuzzer noShrink of + Gen genVal -> + genLength + |> Random.andThen (\i -> (Random.list i genVal)) + |> Gen + + Shrink genTree -> + genLength + |> Random.andThen (\i -> (Random.list i genTree)) + |> Random.map listShrinkHelp + |> Shrink + ) + + +listShrinkHelp : List (RoseTree a) -> RoseTree (List a) +listShrinkHelp listOfTrees = + {- Shrinking a list of RoseTrees + We need to do two things. First, shrink individual values. Second, shorten the list. + To shrink individual values, we create every list copy of the input list where any + one value is replaced by a shrunken form. + To shorten the length of the list, slide windows of various lengths over it. + In all cases, recurse! The goal is to make a little forward progress and then recurse. + -} + let + n = + List.length listOfTrees + + root = + List.map RoseTree.root listOfTrees + + shrinkOne prefix list = + case list of + [] -> + Lazy.List.empty + + (Rose x shrunkenXs) :: more -> + Lazy.List.map (\childTree -> prefix ++ (childTree :: more) |> listShrinkHelp) shrunkenXs + + shrunkenVals = + Lazy.List.numbers + |> Lazy.List.map (\i -> i - 1) + |> Lazy.List.take n + |> Lazy.List.andThen + (\i -> shrinkOne (List.take i listOfTrees) (List.drop i listOfTrees)) + + shortened = + (if n > 6 then + Lazy.List.iterate (\n -> n // 2) n + |> Lazy.List.takeWhile (\x -> x > 0) + else + Lazy.List.fromList (List.range 1 n) + ) + |> Lazy.List.andThen (\len -> shorter len listOfTrees False) + |> Lazy.List.map listShrinkHelp + + shorter windowSize aList recursing = + -- Tricky: take the whole list if we've recursed down here, but don't let a list shrink to itself + if windowSize > List.length aList || (windowSize == List.length aList && not recursing) then + Lazy.List.empty + else + case aList of + [] -> + Lazy.List.empty + + head :: tail -> + Lazy.List.cons (List.take windowSize aList) (shorter windowSize tail True) + in + Lazy.List.append shortened shrunkenVals + |> Lazy.List.cons (RoseTree.singleton []) + |> Rose root + + +{-| Given a fuzzer of a type, create a fuzzer of an array of that type. +Generates random arrays of varying length, favoring shorter arrays. +-} +array : Fuzzer a -> Fuzzer (Array a) +array fuzzer = + map Array.fromList (list fuzzer) + + +{-| Turn a tuple of fuzzers into a fuzzer of tuples. +-} +tuple : ( Fuzzer a, Fuzzer b ) -> Fuzzer ( a, b ) +tuple ( Internal.Fuzzer baseFuzzerA, Internal.Fuzzer baseFuzzerB ) = + Internal.Fuzzer + (\noShrink -> + case ( baseFuzzerA noShrink, baseFuzzerB noShrink ) of + ( Gen genA, Gen genB ) -> + Gen <| Random.map2 (,) genA genB + + ( Shrink genTreeA, Shrink genTreeB ) -> + Shrink <| Random.map2 tupleShrinkHelp genTreeA genTreeB + + err -> + Debug.crash "This shouldn't happen: Fuzz.tuple" err + ) + + +tupleShrinkHelp : RoseTree a -> RoseTree b -> RoseTree ( a, b ) +tupleShrinkHelp ((Rose root1 children1) as rose1) ((Rose root2 children2) as rose2) = + {- Shrinking a tuple of RoseTrees + Recurse on all tuples created by substituting one element for any of its shrunken values. + + A weakness of this algorithm is that it expects that values can be shrunken independently. + That is, to shrink from (a,b) to (a',b'), we must go through (a',b) or (a,b'). + "No pairs sum to zero" is a pathological predicate that cannot be shrunken this way. + -} + let + root = + ( root1, root2 ) + + shrink1 = + Lazy.List.map (\subtree -> tupleShrinkHelp subtree rose2) children1 + + shrink2 = + Lazy.List.map (\subtree -> tupleShrinkHelp rose1 subtree) children2 + in + shrink2 + |> Lazy.List.append shrink1 + |> Rose root + + +{-| Turn a 3-tuple of fuzzers into a fuzzer of 3-tuples. +-} +tuple3 : ( Fuzzer a, Fuzzer b, Fuzzer c ) -> Fuzzer ( a, b, c ) +tuple3 ( Internal.Fuzzer baseFuzzerA, Internal.Fuzzer baseFuzzerB, Internal.Fuzzer baseFuzzerC ) = + Internal.Fuzzer + (\noShrink -> + case ( baseFuzzerA noShrink, baseFuzzerB noShrink, baseFuzzerC noShrink ) of + ( Gen genA, Gen genB, Gen genC ) -> + Gen <| Random.map3 (,,) genA genB genC + + ( Shrink genTreeA, Shrink genTreeB, Shrink genTreeC ) -> + Shrink <| Random.map3 tupleShrinkHelp3 genTreeA genTreeB genTreeC + + err -> + Debug.crash "This shouldn't happen: Fuzz.tuple3" err + ) + + +tupleShrinkHelp3 : RoseTree a -> RoseTree b -> RoseTree c -> RoseTree ( a, b, c ) +tupleShrinkHelp3 ((Rose root1 children1) as rose1) ((Rose root2 children2) as rose2) ((Rose root3 children3) as rose3) = + let + root = + ( root1, root2, root3 ) + + shrink1 = + Lazy.List.map (\subtree -> tupleShrinkHelp3 subtree rose2 rose3) children1 + + shrink2 = + Lazy.List.map (\subtree -> tupleShrinkHelp3 rose1 subtree rose3) children2 + + shrink3 = + Lazy.List.map (\subtree -> tupleShrinkHelp3 rose1 rose2 subtree) children3 + in + shrink3 + |> Lazy.List.append shrink2 + |> Lazy.List.append shrink1 + |> Rose root + + +{-| Turn a 4-tuple of fuzzers into a fuzzer of 4-tuples. +-} +tuple4 : ( Fuzzer a, Fuzzer b, Fuzzer c, Fuzzer d ) -> Fuzzer ( a, b, c, d ) +tuple4 ( Internal.Fuzzer baseFuzzerA, Internal.Fuzzer baseFuzzerB, Internal.Fuzzer baseFuzzerC, Internal.Fuzzer baseFuzzerD ) = + Internal.Fuzzer + (\noShrink -> + case ( baseFuzzerA noShrink, baseFuzzerB noShrink, baseFuzzerC noShrink, baseFuzzerD noShrink ) of + ( Gen genA, Gen genB, Gen genC, Gen genD ) -> + Gen <| Random.map4 (,,,) genA genB genC genD + + ( Shrink genTreeA, Shrink genTreeB, Shrink genTreeC, Shrink genTreeD ) -> + Shrink <| Random.map4 tupleShrinkHelp4 genTreeA genTreeB genTreeC genTreeD + + err -> + Debug.crash "This shouldn't happen: Fuzz.tuple4" err + ) + + +tupleShrinkHelp4 : RoseTree a -> RoseTree b -> RoseTree c -> RoseTree d -> RoseTree ( a, b, c, d ) +tupleShrinkHelp4 rose1 rose2 rose3 rose4 = + let + root = + ( RoseTree.root rose1, RoseTree.root rose2, RoseTree.root rose3, RoseTree.root rose4 ) + + shrink1 = + Lazy.List.map (\subtree -> tupleShrinkHelp4 subtree rose2 rose3 rose4) (RoseTree.children rose1) + + shrink2 = + Lazy.List.map (\subtree -> tupleShrinkHelp4 rose1 subtree rose3 rose4) (RoseTree.children rose2) + + shrink3 = + Lazy.List.map (\subtree -> tupleShrinkHelp4 rose1 rose2 subtree rose4) (RoseTree.children rose3) + + shrink4 = + Lazy.List.map (\subtree -> tupleShrinkHelp4 rose1 rose2 rose3 subtree) (RoseTree.children rose4) + in + shrink4 + |> Lazy.List.append shrink3 + |> Lazy.List.append shrink2 + |> Lazy.List.append shrink1 + |> Rose root + + +{-| Turn a 5-tuple of fuzzers into a fuzzer of 5-tuples. +-} +tuple5 : ( Fuzzer a, Fuzzer b, Fuzzer c, Fuzzer d, Fuzzer e ) -> Fuzzer ( a, b, c, d, e ) +tuple5 ( Internal.Fuzzer baseFuzzerA, Internal.Fuzzer baseFuzzerB, Internal.Fuzzer baseFuzzerC, Internal.Fuzzer baseFuzzerD, Internal.Fuzzer baseFuzzerE ) = + Internal.Fuzzer + (\noShrink -> + case ( baseFuzzerA noShrink, baseFuzzerB noShrink, baseFuzzerC noShrink, baseFuzzerD noShrink, baseFuzzerE noShrink ) of + ( Gen genA, Gen genB, Gen genC, Gen genD, Gen genE ) -> + Gen <| Random.map5 (,,,,) genA genB genC genD genE + + ( Shrink genTreeA, Shrink genTreeB, Shrink genTreeC, Shrink genTreeD, Shrink genTreeE ) -> + Shrink <| Random.map5 tupleShrinkHelp5 genTreeA genTreeB genTreeC genTreeD genTreeE + + err -> + Debug.crash "This shouldn't happen: Fuzz.tuple5" err + ) + + +tupleShrinkHelp5 : RoseTree a -> RoseTree b -> RoseTree c -> RoseTree d -> RoseTree e -> RoseTree ( a, b, c, d, e ) +tupleShrinkHelp5 rose1 rose2 rose3 rose4 rose5 = + let + root = + ( RoseTree.root rose1, RoseTree.root rose2, RoseTree.root rose3, RoseTree.root rose4, RoseTree.root rose5 ) + + shrink1 = + Lazy.List.map (\subtree -> tupleShrinkHelp5 subtree rose2 rose3 rose4 rose5) (RoseTree.children rose1) + + shrink2 = + Lazy.List.map (\subtree -> tupleShrinkHelp5 rose1 subtree rose3 rose4 rose5) (RoseTree.children rose2) + + shrink3 = + Lazy.List.map (\subtree -> tupleShrinkHelp5 rose1 rose2 subtree rose4 rose5) (RoseTree.children rose3) + + shrink4 = + Lazy.List.map (\subtree -> tupleShrinkHelp5 rose1 rose2 rose3 subtree rose5) (RoseTree.children rose4) + + shrink5 = + Lazy.List.map (\subtree -> tupleShrinkHelp5 rose1 rose2 rose3 rose4 subtree) (RoseTree.children rose5) + in + shrink5 + |> Lazy.List.append shrink4 + |> Lazy.List.append shrink3 + |> Lazy.List.append shrink2 + |> Lazy.List.append shrink1 + |> Rose root + + +{-| Create a fuzzer that only and always returns the value provided, and performs no shrinking. This is hardly random, +and so this function is best used as a helper when creating more complicated fuzzers. +-} +constant : a -> Fuzzer a +constant x = + Internal.Fuzzer + (\noShrink -> + if noShrink then + Gen (Random.constant x) + else + Shrink (Random.constant (RoseTree.singleton x)) + ) + + +{-| Map a function over a fuzzer. This applies to both the generated and the shruken values. +-} +map : (a -> b) -> Fuzzer a -> Fuzzer b +map transform (Internal.Fuzzer baseFuzzer) = + Internal.Fuzzer + (\noShrink -> + case baseFuzzer noShrink of + Gen genVal -> + Gen <| Random.map transform genVal + + Shrink genTree -> + Shrink <| Random.map (RoseTree.map transform) genTree + ) + + +{-| Map over two fuzzers. +-} +map2 : (a -> b -> c) -> Fuzzer a -> Fuzzer b -> Fuzzer c +map2 transform fuzzA fuzzB = + map (\( a, b ) -> transform a b) (tuple ( fuzzA, fuzzB )) + + +{-| Map over three fuzzers. +-} +map3 : (a -> b -> c -> d) -> Fuzzer a -> Fuzzer b -> Fuzzer c -> Fuzzer d +map3 transform fuzzA fuzzB fuzzC = + map (\( a, b, c ) -> transform a b c) (tuple3 ( fuzzA, fuzzB, fuzzC )) + + +{-| Map over four fuzzers. +-} +map4 : (a -> b -> c -> d -> e) -> Fuzzer a -> Fuzzer b -> Fuzzer c -> Fuzzer d -> Fuzzer e +map4 transform fuzzA fuzzB fuzzC fuzzD = + map (\( a, b, c, d ) -> transform a b c d) (tuple4 ( fuzzA, fuzzB, fuzzC, fuzzD )) + + +{-| Map over five fuzzers. +-} +map5 : (a -> b -> c -> d -> e -> f) -> Fuzzer a -> Fuzzer b -> Fuzzer c -> Fuzzer d -> Fuzzer e -> Fuzzer f +map5 transform fuzzA fuzzB fuzzC fuzzD fuzzE = + map (\( a, b, c, d, e ) -> transform a b c d e) (tuple5 ( fuzzA, fuzzB, fuzzC, fuzzD, fuzzE )) + + +{-| Map over many fuzzers. This can act as mapN for N > 5. + +The argument order is meant to accomodate chaining: + + map f aFuzzer + |> andMap anotherFuzzer + |> andMap aThirdFuzzer + +Note that shrinking may be better using mapN. +-} +andMap : Fuzzer a -> Fuzzer (a -> b) -> Fuzzer b +andMap = + map2 (|>) + + +{-| Create a fuzzer based on the result of another fuzzer. +-} +andThen : (a -> Fuzzer b) -> Fuzzer a -> Fuzzer b +andThen transform (Internal.Fuzzer baseFuzzer) = + Internal.Fuzzer + (\noShrink -> + case baseFuzzer noShrink of + Gen genVal -> + Gen <| Random.andThen (transform >> Internal.unpackGenVal) genVal + + Shrink genTree -> + Shrink <| andThenRoseTrees transform genTree + ) + + +andThenRoseTrees : (a -> Fuzzer b) -> Generator (RoseTree a) -> Generator (RoseTree b) +andThenRoseTrees transform genTree = + genTree + |> Random.andThen + (\(Rose root branches) -> + let + genOtherChildren : Generator (LazyList (RoseTree b)) + genOtherChildren = + branches + |> Lazy.List.map (\rt -> RoseTree.map (transform >> Internal.unpackGenTree) rt |> unwindRoseTree) + |> unwindLazyList + |> Random.map (Lazy.List.map RoseTree.flatten) + in + Random.map2 + (\(Rose trueRoot rootsChildren) otherChildren -> + Rose trueRoot (Lazy.List.append rootsChildren otherChildren) + ) + (Internal.unpackGenTree (transform root)) + genOtherChildren + ) + + +unwindRoseTree : RoseTree (Generator a) -> Generator (RoseTree a) +unwindRoseTree (Rose genRoot lazyListOfRoseTreesOfGenerators) = + case Lazy.List.headAndTail lazyListOfRoseTreesOfGenerators of + Nothing -> + Random.map RoseTree.singleton genRoot + + Just ( Rose gen children, moreList ) -> + Random.map4 (\a b c d -> Rose a (Lazy.List.cons (Rose b c) d)) + genRoot + gen + (Lazy.List.map unwindRoseTree children |> unwindLazyList) + (Lazy.List.map unwindRoseTree moreList |> unwindLazyList) + + +unwindLazyList : LazyList (Generator a) -> Generator (LazyList a) +unwindLazyList lazyListOfGenerators = + case Lazy.List.headAndTail lazyListOfGenerators of + Nothing -> + Random.constant Lazy.List.empty + + Just ( head, tail ) -> + Random.map2 Lazy.List.cons head (unwindLazyList tail) + + +{-| Create a new `Fuzzer` by providing a list of probabilistic weights to use +with other fuzzers. + +For example, to create a `Fuzzer` that has a 1/4 chance of generating an int +between -1 and -100, and a 3/4 chance of generating one between 1 and 100, +you could do this: + + Fuzz.frequency + [ ( 1, Fuzz.intRange -100 -1 ) + , ( 3, Fuzz.intRange 1 100 ) + ] + +This returns a `Result` because it can fail in a few ways: + +* If you provide an empy list of frequencies +* If any of the weights are less than 0 +* If the weights sum to 0 + +Any of these will lead to a result of `Err`, with a `String` explaining what +went wrong. +-} +frequency : List ( Float, Fuzzer a ) -> Result String (Fuzzer a) +frequency list = + if List.isEmpty list then + Err "You must provide at least one frequency pair." + else if List.any (\( weight, _ ) -> weight < 0) list then + Err "No frequency weights can be less than 0." + else if List.sum (List.map Tuple.first list) <= 0 then + Err "Frequency weights must sum to more than 0." + else + Ok <| + Internal.Fuzzer <| + \noShrink -> + if noShrink then + list + |> List.map (\( weight, fuzzer ) -> ( weight, Internal.unpackGenVal fuzzer )) + |> Random.frequency + |> Gen + else + list + |> List.map (\( weight, fuzzer ) -> ( weight, Internal.unpackGenTree fuzzer )) + |> Random.frequency + |> Shrink + + +{-| Calls `frequency` and handles `Err` results by crashing with the given +error message. + +This is useful in tests, where a crash will simply cause the test run to fail. +There is no danger to a production system there. +-} +frequencyOrCrash : List ( Float, Fuzzer a ) -> Fuzzer a +frequencyOrCrash = + frequency >> okOrCrash + + +okOrCrash : Result String a -> a +okOrCrash result = + case result of + Ok a -> + a + + Err str -> + Debug.crash str diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Fuzz/Internal.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Fuzz/Internal.elm new file mode 100644 index 0000000..a592f41 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Fuzz/Internal.elm @@ -0,0 +1,58 @@ +module Fuzz.Internal exposing (Fuzzer(Fuzzer), Fuzz(..), unpackGenVal, unpackGenTree) + +import RoseTree exposing (RoseTree) +import Random.Pcg exposing (Generator) + + +{- Fuzzers as opt-in RoseTrees + + In the beginning, a Fuzzer was a record of a random generator and a shrinker. + And it was bad, because that makes it impossible to shrink any value created by + mapping over other values. But at least it was fast, and shrinking worked well. + + On the second branch, we created RoseTrees, where every randomly-generated value + also kept a lazy list of shrunken values, which also keep shrunken forms of + themselves. This allows for advanced maps to be implemented, but it was slow. + + On the third branch, we realized that we shouldn't have to pay for shrinking in + the common case of a passing test. So Fuzzers became a function from a boolean + to either another union type. If the function is passed True, it returns a + Generator of a single value; if False, a Generator of a RoseTree of values. + (This is almost certainly dependent types leaning on Debug.crash.) The root of + the RoseTree must equal the single value. Thus the testing harness "opts-in" to + producing a rosetree, doing so only after the single-value generator has caused + a test to fail. + + These two optimizations make the Fuzzer code rather hard to understand, but + allow it to offer a full mapping API, be fast for passing tests, and provide + shrunken values for failing tests. +-} + + +type Fuzzer a + = Fuzzer (Bool -> Fuzz a) + + +type Fuzz a + = Gen (Generator a) + | Shrink (Generator (RoseTree a)) + + +unpackGenVal : Fuzzer a -> Generator a +unpackGenVal (Fuzzer g) = + case g True of + Gen genVal -> + genVal + + err -> + Debug.crash "This shouldn't happen: Fuzz.Internal.unpackGenVal" err + + +unpackGenTree : Fuzzer a -> Generator (RoseTree a) +unpackGenTree (Fuzzer g) = + case g False of + Shrink genTree -> + genTree + + err -> + Debug.crash "This shouldn't happen: Fuzz.Internal.unpackGenTree" err diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/RoseTree.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/RoseTree.elm new file mode 100644 index 0000000..2cc8553 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/RoseTree.elm @@ -0,0 +1,59 @@ +module RoseTree exposing (..) + +{-| RoseTree implementation in Elm using Lazy Lists. + +This implementation is private to elm-test and has non-essential functions removed. +If you need a complete RoseTree implementation, one can be found on elm-package. +-} + +import Lazy.List as LazyList exposing (LazyList, (:::), (+++)) + + +{-| RoseTree type. +A rosetree is a tree with a root whose children are themselves +rosetrees. +-} +type RoseTree a + = Rose a (LazyList (RoseTree a)) + + +{-| Make a singleton rosetree +-} +singleton : a -> RoseTree a +singleton a = + Rose a LazyList.empty + + +{-| Get the root of a rosetree +-} +root : RoseTree a -> a +root (Rose a _) = + a + + +{-| Get the children of a rosetree +-} +children : RoseTree a -> LazyList (RoseTree a) +children (Rose _ c) = + c + + +{-| Add a child to the rosetree. +-} +addChild : RoseTree a -> RoseTree a -> RoseTree a +addChild child (Rose a c) = + Rose a (child ::: c) + + +{-| Map a function over a rosetree +-} +map : (a -> b) -> RoseTree a -> RoseTree b +map f (Rose a c) = + Rose (f a) (LazyList.map (map f) c) + + +{-| Flatten a rosetree of rosetrees. +-} +flatten : RoseTree (RoseTree a) -> RoseTree a +flatten (Rose (Rose a c) cs) = + Rose a (c +++ LazyList.map flatten cs) diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test.elm new file mode 100644 index 0000000..486f6df --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test.elm @@ -0,0 +1,307 @@ +module Test exposing (Test, FuzzOptions, describe, test, filter, concat, fuzz, fuzz2, fuzz3, fuzz4, fuzz5, fuzzWith) + +{-| A module containing functions for creating and managing tests. + +@docs Test, test + +## Organizing Tests + +@docs describe, concat, filter + +## Fuzz Testing + +@docs fuzz, fuzz2, fuzz3, fuzz4, fuzz5, fuzzWith, FuzzOptions +-} + +import Test.Internal as Internal +import Expect exposing (Expectation) +import Fuzz exposing (Fuzzer) + + +{-| A test which has yet to be evaluated. When evaluated, it produces one +or more [`Expectation`](../Expect#Expectation)s. + +See [`test`](#test) and [`fuzz`](#fuzz) for some ways to create a `Test`. +-} +type alias Test = + Internal.Test + + +{-| Run each of the given tests. + + concat [ testDecoder, testSorting ] +-} +concat : List Test -> Test +concat = + Internal.Batch + + +{-| Remove any test unless its description satisfies the given predicate +function. Nested descriptions added with [`describe`](#describe) are not considered. + + describe "String.reverse" + [ test "has no effect on a palindrome" testGoesHere + , test "reverses a known string" anotherTest + , fuzz string "restores the original string if you run it again" oneMore + ] + |> Test.filter (String.contains "original") + + -- only runs the final test + +You can use this to focus on a specific test or two, silencing the failures of +tests you don't want to work on yet, and then remove the call to `Test.filter` +after you're done working on the tests. +-} +filter : (String -> Bool) -> Test -> Test +filter = + Internal.filter + + +{-| Apply a description to a list of tests. + + import Test exposing (describe, test, fuzz) + import Fuzz exposing (int) + import Expect + + + describe "List" + [ describe "reverse" + [ test "has no effect on an empty list" <| + \() -> + List.reverse [] + |> Expect.toEqual [] + , fuzz int "has no effect on a one-item list" <| + \num -> + List.reverse [ num ] + |> Expect.toEqual [ num ] + ] + ] +-} +describe : String -> List Test -> Test +describe desc = + Internal.Batch >> Internal.Labeled desc + + +{-| Return a [`Test`](#Test) that evaluates a single +[`Expectation`](../Expect#Expectation). + + import Test exposing (fuzz) + import Expect + + + test "the empty list has 0 length" <| + \() -> + List.length [] + |> Expect.toEqual 0 +-} +test : String -> (() -> Expectation) -> Test +test desc thunk = + Internal.Labeled desc (Internal.Test (\_ _ -> [ thunk () ])) + + +{-| Options [`fuzzWith`](#fuzzWith) accepts. Currently there is only one but this +API is designed so that it can accept more in the future. + +### `runs` + +The number of times to run each fuzz test. (Default is 100.) + + import Test exposing (fuzzWith) + import Fuzz exposing (list, int) + import Expect + + + fuzzWith { runs = 350 } (list int) "List.length should always be positive" <| + -- This anonymous function will be run 350 times, each time with a + -- randomly-generated fuzzList value. (It will always be a list of ints + -- because of (list int) above.) + \fuzzList -> + fuzzList + |> List.length + |> Expect.atLeast 0 +-} +type alias FuzzOptions = + { runs : Int } + + +{-| Run a [`fuzz`](#fuzz) test with the given [`FuzzOptions`](#FuzzOptions). + +Note that there is no `fuzzWith2`, but you can always pass more fuzz values in +using [`Fuzz.tuple`](../Fuzz#tuple), [`Fuzz.tuple3`](../Fuzz#tuple3), +for example like this: + + import Test exposing (fuzzWith) + import Fuzz exposing (tuple, list, int) + import Expect + + + fuzzWith { runs = 4200 } + (tuple ( list int, int )) + "List.reverse never influences List.member" <| + \(nums, target) -> + List.member target (List.reverse nums) + |> Expect.toEqual (List.member target nums) +-} +fuzzWith : FuzzOptions -> Fuzzer a -> String -> (a -> Expectation) -> Test +fuzzWith options fuzzer desc getTest = + if options.runs < 1 then + test desc <| + \() -> + Expect.fail ("Fuzz test run count must be at least 1, not " ++ toString options.runs) + else + fuzzWithHelp options (fuzz fuzzer desc getTest) + + +fuzzWithHelp : FuzzOptions -> Test -> Test +fuzzWithHelp options test = + case test of + Internal.Test run -> + Internal.Test (\seed _ -> run seed options.runs) + + Internal.Labeled label subTest -> + Internal.Labeled label (fuzzWithHelp options subTest) + + Internal.Batch tests -> + tests + |> List.map (fuzzWithHelp options) + |> Internal.Batch + + +{-| Take a function that produces a test, and calls it several (usually 100) times, using a randomly-generated input +from a [`Fuzzer`](http://package.elm-lang.org/packages/elm-community/elm-test/latest/Fuzz) each time. This allows you to +test that a property that should always be true is indeed true under a wide variety of conditions. The function also +takes a string describing the test. + +These are called "[fuzz tests](https://en.wikipedia.org/wiki/Fuzz_testing)" because of the randomness. +You may find them elsewhere called [property-based tests](http://blog.jessitron.com/2013/04/property-based-testing-what-is-it.html), +[generative tests](http://www.pivotaltracker.com/community/tracker-blog/generative-testing), or +[QuickCheck-style tests](https://en.wikipedia.org/wiki/QuickCheck). + + import Test exposing (fuzz) + import Fuzz exposing (list, int) + import Expect + + + fuzz (list int) "List.length should always be positive" <| + -- This anonymous function will be run 100 times, each time with a + -- randomly-generated fuzzList value. + \fuzzList -> + fuzzList + |> List.length + |> Expect.atLeast 0 +-} +fuzz : + Fuzzer a + -> String + -> (a -> Expectation) + -> Test +fuzz = + Internal.fuzzTest + + +{-| Run a [fuzz test](#fuzz) using two random inputs. + +This is a convenience function that lets you skip calling [`Fuzz.tuple`](../Fuzz#tuple). + +See [`fuzzWith`](#fuzzWith) for an example of writing this in tuple style. + + import Test exposing (fuzz2) + import Fuzz exposing (list, int) + + + fuzz2 (list int) int "List.reverse never influences List.member" <| + \nums target -> + List.member target (List.reverse nums) + |> Expect.toEqual (List.member target nums) +-} +fuzz2 : + Fuzzer a + -> Fuzzer b + -> String + -> (a -> b -> Expectation) + -> Test +fuzz2 fuzzA fuzzB desc = + let + fuzzer = + Fuzz.tuple ( fuzzA, fuzzB ) + in + uncurry >> fuzz fuzzer desc + + +{-| Run a [fuzz test](#fuzz) using three random inputs. + +This is a convenience function that lets you skip calling [`Fuzz.tuple3`](../Fuzz#tuple3). +-} +fuzz3 : + Fuzzer a + -> Fuzzer b + -> Fuzzer c + -> String + -> (a -> b -> c -> Expectation) + -> Test +fuzz3 fuzzA fuzzB fuzzC desc = + let + fuzzer = + Fuzz.tuple3 ( fuzzA, fuzzB, fuzzC ) + in + uncurry3 >> fuzz fuzzer desc + + +{-| Run a [fuzz test](#fuzz) using four random inputs. + +This is a convenience function that lets you skip calling [`Fuzz.tuple4`](../Fuzz#tuple4). +-} +fuzz4 : + Fuzzer a + -> Fuzzer b + -> Fuzzer c + -> Fuzzer d + -> String + -> (a -> b -> c -> d -> Expectation) + -> Test +fuzz4 fuzzA fuzzB fuzzC fuzzD desc = + let + fuzzer = + Fuzz.tuple4 ( fuzzA, fuzzB, fuzzC, fuzzD ) + in + uncurry4 >> fuzz fuzzer desc + + +{-| Run a [fuzz test](#fuzz) using five random inputs. + +This is a convenience function that lets you skip calling [`Fuzz.tuple5`](../Fuzz#tuple5). +-} +fuzz5 : + Fuzzer a + -> Fuzzer b + -> Fuzzer c + -> Fuzzer d + -> Fuzzer e + -> String + -> (a -> b -> c -> d -> e -> Expectation) + -> Test +fuzz5 fuzzA fuzzB fuzzC fuzzD fuzzE desc = + let + fuzzer = + Fuzz.tuple5 ( fuzzA, fuzzB, fuzzC, fuzzD, fuzzE ) + in + uncurry5 >> fuzz fuzzer desc + + + +-- INTERNAL HELPERS -- + + +uncurry3 : (a -> b -> c -> d) -> ( a, b, c ) -> d +uncurry3 fn ( a, b, c ) = + fn a b c + + +uncurry4 : (a -> b -> c -> d -> e) -> ( a, b, c, d ) -> e +uncurry4 fn ( a, b, c, d ) = + fn a b c d + + +uncurry5 : (a -> b -> c -> d -> e -> f) -> ( a, b, c, d, e ) -> f +uncurry5 fn ( a, b, c, d, e ) = + fn a b c d e diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test/Expectation.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test/Expectation.elm new file mode 100644 index 0000000..fdb24b1 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test/Expectation.elm @@ -0,0 +1,16 @@ +module Test.Expectation exposing (Expectation(..), withGiven) + + +type Expectation + = Pass + | Fail String String + + +withGiven : String -> Expectation -> Expectation +withGiven given outcome = + case outcome of + Fail _ message -> + Fail given message + + Pass -> + outcome diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test/Internal.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test/Internal.elm new file mode 100644 index 0000000..b46c711 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test/Internal.elm @@ -0,0 +1,133 @@ +module Test.Internal exposing (Test(..), fuzzTest, filter) + +import Random.Pcg as Random exposing (Generator) +import Test.Expectation exposing (Expectation(..)) +import Dict exposing (Dict) +import Shrink exposing (Shrinker) +import Fuzz exposing (Fuzzer) +import Fuzz.Internal exposing (unpackGenVal, unpackGenTree) +import RoseTree exposing (RoseTree(..)) +import Lazy.List + + +type Test + = Test (Random.Seed -> Int -> List Expectation) + | Labeled String Test + | Batch (List Test) + + +filter : (String -> Bool) -> Test -> Test +filter = + filterHelp False + + +filterHelp : Bool -> (String -> Bool) -> Test -> Test +filterHelp lastCheckPassed isKeepable test = + case test of + Test _ -> + if lastCheckPassed then + test + else + Batch [] + + Labeled desc labeledTest -> + labeledTest + |> filterHelp (isKeepable desc) isKeepable + |> Labeled desc + + Batch tests -> + tests + |> List.map (filterHelp lastCheckPassed isKeepable) + |> Batch + + +fuzzTest : Fuzzer a -> String -> (a -> Expectation) -> Test +fuzzTest fuzzer desc getExpectation = + {- Fuzz test algorithm with opt-in RoseTrees: + Generate a single value by passing the fuzzer True (indicates skip shrinking) + Run the test on that value. If it fails: + Generate the rosetree by passing the fuzzer False *and the same random seed* + Find the new failure by looking at the children for any shrunken values: + If a shrunken value causes a failure, recurse on its children + If no shrunken value replicates the failure, use the root + Whether it passes or fails, do this n times + -} + let + getFailures failures currentSeed remainingRuns = + let + genVal = + unpackGenVal fuzzer + + ( value, nextSeed ) = + Random.step genVal currentSeed + + newFailures = + case getExpectation value of + Pass -> + failures + + failedExpectation -> + let + genTree = + unpackGenTree fuzzer + + ( rosetree, nextSeedAgain ) = + Random.step genTree currentSeed + in + shrinkAndAdd rosetree getExpectation failedExpectation failures + in + if remainingRuns == 1 then + newFailures + else + getFailures newFailures nextSeed (remainingRuns - 1) + + run seed runs = + let + -- Use a Dict so we don't report duplicate inputs. + failures : Dict String Expectation + failures = + getFailures Dict.empty seed runs + in + -- Make sure if we passed, we don't do any more work. + if Dict.isEmpty failures then + [ Pass ] + else + failures + |> Dict.toList + |> List.map formatExpectation + in + Labeled desc (Test run) + + +shrinkAndAdd : RoseTree a -> (a -> Expectation) -> Expectation -> Dict String Expectation -> Dict String Expectation +shrinkAndAdd rootTree getExpectation rootsExpectation dict = + -- Knowing that the root already failed, adds the shrunken failure to the dictionary + let + shrink oldExpectation (Rose root branches) = + case Lazy.List.headAndTail branches of + Just ( (Rose branch _) as rosetree, moreLazyRoseTrees ) -> + -- either way, recurse with the most recent failing expectation, and failing input with its list of shrunken values + case getExpectation branch of + Pass -> + shrink oldExpectation (Rose root moreLazyRoseTrees) + + newExpectation -> + shrink newExpectation rosetree + + Nothing -> + ( root, oldExpectation ) + + ( result, finalExpectation ) = + shrink rootsExpectation rootTree + in + Dict.insert (toString result) finalExpectation dict + + +formatExpectation : ( String, Expectation ) -> Expectation +formatExpectation ( given, expectation ) = + Test.Expectation.withGiven given expectation + + +isFail : Expectation -> Bool +isFail = + (/=) Pass diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test/Runner.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test/Runner.elm new file mode 100644 index 0000000..a4da083 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Test/Runner.elm @@ -0,0 +1,149 @@ +module Test.Runner exposing (Runnable, Runner(..), run, fromTest, formatLabels) + +{-| A collection of functions used by authors of test runners. To run your +own tests, you should use these runners; see the `README` for more information. + +## Runner + +@docs Runner, fromTest + +## Runnable + +@docs Runnable, run + +## Formatting + +@docs formatLabels +-} + +import Test exposing (Test) +import Test.Internal as Internal +import Expect exposing (Expectation) +import Random.Pcg +import String + + +{-| An unevaluated test. Run it with [`run`](#run) to evaluate it into a +list of `Expectation`s. +-} +type Runnable + = Thunk (() -> List Expectation) + + +{-| A structured test runner, incorporating: + +* The expectations to run +* The hierarchy of description strings that describe the results +-} +type Runner + = Runnable Runnable + | Labeled String Runner + | Batch (List Runner) + + +{-| Evaluate a [`Runnable`](#Runnable) to get a list of `Expectation`s. +-} +run : Runnable -> List Expectation +run (Thunk fn) = + fn () + + +{-| Convert a `Test` into a `Runner`. + +In order to run any fuzz tests that the `Test` may have, it requires a default run count as well +as an initial `Random.Pcg.Seed`. `100` is a good run count. To obtain a good random seed, pass a +random 32-bit integer to `Random.Pcg.initialSeed`. You can obtain such an integer by running +`Math.floor(Math.random()*0xFFFFFFFF)` in Node. It's typically fine to hard-code this value into +your Elm code; it's easy and makes your tests reproducible. +-} +fromTest : Int -> Random.Pcg.Seed -> Test -> Runner +fromTest runs seed test = + if runs < 1 then + Thunk (\() -> [ Expect.fail ("Test runner run count must be at least 1, not " ++ toString runs) ]) + |> Runnable + else + case test of + Internal.Test run -> + Thunk (\() -> run seed runs) + |> Runnable + + Internal.Labeled label subTest -> + subTest + |> fromTest runs seed + |> Labeled label + + Internal.Batch subTests -> + subTests + |> List.foldl (distributeSeeds runs) ( seed, [] ) + |> Tuple.second + |> Batch + + +distributeSeeds : Int -> Test -> ( Random.Pcg.Seed, List Runner ) -> ( Random.Pcg.Seed, List Runner ) +distributeSeeds runs test ( startingSeed, runners ) = + case test of + Internal.Test run -> + let + ( seed, nextSeed ) = + Random.Pcg.step Random.Pcg.independentSeed startingSeed + in + ( nextSeed, runners ++ [ Runnable (Thunk (\() -> run seed runs)) ] ) + + Internal.Labeled label subTest -> + let + ( nextSeed, nextRunners ) = + distributeSeeds runs subTest ( startingSeed, [] ) + + finalRunners = + List.map (Labeled label) nextRunners + in + ( nextSeed, runners ++ finalRunners ) + + Internal.Batch tests -> + let + ( nextSeed, nextRunners ) = + List.foldl (distributeSeeds runs) ( startingSeed, [] ) tests + in + ( nextSeed, [ Batch (runners ++ nextRunners) ] ) + + +{-| A standard way to format descriptiona and test labels, to keep things +consistent across test runner implementations. + +The HTML, Node, String, and Log runners all use this. + +What it does: + +* drop any labels that are empty strings +* format the first label differently from the others +* reverse the resulting list + + [ "the actual test that failed" + , "nested description failure" + , "top-level description failure" + ] + |> formatLabels ((++) "↓ ") ((++) "✗ ") + + {- + [ "↓ top-level description failure" + , "↓ nested description failure" + , "✗ the actual test that failed" + ] + -} + +-} +formatLabels : + (String -> format) + -> (String -> format) + -> List String + -> List format +formatLabels formatDescription formatTest labels = + case List.filter (not << String.isEmpty) labels of + [] -> + [] + + test :: descriptions -> + descriptions + |> List.map formatDescription + |> (::) (formatTest test) + |> List.reverse diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Util.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Util.elm new file mode 100644 index 0000000..1cbcd51 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/src/Util.elm @@ -0,0 +1,32 @@ +module Util exposing (..) + +{-| This is where I'm sticking Random helper functions I don't want to add to Pcg. +-} + +import Random.Pcg exposing (..) +import Array exposing (Array) +import String + + +rangeLengthList : Int -> Int -> Generator a -> Generator (List a) +rangeLengthList minLength maxLength generator = + (int minLength maxLength) + |> andThen (\len -> list len generator) + + +rangeLengthArray : Int -> Int -> Generator a -> Generator (Array a) +rangeLengthArray minLength maxLength generator = + rangeLengthList minLength maxLength generator + |> map Array.fromList + + +rangeLengthString : Int -> Int -> Generator Char -> Generator String +rangeLengthString minLength maxLength charGenerator = + (int minLength maxLength) + |> andThen (lengthString charGenerator) + + +lengthString : Generator Char -> Int -> Generator String +lengthString charGenerator stringLength = + list stringLength charGenerator + |> map String.fromList diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Main.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Main.elm new file mode 100644 index 0000000..3aafeb6 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Main.elm @@ -0,0 +1,22 @@ +module Main exposing (..) + +{-| HOW TO RUN THESE TESTS + +$ npm test + +Note that this always uses an initial seed of 902101337, since it can't do effects. +-} + +import Runner.Log +import Html +import Tests + + +main : Program Never () msg +main = + Html.beginnerProgram + { model = () + , update = \_ _ -> () + , view = \() -> Html.text "Check the console for useful output!" + } + |> Runner.Log.run Tests.all diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/README.md b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/README.md new file mode 100644 index 0000000..d02b5de --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/README.md @@ -0,0 +1,6 @@ +## Running the tests for elm-test itself + +1. `cd` into this directory +2. `npm install` +3. `elm package install --yes` +4. `npm test` diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Runner/Log.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Runner/Log.elm new file mode 100644 index 0000000..84c9187 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Runner/Log.elm @@ -0,0 +1,76 @@ +module Runner.Log exposing (run, runWithOptions) + +{-| # Log Runner + +Runs a test and outputs its results using `Debug.log`, then calls `Debug.crash` +if there are any failures. + +This is not the prettiest runner, but it is simple and cross-platform. For +example, you can use it as a crude Node runner like so: + + $ elm-make LogRunnerExample.elm --output=elm.js + $ node elm.js + +This will log the test results to the console, then exit with exit code 0 +if the tests all passed, and 1 if any failed. + +@docs run, runWithOptions +-} + +import Random.Pcg as Random +import Test exposing (Test) +import Runner.String exposing (Summary) +import String + + +{-| Run the test using the default `Test.Runner.String` options. +-} +run : Test -> a -> a +run test = + Runner.String.run test + |> logOutput + + +{-| Run the test using the provided options. +-} +runWithOptions : Int -> Random.Seed -> Test -> a -> a +runWithOptions runs seed test = + Runner.String.runWithOptions runs seed test + |> logOutput + + +summarize : Summary -> String +summarize { output, passed, failed } = + let + headline = + if failed > 0 then + output ++ "\n\nTEST RUN FAILED" + else + "TEST RUN PASSED" + in + String.join "\n" + [ output + , headline ++ "\n" + , "Passed: " ++ toString passed + , "Failed: " ++ toString failed + ] + + +logOutput : Summary -> a -> a +logOutput summary arg = + let + output = + summarize summary ++ "\n\nExit code" + + _ = + if summary.failed > 0 then + output + |> (flip Debug.log 1) + |> (\_ -> Debug.crash "FAILED TEST RUN") + |> (\_ -> ()) + else + output + |> (flip Debug.log 0) + |> (\_ -> ()) + in + arg diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Runner/String.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Runner/String.elm new file mode 100644 index 0000000..1056a77 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Runner/String.elm @@ -0,0 +1,111 @@ +module Runner.String exposing (Summary, run, runWithOptions) + +{-| # String Runner + +Run a test and present its results as a nicely-formatted String, along with +a count of how many tests passed and failed. + +Note that this always uses an initial seed of 902101337, since it can't do effects. + +@docs Summary, run, runWithOptions +-} + +import Random.Pcg as Random +import Test exposing (Test) +import Expect exposing (Expectation) +import String +import Test.Runner exposing (Runner(..)) + + +{-| The output string, the number of passed tests, +and the number of failed tests. +-} +type alias Summary = + { output : String, passed : Int, failed : Int } + + +toOutput : Summary -> Runner -> Summary +toOutput = + flip (toOutputHelp []) + + +toOutputHelp : List String -> Runner -> Summary -> Summary +toOutputHelp labels runner summary = + case runner of + Runnable runnable -> + Test.Runner.run runnable + |> List.foldl fromExpectation summary + + Labeled label subRunner -> + toOutputHelp (label :: labels) subRunner summary + + Batch runners -> + List.foldl (toOutputHelp labels) summary runners + + +fromExpectation : Expectation -> Summary -> Summary +fromExpectation expectation summary = + case Expect.getFailure expectation of + Nothing -> + { summary | passed = summary.passed + 1 } + + Just { given, message } -> + let + prefix = + if String.isEmpty given then + "" + else + "Given " ++ given ++ "\n\n" + + newOutput = + "\n\n" ++ (prefix ++ indentLines message) ++ "\n" + in + { output = summary.output ++ newOutput + , failed = summary.failed + 1 + , passed = summary.passed + } + + +outputLabels : List String -> String +outputLabels labels = + labels + |> Test.Runner.formatLabels ((++) "↓ ") ((++) "✗ ") + |> String.join "\n" + + +defaultSeed : Random.Seed +defaultSeed = + Random.initialSeed 902101337 + + +defaultRuns : Int +defaultRuns = + 100 + + +indentLines : String -> String +indentLines str = + str + |> String.split "\n" + |> List.map ((++) " ") + |> String.join "\n" + + +{-| Run a test and return a tuple of the output message and the number of +tests that failed. + +Fuzz tests use a default run count of 100, and a fixed initial seed. +-} +run : Test -> Summary +run = + runWithOptions defaultRuns defaultSeed + + +{-| Run a test and return a tuple of the output message and the number of +tests that failed. +-} +runWithOptions : Int -> Random.Seed -> Test -> Summary +runWithOptions runs seed test = + test + |> Test.Runner.fromTest runs seed + |> toOutput { output = "", passed = 0, failed = 0 } diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Tests.elm b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Tests.elm new file mode 100644 index 0000000..2de5a61 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/Tests.elm @@ -0,0 +1,229 @@ +module Tests exposing (all) + +import Test exposing (..) +import Test.Expectation exposing (Expectation(..)) +import Test.Internal as TI +import Fuzz exposing (..) +import Dict +import Set +import String +import Expect +import Fuzz.Internal +import RoseTree +import Random.Pcg as Random + + +all : Test +all = + Test.concat + [ readmeExample, bug39, fuzzerTests, shrinkingTests ] + + +{-| Regression test for https://github.com/elm-community/elm-test/issues/39 +-} +bug39 : Test +bug39 = + fuzz (intRange 1 32) "small slice end" <| + \positiveInt -> + positiveInt + |> Expect.greaterThan 0 + + +readmeExample : Test +readmeExample = + describe "The String module" + [ describe "String.reverse" + [ test "has no effect on a palindrome" <| + \() -> + let + palindrome = + "hannah" + in + Expect.equal palindrome (String.reverse palindrome) + , test "reverses a known string" <| + \() -> + "ABCDEFG" + |> String.reverse + |> Expect.equal "GFEDCBA" + , test "equal lists" <| + \() -> + [ 1, 2, 3 ] + |> Expect.equalLists [ 1, 2, 3 ] + , test "equal dicts" <| + \() -> + (Dict.fromList [ ( 1, "one" ), ( 2, "two" ) ]) + |> Expect.equalDicts (Dict.fromList [ ( 1, "one" ), ( 2, "two" ) ]) + , test "equal sets" <| + \() -> + (Set.fromList [ 1, 2, 3 ]) + |> Expect.equalSets (Set.fromList [ 1, 2, 3 ]) + , fuzz string "restores the original string if you run it again" <| + \randomlyGeneratedString -> + randomlyGeneratedString + |> String.reverse + |> String.reverse + |> Expect.equal randomlyGeneratedString + ] + ] + + +testStringLengthIsPreserved : List String -> Expectation +testStringLengthIsPreserved strings = + strings + |> List.map String.length + |> List.sum + |> Expect.equal (String.length (List.foldl (++) "" strings)) + + +fuzzerTests : Test +fuzzerTests = + describe "Fuzzer methods that use Debug.crash don't call it" + [ describe "FuzzN (uses tupleN) testing string length properties" + [ fuzz2 string string "fuzz2" <| + \a b -> + testStringLengthIsPreserved [ a, b ] + , fuzz3 string string string "fuzz3" <| + \a b c -> + testStringLengthIsPreserved [ a, b, c ] + , fuzz4 string string string string "fuzz4" <| + \a b c d -> + testStringLengthIsPreserved [ a, b, c, d ] + , fuzz5 string string string string string "fuzz5" <| + \a b c d e -> + testStringLengthIsPreserved [ a, b, c, d, e ] + ] + , fuzz + (intRange 1 6) + "intRange" + (Expect.greaterThan 0) + , fuzz + (frequencyOrCrash [ ( 1, intRange 1 6 ), ( 1, intRange 1 20 ) ]) + "Fuzz.frequency(OrCrash)" + (Expect.greaterThan 0) + , fuzz (result string int) "Fuzz.result" <| \r -> Expect.pass + , fuzz (andThen (\i -> intRange 0 (2 ^ i)) (intRange 1 8)) + "Fuzz.andThen" + (Expect.atMost 256) + , describe "Whitebox testing using Fuzz.Internal" + [ fuzz (intRange 0 0xFFFFFFFF) "the same value is generated with and without shrinking" <| + \i -> + let + seed = + Random.initialSeed i + + step gen = + Random.step gen seed + + aFuzzer = + tuple5 + ( tuple ( list int, array float ) + , maybe bool + , result unit char + , tuple3 + ( percentage + , map2 (+) int int + , frequencyOrCrash [ ( 1, constant True ), ( 3, constant False ) ] + ) + , tuple3 ( intRange 0 100, floatRange -51 pi, map abs int ) + ) + + valNoShrink = + aFuzzer |> Fuzz.Internal.unpackGenVal |> step |> Tuple.first + + valWithShrink = + aFuzzer |> Fuzz.Internal.unpackGenTree |> step |> Tuple.first |> RoseTree.root + in + Expect.equal valNoShrink valWithShrink + ] + ] + + +testShrinking : Test -> Test +testShrinking test = + case test of + TI.Test runTest -> + TI.Test + (\seed runs -> + let + expectations = + runTest seed runs + + goodShrink expectation = + case expectation of + Pass -> + Just "Expected this test to fail, but it passed!" + + Fail given outcome -> + let + acceptable = + String.split "|" outcome + in + if List.member given acceptable then + Nothing + else + Just <| "Got shrunken value " ++ given ++ " but expected " ++ String.join " or " acceptable + in + expectations + |> List.filterMap goodShrink + |> List.map Expect.fail + |> (\list -> + if List.isEmpty list then + [ Expect.pass ] + else + list + ) + ) + + TI.Labeled desc labeledTest -> + TI.Labeled desc (testShrinking labeledTest) + + TI.Batch tests -> + TI.Batch (List.map testShrinking tests) + + +shrinkingTests : Test +shrinkingTests = + testShrinking <| + describe "Tests that fail intentionally to test shrinking" + [ fuzz2 int int "Every pair of ints has a zero" <| + \i j -> + (i == 0) + || (j == 0) + |> Expect.true "(1,1)" + , fuzz3 int int int "Every triple of ints has a zero" <| + \i j k -> + (i == 0) + || (j == 0) + || (k == 0) + |> Expect.true "(1,1,1)" + , fuzz4 int int int int "Every 4-tuple of ints has a zero" <| + \i j k l -> + (i == 0) + || (j == 0) + || (k == 0) + || (l == 0) + |> Expect.true "(1,1,1,1)" + , fuzz5 int int int int int "Every 5-tuple of ints has a zero" <| + \i j k l m -> + (i == 0) + || (j == 0) + || (k == 0) + || (l == 0) + || (m == 0) + |> Expect.true "(1,1,1,1,1)" + , fuzz (list int) "All lists are sorted" <| + \aList -> + let + checkPair l = + case l of + a :: b :: more -> + if a > b then + False + else + checkPair (b :: more) + + _ -> + True + in + checkPair aList |> Expect.true "[1,0]|[0,-1]" + ] diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/elm-package.json b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/elm-package.json new file mode 100644 index 0000000..8fa2665 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/elm-package.json @@ -0,0 +1,19 @@ +{ + "version": "2.0.1", + "summary": "tests for elm-test, so you can elm-test while you elm-test", + "repository": "https://github.com/elm-community/elm-test.git", + "license": "BSD-3-Clause", + "source-directories": [ + ".", + "../src" + ], + "exposed-modules": [], + "dependencies": { + "elm-community/lazy-list": "1.0.0 <= v < 2.0.0", + "elm-community/shrink": "2.0.0 <= v < 3.0.0", + "elm-lang/core": "5.0.0 <= v < 6.0.0", + "elm-lang/html": "2.0.0 <= v < 3.0.0", + "mgold/elm-random-pcg": "4.0.2 <= v < 5.0.0" + }, + "elm-version": "0.18.0 <= v < 0.19.0" +} diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/package.json b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/package.json new file mode 100644 index 0000000..b113a7b --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/package.json @@ -0,0 +1,22 @@ +{ + "name": "elm-test-tests", + "version": "0.0.0", + "description": "tests for elm-test, so you can elm-test while you elm-test", + "main": "elm.js", + "scripts": { + "test": "node run-tests.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/elm-community/elm-test.git" + }, + "author": "Richard Feldman", + "license": "BSD-3-Clause", + "bugs": { + "url": "https://github.com/elm-community/elm-test/issues" + }, + "homepage": "https://github.com/elm-community/elm-test#readme", + "devDependencies": { + "node-elm-compiler": "4.1.3" + } +} diff --git a/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/run-tests.js b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/run-tests.js new file mode 100644 index 0000000..7caef0f --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/elm-test/3.1.0/tests/run-tests.js @@ -0,0 +1,19 @@ +var compiler = require("node-elm-compiler") + +testFile = "Main.elm" + +compiler.compileToString([testFile], {}).then(function(str) { + try { + eval(str); + + process.exit(0) + } catch (err) { + console.error(err); + + process.exit(1) + } +}).catch(function(err) { + console.error(err); + + process.exit(1) +}); diff --git a/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/.travis.yml b/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/.travis.yml new file mode 100644 index 0000000..cc26c97 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/.travis.yml @@ -0,0 +1,36 @@ +sudo: false +language: node_js +node_js: "node" +os: linux +env: ELM_VERSION=0.18.0 + +cache: + directories: + - test/elm-stuff/build-artifacts + - sysconfcpus + +before_install: + - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config + - | # epic build time improvement - see https://github.com/elm-lang/elm-compiler/issues/1473#issuecomment-245704142 + if [ ! -d sysconfcpus/bin ]; + then + git clone https://github.com/obmarg/libsysconfcpus.git; + cd libsysconfcpus; + ./configure --prefix=$TRAVIS_BUILD_DIR/sysconfcpus; + make && make install; + cd ..; + fi + +install: + - node --version + - npm --version + - cd tests + - npm install -g elm@$ELM_VERSION + - mv $(npm config get prefix)/bin/elm-make $(npm config get prefix)/bin/elm-make-old + - printf '%s\n\n' '#!/bin/bash' 'echo "Running elm-make with sysconfcpus -n 2"' '$TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-make-old "$@"' > $(npm config get prefix)/bin/elm-make + - chmod +x $(npm config get prefix)/bin/elm-make + - npm install + - elm package install --yes + +script: + - npm test diff --git a/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/CHANGELOG.md b/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/CHANGELOG.md new file mode 100644 index 0000000..a9ee59b --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/CHANGELOG.md @@ -0,0 +1,63 @@ +### 2.5.0 + +**Additions:** +- `dict` helps encoding `Dict` + +### 2.4.0 + +**Additions:** +- `collection` helps with decoding array-like JavaScript structures such as `HTMLCollection` +- `combine` helps combining a `List` of decoders into a single `Decoder` for a `List` of such things + +### 2.3.0 + +**Additions:** +- `indexedList` to get access to the current js array index while decoding + +**Other Stuff:** +- `elm-doc-test` is now `elm-verify-examples`! + +### 2.2.0 + +**Additions:** +- `parseInt` and `parseFloat` for weird api's that return numbers as strings +- `doubleEncoded` for a more generic _json as a string in json_ issues + +**Fixes:** +- `optionalField` decodes the field, rather than the surrounding object now. + +**Other Stuff:** +- Code Style conforms to elm-format@exp +- Doc tests! +- Travis integration + +### 2.1.0 + +**Additions:** +- `optionalField : String -> Json.Decode.Decoder a -> Json.Decode.Decoder (Maybe.Maybe a)` - Decode an optional field, succeeding with `Nothing` if it is missing, but still giving an error if it is malformed. + +### 2.0.0 + +**Breaking Changes:** +- Upgrade for Elm 0.18 +- Removed `maybeNull` in favor of `Json.Decode.nullable` +- Removed `lazy` in favor of `Json.Decode.lazy` +- Renamed `apply` to `andMap` and reversed arguments to `Decoder a -> Decoder (a -> b) -> Decoder b` to make it work nicely with `(|>)` + +**Additions:** +- `fromResult : Result String a -> Decoder a` - convert a `Result` to a `Decoder`, helpful in `andThen` callbacks following the removal of `Json.Decode.customDecoder` +- `Json.Encode.Extra.maybe : (a -> Value) -> Maybe a -> Value` - encode a `Maybe a` given an encoder for `a`. Thanks to @hendore for this addition. + +**Other Stuff:** +- Code style conforms to elm-format + +#### 1.1.0 + +**Additions:** +- `Json.Decode.Extra.sequence` - lets you generate a list of `Decoder a` and attempt to apply them to a JSON list. _Authored by @cobalamin_ + + +#### 1.0.0 + +**Breaking Changes:** +- Upgrade for Elm 0.17 diff --git a/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/README.md b/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/README.md new file mode 100644 index 0000000..3d916ec --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/README.md @@ -0,0 +1,9 @@ +[![Build Status](https://travis-ci.org/elm-community/json-extra.svg?branch=master)](https://travis-ci.org/elm-community/json-extra) + +# json-extra + +``` +elm-package install elm-community/json-extra +``` + +Convenience functions for working with JSON diff --git a/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/docs/andMap.md b/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/docs/andMap.md new file mode 100644 index 0000000..a784986 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/docs/andMap.md @@ -0,0 +1,61 @@ +## Json.Decode.Extra.andMap + +Imagine you have a data type for a user + +```elm +import Date (Date) + +type alias User = + { id : Int + , createdAt : Date + , updatedAt : Date + , deletedAt : Maybe Date + , username : Maybe String + , email : Maybe String + , isAdmin : Bool + } +``` + +You can use `andMap` to incrementally apply decoders to your `User` type alias +by using that type alias as a function. Recall that record type aliases are +also functions which accept arguments in the order their fields are declared. In +this case, `User` looks like + +```elm +User : Int -> Date -> Date -> Maybe Date -> Maybe String -> Maybe String -> Bool -> User +``` + +And also recall that Elm functions can be partially applied. We can use these +properties to apply each field of our JSON object to each field in our user one +field at a time. All we need to do is also wrap `User` in a decoder and step +through using `andMap`. + +```elm +userDecoder : Decoder User +userDecoder = + succeed User + |> andMap (field "id" int) + |> andMap (field "createdAt" date) + |> andMap (field "updatedAt" date) + |> andMap (field "deletedAt" (maybe date)) + |> andMap (field "username" (maybe string)) + |> andMap (field "email" (maybe string)) + |> andMap (field "isAdmin" bool) +``` + +This is a shortened form of + +```elm +userDecoder : Decoder User +userDecoder = + succeed User + |> andThen (\f -> map f (field "id" int)) + |> andThen (\f -> map f (field "createdAt" date)) + |> andThen (\f -> map f (field "updatedAt" date)) + |> andThen (\f -> map f (field "deletedAt" (maybe date))) + |> andThen (\f -> map f (field "username" (maybe string))) + |> andThen (\f -> map f (field "email" (maybe string))) + |> andThen (\f -> map f (field "isAdmin" bool)) +``` + +See also: The [docs for `(|:)`](https://github.com/elm-community/json-extra/blob/master/docs/infixAndMap.md) diff --git a/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/docs/infixAndMap.md b/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/docs/infixAndMap.md new file mode 100644 index 0000000..728761b --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/json-extra/2.5.0/docs/infixAndMap.md @@ -0,0 +1,131 @@ +## Json.Decode.Extra.(|:) + + +Infix version of `andMap` that makes for a nice DSL when decoding objects. + +Consider the following type alias for a `Location`: + +```elm +type alias Location = + { id : Int + , name : String + , address : String + } +``` + +We can use `(|:)` to build up a decoder for `Location`: + +```elm +locationDecoder : Decoder Location +locationDecoder = + succeed Location + |: (field "id" int) + |: (field "name" string) + |: (field "address" string) +``` + + + +If you're curious, here's how this works behind the scenes, read on. + +`Location` is a type alias, and type aliases give you a convenience function +that returns an instance of the record in question. Try this out in `elm-repl`: + +```elm +> type alias Location = { id : Int, name: String, address: String } + +> Location + : Int -> String -> String -> Repl.Location + +> Location 1 "The White House" "1600 Pennsylvania Ave" +{ id = 1, name = "The White House", address = "1600 Pennsylvania Ave" } +``` + +In other words, if you call the `Location` function, passing three arguments, +it will return a new `Location` record by filling in each of its fields. (The +argument order is based on the order in which we listed the fields in the +type alias; the first argument sets `id`, the second argument sets `name`, etc.) + +Now try running this through `elm-repl`: + +```elm +> import Json.Decode exposing (succeed, int, string, field) + +> succeed Location + + : Json.Decode.Decoder + (Int -> String -> String -> Repl.Location) +``` + +So `succeed Location` gives us a `Decoder (Int -> String -> String -> Location)`. +That's not what we want! What we want is a `Decoder Location`. All we have so +far is a `Decoder` that wraps not a `Location`, but rather a function that +returns a `Location`. + +What `|: (field "id" int)` does is to take that wrapped function and pass an +argument to it. + +```elm +> import Json.Decode exposing (succeed, int, string, field) + +> (field "id" int) + : Json.Decode.Decoder Int + +> succeed Location |: (field "id" int) + + : Json.Decode.Decoder + (String -> String -> Repl.Location) +``` + +Notice how the wrapped function no longer takes an `Int` as its first argument. +That's because `(|:)` went ahead and supplied one: the `Int` wrapped by the decoder +`(field "id" int)` (which returns a `Decoder Int`). + +Compare: + +```elm +> succeed Location +Decoder (Int -> String -> String -> Location) + +> succeed Location |: (field "id" int) +Decoder (String -> String -> Location) +``` + +We still want a `Decoder Location` and we still don't have it yet. Our decoder +still wraps a function instead of a plain `Location`. However, that function is +now smaller by one argument! + +Let's repeat this pattern to provide the first `String` argument next. + +```elm +> succeed Location +Decoder (Int -> String -> String -> Location) + +> succeed Location |: (field "id" int) +Decoder (String -> String -> Location) + +> succeed Location |: (field "id" int) |: (field "name" string) +Decoder (String -> Location) +``` + +Smaller and smaller! Now we're down from `(Int -> String -> String -> Location)` +to `(String -> Location)`. What happens if we repeat the pattern one more time? + +```elm +> succeed Location +Decoder (Int -> String -> String -> Location) + +> succeed Location |: (field "id" int) +Decoder (String -> String -> Location) + +> succeed Location |: (field "id" int) |: (field "name" string) +Decoder (String -> Location) + +> succeed Location |: (field "id" int) |: (field "name" string) |: (field "address" string) +Decoder Location +``` + +Having now supplied all three arguments to the wrapped function, it has ceased +to be a function. It's now just a plain old `Location`, like we wanted all along. + +We win! diff --git a/part9/tests/elm-stuff/packages/elm-community/lazy-list/1.0.0/README.md b/part9/tests/elm-stuff/packages/elm-community/lazy-list/1.0.0/README.md new file mode 100644 index 0000000..4d6f52a --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/lazy-list/1.0.0/README.md @@ -0,0 +1,5 @@ +# Lazy list implementation in Elm + +Lazy lists are useful for large trees where you will only need one path. They power shrinking in elm-test. + +Prior to 0.18, this repo was elm-lazy-list. Starting with the 0.18 release, the repo is renamed lazy-list and versioning resets to 1.0.0. diff --git a/part9/tests/elm-stuff/packages/elm-community/shrink/2.0.0/README.md b/part9/tests/elm-stuff/packages/elm-community/shrink/2.0.0/README.md new file mode 100644 index 0000000..94fda59 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-community/shrink/2.0.0/README.md @@ -0,0 +1,241 @@ +# Shrinking strategies with elm-community/shrink + +`shrink` is a library for using and creating shrinking strategies. A +shrinking strategy, or shrinker, is a way of taking a value and producing a +list of values which are, some sense, more minimal than the original value. + +Shrinking is heavily used in property-based testing as a way to shrink +failing test cases into a more minimal test case. This is key for being +able to debug code swiftly and easily. Therefore, the main intended use +of this library is to support testing frameworks. You might also use it to +define shrinkers for types you define, in order to test them better. + +Note that `shrink` uses lazy lists instead of lists. This means that `shrink` has a direct dependency on +[elm-community/elm-lazy-list](https://github.com/elm-community/elm-lazy-list). + +```elm +type alias Shrinker a = a -> LazyList a +``` +That is, a shrinker takes a value to shrink, and produces, *lazily*, a list +of shrunken values. + +### Basic Examples + +The following examples show how to use the basic shrinkers and the kinds of +results they produce. Note that we're glossing over the lazy part of this; +pretend there's a `Lazy.List.toList` on the left of each example. + +**Shrink an Int** + +```elm +int 10 == [0,5,7,8,9] +``` + + +**Shrink a String** + +```elm +string "Hello World" == + [""," World","Hellod","llo World","Heo World","HellWorld","Hello rld","Hello Wod","ello World","Hllo World","Helo World","Helo World","Hell World","HelloWorld","Hello orld","Hello Wrld","Hello Wold","Hello Word","Hello Worl","\0ello World","$ello World","6ello World","?ello World","Cello World","Eello World","Fello World","Gello World","H\0llo World","H2llo World","HKllo World","HXllo World","H^llo World","Hallo World","Hcllo World","Hdllo World","He\0lo World","He6lo World","HeQlo World","He^lo World","Heelo World","Hehlo World","Hejlo World","Heklo World","Hel\0o World","Hel6o World","HelQo World","Hel^o World","Heleo World","Helho World","Heljo World","Helko World","Hell\0 World","Hell7 World","HellS World","Hella World","Hellh World","Hellk World","Hellm World","Helln World","Hello\0World","HelloWorld","HelloWorld","HelloWorld","HelloWorld","HelloWorld","Hello \0orld","Hello +orld","Hello Aorld","Hello Lorld","Hello Qorld","Hello Torld","Hello Uorld","Hello Vorld","Hello W\0rld","Hello W7rld","Hello WSrld","Hello Warld","Hello Whrld","Hello Wkrld","Hello Wmrld","Hello Wnrld","Hello Wo\0ld","Hello Wo9ld","Hello WoUld","Hello Wocld","Hello Wojld","Hello Wonld","Hello Wopld","Hello Woqld","Hello Wor\0d","Hello Wor6d","Hello WorQd","Hello Wor^d","Hello Wored","Hello Worhd","Hello Worjd","Hello Workd","Hello Worl\0","Hello Worl2","Hello WorlK","Hello WorlW","Hello Worl]","Hello Worl`","Hello Worlb","Hello Worlc"] +``` + +**Shrink a Maybe Float** + +```elm +maybe float (Just 3.14) == + [Nothing,Just 0,Just 1.57,Just 2.355,Just 2.7475,Just 2.94375,Just 3.041875,Just 3.0909375,Just 3.11546875,Just 3.127734375,Just 3.1338671875,Just 3.1369335937500002,Just 3.138466796875,Just 3.1392333984375,Just 3.1396166992187498,Just 3.1398083496093747] +``` + +**Shrink a List of Bools** + +```elm +list bool [True, False, False, True, False] == + [[],[False,True,False],[True,False,False],[False,False,True,False],[True,False,True,False],[True,False,True,False],[True,False,False,False],[True,False,False,True],[False,False,False,True,False],[True,False,False,False,False]] +``` + + +## Make your own shrinkers + +With `shrink`, it is very easy to make your own shrinkers for your own data +types. + +First of all, let's look at one of the basic shrinkers available in `shrink` +and how it is implemented. + +**Shrinker Bool** + +To shrink a `Bool`, you have to consider the possible values of `Bool`: `True` +and `False`. Intuitively, we understand that `False` is more "minimal" +than `True`. As, such, we would shrink `True` to `False`. As for `False`, +there is no value that is more "minimal" than `False`. As such, we simply +shrink it to the empty list. + +```elm +bool : Shrinker Bool +bool b = case b of + True -> False ::: empty + False -> empty +``` + +*Note that there is no "exact" rule to deciding on whether something is more +"minimal" than another.* The idea is that you want to have one case return +the empty list if possible while other cases move towards the more "minimal" +cases. In this example, we decided that `False` was the more "minimal" case and, +in a sense, moved `True` towards `False` since `False` then returns the empty +list. Obviously, this choice could have been reversed and you would be +justified in doing so. Just remember that *a value should never shrink to itself, +or shrink to something that (through any number of steps) shrinks back to itself.* +This is a recipe for an infinite loop. + +Now that we understand how to make a simple shrinker, let's see how we can use +these simple shrinkers together to make something that can shrink a more +complex data structure. + +**Shrinker Vector** + +Consider the following `Vector` type: + +```elm +type alias Vector = + { x : Float + , y : Float + , z : Float + } +``` + +Our goal is to produce a vector shrinker: + +```elm +vector : Shrinker Vector +``` + +`shrink` provides a basic `Float` shrinker and we can use it in combination +with `map` and `andMap` to make the `Vector` shrinker. + +```elm +vector : Shrinker Vector +vector {x, y, z} = + Vector + `map` float x + `andMap` float y + `andMap` float z +``` + +And voila! Super simple. Let's try this on an even larger structure. + + +**Shrinker Mario** + +Consider the following types: + +```elm +type alias Mario = + { position : Vector + , velocity : Vector + , direction : Direction + } + +type alias Vector = + { x : Float + , y : Float + } + +type Direction + = Left + | Right +``` + +And our goal is to produce a shrinker of Marios. + +```elm +mario : Shrinker Mario +``` + +To do this, we will split the steps. We can notice that we have two distinct +data types we need to shrink: `Vector` and `Direction`. + +For `Vector`, we can use the approach from the previous example: + +```elm +vector : Shrinker Vector +vector {x, y} = + Vector + `map` float x + `andMap` float y +``` + +And for `Direction`, we can apply a similar approach to our `Bool` example: + +```elm +direction : Shrinker Direction +direction dir = case dir of + Left -> empty + Right -> Left ::: empty +``` + +Where `Left` here is considered the "minimal" case. + + +Now, let's put these together: + +```elm +mario : Shrinker Mario +mario m = + Mario + `map` vector m.position + `andMap` vector m.velocity + `andMap` direction m.direction +``` + +And, yay! We now can shrink `Mario`! No mushrooms needed! + +### One more technique + +Sometimes, you want to shrink a data structure but you know intuitively that +it should shrink in a similar fashion to some other data structure. It would +be nice if you could just convert back and from that other data structure and +use its already existing shrinker. + +For example `List` and `Array`. + +In `shrink`, there exists a `List` shrinker: + +```elm +list : Shrinker a -> Shrinker (List a) +``` + +This shrinker is quite involved and does a number of things to shuffle elements, +shrink some elements, preserve others, etc... + +It would be nice if that can be re-used for arrays, because in a high-level +sense, arrays and lists are equivalent. + +This is exactly what `shrink` does and it uses a function called `convert`. + +```elm +convert : (a -> b) -> (b -> a) -> Shrinker a -> Shrinker b +``` + +`convert` converts a shrinker of a's into a shrinker of b's by taking a +two functions to convert to and from b's. + +**IMPORTANT NOTE: Both functions must be perfectly invertible or else this +process may create garbage!** + +By invertible, I mean that `f` and `g` are invertible **if and only if** + +```elm +f (g x) == g (f x) == x +``` + +**for all `x`.** + +Now we can very simply implement a shrinker of arrays as follows: + +```elm +array : Shrinker a -> Shrinker (Array a) +array shrinker = + convert (Array.fromList) (Array.toList) (list shrinker) +``` + +And, ta-da... 0 brain cells were used to get a shrinker on arrays. diff --git a/part9/tests/elm-stuff/packages/elm-lang/lazy/2.0.0/README.md b/part9/tests/elm-stuff/packages/elm-lang/lazy/2.0.0/README.md new file mode 100644 index 0000000..cedf2f3 --- /dev/null +++ b/part9/tests/elm-stuff/packages/elm-lang/lazy/2.0.0/README.md @@ -0,0 +1,69 @@ +# Laziness in Elm + +This package provides the basic primitives for working with laziness in Elm. + + +# Motivating Example + +Maybe you have 100 different graphs you want to show at various times, each +requiring a decent amount of computation. Here are a couple ways to handle +this: + + 1. Compute everything up front. This will introduce a delay on startup, but + it should be quite fast after that. Depending on how much memory is needed + to store each graph, you may be paying a lot there as well. + + 2. Compute each graph whenever you need it. This minimizes startup cost and + uses a minimal amount of memory, but when you are flipping between two + graphs you may be running the same computations again and again. + + 3. Compute each graph whenever you need it and save the result. Again, this + makes startup as fast as possible fast, but since we save the result, + flipping between graphs becomes much quicker. As we look at more graphs + we will need to use more and more memory though. + +All of these strategies are useful in general, but the details of your +particular problem will mean that one of these ways provides the best +experience. This library makes it super easy to use strategy #3. + + +# Pitfalls + +**Laziness + Time** — +Over time, laziness can become a bad strategy. As a very simple example, think +of a timer that counts down from 10 minutes, decrementing every second. Each +step is very cheap to compute. You subtract one from the current time and store +the new time in memory, so each step has a constant cost and memory usage is +constant. Great! If you are lazy, you say “here is how you would subtract +one” and store that *entire computation* in memory. This means our memory +usage grows linearly as each second passes. When we finally need the result, we +might have 10 minutes of computation to run all at once. In the best case, this +introduces a delay that no one *really* notices. In the worst case, this +computation is actually too big to run all at once and crashes. Just like with +dishes or homework, being lazy over time can be quite destructive. + +**Laziness + Concurrency** — +When you add concurrency into the mix, you need to be even more careful with +laziness. As an example, say we are running expensive computations on three +worker threads, and the results are sent to a fourth thread just for rendering. +If our three worker threads are doing their work lazily, they +“finish” super quick and pass the entire workload onto the render +thread. All the work we put into designing this concurrent system is wasted, +everything is run sequentially on the render thread! It is just like working on +a team with lazy people. You have to pay the cost of coordinating with them, +but you end up doing all the work anyway. You are better off making things +single threaded! + + +## Learn More + +One of the most delightful uses of laziness is to create infinite streams of +values. Hopefully we can get a set of interesting challenges together so +you can run through them and get comfortable. + +For a deeper dive, Chris Okasaki's book *Purely Functional Data Structures* +and [thesis](http://www.cs.cmu.edu/~rwh/theses/okasaki.pdf) +have interesting examples of data structures that get great +benefits from laziness, and hopefully it will provide some inspiration for the +problems you face in practice. + diff --git a/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/README.md b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/README.md new file mode 100644 index 0000000..9bdb3ea --- /dev/null +++ b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/README.md @@ -0,0 +1,47 @@ +# Random.Pcg for Elm + +> "The generation of random numbers is too important to be left to chance." – Robert R. Coveyou + +An alternate random number generator built around four principles: + +* **Statistical Quality.** If you use any seed less than 53,668 and generate one bool, it will be `True` – if you're +using core's `Random` module. More sophisticated statistical tests spot patterns in the "random" numbers almost +immediately. Would you want to trust the accuracy of your [fuzz +tests](http://package.elm-lang.org/packages/elm-community/elm-test/latest/) to such a flawed algorithm? This library +produces far less predictable and biased output, especially if you use thousands of random numbers. See +`test/dieharder` for more details. + +* **Useful features.** This library exports `constant` and `andMap`, which are conspicuously absent from core, along +with other helpful functions for composing generators. Particularly interesting is `independentSeed`, which allows for +lazy lists and isolated components to generate as much randomness as they need, when they need it. + +* **Performace.** This library will generate floats about 3.5 times faster than core, and ints do not regress. These +figures stand to improve pending some optimizations to the compiler. You can see the [full +benchmark results](https://github.com/mgold/elm-random-pcg/issues/5#issuecomment-236398261). + +* **Compatibility.** This library is a drop-in replacement for core's Random module. Specifically, you +can replace `import Random` with `import Random.Pcg as Random` and everything will continue to work. (The one exception is third party +libraries like [elm-random-extra](http://package.elm-lang.org/packages/NoRedInk/elm-random-extra/latest/Random-Extra).) + +This is an implementation of [PCG](http://www.pcg-random.org/) by M. E. O'Neil. The generator is **not cryptographically +secure**. + +Please report bugs, feature requests, and other issues [on GitHub](https://github.com/mgold/elm-random-pcg/issues/new). + +## Changelog (major versions only) +### 4.0.0 +* Upgraded for 0.18. +* Argument order of `andThen` flipped. + +### 3.0.0 +* Change implementation to use the RXS-M-SH variant of PCG. Now much faster and not much worse statistically. +* Remove `initialSeed2`, since there are now only 32 bits of state. +* `Random.Pcg.Interop.fission` has been changed to a (core) generator of (PCG) seeds. +* Add `generate` to match core 4.x API. Implemented by Richard Feldman. + +### 2.0.0 +* Upgraded for 0.17. +* `generate` renamed `step` to match core 4.x API. +* Module renamed `Random.Pcg` from `Random.PCG`. +* `split` has been removed; use `independentSeed`. +* `minInt` and `maxInt` values changed to match core. diff --git a/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/README.md b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/README.md new file mode 100644 index 0000000..669d1fb --- /dev/null +++ b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/README.md @@ -0,0 +1,4 @@ +# Random.Pcg Tests + +The tests in this directory are one-off simple programs to confirm that certain functions aren't obviously wrong. +Heavyweight statistical tests are under `dieharder`. Benchmarks (using a 0.16 benchmarking lib) are in `benchmark`. diff --git a/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/benchmark/README.md b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/benchmark/README.md new file mode 100644 index 0000000..6c8183a --- /dev/null +++ b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/benchmark/README.md @@ -0,0 +1,14 @@ +# Benchmarks for Random.Pcg + +These benchmarks are possible thanks to Robin Heggelund Hansen's work benchmarking [collections-ng](https://github.com/Skinney/collections-ng). + +To run them yourself, first download the vendor files (you only need to do this once): +``` +mkdir vendor +cd vendor +wget https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js +wget https://cdn.jsdelivr.net/benchmarkjs/2.1.0/benchmark.js +``` + +Then adjust the import of `Random` in `Bencher.elm` to control which library is being benchmarked. Finally run `sh prep-bench.sh` then +`elm-reactor` in this directory and open `run-benchmarks.html`. diff --git a/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/benchmark/prep-bench.sh b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/benchmark/prep-bench.sh new file mode 100644 index 0000000..d3abcfc --- /dev/null +++ b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/benchmark/prep-bench.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -e + +elm-make --yes --output bench.js Bencher.elm diff --git a/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/dieharder/README.md b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/dieharder/README.md new file mode 100644 index 0000000..e64cdfa --- /dev/null +++ b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/dieharder/README.md @@ -0,0 +1,27 @@ +# Dieharder tests for Random.Pcg + +**These tests rely on a library that has not been update for 0.17.** + +The purpose of testing the random number generator is twofold: one, show the deficiencies in the core implementation; +two, show the correctness of the Pcg implementation *in Elm*. Because we are testing the Elm implementation, not the Pcg +algorithm, we must feed dieharder a file of already-generated random numbers. I've seen sources recommending 10 million +random numbers; these tests use 24 million, but even so the files are "rewound", as many as 2500 times on later tests. + +For the original 19 diehard tests, the core fails 9 tests while Pcg fails none. (One test resulted in "weak" but further +investigation resulted in passing; we expect this on one test in 100). On the entire battery, core passes 29, fails 75, +and is weak in 10. Pcg passes 82, fails 24, and is weak in 8. Some of Pcg's strength may be attributed to its 64 bits of +state, compared to core's 32, but this does not excuse failing the less-comprehensive diehard tests. Conversely, many of +the failures can be attributed to reusing pre-generated random numbers. + +The source Elm is `Dieharder.elm`. Because the tests require more random numbers than can be stored in memory, they +output chunks of 2 million at a time. In order to reuse the code, `compile.sh` rewrites the file to change the Random +implementation. It also writes out the `.txt` data files, and then runs dieharder, logging the results. + +The result log files have been committed to version control; I would have liked to commit the data files but they're +huge, and only take a few minutes to generate. You are free to alter the random seed in the Elm code, and rerun the +tests with `sh compile` (which takes several hours to complete). That said, I encourage you to run it long enough to see +core fail the birthday test, after only five or six seconds of scrutiny. (The `dieharder` tool is available through most +package managers. Once the data files are generated, try `time dieharder -g 202 -f elm-core-random.txt -d 0`.) + +The Pcg paper uses the TestU01 (e.g. BigCrush) suite; I'm using dieharder since it was easier to get to work +reading from a file. diff --git a/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/dieharder/compile.sh b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/dieharder/compile.sh new file mode 100644 index 0000000..b581fdc --- /dev/null +++ b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/dieharder/compile.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -e + +if [ ! -f elm-core-random.txt ]; then + sed -i.bak 's/import Random.Pcg as Random/import Random/g' Dieharder.elm + sed -i.bak 's/elm-random-pcg/elm-core-random/g' Dieharder.elm + elm make Dieharder.elm --output=raw_out.js + sh elm-io.sh raw_out.js generate_files.js + echo "Generating elm-core-random.txt..." + node generate_files.js > elm-core-random.txt + dieharder -g 202 -f elm-core-random.txt -a | tee dieharder-core.log +fi + +if [ ! -f elm-random-pcg.txt ]; then + sed -i.bak 's/import Random$/import Random.Pcg as Random/g' Dieharder.elm + sed -i.bak 's/elm-core-random/elm-random-pcg/g' Dieharder.elm + elm make Dieharder.elm --output=raw_out.js + sh elm-io.sh raw_out.js generate_files.js + echo "Generating elm-random-pcg.txt..." + node generate_files.js > elm-random-pcg.txt + dieharder -g 202 -f elm-random-pcg.txt -a | tee dieharder-pcg.log +fi + diff --git a/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/dieharder/elm-io.sh b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/dieharder/elm-io.sh new file mode 100644 index 0000000..476fb98 --- /dev/null +++ b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/dieharder/elm-io.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# Script for use with the Console library to allow Elm to run on Node. + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +read -d '' handler <<- EOF +(function(){ + if (typeof Elm === "undefined") { throw "elm-io config error: Elm is not defined. Make sure you call elm-io with a real Elm output file"} + if (typeof Elm.Main === "undefined" ) { throw "Elm.Main is not defined, make sure your module is named Main." }; + var worker = Elm.worker(Elm.Main); +})(); +EOF + +cat $1 > $2 +echo "$handler" >> $2 diff --git a/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/port/README.md b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/port/README.md new file mode 100644 index 0000000..2d013b2 --- /dev/null +++ b/part9/tests/elm-stuff/packages/mgold/elm-random-pcg/4.0.2/test/port/README.md @@ -0,0 +1,7 @@ +# Port test + +``` +elm make Test.elm --output=elm.js +``` + +This shows how to hook up ports to the random number generator to produce different values each time. diff --git a/part9/tests/elm-stuff/packages/rtfeldman/html-test-runner/2.0.0/README.md b/part9/tests/elm-stuff/packages/rtfeldman/html-test-runner/2.0.0/README.md new file mode 100644 index 0000000..bdf813a --- /dev/null +++ b/part9/tests/elm-stuff/packages/rtfeldman/html-test-runner/2.0.0/README.md @@ -0,0 +1,8 @@ +# html-test-runner +Run elm-test suites in the browser + +## Try it + +1. `cd examples` +2. `elm-reactor` +3. Visit [http://localhost:8000/HtmlRunnerExample.elm](http://localhost:8000/HtmlRunnerExample.elm) diff --git a/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/.travis.yml b/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/.travis.yml new file mode 100644 index 0000000..0c2dd40 --- /dev/null +++ b/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/.travis.yml @@ -0,0 +1,43 @@ +sudo: false + +cache: + directories: + - test/elm-stuff/build-artifacts + - sysconfcpus + +os: + - linux + - osx + +env: + matrix: + - ELM_VERSION=0.18.0 TARGET_NODE_VERSION=node + - ELM_VERSION=0.18.0 TARGET_NODE_VERSION=0.12 + +before_install: + - if [ ${TRAVIS_OS_NAME} == "osx" ]; + then brew update; brew install nvm; mkdir ~/.nvm; export NVM_DIR=~/.nvm; source $(brew --prefix nvm)/nvm.sh; + fi + - | # epic build time improvement - see https://github.com/elm-lang/elm-compiler/issues/1473#issuecomment-245704142 + if [ ! -d sysconfcpus/bin ]; + then + git clone https://github.com/obmarg/libsysconfcpus.git; + cd libsysconfcpus; + ./configure --prefix=$TRAVIS_BUILD_DIR/sysconfcpus; + make && make install; + cd ..; + fi + +install: + - nvm install $TARGET_NODE_VERSION + - nvm use $TARGET_NODE_VERSION + - node --version + - npm --version + - npm install -g elm@$ELM_VERSION + - mv $(npm config get prefix)/bin/elm-make $(npm config get prefix)/bin/elm-make-old + - echo "#\!/bin/bash\\n\\necho \"Running elm-make with sysconfcpus -n 2\"\\n\\n$TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-make-old \"\$@\"" > $(npm config get prefix)/bin/elm-make + - chmod +x $(npm config get prefix)/bin/elm-make + - npm install + +script: + - npm test diff --git a/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/README.md b/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/README.md new file mode 100644 index 0000000..ea4babf --- /dev/null +++ b/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/README.md @@ -0,0 +1,47 @@ +# node-test-runner [![Version](https://img.shields.io/npm/v/elm-test.svg)](https://www.npmjs.com/package/elm-test) [![Travis build Status](https://travis-ci.org/rtfeldman/node-test-runner.svg?branch=master)](http://travis-ci.org/rtfeldman/node-test-runner) [![AppVeyor build status](https://ci.appveyor.com/api/projects/status/fixcy4ko78di0l31/branch/master?svg=true)](https://ci.appveyor.com/project/rtfeldman/node-test-runner/branch/master) + +Runs [elm-test](https://github.com/elm-community/elm-test) suites from Node.js + +## Installation + +```bash +npm install -g elm-test +``` + +## Usage + +```bash +elm-test init # Adds the elm-test dependency and creates Main.elm and Tests.elm +elm-test # Runs the tests +``` + +Then add your tests to Tests.elm. + + +### Configuration + +The `--compiler` flag can be used to use a version of the Elm compiler that +has not been install globally. + +``` +npm install elm +elm-test --compiler ./node_modules/.bin/elm-make +``` + + +### Travis CI + +If you want to run your tests on Travis CI, here's a good starter `.travis.yml`: + +```yml +language: node_js +node_js: + - "5" +install: + - npm install -g elm + - npm install -g elm-test + - elm-package install -y + - pushd tests && elm-package install -y && popd +script: + - elm-test +``` diff --git a/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/appveyor.yml b/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/appveyor.yml new file mode 100644 index 0000000..10a3bdf --- /dev/null +++ b/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/appveyor.yml @@ -0,0 +1,25 @@ +environment: + ELM_VERSION: "0.18.0" + matrix: + - nodejs_version: "5.0" + - nodejs_version: "0.12" + - nodejs_version: "0.11.13" + +platform: + - x86 + - x64 + +matrix: + fast_finish: true + +install: + - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:Platform + - node --version + - npm --version + - npm install -g elm@%ELM_VERSION% + - npm install + +test_script: + - npm test + +build: off diff --git a/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/bin/elm-test b/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/bin/elm-test new file mode 100644 index 0000000..51ae68c --- /dev/null +++ b/part9/tests/elm-stuff/packages/rtfeldman/node-test-runner/3.0.0/bin/elm-test @@ -0,0 +1,322 @@ +#!/usr/bin/env node + +var processTitle = "elm-test"; + +process.title = processTitle; + +var compile = require("node-elm-compiler").compile, + fs = require("fs-extra"), + chalk = require("chalk"), + path = require("path"), + temp = require("temp").track(), // Automatically cleans up temp files. + util = require("util"), + _ = require("lodash"), + spawn = require("cross-spawn"), + minimist = require("minimist"), + firstline = require("firstline"), + findUp = require("find-up"), + chokidar = require("chokidar"); + +var elm = { + 'elm-package': 'elm-package' +}; +var args = minimist(process.argv.slice(2), { + alias: { + 'help': 'h', + 'yes': 'y', + 'seed': 's', + 'compiler': 'c', + 'report': 'r', + 'watch': 'w' + }, + boolean: [ 'yes', 'warn', 'version', 'help', 'watch' ], + string: [ 'compiler', 'seed', 'report' ] +}); + +if (args.help) { + console.log("Usage: elm-test init [--yes] # Create example tests\n"); + console.log("Usage: elm-test TESTFILE [--compiler /path/to/compiler] # Run TESTFILE\n"); + console.log("Usage: elm-test [--compiler /path/to/compiler] # Run tests/Main.elm\n"); + console.log("Usage: elm-test [--seed integer] # Run with initial fuzzer seed\n"); + console.log("Usage: elm-test [--report json or chalk (default)] # Print results to stdout in given format\n"); + console.log("Usage: elm-test [--watch] # Run tests on file changes\n"); + process.exit(1); +} + +if (args.version) { + console.log(require(path.join(__dirname, "..", "package.json")).version); + process.exit(0); +} + +var report = "chalk"; +if (args.report !== undefined) { + report = args.report; +} + + +checkNodeVersion(); + +function checkNodeVersion() { + var nodeVersionString = process.versions.node; + var nodeVersion = _.map(_.split(nodeVersionString, '.'), _.parseInt); + + if((nodeVersion[0] === 0 && nodeVersion[1] < 11) || + (nodeVersion[0] === 0 && nodeVersion[1] === 11 && nodeVersion[2] < 13)) { + console.log("using node v" + nodeVersionString); + console.error("elm-test requires node v0.11.13 or greater - upgrade the installed version of node and try again"); + process.exit(1); + } +} + +if (args._[0] == "init") { + var copyTemplate = function(templateName, destName) { + if (arguments.length == 1) { + destName = templateName; + } + var source = path.resolve(__dirname, "../templates/" + templateName); + var destination = path.resolve("tests", destName); + if (fs.existsSync(destination)) { + console.log(destination + " already exists"); + } else { + fs.copySync(source, destination); + console.log("Created " + destination); + } + }; + + var ensureDirectory = function(dirName) { + var destination = path.resolve(".", dirName); + if (fs.existsSync(destination)) { + console.log(destination + " already exists"); + } else { + fs.mkdirSync(destination); + console.log("Created " + destination); + } + }; + + var elmOptions = ""; + if (args.yes) { + elmOptions += " --yes"; + } + + ensureDirectory("src"); + ensureDirectory("tests"); + copyTemplate("elm-package.json"); + copyTemplate("Main.elm"); + copyTemplate("Tests.elm"); + copyTemplate("gitignore", ".gitignore"); + process.exit(0); +} + +function evalElmCode (compiledCode, testModuleName) { + var Elm = function(module) { eval(compiledCode); return module.exports; }({}); + + // Make sure necessary things are defined. + if (typeof Elm === 'undefined') { throw 'elm-io config error: Elm is not defined. Make sure you provide a file compiled by Elm!'; } + + function getTestModule() { + var module = Elm; + var moduleParts = testModuleName.split('.'); + while (moduleParts.length) { + var modulePart = moduleParts.shift(); + if (module[modulePart] === undefined) { + return undefined; + } + + module = module[modulePart]; + } + + return module; + } + + var testModule = getTestModule(); + if (testModule === undefined) { throw 'Elm.' + testModuleName + ' is not defined. Make sure you provide a file compiled by Elm!'; } + + var initialSeed = null; + + if (args.seed !== undefined) { + initialSeed = args.seed; + } + + + + + // Apply Node polyfills as necessary. + var window = {Date: Date, addEventListener: function() {}, removeEventListener: function() {}}; + var document = {body: {}, createTextNode: function() {}}; + pathToMake = args.compiler; + if (typeof XMLHttpRequest === 'undefined') { XMLHttpRequest = function() { return { addEventListener: function() {}, open: function() {}, send: function() {} }; }; } + if (typeof FormData === 'undefined') { FormData = function () { this._data = []; }; FormData.prototype.append = function () { this._data.push(Array.prototype.slice.call(arguments)); }; } + + // Fix Windows Unicode problems. Credit to https://github.com/sindresorhus/figures for the Windows compat idea! + var windowsSubstitutions = [[/[↓✗►]/g, '>'], [/╵│╷╹┃╻/g, '|'], [/═/g, '='],, [/▔/g, '-'], [/✔/g, '√']]; + + function windowsify(str) { + return windowsSubstitutions.reduce( + function(result, sub) { return result.replace(sub[0], sub[1]); }, str); + } + + function chalkify(messages) { + return messages.map(function(msg) { + var path = msg.styles; + var text = process.platform === 'win32' ? windowsify(msg.text) : msg.text; + + if (path.length === 0) { + return text; + } else { + var fn = chalk; + + path.forEach(function(nextPath) { fn = fn[nextPath]; }); + + return fn(text); + } + }).join(''); + } + + // Run the Elm app. + var app = testModule.worker({seed: initialSeed, report: report}); + + // Receive messages from ports and translate them into appropriate JS calls. + app.ports.emit.subscribe(function(msg) { + var msgType = msg[0]; + var data = msg[1]; + + if (msgType === 'FINISHED') { + if (data.format === "CHALK") { + console.log(chalkify(data.message)); + } else { + console.log(JSON.stringify(data.message)); + } + + if (!args.watch) { + process.exit(data.exitCode); + } + } else if (msgType === "STARTED" || msgType === "TEST_COMPLETED") { + if (data.format === "CHALK") { + console.log(chalkify(data.message)); + } else { + console.log(JSON.stringify(data.message)); + } + + } + }); +} + + +var testFile = args._[0], + cwd = __dirname, + pathToMake = undefined; + +function spawnCompiler(cmd, args, opts) { + var compilerOpts = + _.defaults({stdio: [process.stdin, report === "chalk" ? process.stdout : 'ignore', process.stderr] }, opts); + + return spawn(cmd, args, compilerOpts); +} + +if (typeof testFile == "undefined") { + testFile = "tests/Main.elm"; +} + +if (args.compiler !== undefined) { + pathToMake = args.compiler; + + if (!pathToMake) { + console.error("The --compiler option must be given a path to an elm-make executable."); + process.exit(1); + } +} + +if (!fs.existsSync(testFile)) { + console.error("Could not find file " + testFile); + process.exit(1); +} + +var testModulePromise = firstline(testFile).then(function (testModuleLine) { + var moduleMatches = testModuleLine.match(/^(?:port\s+)?module\s+([^\s]+)/); + if (moduleMatches) { + return moduleMatches[1]; + } + + return 'Main'; +}); + +function infoLog(msg) { + if (report === "chalk") { + console.log(msg); + } +} + +findUp('elm-package.json', { cwd: path.dirname(testFile) }) + .then(function (elmPackagePath) { + var elmRootDir = path.dirname(elmPackagePath); + if (fs.realpathSync(elmRootDir) !== fs.realpathSync(process.cwd())) { + testFile = path.relative(elmRootDir, testFile); + process.chdir(elmRootDir); + } + + if (args.watch) { + infoLog('Running in watch mode'); + + var watcher = chokidar.watch('**/*.elm', { ignoreInitial: true }); + + var eventNameMap = { + add: 'added', + addDir: 'added', + change: 'changed', + unlink: 'removed', + unlinkDir: 'removed', + }; + + watcher.on('all', function (event, filePath) { + var relativePath = path.relative(elmRootDir, filePath); + var eventName = eventNameMap[event] || event; + + infoLog('\n' + relativePath + ' ' + eventName + '. Rebuilding!'); + + runTests(); + }); + } + + function runTests () { + temp.open({ prefix:'elm_test_', suffix:'.js' }, function(err, info) { + var dest = info.path; + var compileProcess = compile( [testFile], { + output: dest, + verbose: args.verbose, + yes: true, + spawn: spawnCompiler, + pathToMake: pathToMake, + warn:args.warn + }); + + compileProcess.on('close', function(exitCode) { + if (exitCode !== 0) { + console.error("Compilation failed for", testFile); + if (!args.watch) { + process.exit(exitCode); + } + } else { + testModulePromise.then(function (testModuleName) { + evalElmCode(fs.readFileSync(dest, {encoding: "utf8"}), testModuleName); + }); + } + }); + }); + } + + runTests(); + }); + + +process.on('uncaughtException', function(error) { + if (/ an argument in Javascript/.test(error)) { + // Handle arg mismatch between js and elm code. Expected message from Elm: + // "You are giving module `Main` an argument in JavaScript. + // This module does not take arguments though! You probably need to change the + // initialization code to something like `Elm.Main.fullscreen()`]" + console.error("Error starting the node-test-runner."); + console.error("Please check your Javascript 'elm-test' and Elm 'node-test-runner' package versions are compatible"); + } else { + console.error("Unhandled exception while running the tests:", error); + } +});