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
This commit is contained in:
Richard Feldman
2016-06-26 01:00:27 -07:00
parent 00e641d186
commit b557ee0842
45 changed files with 481 additions and 883 deletions

View File

@@ -1,14 +1,15 @@
module ElmHub exposing (..) module ElmHub exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, target, href, property, defaultValue) import Html.Attributes exposing (..)
import Html.Events exposing (..) import Html.Events exposing (..)
import Html.App
import Http import Http
import Auth import Auth
import Task exposing (Task) import Task exposing (Task)
import Json.Decode exposing (Decoder) import Json.Decode exposing (Decoder)
import Dict exposing (Dict) import Dict exposing (Dict)
import SearchResult import SearchResult exposing (ResultId)
searchFeed : String -> Cmd Msg searchFeed : String -> Cmd Msg
@@ -26,13 +27,13 @@ searchFeed query =
responseDecoder : Decoder (List SearchResult.Model) responseDecoder : Decoder (List SearchResult.Model)
responseDecoder = responseDecoder =
-- TODO make use of SearchResult's decoder Json.Decode.at [ "items" ] (Json.Decode.list SearchResult.decoder)
Json.Decode.succeed []
type alias Model = type alias Model =
{ query : String { query : String
, results : Dict SearchResult.ResultId SearchResult.Model , results : Dict ResultId SearchResult.Model
, errorMessage : Maybe String
} }
@@ -40,6 +41,7 @@ initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = Dict.empty , results = Dict.empty
, errorMessage = Nothing
} }
@@ -52,23 +54,42 @@ view model =
] ]
, input [ class "search-query", onInput SetQuery, defaultValue model.query ] [] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ] , button [ class "search-button", onClick Search ] [ text "Search" ]
, viewErrorMessage model.errorMessage
, ul [ class "results" ] (viewSearchResults model.results) , 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 = viewSearchResults results =
results results
|> Dict.values |> Dict.values
|> List.sortBy (.stars >> negate) |> 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 type Msg
= Search = Search
| SetQuery String | SetQuery String
| DeleteById SearchResult.ResultId | UpdateSearchResult ResultId SearchResult.Msg
| SetResults (List SearchResult.Model)
| HandleSearchResponse (List SearchResult.Model) | HandleSearchResponse (List SearchResult.Model)
| HandleSearchError Http.Error | HandleSearchError Http.Error
@@ -77,24 +98,43 @@ update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = update msg model =
case msg of case msg of
Search -> Search ->
( model, searchFeed model.query ) model ! [ searchFeed model.query ]
SetQuery 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 let
resultsById : Dict SearchResult.ResultId SearchResult.Model resultsById : Dict ResultId SearchResult.Model
resultsById = resultsById =
results results
|> List.map (\result -> ( result.id, result )) |> List.map (\result -> ( result.id, result ))
|> Dict.fromList |> Dict.fromList
in in
( { model | results = resultsById }, Cmd.none ) { model | results = resultsById } ! []
DeleteById id -> UpdateSearchResult id childMsg ->
case Dict.get id model.results of
Nothing ->
model ! []
Just childModel ->
let let
newModel = ( newChildModel, childCmd ) =
{ model | results = Dict.remove id model.results } SearchResult.update childMsg childModel
cmd =
Cmd.map (UpdateSearchResult id) childCmd
newResults =
Dict.insert id newChildModel model.results
in in
( newModel, Cmd.none ) { model | results = newResults } ! [ cmd ]

View File

@@ -1,6 +1,7 @@
module Main exposing (..) module Main exposing (..)
import ElmHub exposing (..) import ElmHub exposing (..)
import Html.App
main : Program Never main : Program Never
@@ -9,5 +10,5 @@ main =
{ view = view { view = view
, update = update , update = update
, init = ( initialModel, searchFeed initialModel.query ) , init = ( initialModel, searchFeed initialModel.query )
, inputs = [] , subscriptions = \_ -> Sub.none
} }

View File

