feat: complete chapter 6 - testing
parent
b6cb2a2750
commit
8d6cc1db71
|
@ -0,0 +1 @@
|
|||
elm-stuff
|
|
@ -23,7 +23,9 @@
|
|||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"direct": {
|
||||
"elm-explorations/test": "2.2.0"
|
||||
},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,34 @@
|
|||
port module PhotoGroove exposing (main)
|
||||
port module PhotoGroove exposing
|
||||
( Model
|
||||
, Msg(..)
|
||||
, Photo
|
||||
, Status(..)
|
||||
, initialModel
|
||||
, main
|
||||
, photoDecoder
|
||||
, photoFromUrl
|
||||
, update
|
||||
, urlPrefix
|
||||
, view
|
||||
)
|
||||
|
||||
import Array exposing (Array)
|
||||
import Browser
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr exposing (class, classList, id, name, src, title, type_)
|
||||
import Html.Events exposing (onClick, on)
|
||||
import Html.Events exposing (on, onClick)
|
||||
import Http
|
||||
import Json.Decode exposing (Decoder, at, int, list, string, succeed)
|
||||
import Json.Decode.Pipeline exposing (optional, required)
|
||||
import Json.Encode
|
||||
import Random
|
||||
|
||||
|
||||
urlPrefix : String
|
||||
urlPrefix =
|
||||
"https://elm-in-action.com/"
|
||||
|
||||
|
||||
type Msg
|
||||
= ClickedPhoto String
|
||||
| SetSize ThumbnailSize
|
||||
|
@ -26,6 +40,7 @@ type Msg
|
|||
| SlidRipple Int
|
||||
| SlidNoise Int
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div [ class "content" ] <|
|
||||
|
@ -39,6 +54,7 @@ view model =
|
|||
Errored errorMessage ->
|
||||
[ text ("Error: " ++ errorMessage) ]
|
||||
|
||||
|
||||
viewFilter : (Int -> Msg) -> String -> Int -> Html Msg
|
||||
viewFilter toMsg name magnitude =
|
||||
div [ class "filter-slider" ]
|
||||
|
@ -52,6 +68,7 @@ viewFilter toMsg name magnitude =
|
|||
, label [] [ text (String.fromInt magnitude) ]
|
||||
]
|
||||
|
||||
|
||||
viewLoaded : List Photo -> String -> Model -> List (Html Msg)
|
||||
viewLoaded photos selectedUrl model =
|
||||
[ h1 [] [ text "Photo Groove" ]
|
||||
|
@ -72,6 +89,7 @@ viewLoaded photos selectedUrl model =
|
|||
, canvas [ id "main-canvas", class "large" ] []
|
||||
]
|
||||
|
||||
|
||||
viewThumbnail : String -> Photo -> Html Msg
|
||||
viewThumbnail selectedUrl thumb =
|
||||
img
|
||||
|
@ -82,6 +100,7 @@ viewThumbnail selectedUrl thumb =
|
|||
]
|
||||
[]
|
||||
|
||||
|
||||
viewSizeChooser : ThumbnailSize -> Html Msg
|
||||
viewSizeChooser size =
|
||||
label []
|
||||
|
@ -89,35 +108,45 @@ viewSizeChooser size =
|
|||
, text (sizeToString size)
|
||||
]
|
||||
|
||||
|
||||
sizeToString : ThumbnailSize -> String
|
||||
sizeToString size =
|
||||
case size of
|
||||
Small ->
|
||||
"small"
|
||||
|
||||
Medium ->
|
||||
"medium"
|
||||
|
||||
Large ->
|
||||
"large"
|
||||
|
||||
|
||||
type ThumbnailSize
|
||||
= Small
|
||||
| Medium
|
||||
| Large
|
||||
|
||||
|
||||
port setFilters : FilterOptions -> Cmd msg
|
||||
|
||||
|
||||
port activityChanges : (String -> msg) -> Sub msg
|
||||
|
||||
|
||||
type alias FilterOptions =
|
||||
{ url : String
|
||||
, filters : List { name : String, amount : Float }
|
||||
}
|
||||
|
||||
|
||||
type alias Photo =
|
||||
{ url : String
|
||||
, size : Int
|
||||
, title : String
|
||||
}
|
||||
|
||||
|
||||
photoDecoder : Decoder Photo
|
||||
photoDecoder =
|
||||
succeed Photo
|
||||
|
@ -125,11 +154,13 @@ photoDecoder =
|
|||
|> required "size" int
|
||||
|> optional "title" string "(untitled)"
|
||||
|
||||
|
||||
type Status
|
||||
= Loading
|
||||
| Loaded (List Photo) String
|
||||
| Errored String
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ status : Status
|
||||
, activity : String
|
||||
|
@ -139,6 +170,7 @@ type alias Model =
|
|||
, noise : Int
|
||||
}
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
|
@ -200,6 +232,7 @@ update msg model =
|
|||
SlidNoise noise ->
|
||||
applyFilters { model | noise = noise }
|
||||
|
||||
|
||||
applyFilters : Model -> ( Model, Cmd msg )
|
||||
applyFilters model =
|
||||
case model.status of
|
||||
|
@ -222,16 +255,20 @@ applyFilters model =
|
|||
Errored errorMessage ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
selectUrl : String -> Status -> Status
|
||||
selectUrl url status =
|
||||
case status of
|
||||
Loaded photos _ ->
|
||||
Loaded photos url
|
||||
|
||||
Loading ->
|
||||
status
|
||||
|
||||
Errored errorMessage ->
|
||||
status
|
||||
|
||||
|
||||
initialModel : Model
|
||||
initialModel =
|
||||
{ status = Loading
|
||||
|
@ -242,6 +279,7 @@ initialModel =
|
|||
, noise = 5
|
||||
}
|
||||
|
||||
|
||||
initialCmd : Cmd Msg
|
||||
initialCmd =
|
||||
Http.get
|
||||
|
@ -249,6 +287,7 @@ initialCmd =
|
|||
, expect = Http.expectJson GotPhotos (list photoDecoder)
|
||||
}
|
||||
|
||||
|
||||
main : Program Float Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
|
@ -258,6 +297,7 @@ main =
|
|||
, subscriptions = subscriptions
|
||||
}
|
||||
|
||||
|
||||
init : Float -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
let
|
||||
|
@ -266,16 +306,24 @@ init flags =
|
|||
in
|
||||
( { initialModel | activity = activity }, initialCmd )
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
activityChanges GotActivity
|
||||
|
||||
|
||||
rangeSlider : List (Attribute msg) -> List (Html msg) -> Html msg
|
||||
rangeSlider attributes children =
|
||||
node "range-slider" attributes children
|
||||
|
||||
|
||||
onSlide : (Int -> msg) -> Attribute msg
|
||||
onSlide toMsg =
|
||||
at [ "detail", "userSlidTo" ] int
|
||||
|> Json.Decode.map toMsg
|
||||
|> on "slide"
|
||||
|
||||
|
||||
photoFromUrl : String -> Photo
|
||||
photoFromUrl url =
|
||||
{ url = url, size = 0, title = "" }
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
module PhotoGrooveTests exposing (..)
|
||||
|
||||
import Expect exposing (Expectation)
|
||||
import Fuzz exposing (Fuzzer, int, list, string)
|
||||
import Html.Attributes as Attr exposing (src)
|
||||
import Json.Decode as Decode exposing (decodeValue)
|
||||
import Json.Encode as Encode
|
||||
import PhotoGroove
|
||||
exposing
|
||||
( Model
|
||||
, Msg(..)
|
||||
, Photo
|
||||
, Status(..)
|
||||
, initialModel
|
||||
, photoFromUrl
|
||||
, update
|
||||
, urlPrefix
|
||||
, view
|
||||
)
|
||||
import Test exposing (..)
|
||||
import Test.Html.Event as Event
|
||||
import Test.Html.Query as Query
|
||||
import Test.Html.Selector exposing (attribute, tag, text)
|
||||
|
||||
|
||||
decoderTest : Test
|
||||
decoderTest =
|
||||
fuzz2 string int "title defaults to (untitled)" <|
|
||||
\url size ->
|
||||
[ ( "url", Encode.string url )
|
||||
, ( "size", Encode.int size )
|
||||
]
|
||||
|> Encode.object
|
||||
|> decodeValue PhotoGroove.photoDecoder
|
||||
|> Result.map .title
|
||||
|> Expect.equal (Ok "(untitled)")
|
||||
|
||||
|
||||
sliders : Test
|
||||
sliders =
|
||||
describe "Slider sets the desired field in the model"
|
||||
[ testSlider "SlidHue" SlidHue .hue
|
||||
, testSlider "SlidRipple" SlidRipple .ripple
|
||||
, testSlider "SlidNoise" SlidNoise .noise
|
||||
]
|
||||
|
||||
|
||||
testSlider : String -> (Int -> Msg) -> (Model -> Int) -> Test
|
||||
testSlider description toMsg amountFromModel =
|
||||
fuzz int description <|
|
||||
\amount ->
|
||||
initialModel
|
||||
|> update (toMsg amount)
|
||||
|> Tuple.first
|
||||
|> amountFromModel
|
||||
|> Expect.equal amount
|
||||
|
||||
|
||||
noPhotosNoThumbnails : Test
|
||||
noPhotosNoThumbnails =
|
||||
test "No thumbnails render when there are no photos to render." <|
|
||||
\_ ->
|
||||
initialModel
|
||||
|> PhotoGroove.view
|
||||
|> Query.fromHtml
|
||||
|> Query.findAll [ tag "img" ]
|
||||
|> Query.count (Expect.equal 0)
|
||||
|
||||
|
||||
thumbnailRendered : String -> Query.Single msg -> Expectation
|
||||
thumbnailRendered url query =
|
||||
query
|
||||
|> Query.findAll [ tag "img", attribute (Attr.src (urlPrefix ++ url)) ]
|
||||
|> Query.count (Expect.atLeast 1)
|
||||
|
||||
|
||||
thumbnailsWork : Test
|
||||
thumbnailsWork =
|
||||
fuzz urlFuzzer "URLs render as thumbnail" <|
|
||||
\urls ->
|
||||
let
|
||||
thumbnailChecks : List (Query.Single msg -> Expectation)
|
||||
thumbnailChecks =
|
||||
List.map thumbnailRendered urls
|
||||
in
|
||||
{ initialModel | status = Loaded (List.map photoFromUrl urls) "" }
|
||||
|> view
|
||||
|> Query.fromHtml
|
||||
|> Expect.all thumbnailChecks
|
||||
|
||||
|
||||
urlFuzzer : Fuzzer (List String)
|
||||
urlFuzzer =
|
||||
Fuzz.intRange 1 5
|
||||
|> Fuzz.map urlsFromCount
|
||||
|
||||
|
||||
urlsFromCount : Int -> List String
|
||||
urlsFromCount urlCount =
|
||||
List.range 1 urlCount
|
||||
|> List.map (\num -> String.fromInt num ++ ".png")
|
||||
|
||||
|
||||
clickThumbnail : Test
|
||||
clickThumbnail =
|
||||
fuzz3 urlFuzzer string urlFuzzer "clicking a thumbnail selects it" <|
|
||||
\urlsBefore urlToSelect urlsAfter ->
|
||||
let
|
||||
url =
|
||||
urlToSelect ++ ".jpeg"
|
||||
|
||||
photos =
|
||||
(urlsBefore ++ url :: urlsAfter)
|
||||
|> List.map photoFromUrl
|
||||
|
||||
srcToClick =
|
||||
urlPrefix ++ url
|
||||
in
|
||||
{ initialModel | status = Loaded photos "" }
|
||||
|> view
|
||||
|> Query.fromHtml
|
||||
|> Query.find [ tag "img", attribute (Attr.src srcToClick) ]
|
||||
|> Event.simulate Event.click
|
||||
|> Event.expect (ClickedPhoto url)
|
Loading…
Reference in New Issue