From b557ee08427ba49f756886aa85bb70636d704ccd Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 26 Jun 2016 01:00:27 -0700 Subject: [PATCH] Revise tests Link to fuzz test docs and Fuzzer docs Move part9 to be part12 Update part11 Update 12, and some other Mains Rearrange things, drop 2 modules Add a new part12 Fix READMEs Move some things up a directory Update part11 Use ! [] Update parts7-9 Fix part12g Swap part11 and part12 Fix readmes for part11 and part12 Add HtmlRunner to part8 Update part8 and part9 READMEs rm part10/test --- part10/ElmHub.elm | 80 ++++++++++++++----- part10/Main.elm | 3 +- part10/README.md | 4 + part10/SearchResult.elm | 53 +++++++----- part10/style.css | 9 --- part10/test/TestRunner.elm | 15 ---- part10/test/Tests.elm | 35 -------- part10/test/elm-package.json | 18 ----- part11/ElmHub.elm | 78 +++++++++++++----- part11/Main.elm | 3 +- part11/SearchResult.elm | 74 +++++++++++------ part12/ElmHub.elm | 90 ++++++++++++--------- {part13 => part12}/ElmHub/Css.elm | 0 part12/Main.elm | 3 +- part12/README.md | 8 +- part12/SearchResult.elm | 22 ++--- {part13 => part12}/Stylesheets.elm | 0 part12/elm-package.json | 5 +- part12/style.css | 94 +--------------------- part12/test/elm-package.json | 1 + part13/ElmHub.elm | 124 ----------------------------- part13/Main.elm | 13 --- part13/README.md | 30 ------- part13/SearchResult.elm | 59 -------------- part13/asdf/elm-package.json | 19 ----- part13/asdf/style.css | 101 ----------------------- part13/elm-hub.png | Bin 2126 -> 0 bytes part13/elm-package.json | 19 ----- part13/index.html | 20 ----- part13/style.css | 6 -- part13/test/TestRunner.elm | 15 ---- part13/test/Tests.elm | 35 -------- part13/test/elm-package.json | 19 ----- part7/ElmHub.elm | 29 +++---- part7/Main.elm | 4 +- part8/ElmHub.elm | 24 +++--- part8/Main.elm | 4 +- part8/README.md | 19 ++++- part8/test/HtmlRunner.elm | 16 ++++ part8/test/NodeRunner.elm | 19 +++++ part8/test/Tests.elm | 89 ++++++++++++--------- part8/test/elm-package.json | 9 ++- part9/ElmHub.elm | 52 +++++++----- part9/Main.elm | 25 +----- part9/README.md | 19 ++++- 45 files changed, 481 insertions(+), 883 deletions(-) delete mode 100644 part10/test/TestRunner.elm delete mode 100644 part10/test/Tests.elm delete mode 100644 part10/test/elm-package.json rename {part13 => part12}/ElmHub/Css.elm (100%) rename {part13 => part12}/Stylesheets.elm (100%) delete mode 100644 part13/ElmHub.elm delete mode 100644 part13/Main.elm delete mode 100644 part13/README.md delete mode 100644 part13/SearchResult.elm delete mode 100644 part13/asdf/elm-package.json delete mode 100644 part13/asdf/style.css delete mode 100644 part13/elm-hub.png delete mode 100644 part13/elm-package.json delete mode 100644 part13/index.html delete mode 100644 part13/style.css delete mode 100644 part13/test/TestRunner.elm delete mode 100644 part13/test/Tests.elm delete mode 100644 part13/test/elm-package.json create mode 100644 part8/test/HtmlRunner.elm create mode 100644 part8/test/NodeRunner.elm diff --git a/part10/ElmHub.elm b/part10/ElmHub.elm index c7939bd..57bc0e7 100644 --- a/part10/ElmHub.elm +++ b/part10/ElmHub.elm @@ -1,14 +1,15 @@ module ElmHub exposing (..) import Html exposing (..) -import Html.Attributes exposing (class, target, href, property, defaultValue) +import Html.Attributes exposing (..) import Html.Events exposing (..) +import Html.App import Http import Auth import Task exposing (Task) import Json.Decode exposing (Decoder) import Dict exposing (Dict) -import SearchResult +import SearchResult exposing (ResultId) searchFeed : String -> Cmd Msg @@ -26,13 +27,13 @@ searchFeed query = responseDecoder : Decoder (List SearchResult.Model) responseDecoder = - -- TODO make use of SearchResult's decoder - Json.Decode.succeed [] + Json.Decode.at [ "items" ] (Json.Decode.list SearchResult.decoder) type alias Model = { query : String - , results : Dict SearchResult.ResultId SearchResult.Model + , results : Dict ResultId SearchResult.Model + , errorMessage : Maybe String } @@ -40,6 +41,7 @@ initialModel : Model initialModel = { query = "tutorial" , results = Dict.empty + , errorMessage = Nothing } @@ -52,23 +54,42 @@ view model = ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] [] , button [ class "search-button", onClick Search ] [ text "Search" ] + , viewErrorMessage model.errorMessage , ul [ class "results" ] (viewSearchResults model.results) ] -viewSearchResults : Dict SearchResult.ResultId SearchResult.Model -> List (Html a) +viewErrorMessage : Maybe String -> Html a +viewErrorMessage errorMessage = + case errorMessage of + Just message -> + div [ class "error" ] [ text message ] + + Nothing -> + text "" + + +viewSearchResults : Dict ResultId SearchResult.Model -> List (Html Msg) viewSearchResults results = results |> Dict.values |> List.sortBy (.stars >> negate) - |> List.map (\_ -> div [] [ text "TODO replace this line with view logic from SearchResult" ]) + |> List.map viewSearchResult + + +viewSearchResult : SearchResult.Model -> Html Msg +viewSearchResult result = + -- TODO call SearchResult.view to render a search result. + -- + -- Hint: Use Html.App.map and UpdateSearchResult to translate from + -- SearchResult.Msg into the Msg type we have defined in this module. + div [] [] type Msg = Search | SetQuery String - | DeleteById SearchResult.ResultId - | SetResults (List SearchResult.Model) + | UpdateSearchResult ResultId SearchResult.Msg | HandleSearchResponse (List SearchResult.Model) | HandleSearchError Http.Error @@ -77,24 +98,43 @@ update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Search -> - ( model, searchFeed model.query ) + model ! [ searchFeed model.query ] SetQuery query -> - ( { model | query = query }, Cmd.none ) + { model | query = query, errorMessage = Nothing } ! [] - SetResults results -> + HandleSearchError error -> + case error of + Http.UnexpectedPayload str -> + { model | errorMessage = Just str } ! [] + + _ -> + { model | errorMessage = Just "Error loading search results" } ! [] + + HandleSearchResponse results -> let - resultsById : Dict SearchResult.ResultId SearchResult.Model + resultsById : Dict ResultId SearchResult.Model resultsById = results |> List.map (\result -> ( result.id, result )) |> Dict.fromList in - ( { model | results = resultsById }, Cmd.none ) + { model | results = resultsById } ! [] - DeleteById id -> - let - newModel = - { model | results = Dict.remove id model.results } - in - ( newModel, Cmd.none ) + UpdateSearchResult id childMsg -> + case Dict.get id model.results of + Nothing -> + model ! [] + + Just childModel -> + let + ( newChildModel, childCmd ) = + SearchResult.update childMsg childModel + + cmd = + Cmd.map (UpdateSearchResult id) childCmd + + newResults = + Dict.insert id newChildModel model.results + in + { model | results = newResults } ! [ cmd ] diff --git a/part10/Main.elm b/part10/Main.elm index c60133a..98f25b7 100644 --- a/part10/Main.elm +++ b/part10/Main.elm @@ -1,6 +1,7 @@ module Main exposing (..) import ElmHub exposing (..) +import Html.App main : Program Never @@ -9,5 +10,5 @@ main = { view = view , update = update , init = ( initialModel, searchFeed initialModel.query ) - , inputs = [] + , subscriptions = \_ -> Sub.none } diff --git a/part10/README.md b/part10/README.md index 99af713..80d1209 100644 --- a/part10/README.md +++ b/part10/README.md @@ -18,3 +18,7 @@ to fail; in that case, just run `elm-package install` again.) ```bash elm-live Main.elm --open --output=elm.js ``` + +## References + +* [Elm Architecture Tutorial](https://github.com/evancz/elm-architecture-tutorial) diff --git a/part10/SearchResult.elm b/part10/SearchResult.elm index 47ef0c5..fa7c873 100644 --- a/part10/SearchResult.elm +++ b/part10/SearchResult.elm @@ -2,19 +2,26 @@ module SearchResult exposing (..) import Html exposing (..) import Html.Attributes exposing (class, target, href, property, defaultValue) +import Html.Events exposing (..) import Json.Decode exposing (Decoder) import Json.Decode.Pipeline exposing (..) +type alias Model = + { id : Int + , name : String + , stars : Int + , expanded : Bool + } + + type alias ResultId = Int -type alias Model = - { id : ResultId - , name : String - , stars : Int - } +type Msg + = Expand + | Collapse decoder : Decoder Model @@ -23,19 +30,29 @@ decoder = |> required "id" Json.Decode.int |> required "full_name" Json.Decode.string |> required "stargazers_count" Json.Decode.int + |> hardcoded True -view : Model -> Html a -view result = - li [] - [ span [ class "star-count" ] [ text (toString result.stars) ] - , a - [ href ("https://github.com/" ++ result.name) - , target "_blank" +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + -- TODO implement Expand and Collapse logic + model ! [] + + +view : Model -> Html Msg +view model = + if model.expanded then + li [] + [ span [ class "star-count" ] [ text (toString model.stars) ] + , a [ href ("https://github.com/" ++ model.name), target "_blank" ] + [ text model.name ] + -- TODO send a Collapse message on click + , button [ class "hide-result" ] + [ text "X" ] + ] + else + li [] + -- TODO send an Expand message on click + [ button [ class "expand-result" ] + [ text "Show" ] ] - [ text result.name ] - , button - -- TODO onClick, send a delete action to the address - [ class "hide-result" ] - [ text "X" ] - ] diff --git a/part10/style.css b/part10/style.css index d030733..64c74d7 100644 --- a/part10/style.css +++ b/part10/style.css @@ -90,12 +90,3 @@ a:hover { button:focus, input:focus { outline: none; } - -.error { - background-color: #FF9632; - padding: 20px; - box-sizing: border-box; - overflow-x: auto; - font-family: monospace; - font-size: 18px; -} diff --git a/part10/test/TestRunner.elm b/part10/test/TestRunner.elm deleted file mode 100644 index 0baa6f2..0000000 --- a/part10/test/TestRunner.elm +++ /dev/null @@ -1,15 +0,0 @@ -module Main where - -import Signal exposing (Signal) - -import ElmTest exposing (consoleRunner) -import Console exposing (IO, run) -import Task - -import Tests - -console : IO () -console = consoleRunner Tests.all - -port runner : Signal (Task.Task x ()) -port runner = run console diff --git a/part10/test/Tests.elm b/part10/test/Tests.elm deleted file mode 100644 index 7c06fe2..0000000 --- a/part10/test/Tests.elm +++ /dev/null @@ -1,35 +0,0 @@ -module Tests (..) where - -import ElmTest exposing (..) -import ElmHub exposing (responseDecoder) -import Json.Decode exposing (decodeString) - - -all : Test -all = - suite - "Decoding responses from GitHub" - [ test "they can decode empty responses" - <| let - emptyResponse = - """{ "items": [] }""" - in - assertEqual - (decodeString responseDecoder emptyResponse) - (Ok []) - , test "they can decode responses with results in them" - <| let - response = - """{ "items": [ - { "id": 5, "full_name": "foo", "stargazers_count": 42 }, - { "id": 3, "full_name": "bar", "stargazers_count": 77 } - ] }""" - in - assertEqual - (decodeString responseDecoder response) - (Ok - [ { id = 5, name = "foo", stars = 42 } - , { id = 3, name = "bar", stars = 77 } - ] - ) - ] diff --git a/part10/test/elm-package.json b/part10/test/elm-package.json deleted file mode 100644 index a9183a1..0000000 --- a/part10/test/elm-package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": "1.0.0", - "summary": "Like GitHub, but for Elm stuff.", - "repository": "https://github.com/rtfeldman/elm-workshop.git", - "license": "BSD-3-Clause", - "source-directories": [ - ".", - ".." - ], - "exposed-modules": [], - "dependencies": { - "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", - "elm-lang/core": "4.0.1 <= v < 5.0.0", - "elm-lang/html": "1.0.0 <= v < 2.0.0", - "evancz/elm-http": "3.0.1 <= v < 4.0.0" - }, - "elm-version": "0.17.0 <= v < 0.18.0" -} diff --git a/part11/ElmHub.elm b/part11/ElmHub.elm index d6f42c3..97697d3 100644 --- a/part11/ElmHub.elm +++ b/part11/ElmHub.elm @@ -3,13 +3,13 @@ module ElmHub exposing (..) import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (..) +import Html.App import Http import Auth import Task exposing (Task) import Json.Decode exposing (Decoder) -import Json.Encode import Dict exposing (Dict) -import SearchResult +import SearchResult exposing (ResultId) searchFeed : String -> Cmd Msg @@ -27,12 +27,13 @@ searchFeed query = responseDecoder : Decoder (List SearchResult.Model) responseDecoder = - "items" := Json.Decode.list SearchResult.decoder + Json.Decode.at [ "items" ] (Json.Decode.list SearchResult.decoder) type alias Model = { query : String - , results : Dict SearchResult.ResultId SearchResult.Model + , results : Dict ResultId SearchResult.Model + , errorMessage : Maybe String } @@ -40,6 +41,7 @@ initialModel : Model initialModel = { query = "tutorial" , results = Dict.empty + , errorMessage = Nothing } @@ -52,17 +54,28 @@ view model = ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] [] , button [ class "search-button", onClick Search ] [ text "Search" ] + , viewErrorMessage model.errorMessage , ul [ class "results" ] (viewSearchResults model.results) ] -viewSearchResults : Dict SearchResult.ResultId SearchResult.Model -> List (Html a) +viewErrorMessage : Maybe String -> Html a +viewErrorMessage errorMessage = + case errorMessage of + Just message -> + div [ class "error" ] [ text message ] + + Nothing -> + text "" + + +viewSearchResults : Dict ResultId SearchResult.Model -> List (Html Msg) viewSearchResults results = results |> Dict.values |> List.sortBy (.stars >> negate) |> filterResults - |> List.map (SearchResult.view address DeleteById) + |> List.map viewSearchResult filterResults : List SearchResult.Model -> List SearchResult.Model @@ -72,35 +85,62 @@ filterResults results = results +viewSearchResult : SearchResult.Model -> Html Msg +viewSearchResult result = + result + |> SearchResult.view + |> Html.App.map (UpdateSearchResult result.id) + + type Msg = Search | SetQuery String - | DeleteById SearchResult.ResultId - | SetResults (List SearchResult.Model) + | UpdateSearchResult ResultId SearchResult.Msg + | HandleSearchResponse (List SearchResult.Model) + | HandleSearchError Http.Error update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Search -> - ( model, searchFeed model.query ) + model ! [ searchFeed model.query ] SetQuery query -> - ( { model | query = query }, Cmd.none ) + { model | query = query, errorMessage = Nothing } ! [] - SetResults results -> + HandleSearchError error -> + case error of + Http.UnexpectedPayload str -> + { model | errorMessage = Just str } ! [] + + _ -> + { model | errorMessage = Just "Error loading search results" } ! [] + + HandleSearchResponse results -> let - resultsById : Dict SearchResult.ResultId SearchResult.Model + resultsById : Dict ResultId SearchResult.Model resultsById = results |> List.map (\result -> ( result.id, result )) |> Dict.fromList in - ( { model | results = resultsById }, Cmd.none ) + { model | results = resultsById } ! [] - DeleteById id -> - let - newModel = - { model | results = Dict.remove id model.results } - in - ( newModel, Cmd.none ) + UpdateSearchResult id childMsg -> + case Dict.get id model.results of + Nothing -> + model ! [] + + Just childModel -> + let + ( newChildModel, childCmd ) = + SearchResult.update childMsg childModel + + cmd = + Cmd.map (UpdateSearchResult id) childCmd + + newResults = + Dict.insert id newChildModel model.results + in + { model | results = newResults } ! [ cmd ] diff --git a/part11/Main.elm b/part11/Main.elm index c60133a..98f25b7 100644 --- a/part11/Main.elm +++ b/part11/Main.elm @@ -1,6 +1,7 @@ module Main exposing (..) import ElmHub exposing (..) +import Html.App main : Program Never @@ -9,5 +10,5 @@ main = { view = view , update = update , init = ( initialModel, searchFeed initialModel.query ) - , inputs = [] + , subscriptions = \_ -> Sub.none } diff --git a/part11/SearchResult.elm b/part11/SearchResult.elm index f36b29d..a251fb4 100644 --- a/part11/SearchResult.elm +++ b/part11/SearchResult.elm @@ -1,45 +1,67 @@ module SearchResult exposing (..) import Html exposing (..) -import Html.Attributes exposing (..) +import Html.Attributes exposing (class, target, href, property, defaultValue) import Html.Events exposing (..) import Json.Decode exposing (Decoder) -import Dict exposing (Dict) +import Json.Decode.Pipeline exposing (..) + + +type alias Model = + { id : Int + , name : String + , stars : Int + , expanded : Bool + } type alias ResultId = Int -type alias Model = - { id : ResultId - , name : String - , stars : Int - } +type Msg + = Expand + | Collapse decoder : Decoder Model decoder = - Json.Decode.object3 Model - ("id" := Json.Decode.int) - ("full_name" := Json.Decode.string) - ("stargazers_count" := Json.Decode.int) + decode Model + |> required "id" Json.Decode.int + |> required "full_name" Json.Decode.string + |> required "stargazers_count" Json.Decode.int + |> hardcoded True -view : Address a -> (Int -> a) -> Model -> Html -view address delete result = +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Expand -> + { model | expanded = True } ! [] + + Collapse -> + { model | expanded = False } ! [] + + +view : Model -> Html Msg +view model = li [] - [ span [ class "star-count" ] [ text (toString result.stars) ] - , a - [ href - ("https://github.com/" - ++ (Debug.log "TODO we should not see this when typing in the search box!" - result.name - ) - ) - , target "_blank" + <| if model.expanded then + [ span [ class "star-count" ] [ text (toString model.stars) ] + , a + [ href + ("https://github.com/" + ++ (Debug.log "TODO we should not see this when typing in the search box!" + model.name + ) + ) + , target "_blank" + ] + [ text model.name ] + , button [ class "hide-result", onClick Collapse ] + [ text "X" ] + ] + else + [ button [ class "expand-result", onClick Expand ] + [ text "Show" ] ] - [ text result.name ] - , button [ class "hide-result", onClick (delete result.id) ] - [ text "X" ] - ] diff --git a/part12/ElmHub.elm b/part12/ElmHub.elm index 3a683b9..29f05b3 100644 --- a/part12/ElmHub.elm +++ b/part12/ElmHub.elm @@ -3,42 +3,37 @@ module ElmHub exposing (..) import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (..) -import Html.Lazy exposing (..) +import Html.App import Http import Auth import Task exposing (Task) import Json.Decode exposing (Decoder) -import Json.Encode import Dict exposing (Dict) import SearchResult exposing (ResultId) -searchFeed : String -> Task x Msg +searchFeed : String -> Cmd Msg searchFeed query = let - -- See https://developer.github.com/v3/search/#example for how to customize! url = "https://api.github.com/search/repositories?access_token=" ++ Auth.token ++ "&q=" ++ query ++ "+language:elm&sort=stars&order=desc" - - task = - Http.get responseDecoder url - |> Task.map SetResults in - Task.onError task (\_ -> Task.succeed (SetResults [])) + Task.perform HandleSearchError HandleSearchResponse (Http.get responseDecoder url) responseDecoder : Decoder (List SearchResult.Model) responseDecoder = - "items" := Json.Decode.list SearchResult.decoder + Json.Decode.at [ "items" ] (Json.Decode.list SearchResult.decoder) type alias Model = { query : String - , results : Dict SearchResult.ResultId SearchResult.Model + , results : Dict ResultId SearchResult.Model + , errorMessage : Maybe String } @@ -46,6 +41,7 @@ initialModel : Model initialModel = { query = "tutorial" , results = Dict.empty + , errorMessage = Nothing } @@ -58,67 +54,85 @@ view model = ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] [] , button [ class "search-button", onClick Search ] [ text "Search" ] + , viewErrorMessage model.errorMessage , ul [ class "results" ] (viewSearchResults model.results) ] -viewSearchResults : Dict ResultId SearchResult.Model -> List Html +viewErrorMessage : Maybe String -> Html a +viewErrorMessage errorMessage = + case errorMessage of + Just message -> + div [ class "error" ] [ text message ] + + Nothing -> + text "" + + +viewSearchResults : Dict ResultId SearchResult.Model -> List (Html Msg) viewSearchResults results = results |> Dict.values |> List.sortBy (.stars >> negate) - |> List.map (viewSearchResult address) + |> List.map viewSearchResult viewSearchResult : SearchResult.Model -> Html Msg viewSearchResult result = - SearchResult.view (Signal.forwardTo address (UpdateSearchResult result.id)) - result + result + |> SearchResult.view + |> Html.App.map (UpdateSearchResult result.id) type Msg = Search | SetQuery String - | SetResults (List SearchResult.Model) | UpdateSearchResult ResultId SearchResult.Msg + | HandleSearchResponse (List SearchResult.Model) + | HandleSearchError Http.Error update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Search -> - ( model, searchFeed model.query ) + model ! [ searchFeed model.query ] SetQuery query -> - ( { model | query = query }, Cmd.none ) + { model | query = query, errorMessage = Nothing } ! [] - SetResults results -> + HandleSearchError error -> + case error of + Http.UnexpectedPayload str -> + { model | errorMessage = Just str } ! [] + + _ -> + { model | errorMessage = Just "Error loading search results" } ! [] + + HandleSearchResponse results -> let - resultsById : Dict SearchResult.ResultId SearchResult.Model + resultsById : Dict ResultId SearchResult.Model resultsById = results |> List.map (\result -> ( result.id, result )) |> Dict.fromList in - ( { model | results = resultsById }, Cmd.none ) + { model | results = resultsById } ! [] UpdateSearchResult id childMsg -> - let - updated = - model.results - |> Dict.get id - |> Maybe.map (SearchResult.update childMsg) - in - case updated of - Nothing -> - ( model, Cmd.none ) + case Dict.get id model.results of + Nothing -> + model ! [] - Just ( newChildModel, childEffects ) -> - let - effects = - Effects.map (UpdateSearchResult id) childEffects + Just childModel -> + let + ( newChildModel, childCmd ) = + SearchResult.update childMsg childModel - newResults = - Dict.insert id newChildModel model.results - in - ( { model | results = newResults }, effects ) + cmd = + Cmd.map (UpdateSearchResult id) childCmd + + newResults = + Dict.insert id newChildModel model.results + in + { model | results = newResults } ! [ cmd ] diff --git a/part13/ElmHub/Css.elm b/part12/ElmHub/Css.elm similarity index 100% rename from part13/ElmHub/Css.elm rename to part12/ElmHub/Css.elm diff --git a/part12/Main.elm b/part12/Main.elm index c60133a..98f25b7 100644 --- a/part12/Main.elm +++ b/part12/Main.elm @@ -1,6 +1,7 @@ module Main exposing (..) import ElmHub exposing (..) +import Html.App main : Program Never @@ -9,5 +10,5 @@ main = { view = view , update = update , init = ( initialModel, searchFeed initialModel.query ) - , inputs = [] + , subscriptions = \_ -> Sub.none } diff --git a/part12/README.md b/part12/README.md index 4c4be57..98ad1bc 100644 --- a/part12/README.md +++ b/part12/README.md @@ -19,6 +19,12 @@ to fail; in that case, just run `elm-package install` again.) elm-live Main.elm --open --output=elm.js ``` +## Compiling CSS + +```bash +elm css Stylesheets.elm +``` + ## References -* [Elm Architecture Tutorial](https://github.com/evancz/elm-architecture-tutorial) +* [Elm CSS documentation](http://package.elm-lang.org/packages/rtfeldman/elm-css/1.1.0/) diff --git a/part12/SearchResult.elm b/part12/SearchResult.elm index 015814e..d4ff41d 100644 --- a/part12/SearchResult.elm +++ b/part12/SearchResult.elm @@ -35,25 +35,25 @@ decoder = update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = - -- TODO implement Expand and Collapse logic - ( model, Cmd.none ) + case msg of + Expand -> + { model | expanded = True } ! [] + + Collapse -> + { model | expanded = False } ! [] view : Model -> Html Msg view model = - li [] <| - if model.expanded then + li [] + <| if model.expanded then [ span [ class "star-count" ] [ text (toString model.stars) ] , a [ href ("https://github.com/" ++ model.name), target "_blank" ] [ text model.name ] - , button - -- TODO when the user clicks, send a Collapse action - [ class "hide-result" ] + , button [ class "hide-result", onClick Collapse ] [ text "X" ] ] - else - [ button - -- TODO when the user clicks, send an Expand action - [ class "expand-result" ] + else + [ button [ class "expand-result", onClick Expand ] [ text "Show" ] ] diff --git a/part13/Stylesheets.elm b/part12/Stylesheets.elm similarity index 100% rename from part13/Stylesheets.elm rename to part12/Stylesheets.elm diff --git a/part12/elm-package.json b/part12/elm-package.json index a9183a1..691c683 100644 --- a/part12/elm-package.json +++ b/part12/elm-package.json @@ -12,7 +12,8 @@ "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", - "evancz/elm-http": "3.0.1 <= v < 4.0.0" + "evancz/elm-http": "3.0.1 <= v < 4.0.0", + "rtfeldman/elm-css": "3.1.0 <= v < 4.0.0" }, "elm-version": "0.17.0 <= v < 0.18.0" -} +} \ No newline at end of file diff --git a/part12/style.css b/part12/style.css index 64c74d7..9d13aae 100644 --- a/part12/style.css +++ b/part12/style.css @@ -1,92 +1,6 @@ - .content { - width: 960px; - margin: 0 auto; - padding: 30px; - font-family: Helvetica, Arial, serif; -} - -header { - position: relative; - padding: 6px 12px; - height: 36px; - background-color: rgb(96, 181, 204); -} - -h1 { - color: white; - font-weight: normal; - margin: 0; -} - -.tagline { - color: #eee; - position: absolute; - right: 16px; - top: 12px; - font-size: 24px; - font-style: italic; -} - -.results { - list-style-image: url('http://img-cache.cdn.gaiaonline.com/76bd5c99d8f2236e9d3672510e933fdf/http://i278.photobucket.com/albums/kk81/d3m3nt3dpr3p/Tiny-Star-Icon.png'); - list-style-position: inside; - padding: 0; -} - -.results li { - font-size: 18px; - margin-bottom: 16px; -} - -.star-count { - font-weight: bold; - margin-right: 16px; -} - -a { - color: rgb(96, 181, 204); - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -.search-query { - padding: 8px; - font-size: 24px; - margin-bottom: 18px; - margin-top: 36px; -} - -.search-button { - padding: 8px 16px; - font-size: 24px; - color: white; - border: 1px solid #ccc; - background-color: rgb(96, 181, 204); - margin-left: 12px -} - -.search-button:hover { - color: rgb(96, 181, 204); - background-color: white; -} - -.hide-result { - background-color: transparent; - border: 0; - font-weight: bold; - font-size: 18px; - margin-left: 18px; - cursor: pointer; -} - -.hide-result:hover { - color: rgb(96, 181, 204); -} - -button:focus, input:focus { - outline: none; + width: 960px; + margin: 0 auto; + padding: 30px; + font-family: Helvetica, Arial, serif; } diff --git a/part12/test/elm-package.json b/part12/test/elm-package.json index a9183a1..35abeae 100644 --- a/part12/test/elm-package.json +++ b/part12/test/elm-package.json @@ -12,6 +12,7 @@ "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", + "rtfeldman/elm-css": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0" }, "elm-version": "0.17.0 <= v < 0.18.0" diff --git a/part13/ElmHub.elm b/part13/ElmHub.elm deleted file mode 100644 index 2f8a93b..0000000 --- a/part13/ElmHub.elm +++ /dev/null @@ -1,124 +0,0 @@ -module ElmHub exposing (..) - -import Html exposing (..) -import Html.Attributes exposing (..) -import Html.Events exposing (..) -import Html.Lazy exposing (..) -import Http -import Auth -import Task exposing (Task) -import Json.Decode exposing (Decoder) -import Json.Encode -import Dict exposing (Dict) -import SearchResult exposing (ResultId) - - -searchFeed : String -> Task x Msg -searchFeed query = - let - -- See https://developer.github.com/v3/search/#example for how to customize! - url = - "https://api.github.com/search/repositories?access_token=" - ++ Auth.token - ++ "&q=" - ++ query - ++ "+language:elm&sort=stars&order=desc" - - task = - Http.get responseDecoder url - |> Task.map SetResults - in - Task.onError task (\_ -> Task.succeed (SetResults [])) - - -responseDecoder : Decoder (List SearchResult.Model) -responseDecoder = - "items" := Json.Decode.list SearchResult.decoder - - -type alias Model = - { query : String - , results : Dict SearchResult.ResultId SearchResult.Model - } - - -initialModel : Model -initialModel = - { query = "tutorial" - , results = Dict.empty - } - - -view : Model -> Html Msg -view model = - div [ class "content" ] - [ header [] - [ h1 [] [ text "ElmHub" ] - , span [ class "tagline" ] [ text "“Like GitHub, but for Elm things.”" ] - ] - , input [ class "search-query", onInput SetQuery, defaultValue model.query ] [] - , button [ class "search-button", onClick Search ] [ text "Search" ] - , ul [ class "results" ] (viewSearchResults model.results) - ] - - -viewSearchResults : Dict ResultId SearchResult.Model -> List Html -viewSearchResults results = - results - |> Dict.values - |> List.sortBy (.stars >> negate) - |> List.map (viewSearchResult address) - - -viewSearchResult : Address Action -> SearchResult.Model -> Html -viewSearchResult result = - SearchResult.view (Signal.forwardTo address (UpdateSearchResult result.id)) - result - - -type Msg - = Search - | SetQuery String - | SetResults (List SearchResult.Model) - | UpdateSearchResult ResultId SearchResult.Action - - -update : Msg -> Model -> ( Model, Cmd Msg ) -update msg model = - case msg of - Search -> - ( model, searchFeed model.query ) - - SetQuery query -> - ( { model | query = query }, Cmd.none ) - - SetResults results -> - let - resultsById : Dict SearchResult.ResultId SearchResult.Model - resultsById = - results - |> List.map (\result -> ( result.id, result )) - |> Dict.fromList - in - ( { model | results = resultsById }, Cmd.none ) - - UpdateSearchResult id childAction -> - let - updated = - model.results - |> Dict.get id - |> Maybe.map (SearchResult.update childAction) - in - case updated of - Nothing -> - ( model, Cmd.none ) - - Just ( newChildModel, childEffects ) -> - let - effects = - Effects.map (UpdateSearchResult id) childEffects - - newResults = - Dict.insert id newChildModel model.results - in - ( { model | results = newResults }, effects ) diff --git a/part13/Main.elm b/part13/Main.elm deleted file mode 100644 index c60133a..0000000 --- a/part13/Main.elm +++ /dev/null @@ -1,13 +0,0 @@ -module Main exposing (..) - -import ElmHub exposing (..) - - -main : Program Never -main = - Html.App.program - { view = view - , update = update - , init = ( initialModel, searchFeed initialModel.query ) - , inputs = [] - } diff --git a/part13/README.md b/part13/README.md deleted file mode 100644 index 66204f7..0000000 --- a/part13/README.md +++ /dev/null @@ -1,30 +0,0 @@ -Part 13 -======= - -The instructor will paste notes from the lesson, including code examples from -Q&A, in [this document](https://docs.google.com/document/d/1ApuSOk9DP0YsQrxhW7-WE8UOEAV4PPnLDDeqUOL2o5k/edit?usp=sharing). - -## Installation - -```bash -elm-package install -``` - -(Answer `y` at the prompt. In rare cases a known issue can cause the download -to fail; in that case, just run `elm-package install` again.) - -## Building - -```bash -elm-live Main.elm --open --output=elm.js -``` - -## Compiling CSS - -```bash -elm css Stylesheets.elm -``` - -## References - -* [Elm CSS documentation](http://package.elm-lang.org/packages/rtfeldman/elm-css/1.1.0/) diff --git a/part13/SearchResult.elm b/part13/SearchResult.elm deleted file mode 100644 index a6e8e04..0000000 --- a/part13/SearchResult.elm +++ /dev/null @@ -1,59 +0,0 @@ -module SearchResult exposing (..) - -import Html exposing (..) -import Html.Attributes exposing (class, target, href, property, defaultValue) -import Html.Events exposing (..) -import Json.Decode exposing (Decoder) -import Json.Decode.Pipeline exposing (..) - - -type alias Model = - { id : Int - , name : String - , stars : Int - , expanded : Bool - } - - -type alias ResultId = - Int - - -type Msg - = Expand - | Collapse - - -decoder : Decoder Model -decoder = - decode Model - |> required "id" Json.Decode.int - |> required "full_name" Json.Decode.string - |> required "stargazers_count" Json.Decode.int - |> hardcoded True - - -update : Msg -> Model -> ( Model, Cmd Msg ) -update msg model = - case msg of - Expand -> - ( { model | expanded = True }, Cmd.none ) - - Collapse -> - ( { model | expanded = False }, Cmd.none ) - - -view : Model -> Html Msg -view model = - li [] - <| if model.expanded then - [ span [ class "star-count" ] [ text (toString model.stars) ] - , a [ href ("https://github.com/" ++ model.name), target "_blank" ] - [ text model.name ] - , button [ class "hide-result", onClick Collapse ] - [ text "X" ] - ] - else - [ button [ class "expand-result", onClick Expand ] - [ text "Show" ] - ] diff --git a/part13/asdf/elm-package.json b/part13/asdf/elm-package.json deleted file mode 100644 index 35abeae..0000000 --- a/part13/asdf/elm-package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": "1.0.0", - "summary": "Like GitHub, but for Elm stuff.", - "repository": "https://github.com/rtfeldman/elm-workshop.git", - "license": "BSD-3-Clause", - "source-directories": [ - ".", - ".." - ], - "exposed-modules": [], - "dependencies": { - "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", - "elm-lang/core": "4.0.1 <= v < 5.0.0", - "elm-lang/html": "1.0.0 <= v < 2.0.0", - "rtfeldman/elm-css": "1.0.0 <= v < 2.0.0", - "evancz/elm-http": "3.0.1 <= v < 4.0.0" - }, - "elm-version": "0.17.0 <= v < 0.18.0" -} diff --git a/part13/asdf/style.css b/part13/asdf/style.css deleted file mode 100644 index d030733..0000000 --- a/part13/asdf/style.css +++ /dev/null @@ -1,101 +0,0 @@ - -.content { - width: 960px; - margin: 0 auto; - padding: 30px; - font-family: Helvetica, Arial, serif; -} - -header { - position: relative; - padding: 6px 12px; - height: 36px; - background-color: rgb(96, 181, 204); -} - -h1 { - color: white; - font-weight: normal; - margin: 0; -} - -.tagline { - color: #eee; - position: absolute; - right: 16px; - top: 12px; - font-size: 24px; - font-style: italic; -} - -.results { - list-style-image: url('http://img-cache.cdn.gaiaonline.com/76bd5c99d8f2236e9d3672510e933fdf/http://i278.photobucket.com/albums/kk81/d3m3nt3dpr3p/Tiny-Star-Icon.png'); - list-style-position: inside; - padding: 0; -} - -.results li { - font-size: 18px; - margin-bottom: 16px; -} - -.star-count { - font-weight: bold; - margin-right: 16px; -} - -a { - color: rgb(96, 181, 204); - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -.search-query { - padding: 8px; - font-size: 24px; - margin-bottom: 18px; - margin-top: 36px; -} - -.search-button { - padding: 8px 16px; - font-size: 24px; - color: white; - border: 1px solid #ccc; - background-color: rgb(96, 181, 204); - margin-left: 12px -} - -.search-button:hover { - color: rgb(96, 181, 204); - background-color: white; -} - -.hide-result { - background-color: transparent; - border: 0; - font-weight: bold; - font-size: 18px; - margin-left: 18px; - cursor: pointer; -} - -.hide-result:hover { - color: rgb(96, 181, 204); -} - -button:focus, input:focus { - outline: none; -} - -.error { - background-color: #FF9632; - padding: 20px; - box-sizing: border-box; - overflow-x: auto; - font-family: monospace; - font-size: 18px; -} diff --git a/part13/elm-hub.png b/part13/elm-hub.png deleted file mode 100644 index ba32816a8df14eb878b6e39914bf889c799673aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2126 zcmV-U2(kBxP)WFU8GbZ8()Nlj2>E@cM*00**3L_t(o!=;ydbj?>5 z$3Of0?(g165Jo7`23@5cL{u*(iFtTbkbm9H-uPq*82VNJHLI-_xsy>fA>B|sAtSM zp1^{x2LYH{oM>pRqXqdj!cd^I-y1mKuBdR8mM*J|Y-yTigNUyJUc;*#K5_;)>)Rex zuYpoQ@J7IVM9v}1208;6rb@IoYP_mT=e{c7)x!fm-y&u6wa6LZB(3mBy#(ZbALkxV zV1CzwLGZ0WzdFn`g81BlkHP|M2jGv+kxE;e4nP4p8q7xY~ToAKcwgUhYrDEANWgSrq4chq1m2Z zZU0+t+L@#jEk*AA04;C1S$63do0=}|IVv+|Pw_(~q0O|}ugg<;uWlQ#{pKxF0{T8$ z)KGbwSu%^o+fw7R!y;@g9$E!Jw#_h}rcvzftX46 zQ^PfS`~0{A7lZxe&i+zM1^H{2kUT!a5RukEn8)KerIhl051AB7Ff^0rFdG;#nXbk(DW~ z%$*jv2XIu^99=nR%A$6Wy&JlPjRBA+2NPj4;rD0Fr^H0DCQPpEu{4=*$+wUYevt%@+}TUY&dO+{u}7 zKUwOs+3mALNhoYJn)AgOw3A{Yvv&%5fYqJ8OY(r3S>3}c2UWrQdQ zQ4;59{7CefW9m=MA1%A44Ar-qgjOJJwAO=e`{hELvlosHin^NNuro!3%SLRsUSA3f z3vpQl(41;g)!<(5iZ64Qr*~MBKhH4j(?rx=T~tI+N@5*N9&dNHsC0Hz%+)J~q56x6 zp*4|iFYiZ}o?AMGvr|n6zZan#7;D6KO(+qO4}n`iJBvV#*Zn@<(*NbYHM8T^y;(N9 zV~U8F_wKENQmT`qS&xqAKUp~=s%_*YWvXXAZk^k+q;&Bh^SgA@!C(<`fpo1oB;P9_ zGr~&7D4+>YAtF~dy|?)M=q>w}n~pFSB4Iq(&_0jnUtjM?82XD>oy|^qepIn;?`~gt zOCAe%a8qk;G#O6iJz&}kiYz3>ehWrZT7Iq7P#N4mXFto3T0BAO9BLZ zZuhQ@!xEFuWfNl$C%R=apM~0A+hSG16jx@@3$0UJnFU(=Gs_PjC`~QczbFJ;qxCCR zn1bUXa3-Xv*g?;;6r^kp$8)I#2e%a+;&LEB-zy*Re#I6Z4T3irZV|}K&LYJ%BLbKj z@cWyVpFTOY&7W6HF--e3L>#~cpeG{zLcHbyHUXPM2(<&zzze{)fVP?DkZl7#TeJ4~ zlW%){T=i)t`3q4^hTqW(lu}-w*R!eO(l_Hg``0VN5!bB>>6_N_`bxqXDyD#a86SB*J#R;%URQ zSFTHX<`_WQ&cj1MwjedVBLKXYljNL~R(Pm6sBBuA*hvv$(#3(KeBi2!%5+hWI?ZoQ2FUnkP$ga&JuvM!XqPqcPs@3Z7zZQ zI48+@xi-efp z;Y#3~CBc6KFXtpV^Cno}K@#U+5ikunWUbo|)O$VtKXD7qJ0u8@X8-^I07*qoM6N<$ Ef>T55F8}}l diff --git a/part13/elm-package.json b/part13/elm-package.json deleted file mode 100644 index 35abeae..0000000 --- a/part13/elm-package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": "1.0.0", - "summary": "Like GitHub, but for Elm stuff.", - "repository": "https://github.com/rtfeldman/elm-workshop.git", - "license": "BSD-3-Clause", - "source-directories": [ - ".", - ".." - ], - "exposed-modules": [], - "dependencies": { - "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", - "elm-lang/core": "4.0.1 <= v < 5.0.0", - "elm-lang/html": "1.0.0 <= v < 2.0.0", - "rtfeldman/elm-css": "1.0.0 <= v < 2.0.0", - "evancz/elm-http": "3.0.1 <= v < 4.0.0" - }, - "elm-version": "0.17.0 <= v < 0.18.0" -} diff --git a/part13/index.html b/part13/index.html deleted file mode 100644 index 93fc00c..0000000 --- a/part13/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - ElmHub - - - - - - - - - - - - diff --git a/part13/style.css b/part13/style.css deleted file mode 100644 index 9d13aae..0000000 --- a/part13/style.css +++ /dev/null @@ -1,6 +0,0 @@ -.content { - width: 960px; - margin: 0 auto; - padding: 30px; - font-family: Helvetica, Arial, serif; -} diff --git a/part13/test/TestRunner.elm b/part13/test/TestRunner.elm deleted file mode 100644 index 0baa6f2..0000000 --- a/part13/test/TestRunner.elm +++ /dev/null @@ -1,15 +0,0 @@ -module Main where - -import Signal exposing (Signal) - -import ElmTest exposing (consoleRunner) -import Console exposing (IO, run) -import Task - -import Tests - -console : IO () -console = consoleRunner Tests.all - -port runner : Signal (Task.Task x ()) -port runner = run console diff --git a/part13/test/Tests.elm b/part13/test/Tests.elm deleted file mode 100644 index 7c06fe2..0000000 --- a/part13/test/Tests.elm +++ /dev/null @@ -1,35 +0,0 @@ -module Tests (..) where - -import ElmTest exposing (..) -import ElmHub exposing (responseDecoder) -import Json.Decode exposing (decodeString) - - -all : Test -all = - suite - "Decoding responses from GitHub" - [ test "they can decode empty responses" - <| let - emptyResponse = - """{ "items": [] }""" - in - assertEqual - (decodeString responseDecoder emptyResponse) - (Ok []) - , test "they can decode responses with results in them" - <| let - response = - """{ "items": [ - { "id": 5, "full_name": "foo", "stargazers_count": 42 }, - { "id": 3, "full_name": "bar", "stargazers_count": 77 } - ] }""" - in - assertEqual - (decodeString responseDecoder response) - (Ok - [ { id = 5, name = "foo", stars = 42 } - , { id = 3, name = "bar", stars = 77 } - ] - ) - ] diff --git a/part13/test/elm-package.json b/part13/test/elm-package.json deleted file mode 100644 index 35abeae..0000000 --- a/part13/test/elm-package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": "1.0.0", - "summary": "Like GitHub, but for Elm stuff.", - "repository": "https://github.com/rtfeldman/elm-workshop.git", - "license": "BSD-3-Clause", - "source-directories": [ - ".", - ".." - ], - "exposed-modules": [], - "dependencies": { - "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", - "elm-lang/core": "4.0.1 <= v < 5.0.0", - "elm-lang/html": "1.0.0 <= v < 2.0.0", - "rtfeldman/elm-css": "1.0.0 <= v < 2.0.0", - "evancz/elm-http": "3.0.1 <= v < 4.0.0" - }, - "elm-version": "0.17.0 <= v < 0.18.0" -} diff --git a/part7/ElmHub.elm b/part7/ElmHub.elm index ee88a60..e01d8d6 100644 --- a/part7/ElmHub.elm +++ b/part7/ElmHub.elm @@ -6,7 +6,6 @@ import Html.Events exposing (..) import Auth import Json.Decode exposing (Decoder) import Json.Decode.Pipeline exposing (..) -import Json.Encode getQueryString : String -> String @@ -97,9 +96,8 @@ type Msg = Search | SetQuery String | DeleteById ResultId - | SetResults (List SearchResult) - | SetErrorMessage (Maybe String) - | DoNothing + | HandleSearchResponse (List SearchResult) + | HandleSearchError (Maybe String) update : (String -> Cmd Msg) -> Msg -> Model -> ( Model, Cmd Msg ) @@ -111,31 +109,28 @@ update searchFeed msg model = SetQuery query -> ( { model | query = query }, Cmd.none ) - SetResults results -> + HandleSearchResponse results -> ( { model | results = results }, Cmd.none ) - SetErrorMessage errorMessage -> - ( { model | errorMessage = errorMessage }, Cmd.none ) + HandleSearchError error -> + ( { model | errorMessage = error }, Cmd.none ) - DeleteById idToHide -> + DeleteById idToDelete -> let newResults = model.results - |> List.filter (\{ id } -> id /= idToHide) + |> List.filter (\{ id } -> id /= idToDelete) newModel = { model | results = newResults } in ( newModel, Cmd.none ) - DoNothing -> - ( model, Cmd.none ) - -decodeGithubResponse : Json.Encode.Value -> Msg +decodeGithubResponse : Json.Decode.Value -> Msg decodeGithubResponse value = - -- TODO use Json.Decode.DecodeValue to decode the response into an Action. + -- TODO use Json.Decode.DecodeValue to decode the response into a Msg. -- - -- Hint: look at ElmHub.elm, specifically the definition of Action and - -- the deefinition of responseDecoder - SetErrorMessage (Just "TODO decode the response!") + -- Hint: look at the definition of Msg and + -- the definition of responseDecoder + HandleSearchError (Just "TODO decode the response!") diff --git a/part7/Main.elm b/part7/Main.elm index faa0c13..45c9682 100644 --- a/part7/Main.elm +++ b/part7/Main.elm @@ -19,10 +19,10 @@ decodeResponse : Json.Decode.Value -> Msg decodeResponse json = case Json.Decode.decodeValue responseDecoder json of Err err -> - SetErrorMessage (Just err) + HandleSearchError (Just err) Ok results -> - SetResults results + HandleSearchResponse results port githubSearch : String -> Cmd msg diff --git a/part8/ElmHub.elm b/part8/ElmHub.elm index ee88a60..20243e2 100644 --- a/part8/ElmHub.elm +++ b/part8/ElmHub.elm @@ -6,7 +6,6 @@ import Html.Events exposing (..) import Auth import Json.Decode exposing (Decoder) import Json.Decode.Pipeline exposing (..) -import Json.Encode getQueryString : String -> String @@ -97,8 +96,8 @@ type Msg = Search | SetQuery String | DeleteById ResultId - | SetResults (List SearchResult) - | SetErrorMessage (Maybe String) + | HandleSearchResponse (List SearchResult) + | HandleSearchError (Maybe String) | DoNothing @@ -111,11 +110,11 @@ update searchFeed msg model = SetQuery query -> ( { model | query = query }, Cmd.none ) - SetResults results -> + HandleSearchResponse results -> ( { model | results = results }, Cmd.none ) - SetErrorMessage errorMessage -> - ( { model | errorMessage = errorMessage }, Cmd.none ) + HandleSearchError error -> + ( { model | errorMessage = error }, Cmd.none ) DeleteById idToHide -> let @@ -132,10 +131,11 @@ update searchFeed msg model = ( model, Cmd.none ) -decodeGithubResponse : Json.Encode.Value -> Msg +decodeGithubResponse : Json.Decode.Value -> Msg decodeGithubResponse value = - -- TODO use Json.Decode.DecodeValue to decode the response into an Action. - -- - -- Hint: look at ElmHub.elm, specifically the definition of Action and - -- the deefinition of responseDecoder - SetErrorMessage (Just "TODO decode the response!") + case Json.Decode.decodeValue responseDecoder value of + Ok results -> + HandleSearchResponse results + + Err err -> + HandleSearchError (Just err) diff --git a/part8/Main.elm b/part8/Main.elm index faa0c13..45c9682 100644 --- a/part8/Main.elm +++ b/part8/Main.elm @@ -19,10 +19,10 @@ decodeResponse : Json.Decode.Value -> Msg decodeResponse json = case Json.Decode.decodeValue responseDecoder json of Err err -> - SetErrorMessage (Just err) + HandleSearchError (Just err) Ok results -> - SetResults results + HandleSearchResponse results port githubSearch : String -> Cmd msg diff --git a/part8/README.md b/part8/README.md index f0358fc..f087c33 100644 --- a/part8/README.md +++ b/part8/README.md @@ -21,12 +21,29 @@ elm-live Main.elm --open --output=elm.js ## Running Tests +First do this: + ```bash cd test elm-package install -elm-test Test.elm ``` +Then do either (or both!) of the following: + +#### Running tests on the command line + +```bash +elm-test NodeRunner.elm +``` + +#### Running tests in a browser + +```bash +elm-reactor +``` + +Then visit [localhost:8000](http://localhost:8000) and choose `Html.elm`. + ## References * [Using Elm packages](https://github.com/elm-lang/elm-package/blob/master/README.md#basic-usage) diff --git a/part8/test/HtmlRunner.elm b/part8/test/HtmlRunner.elm new file mode 100644 index 0000000..24ba2cb --- /dev/null +++ b/part8/test/HtmlRunner.elm @@ -0,0 +1,16 @@ +module HtmlRunner exposing (..) + +import Tests +import Test.Runner.Html as Runner + + +-- To run this: +-- +-- cd into part8/test +-- elm-reactor +-- navigate to HtmlRunner.elm + + +main : Program Never +main = + Runner.run Tests.all diff --git a/part8/test/NodeRunner.elm b/part8/test/NodeRunner.elm new file mode 100644 index 0000000..6cbe0f5 --- /dev/null +++ b/part8/test/NodeRunner.elm @@ -0,0 +1,19 @@ +port module Main exposing (..) + +import Tests +import Test.Runner.Node as Runner +import Json.Decode exposing (Value) + + +-- To run this: +-- +-- cd into part8/test +-- elm-test NodeRunner.elm + + +main : Program Never +main = + Runner.run emit Tests.all + + +port emit : ( String, Value ) -> Cmd msg diff --git a/part8/test/Tests.elm b/part8/test/Tests.elm index ad81e51..8263b8c 100644 --- a/part8/test/Tests.elm +++ b/part8/test/Tests.elm @@ -1,55 +1,70 @@ -port module Main exposing (..) +module Tests exposing (..) import Test exposing (..) +import Fuzz exposing (..) import Expect exposing (Expectation) import ElmHub exposing (responseDecoder) import Json.Decode exposing (decodeString, Value) -import Test.Runner.Node as Runner +import String -main : Program Never -main = - describe "Decoding responses from GitHub" - [ test "they can decode empty responses" +all : Test +all = + describe "GitHub Response Decoder" + [ test "it results in an Err for invalid JSON" <| \() -> let - emptyResponse = - """{ "items": [] }""" - in - Expect.equal (Ok []) - (decodeString responseDecoder emptyResponse) - , test "they can decode responses with results in them" - <| \() -> - let - response = - """{ "items": [ - /* TODO: dummy JSON goes here */ - ] }""" - in - Expect.equal - (Ok - [ { id = 5, name = "foo", stars = 42 } - , { id = 3, name = "bar", stars = 77 } - ] - ) - (decodeString responseDecoder response) - , test "they result in an error for invalid JSON" - <| \() -> - let - response = + json = """{ "pizza": [] }""" isErrorResult result = -- TODO return True if the given Result is an Err of some sort, -- and False if it is an Ok of some sort. -- - -- Result docs: http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Result + -- Result docs: http://package.elm-lang.org/packages/elm-lang/core/4.0.1/Result False in - Expect.true "Expected decoding an invalid response to return an Err." - (isErrorResult (decodeString responseDecoder response)) + json + |> decodeString responseDecoder + |> isErrorResult + |> Expect.true "Expected decoding an invalid response to return an Err." + , test "it successfully decodes a valid response" + <| \() -> + """{ "items": [ + /* TODO: put JSON here! */ + ] }""" + |> decodeString responseDecoder + |> Expect.equal + (Ok + [ { id = 5, name = "foo", stars = 42 } + , { id = 3, name = "bar", stars = 77 } + ] + ) + , test "it decodes one SearchResult for each 'item' in the JSON" + <| \() -> + let + -- TODO convert this to a fuzz test that generates a random + -- list of ids instead of this hardcoded list of three ids. + -- + -- fuzz test docs: http://package.elm-lang.org/packages/project-fuzzball/test/2.0.1/Test#fuzz + -- Fuzzer docs: http://package.elm-lang.org/packages/project-fuzzball/test/2.0.1/Fuzz + ids = + [ 12, 5, 76 ] + + jsonFromId id = + """{"id": """ ++ toString id ++ """, "full_name": "foo", "stargazers_count": 42}""" + + jsonItems = + String.join ", " (List.map jsonFromId ids) + + json = + """{ "items": [""" ++ jsonItems ++ """] }""" + in + case decodeString responseDecoder json of + Ok results -> + List.length results + |> Expect.equal (List.length ids) + + Err err -> + Expect.fail ("JSON decoding failed unexpectedly: " ++ err) ] - |> Runner.run emit - - -port emit : ( String, Value ) -> Cmd msg diff --git a/part8/test/elm-package.json b/part8/test/elm-package.json index 8525bcc..e3cceb5 100644 --- a/part8/test/elm-package.json +++ b/part8/test/elm-package.json @@ -9,12 +9,13 @@ ], "exposed-modules": [], "dependencies": { - "project-fuzzball/test": "2.0.1 <= v < 3.0.0", - "project-fuzzball/node": "1.0.2 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", - "evancz/elm-http": "3.0.1 <= v < 4.0.0" + "evancz/elm-http": "3.0.1 <= v < 4.0.0", + "project-fuzzball/node": "1.0.2 <= v < 2.0.0", + "project-fuzzball/test": "1.0.1 <= v < 2.0.0", + "project-fuzzball/test-runner": "1.0.1 <= v < 2.0.0" }, "elm-version": "0.17.0 <= v < 0.18.0" -} +} \ No newline at end of file diff --git a/part9/ElmHub.elm b/part9/ElmHub.elm index f4b0fd1..83e42ca 100644 --- a/part9/ElmHub.elm +++ b/part9/ElmHub.elm @@ -7,16 +7,21 @@ import Auth import Json.Decode exposing (Decoder) import Json.Decode.Pipeline exposing (..) import Dict exposing (Dict) +import Http +import Task -getQueryUrl : String -> String -getQueryUrl query = - -- See https://developer.github.com/v3/search/#example for how to customize! - "https://api.github.com/search/repositories?access_token=" - ++ Auth.token - ++ "&q=" - ++ query - ++ "+language:elm&sort=stars&order=desc" +searchFeed : String -> Cmd Msg +searchFeed query = + let + url = + "https://api.github.com/search/repositories?access_token=" + ++ Auth.token + ++ "&q=" + ++ query + ++ "+language:elm&sort=stars&order=desc" + in + Task.perform HandleSearchError HandleSearchResponse (Http.get responseDecoder url) responseDecoder : Decoder (List SearchResult) @@ -92,35 +97,40 @@ type Msg = Search | SetQuery String | DeleteById ResultId - | SetResults (List SearchResult) - | SetErrorMessage (Maybe String) + | HandleSearchResponse (List SearchResult) + | HandleSearchError Http.Error | DoNothing -update : (String -> Cmd Msg) -> Msg -> Model -> ( Model, Cmd Msg ) -update searchFeed msg model = +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = case msg of Search -> - ( model, searchFeed (getQueryUrl model.query) ) + model ! [ searchFeed model.query ] SetQuery query -> - ( { model | query = query }, Cmd.none ) + { model | query = query } ! [] - SetResults results -> + HandleSearchError error -> + case error of + Http.UnexpectedPayload str -> + { model | errorMessage = Just str } ! [] + + _ -> + { model | errorMessage = Just "Error loading search results" } ! [] + + HandleSearchResponse results -> let resultsById : Dict ResultId SearchResult resultsById = -- TODO convert results list into a Dict Dict.empty in - ( { model | results = resultsById }, Cmd.none ) + { model | results = resultsById } ! [] DeleteById id -> -- TODO delete the result with the given id - ( model, Cmd.none ) - - SetErrorMessage errorMessage -> - ( { model | errorMessage = errorMessage }, Cmd.none ) + model ! [] DoNothing -> - ( model, Cmd.none ) + model ! [] diff --git a/part9/Main.elm b/part9/Main.elm index 718fc54..98f25b7 100644 --- a/part9/Main.elm +++ b/part9/Main.elm @@ -1,31 +1,14 @@ -port module Main exposing (..) +module Main exposing (..) import ElmHub exposing (..) import Html.App -import Json.Decode exposing (Value) main : Program Never main = Html.App.program { view = view - , update = update githubSearch - , init = ( initialModel, githubSearch (getQueryUrl initialModel.query) ) - , subscriptions = \_ -> githubResponse decodeResponse + , update = update + , init = ( initialModel, searchFeed initialModel.query ) + , subscriptions = \_ -> Sub.none } - - -decodeResponse : Json.Decode.Value -> Msg -decodeResponse json = - case Json.Decode.decodeValue responseDecoder json of - Err err -> - SetErrorMessage (Just err) - - Ok results -> - SetResults results - - -port githubSearch : String -> Cmd msg - - -port githubResponse : (Json.Decode.Value -> msg) -> Sub msg diff --git a/part9/README.md b/part9/README.md index 93f7a37..68f4d82 100644 --- a/part9/README.md +++ b/part9/README.md @@ -21,8 +21,25 @@ elm-live Main.elm --open --output=elm.js ## Running Tests +First do this: + ```bash cd test elm-package install -elm-test Test.elm ``` + +Then do either (or both!) of the following: + +#### Running tests on the command line + +```bash +elm-test NodeRunner.elm +``` + +#### Running tests in a browser + +```bash +elm-reactor +``` + +Then visit [localhost:8000](http://localhost:8000) and choose `Html.elm`.