@@ -18,3 +18,7 @@ to fail; in that case, just run `elm-package install` again.)
```bash ```bash
elm-live Main.elm --open --output=elm.js elm-live Main.elm --open --output=elm.js
``` ```
## References
* [Elm Architecture Tutorial](https://github.com/evancz/elm-architecture-tutorial)

View File

@@ -2,19 +2,26 @@ module SearchResult exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, target, href, property, defaultValue) import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..)
import Json.Decode exposing (Decoder) import Json.Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
type alias Model =
{ id : Int
, name : String
, stars : Int
, expanded : Bool
}
type alias ResultId = type alias ResultId =
Int Int
type alias Model = type Msg
{ id : ResultId = Expand
, name : String | Collapse
, stars : Int
}
decoder : Decoder Model decoder : Decoder Model
@@ -23,19 +30,29 @@ decoder =
|> required "id" Json.Decode.int |> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string |> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int |> required "stargazers_count" Json.Decode.int
|> hardcoded True
view : Model -> Html a update : Msg -> Model -> ( Model, Cmd Msg )
view result = update msg model =
-- TODO implement Expand and Collapse logic
model ! []
view : Model -> Html Msg
view model =
if model.expanded then
li [] li []
[ span [ class "star-count" ] [ text (toString result.stars) ] [ span [ class "star-count" ] [ text (toString model.stars) ]
, a , a [ href ("https://github.com/" ++ model.name), target "_blank" ]
[ href ("https://github.com/" ++ result.name) [ text model.name ]
, target "_blank" -- TODO send a Collapse message on click
] , button [ class "hide-result" ]
[ text result.name ]
, button
-- TODO onClick, send a delete action to the address
[ class "hide-result" ]
[ text "X" ] [ text "X" ]
] ]
else
li []
-- TODO send an Expand message on click
[ button [ class "expand-result" ]
[ text "Show" ]
]

View File

@@ -90,12 +90,3 @@ a:hover {
button:focus, input:focus { button:focus, input:focus {
outline: none; outline: none;
} }
.error {
background-color: #FF9632;
padding: 20px;
box-sizing: border-box;
overflow-x: auto;
font-family: monospace;
font-size: 18px;
}

View File

@@ -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

View File

@@ -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 }
]
)
]

View File

@@ -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"
}

View File

@@ -3,13 +3,13 @@ module ElmHub exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (..) import Html.Events exposing (..)
import Html.App
import Http import Http
import Auth import Auth
import Task exposing (Task) import Task exposing (Task)
import Json.Decode exposing (Decoder) import Json.Decode exposing (Decoder)
import Json.Encode
import Dict exposing (Dict) import Dict exposing (Dict)
import SearchResult import SearchResult exposing (ResultId)
searchFeed : String -> Cmd Msg searchFeed : String -> Cmd Msg
@@ -27,12 +27,13 @@ searchFeed query =
responseDecoder : Decoder (List SearchResult.Model) responseDecoder : Decoder (List SearchResult.Model)
responseDecoder = responseDecoder =
"items" := Json.Decode.list SearchResult.decoder Json.Decode.at [ "items" ] (Json.Decode.list SearchResult.decoder)
type alias Model = type alias Model =
{ query : String { query : String
, results : Dict SearchResult.ResultId SearchResult.Model , results : Dict ResultId SearchResult.Model
, errorMessage : Maybe String
} }
@@ -40,6 +41,7 @@ initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = Dict.empty , results = Dict.empty
, errorMessage = Nothing
} }
@@ -52,17 +54,28 @@ view model =
] ]
, input [ class "search-query", onInput SetQuery, defaultValue model.query ] [] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ] , button [ class "search-button", onClick Search ] [ text "Search" ]
, viewErrorMessage model.errorMessage
, ul [ class "results" ] (viewSearchResults model.results) , 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 = viewSearchResults results =
results results
|> Dict.values |> Dict.values
|> List.sortBy (.stars >> negate) |> List.sortBy (.stars >> negate)
|> filterResults |> filterResults
|> List.map (SearchResult.view address DeleteById) |> List.map viewSearchResult
filterResults : List SearchResult.Model -> List SearchResult.Model filterResults : List SearchResult.Model -> List SearchResult.Model
@@ -72,35 +85,62 @@ filterResults results =
results results
viewSearchResult : SearchResult.Model -> Html Msg
viewSearchResult result =
result
|> SearchResult.view
|> Html.App.map (UpdateSearchResult result.id)
type Msg type Msg
= Search = Search
| SetQuery String | SetQuery String
| DeleteById SearchResult.ResultId | UpdateSearchResult ResultId SearchResult.Msg
| SetResults (List SearchResult.Model) | HandleSearchResponse (List SearchResult.Model)
| HandleSearchError Http.Error
update : Msg -> Model -> ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = update msg model =
case msg of case msg of
Search -> Search ->
( model, searchFeed model.query ) model ! [ searchFeed model.query ]
SetQuery 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 let
resultsById : Dict SearchResult.ResultId SearchResult.Model resultsById : Dict ResultId SearchResult.Model
resultsById = resultsById =
results results
|> List.map (\result -> ( result.id, result )) |> List.map (\result -> ( result.id, result ))
|> Dict.fromList |> Dict.fromList
in in
( { model | results = resultsById }, Cmd.none ) { model | results = resultsById } ! []
DeleteById id -> UpdateSearchResult id childMsg ->
case Dict.get id model.results of
Nothing ->
model ! []
Just childModel ->
let let
newModel = ( newChildModel, childCmd ) =
{ model | results = Dict.remove id model.results } SearchResult.update childMsg childModel
cmd =
Cmd.map (UpdateSearchResult id) childCmd
newResults =
Dict.insert id newChildModel model.results
in in
( newModel, Cmd.none ) { model | results = newResults } ! [ cmd ]

