elm-pages-v3-beta/tests/FormTests.elm
2022-01-27 20:21:49 -08:00

415 lines
16 KiB
Elm

module FormTests exposing (all)
import Date
import Dict
import Expect
import Form
import Form.Value
import Test exposing (Test, describe, skip, test)
import Time
all : Test
all =
describe "Form"
[ test "succeed" <|
\() ->
let
form =
Form.succeed ()
in
form
|> Form.runClientValidations (Form.init form)
|> expectDecodeNoErrors ()
, test "single field" <|
\() ->
Form.succeed identity
|> Form.with
(Form.text "first" toInput
|> Form.required "Required"
)
|> Form.runClientValidations
{ fields =
Dict.fromList
[ ( "first", field "Jane" )
]
, isSubmitting = Form.NotSubmitted
, formErrors = Dict.empty
}
|> expectDecodeNoErrors "Jane"
, test "run a single field's validation on blur" <|
\() ->
let
form =
Form.succeed identity
|> Form.with
(Form.date "dob"
{ invalid = \_ -> "Invalid date"
}
toInput
)
in
form
|> Form.init
|> updateField form ( "dob", "This is not a valid date" )
|> expectErrors
[ ( "dob", [ "Invalid date" ] ) ]
, test "custom client validation" <|
\() ->
let
form =
Form.succeed identity
|> Form.with
(Form.text "first" toInput
|> Form.required "Required"
|> Form.withClientValidation
(\first ->
if first |> String.toList |> List.head |> Maybe.withDefault 'a' |> Char.isUpper then
Ok first
else
Err "Needs to be capitalized"
)
)
in
form
|> Form.init
|> updateField form ( "first", "jane" )
|> expectErrors
[ ( "first", [ "Needs to be capitalized" ] ) ]
, test "init dict includes default values" <|
\() ->
Form.succeed identity
|> Form.with
(Form.text "first" toInput
|> Form.withInitialValue ("Jane" |> Form.Value.string)
)
|> Form.init
|> Form.rawValues
|> Expect.equal
(Dict.fromList [ ( "first", "Jane" ) ])
, test "client validations are available on init" <|
\() ->
Form.succeed identity
|> Form.with
(Form.text "first" toInput
|> Form.withInitialValue ("Jane" |> Form.Value.string)
|> Form.withClientValidation (\_ -> Err "This error always occurs")
)
|> Form.init
|> Form.hasErrors2
|> Expect.true "expected errors"
, test "dependent validations" <|
\() ->
Form.succeed Tuple.pair
|> Form.with
(Form.date "checkin"
{ invalid = \_ -> "Invalid date" }
toInput
|> Form.required "Required"
|> Form.withInitialValue (Date.fromCalendarDate 2022 Time.Jan 1 |> Form.Value.date)
)
|> Form.with
(Form.date "checkout"
{ invalid = \_ -> "Invalid date" }
toInput
|> Form.required "Required"
|> Form.withInitialValue (Date.fromCalendarDate 2022 Time.Jan 1 |> Form.Value.date)
)
|> Form.validate
(\_ ->
[ ( "checkin", [ "Must be before checkout date." ] )
]
)
|> Form.init
|> expectErrors
[ ( "checkin", [ "Must be before checkout date." ] )
, ( "checkout", [] )
]
, test "initial validations only run once" <|
\() ->
Form.succeed identity
|> Form.with
(Form.text "name" toInput
|> Form.required "Required"
)
|> expectErrorsAfterUpdates
[ ( "name", [ "Required" ] )
]
, test "parses time" <|
\() ->
let
form =
Form.succeed identity
|> Form.with
(Form.time "checkin-time"
{ invalid = \_ -> "Invalid time" }
toInput
|> Form.required "Required"
)
in
form
|> expectDecodeNoErrors2 [ ( "checkin-time", "08:45" ) ]
{ hours = 8
, minutes = 45
}
, test "no duplicate validation errors from update call" <|
\() ->
Form.succeed identity
|> Form.with
(Form.text "name" toInput
|> Form.required "Required"
)
|> expectErrorsAfterUpdates
[ ( "name", [ "Required" ] )
]
, test "runs proceeding validations even when there are prior errors" <|
\() ->
Form.succeed Tuple.pair
|> Form.with
(Form.text "first" toInput
|> Form.required "Required"
)
|> Form.with
(Form.text "last" toInput
|> Form.required "Required"
)
|> expectErrorsAfterUpdates
[ ( "first", [ "Required" ] )
, ( "last", [ "Required" ] )
]
, skip <|
test "form-level validations are when there are recoverable field-level errors" <|
\() ->
let
form =
Form.succeed Tuple.pair
|> Form.with
(Form.text "password" toInput
|> Form.required "Required"
)
|> Form.with
(Form.text "password-confirmation" toInput
|> Form.required "Required"
)
|> Form.validate
(\( password, passwordConfirmation ) ->
if password == passwordConfirmation then
[]
else
[ ( "password-confirmation", [ "Passwords must match." ] )
]
)
|> Form.appendForm Tuple.pair
(Form.succeed identity
|> Form.with
(Form.text "name" toInput
|> Form.required "Required"
)
)
in
form
|> Form.init
|> updateField form ( "password", "abcd" )
|> updateField form ( "password-confirmation", "abcd" )
|> expectErrors
[ ( "name", [ "Required" ] )
, ( "password", [] )
, ( "password-confirmation", [ "Passwords must match." ] )
]
, skip <|
test "dependent validations are run when other fields have recoverable errors" <|
\() ->
Form.succeed Tuple.pair
|> Form.with
(Form.date "checkin"
{ invalid = \_ -> "Invalid date" }
toInput
|> Form.required "Required"
|> Form.withInitialValue (Date.fromCalendarDate 2022 Time.Jan 1 |> Form.Value.date)
)
|> Form.with
(Form.date "checkout"
{ invalid = \_ -> "Invalid date" }
toInput
|> Form.required "Required"
|> Form.withInitialValue (Date.fromCalendarDate 2022 Time.Jan 1 |> Form.Value.date)
)
|> Form.validate
(\_ ->
[ ( "checkin", [ "Must be before checkout date." ] )
]
)
|> Form.appendForm Tuple.pair
(Form.succeed identity
|> Form.with
(Form.text "name" toInput
|> Form.required "Required"
)
)
|> expectErrorsAfterUpdates
[ ( "checkin", [ "Must be before checkout date." ] )
, ( "checkout", [] )
, ( "name", [ "Required" ] )
]
, test "min validation runs in pure elm" <|
\() ->
Form.succeed identity
|> Form.with
(Form.range "rating"
{ missing = "Missing"
, invalid = \_ -> "Invalid"
}
{ initial = 3, min = 1, max = 5 }
toInput
)
|> performUpdatesThenExpectErrors
[ ( "rating", "-1" ) ]
[ ( "rating", [ "Invalid" ] )
]
, test "invalid floats give error for float input" <|
\() ->
Form.succeed identity
|> Form.with
(Form.float "factor"
{ invalid = \_ -> "Invalid"
}
toInput
)
|> performUpdatesThenExpectErrors
[ ( "factor", "abc" ) ]
[ ( "factor", [ "Invalid" ] )
]
, test "invalid ints give error for float input" <|
\() ->
Form.succeed identity
|> Form.with
(Form.int "factor"
{ invalid = \_ -> "Invalid"
}
toInput
)
|> performUpdatesThenExpectErrors
[ ( "factor", "abc" ) ]
[ ( "factor", [ "Invalid" ] )
]
]
expectDecodeNoErrors2 : List ( String, String ) -> decoded -> Form.Form String decoded view -> Expect.Expectation
expectDecodeNoErrors2 updates expected form =
let
formModel =
form
|> Form.init
|> updateFieldsWithValues updates form
in
Form.runClientValidations formModel form
|> Expect.equal
(Ok ( expected, [] ))
expectDecodeNoErrors : decoded -> Result error ( decoded, List b ) -> Expect.Expectation
expectDecodeNoErrors decoded actual =
actual
|> Expect.equal (Ok ( decoded, [] ))
updateField : Form.Form String value view -> ( String, String ) -> Form.Model -> Form.Model
updateField form ( name, value ) model =
model
|> Form.update (\_ -> ()) (\_ -> ()) form (Form.OnFieldInput { name = name, value = value })
|> Tuple.first
expectErrors : List ( String, List String ) -> Form.Model -> Expect.Expectation
expectErrors expected form =
form.fields
|> Dict.map
(\key value ->
value.errors
++ (form.formErrors
|> Dict.get key
|> Maybe.withDefault []
)
)
|> Expect.equalDicts (Dict.fromList expected)
updateAllFields : List String -> Form.Form String value view -> Form.Model -> Form.Model
updateAllFields fields form model =
fields
|> List.foldl
(\fieldName modelSoFar ->
modelSoFar
|> updateField form ( fieldName, "" )
)
model
updateFieldsWithValues : List ( String, String ) -> Form.Form String value view -> Form.Model -> Form.Model
updateFieldsWithValues fields form model =
fields
|> List.foldl
(\( fieldName, fieldValue ) modelSoFar ->
modelSoFar
|> updateField form ( fieldName, fieldValue )
)
model
expectErrorsAfterUpdates : List ( String, List String ) -> Form.Form String value view -> Expect.Expectation
expectErrorsAfterUpdates expected form =
let
fieldsToUpdate : List String
fieldsToUpdate =
expected |> List.map Tuple.first
model : Form.Model
model =
Form.init form
in
Expect.all
([ model
, updateAllFields fieldsToUpdate form model
]
|> List.map
(\formModel () ->
formModel.fields
|> Dict.map (\_ value -> value.errors)
|> Expect.equalDicts (Dict.fromList expected)
)
)
()
performUpdatesThenExpectErrors : List ( String, String ) -> List ( String, List String ) -> Form.Form String value view -> Expect.Expectation
performUpdatesThenExpectErrors updatesToPerform expected form =
let
model : Form.Model
model =
Form.init form
in
Expect.all
([ updateFieldsWithValues updatesToPerform form model
]
|> List.map
(\formModel () ->
formModel.fields
|> Dict.map (\_ value -> value.errors)
|> Expect.equalDicts (Dict.fromList expected)
)
)
()
field : a -> { raw : Maybe a, errors : List b, status : Form.FieldStatus }
field value =
{ raw = Just value, errors = [], status = Form.NotVisited }
toInput : a -> ()
toInput _ =
()