2022-01-06 06:57:25 +03:00
|
|
|
module FormTests exposing (all)
|
|
|
|
|
2022-01-17 02:29:06 +03:00
|
|
|
import Date
|
2022-01-06 06:57:25 +03:00
|
|
|
import Dict
|
|
|
|
import Expect
|
|
|
|
import Form
|
2022-01-17 02:29:06 +03:00
|
|
|
import Form.Value
|
2022-03-11 22:52:33 +03:00
|
|
|
import Test exposing (Test, describe, test)
|
2022-01-17 02:29:06 +03:00
|
|
|
import Time
|
2022-01-06 06:57:25 +03:00
|
|
|
|
|
|
|
|
2022-01-14 01:18:53 +03:00
|
|
|
all : Test
|
2022-01-06 06:57:25 +03:00
|
|
|
all =
|
|
|
|
describe "Form"
|
|
|
|
[ test "succeed" <|
|
|
|
|
\() ->
|
2022-01-10 21:14:02 +03:00
|
|
|
let
|
|
|
|
form =
|
|
|
|
Form.succeed ()
|
|
|
|
in
|
|
|
|
form
|
|
|
|
|> Form.runClientValidations (Form.init form)
|
2022-01-14 01:18:53 +03:00
|
|
|
|> expectDecodeNoErrors ()
|
2022-01-06 06:57:25 +03:00
|
|
|
, test "single field" <|
|
|
|
|
\() ->
|
|
|
|
Form.succeed identity
|
2022-01-16 06:29:30 +03:00
|
|
|
|> Form.with
|
|
|
|
(Form.text "first" toInput
|
|
|
|
|> Form.required "Required"
|
|
|
|
)
|
2022-01-06 06:57:25 +03:00
|
|
|
|> Form.runClientValidations
|
2022-01-11 03:35:45 +03:00
|
|
|
{ fields =
|
|
|
|
Dict.fromList
|
2022-01-11 20:33:45 +03:00
|
|
|
[ ( "first", field "Jane" )
|
2022-01-11 03:35:45 +03:00
|
|
|
]
|
|
|
|
, isSubmitting = Form.NotSubmitted
|
2022-01-15 20:06:19 +03:00
|
|
|
, formErrors = Dict.empty
|
2022-01-11 03:35:45 +03:00
|
|
|
}
|
2022-01-14 01:18:53 +03:00
|
|
|
|> expectDecodeNoErrors "Jane"
|
2022-01-06 18:57:16 +03:00
|
|
|
, test "run a single field's validation on blur" <|
|
|
|
|
\() ->
|
2022-01-15 20:06:19 +03:00
|
|
|
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" ] ) ]
|
2022-01-06 20:44:46 +03:00
|
|
|
, test "custom client validation" <|
|
|
|
|
\() ->
|
2022-01-15 20:06:19 +03:00
|
|
|
let
|
|
|
|
form =
|
|
|
|
Form.succeed identity
|
|
|
|
|> Form.with
|
|
|
|
(Form.text "first" toInput
|
2022-01-16 06:29:30 +03:00
|
|
|
|> Form.required "Required"
|
2022-01-15 20:06:19 +03:00
|
|
|
|> Form.withClientValidation
|
|
|
|
(\first ->
|
|
|
|
if first |> String.toList |> List.head |> Maybe.withDefault 'a' |> Char.isUpper then
|
|
|
|
Ok first
|
2022-01-06 20:44:46 +03:00
|
|
|
|
2022-01-15 20:06:19 +03:00
|
|
|
else
|
|
|
|
Err "Needs to be capitalized"
|
|
|
|
)
|
2022-01-06 20:44:46 +03:00
|
|
|
)
|
2022-01-15 20:06:19 +03:00
|
|
|
in
|
|
|
|
form
|
|
|
|
|> Form.init
|
|
|
|
|> updateField form ( "first", "jane" )
|
|
|
|
|> expectErrors
|
|
|
|
[ ( "first", [ "Needs to be capitalized" ] ) ]
|
2022-01-10 21:14:02 +03:00
|
|
|
, test "init dict includes default values" <|
|
|
|
|
\() ->
|
|
|
|
Form.succeed identity
|
|
|
|
|> Form.with
|
|
|
|
(Form.text "first" toInput
|
2022-01-17 02:29:06 +03:00
|
|
|
|> Form.withInitialValue ("Jane" |> Form.Value.string)
|
2022-01-10 21:14:02 +03:00
|
|
|
)
|
|
|
|
|> Form.init
|
|
|
|
|> Form.rawValues
|
|
|
|
|> Expect.equal
|
|
|
|
(Dict.fromList [ ( "first", "Jane" ) ])
|
2022-01-11 05:33:24 +03:00
|
|
|
, test "client validations are available on init" <|
|
|
|
|
\() ->
|
|
|
|
Form.succeed identity
|
|
|
|
|> Form.with
|
|
|
|
(Form.text "first" toInput
|
2022-01-17 02:29:06 +03:00
|
|
|
|> Form.withInitialValue ("Jane" |> Form.Value.string)
|
2022-01-11 05:33:24 +03:00
|
|
|
|> Form.withClientValidation (\_ -> Err "This error always occurs")
|
|
|
|
)
|
|
|
|
|> Form.init
|
2022-05-12 01:37:18 +03:00
|
|
|
|> Form.hasErrors
|
2022-01-11 05:33:24 +03:00
|
|
|
|> Expect.true "expected errors"
|
2022-01-15 20:06:19 +03:00
|
|
|
, test "dependent validations" <|
|
|
|
|
\() ->
|
|
|
|
Form.succeed Tuple.pair
|
|
|
|
|> Form.with
|
2022-01-16 06:29:30 +03:00
|
|
|
(Form.date "checkin"
|
|
|
|
{ invalid = \_ -> "Invalid date" }
|
2022-01-15 20:06:19 +03:00
|
|
|
toInput
|
2022-01-16 06:29:30 +03:00
|
|
|
|> Form.required "Required"
|
2022-01-17 02:29:06 +03:00
|
|
|
|> Form.withInitialValue (Date.fromCalendarDate 2022 Time.Jan 1 |> Form.Value.date)
|
2022-01-15 20:06:19 +03:00
|
|
|
)
|
|
|
|
|> Form.with
|
2022-01-16 06:29:30 +03:00
|
|
|
(Form.date "checkout"
|
|
|
|
{ invalid = \_ -> "Invalid date" }
|
2022-01-15 20:06:19 +03:00
|
|
|
toInput
|
2022-01-16 06:29:30 +03:00
|
|
|
|> Form.required "Required"
|
2022-01-17 02:29:06 +03:00
|
|
|
|> Form.withInitialValue (Date.fromCalendarDate 2022 Time.Jan 1 |> Form.Value.date)
|
2022-01-15 20:06:19 +03:00
|
|
|
)
|
|
|
|
|> Form.validate
|
2022-01-28 03:03:42 +03:00
|
|
|
(\_ ->
|
2022-01-14 19:42:14 +03:00
|
|
|
[ ( "checkin", [ "Must be before checkout date." ] )
|
2022-01-13 20:06:58 +03:00
|
|
|
]
|
2022-01-15 20:06:19 +03:00
|
|
|
)
|
|
|
|
|> Form.init
|
|
|
|
|> expectErrors
|
|
|
|
[ ( "checkin", [ "Must be before checkout date." ] )
|
|
|
|
, ( "checkout", [] )
|
|
|
|
]
|
2022-01-13 20:06:58 +03:00
|
|
|
, test "initial validations only run once" <|
|
|
|
|
\() ->
|
2022-01-15 02:28:17 +03:00
|
|
|
Form.succeed identity
|
2022-01-13 20:06:58 +03:00
|
|
|
|> Form.with
|
|
|
|
(Form.text "name" toInput
|
2022-01-14 19:42:14 +03:00
|
|
|
|> Form.required "Required"
|
2022-01-13 20:06:58 +03:00
|
|
|
)
|
2022-01-15 02:28:17 +03:00
|
|
|
|> expectErrorsAfterUpdates
|
2022-01-14 19:42:14 +03:00
|
|
|
[ ( "name", [ "Required" ] )
|
2022-01-13 20:06:58 +03:00
|
|
|
]
|
2022-01-17 01:31:08 +03:00
|
|
|
, 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
|
|
|
|
}
|
2022-01-13 20:06:58 +03:00
|
|
|
, test "no duplicate validation errors from update call" <|
|
|
|
|
\() ->
|
2022-01-15 02:28:17 +03:00
|
|
|
Form.succeed identity
|
|
|
|
|> Form.with
|
|
|
|
(Form.text "name" toInput
|
|
|
|
|> Form.required "Required"
|
|
|
|
)
|
|
|
|
|> expectErrorsAfterUpdates
|
2022-01-14 19:42:14 +03:00
|
|
|
[ ( "name", [ "Required" ] )
|
2022-01-13 20:06:58 +03:00
|
|
|
]
|
2022-01-15 02:28:17 +03:00
|
|
|
, 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" ] )
|
|
|
|
]
|
2022-01-13 20:06:58 +03:00
|
|
|
|
2022-03-04 20:39:33 +03:00
|
|
|
--, test "form-level validations are run 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." ] )
|
|
|
|
-- ]
|
|
|
|
--, 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" ] )
|
|
|
|
-- ]
|
2022-01-17 20:56:20 +03:00
|
|
|
, 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" ] )
|
|
|
|
]
|
2022-01-17 21:31:37 +03:00
|
|
|
, 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" ] )
|
|
|
|
]
|
2022-01-06 06:57:25 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2022-05-12 01:37:18 +03:00
|
|
|
expectDecodeNoErrors2 : List ( String, String ) -> decoded -> Form.Form () String decoded view -> Expect.Expectation
|
2022-01-17 01:31:08 +03:00
|
|
|
expectDecodeNoErrors2 updates expected form =
|
|
|
|
let
|
|
|
|
formModel =
|
|
|
|
form
|
|
|
|
|> Form.init
|
|
|
|
|> updateFieldsWithValues updates form
|
|
|
|
in
|
|
|
|
Form.runClientValidations formModel form
|
|
|
|
|> Expect.equal
|
|
|
|
(Ok ( expected, [] ))
|
|
|
|
|
|
|
|
|
2022-01-14 01:18:53 +03:00
|
|
|
expectDecodeNoErrors : decoded -> Result error ( decoded, List b ) -> Expect.Expectation
|
|
|
|
expectDecodeNoErrors decoded actual =
|
|
|
|
actual
|
|
|
|
|> Expect.equal (Ok ( decoded, [] ))
|
|
|
|
|
|
|
|
|
2022-05-12 01:37:18 +03:00
|
|
|
updateField : Form.Form () String value view -> ( String, String ) -> Form.Model -> Form.Model
|
2022-01-13 20:06:58 +03:00
|
|
|
updateField form ( name, value ) model =
|
|
|
|
model
|
2022-05-12 01:37:18 +03:00
|
|
|
|> Form.update (\_ -> ()) () (\_ -> ()) form (Form.OnFieldInput { name = name, value = value })
|
2022-01-13 20:06:58 +03:00
|
|
|
|> Tuple.first
|
|
|
|
|
|
|
|
|
2022-01-14 19:42:14 +03:00
|
|
|
expectErrors : List ( String, List String ) -> Form.Model -> Expect.Expectation
|
2022-01-13 20:06:58 +03:00
|
|
|
expectErrors expected form =
|
|
|
|
form.fields
|
2022-01-15 20:06:19 +03:00
|
|
|
|> Dict.map
|
|
|
|
(\key value ->
|
|
|
|
value.errors
|
|
|
|
++ (form.formErrors
|
|
|
|
|> Dict.get key
|
|
|
|
|> Maybe.withDefault []
|
|
|
|
)
|
|
|
|
)
|
2022-01-13 20:06:58 +03:00
|
|
|
|> Expect.equalDicts (Dict.fromList expected)
|
|
|
|
|
|
|
|
|
2022-05-12 01:37:18 +03:00
|
|
|
updateAllFields : List String -> Form.Form () String value view -> Form.Model -> Form.Model
|
2022-01-15 02:28:17 +03:00
|
|
|
updateAllFields fields form model =
|
|
|
|
fields
|
|
|
|
|> List.foldl
|
|
|
|
(\fieldName modelSoFar ->
|
|
|
|
modelSoFar
|
|
|
|
|> updateField form ( fieldName, "" )
|
|
|
|
)
|
|
|
|
model
|
|
|
|
|
|
|
|
|
2022-05-12 01:37:18 +03:00
|
|
|
updateFieldsWithValues : List ( String, String ) -> Form.Form () String value view -> Form.Model -> Form.Model
|
2022-01-17 01:31:08 +03:00
|
|
|
updateFieldsWithValues fields form model =
|
|
|
|
fields
|
|
|
|
|> List.foldl
|
|
|
|
(\( fieldName, fieldValue ) modelSoFar ->
|
|
|
|
modelSoFar
|
|
|
|
|> updateField form ( fieldName, fieldValue )
|
|
|
|
)
|
|
|
|
model
|
|
|
|
|
|
|
|
|
2022-05-12 01:37:18 +03:00
|
|
|
expectErrorsAfterUpdates : List ( String, List String ) -> Form.Form () String value view -> Expect.Expectation
|
2022-01-15 02:28:17 +03:00
|
|
|
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
|
2022-01-28 03:03:42 +03:00
|
|
|
|> Dict.map (\_ value -> value.errors)
|
2022-01-15 02:28:17 +03:00
|
|
|
|> Expect.equalDicts (Dict.fromList expected)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
()
|
|
|
|
|
|
|
|
|
2022-05-12 01:37:18 +03:00
|
|
|
performUpdatesThenExpectErrors : List ( String, String ) -> List ( String, List String ) -> Form.Form () String value view -> Expect.Expectation
|
2022-01-17 20:56:20 +03:00
|
|
|
performUpdatesThenExpectErrors updatesToPerform expected form =
|
|
|
|
let
|
|
|
|
model : Form.Model
|
|
|
|
model =
|
|
|
|
Form.init form
|
|
|
|
in
|
|
|
|
Expect.all
|
|
|
|
([ updateFieldsWithValues updatesToPerform form model
|
|
|
|
]
|
|
|
|
|> List.map
|
|
|
|
(\formModel () ->
|
|
|
|
formModel.fields
|
2022-01-28 03:03:42 +03:00
|
|
|
|> Dict.map (\_ value -> value.errors)
|
2022-01-17 20:56:20 +03:00
|
|
|
|> Expect.equalDicts (Dict.fromList expected)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
()
|
|
|
|
|
|
|
|
|
2022-01-28 07:21:49 +03:00
|
|
|
field : a -> { raw : Maybe a, errors : List b, status : Form.FieldStatus }
|
2022-01-11 20:33:45 +03:00
|
|
|
field value =
|
|
|
|
{ raw = Just value, errors = [], status = Form.NotVisited }
|
|
|
|
|
|
|
|
|
2022-01-28 07:21:49 +03:00
|
|
|
toInput : a -> ()
|
2022-01-06 06:57:25 +03:00
|
|
|
toInput _ =
|
|
|
|
()
|