View File

@@ -1,6 +1,7 @@
module Main exposing (..) module Main exposing (..)
import ElmHub exposing (..) import ElmHub exposing (..)
import Html.App
main : Program Never main : Program Never
@@ -9,5 +10,5 @@ main =
{ view = view { view = view
, update = update , update = update
, init = ( initialModel, searchFeed initialModel.query ) , init = ( initialModel, searchFeed initialModel.query )
, inputs = [] , subscriptions = \_ -> Sub.none
} }

View File

@@ -1,45 +1,67 @@
module SearchResult exposing (..) module SearchResult exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..) import Html.Events exposing (..)
import Json.Decode exposing (Decoder) 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 = type alias ResultId =
Int Int
type alias Model = type Msg
{ id : ResultId = Expand
, name : String | Collapse
, stars : Int
}
decoder : Decoder Model decoder : Decoder Model
decoder = decoder =
Json.Decode.object3 Model decode Model
("id" := Json.Decode.int) |> required "id" Json.Decode.int
("full_name" := Json.Decode.string) |> required "full_name" Json.Decode.string
("stargazers_count" := Json.Decode.int) |> required "stargazers_count" Json.Decode.int
|> hardcoded True
view : Address a -> (Int -> a) -> Model -> Html update : Msg -> Model -> ( Model, Cmd Msg )
view address delete result = update msg model =
case msg of
Expand ->
{ model | expanded = True } ! []
Collapse ->
{ model | expanded = False } ! []
view : Model -> Html Msg
view model =
li [] li []
[ span [ class "star-count" ] [ text (toString result.stars) ] <| if model.expanded then
[ span [ class "star-count" ] [ text (toString model.stars) ]
, a , a
[ href [ href
("https://github.com/" ("https://github.com/"
++ (Debug.log "TODO we should not see this when typing in the search box!" ++ (Debug.log "TODO we should not see this when typing in the search box!"
result.name model.name
) )
) )
, target "_blank" , target "_blank"
] ]
[ text result.name ] [ text model.name ]
, button [ class "hide-result", onClick (delete result.id) ] , button [ class "hide-result", onClick Collapse ]
[ text "X" ] [ text "X" ]
] ]
else
[ button [ class "expand-result", onClick Expand ]
[ text "Show" ]
]

View File

@@ -3,42 +3,37 @@ module ElmHub exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (..) import Html.Events exposing (..)
import Html.Lazy exposing (..) import Html.App
import Http import Http
import Auth import Auth
import Task exposing (Task) import Task exposing (Task)
import Json.Decode exposing (Decoder) import Json.Decode exposing (Decoder)
import Json.Encode
import Dict exposing (Dict) import Dict exposing (Dict)
import SearchResult exposing (ResultId) import SearchResult exposing (ResultId)
searchFeed : String -> Task x Msg searchFeed : String -> Cmd Msg
searchFeed query = searchFeed query =
let let
-- See https://developer.github.com/v3/search/#example for how to customize!
url = url =
"https://api.github.com/search/repositories?access_token=" "https://api.github.com/search/repositories?access_token="
++ Auth.token ++ Auth.token
++ "&q=" ++ "&q="
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm&sort=stars&order=desc"
task =
Http.get responseDecoder url
|> Task.map SetResults
in in
Task.onError task (\_ -> Task.succeed (SetResults [])) Task.perform HandleSearchError HandleSearchResponse (Http.get responseDecoder url)
responseDecoder : Decoder (List SearchResult.Model) responseDecoder : Decoder (List SearchResult.Model)
responseDecoder = responseDecoder =
"items" := Json.Decode.list SearchResult.decoder Json.Decode.at [ "items" ] (Json.Decode.list SearchResult.decoder)
type alias Model = type alias Model =
{ query : String { query : String
, results : Dict SearchResult.ResultId SearchResult.Model , results : Dict ResultId SearchResult.Model
, errorMessage : Maybe String
} }
@@ -46,6 +41,7 @@ initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = Dict.empty , results = Dict.empty
, errorMessage = Nothing
} }
@@ -58,67 +54,85 @@ view model =
] ]
, input [ class "search-query", onInput SetQuery, defaultValue model.query ] [] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ] , button [ class "search-button", onClick Search ] [ text "Search" ]
, viewErrorMessage model.errorMessage
, ul [ class "results" ] (viewSearchResults model.results) , 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 = viewSearchResults results =
results results
|> Dict.values |> Dict.values
|> List.sortBy (.stars >> negate) |> List.sortBy (.stars >> negate)
|> List.map (viewSearchResult address) |> List.map viewSearchResult
viewSearchResult : SearchResult.Model -> Html Msg viewSearchResult : SearchResult.Model -> Html Msg
viewSearchResult result = viewSearchResult result =
SearchResult.view (Signal.forwardTo address (UpdateSearchResult result.id))
result result
|> SearchResult.view
|> Html.App.map (UpdateSearchResult result.id)
type Msg type Msg
= Search = Search
| SetQuery String | SetQuery String
| SetResults (List SearchResult.Model)
| UpdateSearchResult ResultId SearchResult.Msg | UpdateSearchResult ResultId SearchResult.Msg
| HandleSearchResponse (List SearchResult.Model)
| HandleSearchError Http.Error
update : Msg -> Model -> ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = update msg model =
case msg of case msg of
Search -> Search ->
( model, searchFeed model.query ) model ! [ searchFeed model.query ]
SetQuery 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 let
resultsById : Dict SearchResult.ResultId SearchResult.Model resultsById : Dict ResultId SearchResult.Model
resultsById = resultsById =
results results
|> List.map (\result -> ( result.id, result )) |> List.map (\result -> ( result.id, result ))
|> Dict.fromList |> Dict.fromList
in in
( { model | results = resultsById }, Cmd.none ) { model | results = resultsById } ! []
UpdateSearchResult id childMsg -> UpdateSearchResult id childMsg ->
let case Dict.get id model.results of
updated =
model.results
|> Dict.get id
|> Maybe.map (SearchResult.update childMsg)
in
case updated of
Nothing -> Nothing ->
( model, Cmd.none ) model ! []
Just ( newChildModel, childEffects ) -> Just childModel ->
let let
effects = ( newChildModel, childCmd ) =
Effects.map (UpdateSearchResult id) childEffects SearchResult.update childMsg childModel
cmd =
Cmd.map (UpdateSearchResult id) childCmd
newResults = newResults =
Dict.insert id newChildModel model.results Dict.insert id newChildModel model.results
in in
( { model | results = newResults }, effects ) { model | results = newResults } ! [ cmd ]

View File

@@ -1,6 +1,7 @@
module Main exposing (..) module Main exposing (..)
import ElmHub exposing (..) import ElmHub exposing (..)
import Html.App
main : Program Never main : Program Never
@@ -9,5 +10,5 @@ main =
{ view = view { view = view
, update = update , update = update
, init = ( initialModel, searchFeed initialModel.query ) , init = ( initialModel, searchFeed initialModel.query )
, inputs = [] , subscriptions = \_ -> Sub.none
} }

View File

@@ -19,6 +19,12 @@ to fail; in that case, just run `elm-package install` again.)
elm-live Main.elm --open --output=elm.js elm-live Main.elm --open --output=elm.js
``` ```
## Compiling CSS
```bash
elm css Stylesheets.elm
```
## References ## 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/)

View File

@@ -35,25 +35,25 @@ decoder =
update : Msg -> Model -> ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = update msg model =
-- TODO implement Expand and Collapse logic case msg of
( model, Cmd.none ) Expand ->
{ model | expanded = True } ! []
Collapse ->
{ model | expanded = False } ! []
view : Model -> Html Msg view : Model -> Html Msg
view model = view model =
li [] <| li []
if model.expanded then <| if model.expanded then
[ span [ class "star-count" ] [ text (toString model.stars) ] [ span [ class "star-count" ] [ text (toString model.stars) ]
, a [ href ("https://github.com/" ++ model.name), target "_blank" ] , a [ href ("https://github.com/" ++ model.name), target "_blank" ]
[ text model.name ] [ text model.name ]
, button , button [ class "hide-result", onClick Collapse ]
-- TODO when the user clicks, send a Collapse action
[ class "hide-result" ]
[ text "X" ] [ text "X" ]
] ]
else else
[ button [ button [ class "expand-result", onClick Expand ]
-- TODO when the user clicks, send an Expand action
[ class "expand-result" ]
[ text "Show" ] [ text "Show" ]
] ]

View File

@@ -12,7 +12,8 @@
"NoRedInk/elm-decode-pipeline": "1.1.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/core": "4.0.1 <= v < 5.0.0",
"elm-lang/html": "1.0.0 <= v < 2.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" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -1,92 +1,6 @@
.content { .content {
width: 960px; width: 960px;
margin: 0 auto; margin: 0 auto;
padding: 30px; padding: 30px;
font-family: Helvetica, Arial, serif; 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;
}

View File

@@ -12,6 +12,7 @@
"NoRedInk/elm-decode-pipeline": "1.1.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/core": "4.0.1 <= v < 5.0.0",
"elm-lang/html": "1.0.0 <= v < 2.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" "evancz/elm-http": "3.0.1 <= v < 4.0.0"
}, },
"elm-version": "0.17.0 <= v < 0.18.0" "elm-version": "0.17.0 <= v < 0.18.0"

View File

@@ -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 )

View File

@@ -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 = []
}

View File

@@ -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/)

View File

@@ -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" ]
]

View File

@@ -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"
}

View File

@@ -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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -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"
}

View File

@@ -1,20 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>ElmHub</title>
<script type="text/javascript" src="elm.js"></script>
<link rel="stylesheet" href="style.css">
<link rel="icon" type="image/png" href="elm-hub.png">
</head>
<body>
</body>
<script type="text/javascript">
var app = Elm.Main.fullscreen();
</script>
</html>

View File

@@ -1,6 +0,0 @@
.content {
width: 960px;
margin: 0 auto;
padding: 30px;
font-family: Helvetica, Arial, serif;
}

View File

@@ -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

View File

@@ -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 }
]
)
]

View File

@@ -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"
}

View File

@@ -6,7 +6,6 @@ import Html.Events exposing (..)
import Auth import Auth
import Json.Decode exposing (Decoder) import Json.Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Json.Encode
getQueryString : String -> String getQueryString : String -> String
@@ -97,9 +96,8 @@ type Msg
= Search = Search
| SetQuery String | SetQuery String
| DeleteById ResultId | DeleteById ResultId
| SetResults (List SearchResult) | HandleSearchResponse (List SearchResult)
| SetErrorMessage (Maybe String) | HandleSearchError (Maybe String)
| DoNothing
update : (String -> Cmd Msg) -> Msg -> Model -> ( Model, Cmd Msg ) update : (String -> Cmd Msg) -> Msg -> Model -> ( Model, Cmd Msg )
@@ -111,31 +109,28 @@ update searchFeed msg model =
SetQuery query -> SetQuery query ->
( { model | query = query }, Cmd.none ) ( { model | query = query }, Cmd.none )
SetResults results -> HandleSearchResponse results ->
( { model | results = results }, Cmd.none ) ( { model | results = results }, Cmd.none )
SetErrorMessage errorMessage -> HandleSearchError error ->
( { model | errorMessage = errorMessage }, Cmd.none ) ( { model | errorMessage = error }, Cmd.none )
DeleteById idToHide -> DeleteById idToDelete ->
let let
newResults = newResults =
model.results model.results
|> List.filter (\{ id } -> id /= idToHide) |> List.filter (\{ id } -> id /= idToDelete)
newModel = newModel =
{ model | results = newResults } { model | results = newResults }
in in
( newModel, Cmd.none ) ( newModel, Cmd.none )
DoNothing ->
( model, Cmd.none )
decodeGithubResponse : Json.Decode.Value -> Msg
decodeGithubResponse : Json.Encode.Value -> Msg
decodeGithubResponse value = 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 -- Hint: look at the definition of Msg and
-- the deefinition of responseDecoder -- the definition of responseDecoder
SetErrorMessage (Just "TODO decode the response!") HandleSearchError (Just "TODO decode the response!")

View File

@@ -19,10 +19,10 @@ decodeResponse : Json.Decode.Value -> Msg
decodeResponse json = decodeResponse json =
case Json.Decode.decodeValue responseDecoder json of case Json.Decode.decodeValue responseDecoder json of
Err err -> Err err ->
SetErrorMessage (Just err) HandleSearchError (Just err)
Ok results -> Ok results ->
SetResults results HandleSearchResponse results
port githubSearch : String -> Cmd msg port githubSearch : String -> Cmd msg

View File

@@ -6,7 +6,6 @@ import Html.Events exposing (..)
import Auth import Auth
import Json.Decode exposing (Decoder) import Json.Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Json.Encode
getQueryString : String -> String getQueryString : String -> String
@@ -97,8 +96,8 @@ type Msg
= Search = Search
| SetQuery String | SetQuery String
| DeleteById ResultId | DeleteById ResultId
| SetResults (List SearchResult) | HandleSearchResponse (List SearchResult)
| SetErrorMessage (Maybe String) | HandleSearchError (Maybe String)
| DoNothing | DoNothing
@@ -111,11 +110,11 @@ update searchFeed msg model =
SetQuery query -> SetQuery query ->
( { model | query = query }, Cmd.none ) ( { model | query = query }, Cmd.none )
SetResults results -> HandleSearchResponse results ->
( { model | results = results }, Cmd.none ) ( { model | results = results }, Cmd.none )
SetErrorMessage errorMessage -> HandleSearchError error ->
( { model | errorMessage = errorMessage }, Cmd.none ) ( { model | errorMessage = error }, Cmd.none )
DeleteById idToHide -> DeleteById idToHide ->
let let
@@ -132,10 +131,11 @@ update searchFeed msg model =
( model, Cmd.none ) ( model, Cmd.none )
decodeGithubResponse : Json.Encode.Value -> Msg decodeGithubResponse : Json.Decode.Value -> Msg
decodeGithubResponse value = decodeGithubResponse value =
-- TODO use Json.Decode.DecodeValue to decode the response into an Action. case Json.Decode.decodeValue responseDecoder value of
-- Ok results ->
-- Hint: look at ElmHub.elm, specifically the definition of Action and HandleSearchResponse results
-- the deefinition of responseDecoder
SetErrorMessage (Just "TODO decode the response!") Err err ->
HandleSearchError (Just err)

View File

@@ -19,10 +19,10 @@ decodeResponse : Json.Decode.Value -> Msg
decodeResponse json = decodeResponse json =
case Json.Decode.decodeValue responseDecoder json of case Json.Decode.decodeValue responseDecoder json of
Err err -> Err err ->
SetErrorMessage (Just err) HandleSearchError (Just err)
Ok results -> Ok results ->
SetResults results HandleSearchResponse results
port githubSearch : String -> Cmd msg port githubSearch : String -> Cmd msg

View File

@@ -21,12 +21,29 @@ elm-live Main.elm --open --output=elm.js
## Running Tests ## Running Tests
First do this:
```bash ```bash
cd test cd test
elm-package install 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 ## References
* [Using Elm packages](https://github.com/elm-lang/elm-package/blob/master/README.md#basic-usage) * [Using Elm packages](https://github.com/elm-lang/elm-package/blob/master/README.md#basic-usage)

16
part8/test/HtmlRunner.elm Normal file
View File

@@ -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

19
part8/test/NodeRunner.elm Normal file
View File

@@ -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

View File

@@ -1,55 +1,70 @@
port module Main exposing (..) module Tests exposing (..)
import Test exposing (..) import Test exposing (..)
import Fuzz exposing (..)
import Expect exposing (Expectation) import Expect exposing (Expectation)
import ElmHub exposing (responseDecoder) import ElmHub exposing (responseDecoder)
import Json.Decode exposing (decodeString, Value) import Json.Decode exposing (decodeString, Value)
import Test.Runner.Node as Runner import String
main : Program Never all : Test
main = all =
describe "Decoding responses from GitHub" describe "GitHub Response Decoder"
[ test "they can decode empty responses" [ test "it results in an Err for invalid JSON"
<| \() -> <| \() ->
let let
emptyResponse = json =
"""{ "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 =
"""{ "pizza": [] }""" """{ "pizza": [] }"""
isErrorResult result = isErrorResult result =
-- TODO return True if the given Result is an Err of some sort, -- TODO return True if the given Result is an Err of some sort,
-- and False if it is an Ok 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 False
in in
Expect.true "Expected decoding an invalid response to return an Err." json
(isErrorResult (decodeString responseDecoder response)) |> 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 }
] ]
|> Runner.run emit )
, 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}"""
port emit : ( String, Value ) -> Cmd msg 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)
]

View File

@@ -9,12 +9,13 @@
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "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", "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0",
"elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"elm-lang/html": "1.0.0 <= v < 2.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" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -7,16 +7,21 @@ import Auth
import Json.Decode exposing (Decoder) import Json.Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Dict exposing (Dict) import Dict exposing (Dict)
import Http
import Task
getQueryUrl : String -> String searchFeed : String -> Cmd Msg
getQueryUrl query = searchFeed query =
-- See https://developer.github.com/v3/search/#example for how to customize! let
url =
"https://api.github.com/search/repositories?access_token=" "https://api.github.com/search/repositories?access_token="
++ Auth.token ++ Auth.token
++ "&q=" ++ "&q="
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm&sort=stars&order=desc"
in
Task.perform HandleSearchError HandleSearchResponse (Http.get responseDecoder url)
responseDecoder : Decoder (List SearchResult) responseDecoder : Decoder (List SearchResult)
@@ -92,35 +97,40 @@ type Msg
= Search = Search
| SetQuery String | SetQuery String
| DeleteById ResultId | DeleteById ResultId
| SetResults (List SearchResult) | HandleSearchResponse (List SearchResult)
| SetErrorMessage (Maybe String) | HandleSearchError Http.Error
| DoNothing | DoNothing
update : (String -> Cmd Msg) -> Msg -> Model -> ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg )
update searchFeed msg model = update msg model =
case msg of case msg of
Search -> Search ->
( model, searchFeed (getQueryUrl model.query) ) model ! [ searchFeed model.query ]
SetQuery 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 let
resultsById : Dict ResultId SearchResult resultsById : Dict ResultId SearchResult
resultsById = resultsById =
-- TODO convert results list into a Dict -- TODO convert results list into a Dict
Dict.empty Dict.empty
in in
( { model | results = resultsById }, Cmd.none ) { model | results = resultsById } ! []
DeleteById id -> DeleteById id ->
-- TODO delete the result with the given id -- TODO delete the result with the given id
( model, Cmd.none ) model ! []
SetErrorMessage errorMessage ->
( { model | errorMessage = errorMessage }, Cmd.none )
DoNothing -> DoNothing ->
( model, Cmd.none ) model ! []

View File

@@ -1,31 +1,14 @@
port module Main exposing (..) module Main exposing (..)
import ElmHub exposing (..) import ElmHub exposing (..)
import Html.App import Html.App
import Json.Decode exposing (Value)
main : Program Never main : Program Never
main = main =
Html.App.program Html.App.program
{ view = view { view = view
, update = update githubSearch , update = update
, init = ( initialModel, githubSearch (getQueryUrl initialModel.query) ) , init = ( initialModel, searchFeed initialModel.query )
, subscriptions = \_ -> githubResponse decodeResponse , 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

View File

@@ -21,8 +21,25 @@ elm-live Main.elm --open --output=elm.js
## Running Tests ## Running Tests
First do this:
```bash ```bash
cd test cd test
elm-package install 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`.