Add part4

This commit is contained in:
Richard Feldman
2018-04-30 05:51:20 -04:00
parent 29fbd5c1c1
commit 202fde8eb4
240 changed files with 34585 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
# Elm packages and build artifacts
elm-stuff

View File

@@ -0,0 +1,5 @@
## Releases
| Version | Notes |
| ------- | ----- |
| [**2.0.0**](https://github.com/rtfeldman/elm-validate/tree/1.0.0) | Change Validator to be a union type, replace `ifInvalid` with `ifTrue` and `ifFalse`, rearrange arguments of `ifBlank` and similar functions, replace `eager` with `firstError`, and expose `isBlank`, `isInt`, and `isValidEmail`.
| [**1.0.0**](https://github.com/rtfeldman/elm-validate/tree/1.0.0) | Initial Release

View File

@@ -0,0 +1,28 @@
Copyright (c) 2015, Richard Feldman
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of elm-validate nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,63 @@
# elm-validate
`elm-validate` provides convenience functions for validating data.
It is based around the idea of a `Validator`, which runs checks on a
subject and returns a list of errors representing anything invalid about
that subject. If the list is empty, the subject is valid.
For example:
```elm
import Validate exposing (ifBlank, ifNotInt, validate)
type alias Model =
{ name : String, email : String, age : String, selections : List String }
modelValidator : Validator Model String
modelValidator =
Validate.all
[ ifBlank .name "Please enter a name."
, ifBlank .email "Please enter an email address."
, ifNotInt .age "Age must be a whole number."
, ifEmptyList .selections "Please select at least one."
]
validate modelValidator
{ name = "Sam", email = "", age = "abc", selections = [ "cats" ] }
== [ "Please enter an email address.", "Age must be a whole number." ]
--> True
```
You can represent your errors however you like. One nice approach is to use
tuple of the error message and the field responsible for the error:
```elm
type Field =
Name | Email | Age | Selections
modelValidator : Validator ( Field, String ) Model
modelValidator =
Validate.all
[ ifBlank .name ( Name, "Please enter a name." )
, ifBlank .email ( Email, "Please enter an email address." )
, ifNotInt .age ( Age, "Age must be a whole number." )
, ifEmptyList .selections ( Selections, "Please select at least one." )
]
type alias Model =
{ name : String, email : String, age : String }
validate modelValidator
{ name = "Sam", email = "", age = "abc", selections = [ "cats" ] }
== [ ( Email, "Please enter an email address." )
, ( Age, "Age must be a whole number." )
]
--> True
```

View File

@@ -0,0 +1,16 @@
{
"version": "2.0.0",
"summary": "Convenience functions for validating data.",
"repository": "https://github.com/rtfeldman/elm-validate.git",
"license": "BSD-3-Clause",
"source-directories": [
"src"
],
"exposed-modules": [
"Validate"
],
"dependencies": {
"elm-lang/core": "5.0.0 <= v < 6.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}

View File

@@ -0,0 +1,36 @@
module SignupForm exposing (..)
import Validate exposing (Validator, ifBlank, ifEmptyList, ifInvalidEmail, ifNotInt, validate)
type alias Model =
{ name : String, email : String, age : String, selections : List Float }
type Field
= Name
| Email
| Age
| Selections
modelValidator : Validator ( Field, String ) Model
modelValidator =
Validate.all
[ ifBlank .name ( Name, "Please enter a name." )
, Validate.firstError
[ ifBlank .email ( Email, "Please enter an email address." )
, ifInvalidEmail .email ( Email, "This is not a valid email address." )
]
, ifNotInt .age ( Age, "Age must be a whole number." )
, ifEmptyList .selections ( Selections, "Please select at least one." )
]
result : Bool
result =
validate modelValidator
{ name = "Sam", email = "", age = "abc", selections = [ 1.2 ] }
== [ ( Email, "Please enter an email address." )
, ( Age, "Age must be a whole number." )
]

View File

@@ -0,0 +1,15 @@
{
"version": "1.0.0",
"summary": "Examples for elm-validate",
"repository": "https://github.com/rtfeldman/elm-validate.git",
"license": "BSD-3-Clause",
"source-directories": [
"../src",
"."
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "5.0.0 <= v < 6.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}

View File

@@ -0,0 +1,377 @@
module Validate
exposing
( Validator
, all
, any
, firstError
, ifBlank
, ifEmptyDict
, ifEmptyList
, ifEmptySet
, ifFalse
, ifInvalidEmail
, ifNotInt
, ifNothing
, ifTrue
, isBlank
, isInt
, isValidEmail
, validate
)
{-| Convenience functions for validating data.
import Validate exposing (ifBlank, ifNotInt, validate)
type Field = Name | Email | Age
type alias Model = { name : String, email : String, age : String }
modelValidator : Validator String Model
modelValidator =
Validate.all
[ ifBlank .name "Please enter a name."
, Validate.firstError
[ ifBlank .email "Please enter an email address."
, ifInvalidEmail .email "This is not a valid email address."
]
, ifNotInt .age "Age must be a whole number."
]
validate modelValidator { name = "Sam", email = "blah", age = "abc" }
--> [ "This is not a valid email address.", "Age must be a whole number." ]
# Validating a subject
@docs Validator, validate
# Creating validators
@docs ifBlank, ifNotInt, ifEmptyList, ifEmptyDict, ifEmptySet, ifNothing, ifInvalidEmail, ifTrue, ifFalse
# Combining validators
@docs all, any, firstError
# Checking values directly
@docs isBlank, isInt, isValidEmail
-}
import Dict exposing (Dict)
import Regex exposing (Regex)
import Set exposing (Set)
import String
-- VALIDATING A SUBJECT --
{-| A `Validator` contains a function which takes a subject and returns a list
of errors describing anything invalid about that subject.
Pass it to [`validate`](#validate) to get the list of errors.
An empty error list means the subject was valid.
-}
type Validator error subject
= Validator (subject -> List error)
{-| Return an error if the given predicate returns `True` for the given
subject.
import Validate exposing (ifBlank, ifNotInt, validate)
type Field = Name | Email | Age
type alias Model = { name : String, email : String, age : String }
modelValidator : Validator ( Field, String ) Model
modelValidator =
Validate.all
[ ifBlank .name ( Name, "Please enter a name." )
, ifBlank .email ( Email, "Please enter an email address." )
, ifNotInt .age ( Age, "Age must be a whole number." )
]
validate modelValidator { name = "Sam", email = "", age = "abc" }
--> [ ( Email, "Please enter an email address." ), ( Age, "Age must be a whole number." ) ]
-}
validate : Validator error subject -> subject -> List error
validate (Validator getErrors) subject =
getErrors subject
-- CONSTRUCTING VALIDATORS --
{-| Return an error if the given `String` is empty, or if it contains only
whitespace characters.
import Validate exposing (ifBlank, ifNotInt)
modelValidator : Validator Model String
modelValidator =
Validate.all
[ ifBlank .name "Please enter a name."
, ifBlank .email "Please enter an email address."
]
-}
ifBlank : (subject -> String) -> error -> Validator error subject
ifBlank subjectToString error =
ifTrue (\subject -> isBlank (subjectToString subject)) error
{-| Return an error if the given `String` cannot be parsed as an `Int`.
-}
ifNotInt : (subject -> String) -> error -> Validator error subject
ifNotInt subjectToString error =
ifFalse (\subject -> isInt (subjectToString subject)) error
{-| Return an error if a `List` is empty.
-}
ifEmptyList : (subject -> List a) -> error -> Validator error subject
ifEmptyList subjectToList error =
ifTrue (\subject -> List.isEmpty (subjectToList subject)) error
{-| Return an error if a `Dict` is empty.
-}
ifEmptyDict : (subject -> Dict comparable v) -> error -> Validator error subject
ifEmptyDict subjectToDict error =
ifTrue (\subject -> Dict.isEmpty (subjectToDict subject)) error
{-| Return an error if a `Set` is empty.
-}
ifEmptySet : (subject -> Set comparable) -> error -> Validator error subject
ifEmptySet subjectToSet error =
ifTrue (\subject -> Set.isEmpty (subjectToSet subject)) error
{-| Return an error if a `Maybe` is `Nothing`.
-}
ifNothing : (subject -> Maybe a) -> error -> Validator error subject
ifNothing subjectToMaybe error =
ifTrue (\subject -> subjectToMaybe subject == Nothing) error
{-| Return an error if an email address is malformed.
-}
ifInvalidEmail : (subject -> String) -> error -> Validator error subject
ifInvalidEmail subjectToEmail error =
ifFalse (\subject -> isValidEmail (subjectToEmail subject)) error
{-| Return an error if a predicate returns `True` for the given
subject.
import Validate exposing (ifTrue)
modelValidator : Validator Model String
modelValidator =
ifTrue (\model -> countSelected model < 2)
"Please select at least two."
-}
ifTrue : (subject -> Bool) -> error -> Validator error subject
ifTrue test error =
let
getErrors subject =
if test subject then
[ error ]
else
[]
in
Validator getErrors
{-| Return an error if a predicate returns `False` for the given
subject.
import Validate exposing (ifFalse)
modelValidator : Validator Model String
modelValidator =
ifFalse (\model -> countSelected model >= 2)
"Please select at least two."
-}
ifFalse : (subject -> Bool) -> error -> Validator error subject
ifFalse test error =
let
getErrors subject =
if test subject then
[]
else
[ error ]
in
Validator getErrors
-- COMBINING VALIDATORS --
{-| Run each of the given validators, in order, and return their concatenated
error lists.
import Validate exposing (ifBlank, ifNotInt)
modelValidator : Validator Model String
modelValidator =
Validate.all
[ ifBlank .name "Please enter a name."
, ifBlank .email "Please enter an email address."
, ifNotInt .age "Age must be a whole number."
]
-}
all : List (Validator error subject) -> Validator error subject
all validators =
let
newGetErrors subject =
let
accumulateErrors (Validator getErrors) totalErrors =
totalErrors ++ getErrors subject
in
List.foldl accumulateErrors [] validators
in
Validator newGetErrors
{-| Run each of the given validators, in order, stopping after the first error
and returning it. If no errors are encountered, return `Nothing`.
import Validate exposing (ifBlank, ifInvalidEmail, ifNotInt)
type alias Model =
{ email : String, age : String }
modelValidator : Validator String Model
modelValidator =
Validate.all
[ Validate.firstError
[ ifBlank .email "Please enter an email address."
, ifInvalidEmail .email "This is not a valid email address."
]
, ifNotInt .age "Age must be a whole number."
]
validate modelValidator { email = " ", age = "5" }
--> [ "Please enter an email address." ]
validate modelValidator { email = "blah", age = "5" }
--> [ "This is not a valid email address." ]
validate modelValidator { email = "foo@bar.com", age = "5" }
--> []
-}
firstError : List (Validator error subject) -> Validator error subject
firstError validators =
let
getErrors subject =
firstErrorHelp validators subject
in
Validator getErrors
firstErrorHelp : List (Validator error subject) -> subject -> List error
firstErrorHelp validators subject =
case validators of
[] ->
[]
(Validator getErrors) :: rest ->
case getErrors subject of
[] ->
firstErrorHelp rest subject
errors ->
errors
{-| Return `True` if none of the given validators returns any errors for the given
subject, and `False` if any validator returns one or more errors.
-}
any : List (Validator error subject) -> subject -> Bool
any validators subject =
case validators of
[] ->
True
(Validator getErrors) :: others ->
case getErrors subject of
[] ->
any others subject
error :: _ ->
False
-- CHECKING VALUES DIRECTLY --
{-| Returns `True` if the given string is nothing but whitespace.
[`ifBlank`](#ifBlank) uses this under the hood.
-}
isBlank : String -> Bool
isBlank str =
Regex.contains lacksNonWhitespaceChars str
{-| Returns `True` if the email is malformed.
[`ifInvalidEmail`](#ifInvalidEmail) uses this under the hood.
-}
isValidEmail : String -> Bool
isValidEmail email =
Regex.contains validEmail email
{-| Returns `True` if `String.toInt` on the given string returns an `Ok`.
[`ifNotInt`](#ifNotInt) uses this under the hood.
-}
isInt : String -> Bool
isInt str =
case String.toInt str of
Ok _ ->
True
Err _ ->
False
-- INTERNAL HELPERS --
lacksNonWhitespaceChars : Regex
lacksNonWhitespaceChars =
Regex.regex "^\\s*$"
validEmail : Regex
validEmail =
Regex.regex "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
|> Regex.caseInsensitive

View File

@@ -0,0 +1,52 @@
module ValidateTests exposing (..)
import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer, int, list, string)
import Test exposing (..)
import Validate
blankness : Test
blankness =
describe "blankness"
[ test "empty string is blank" <|
\() ->
""
|> Validate.isBlank
|> Expect.true "Validate.isBlank should have considered empty string blank"
, fuzz whitespace "whitespace characters are blank" <|
\str ->
str
|> Validate.isBlank
|> Expect.true "Validate.isBlank should consider whitespace blank"
, fuzz2 whitespace whitespace "non-whitespace characters mean it's not blank" <|
\prefix suffix ->
(prefix ++ "_" ++ suffix)
|> Validate.isBlank
|> Expect.false "Validate.isBlank shouldn't consider strings containing non-whitespace characters blank"
]
email : Test
email =
describe "email"
[ test "empty string is not a valid email" <|
\() ->
""
|> Validate.isValidEmail
|> Expect.false "Validate.isValidEmail should have considered empty string blank"
, test "valid email is valid" <|
\() ->
"foo@bar.com"
|> Validate.isValidEmail
|> Expect.true "Validate.isValidEmail should have considered foo@bar.com a valid email address"
]
whitespace : Fuzzer String
whitespace =
[ ' ', ' ', '\t', '\n' ]
|> List.map Fuzz.constant
|> Fuzz.oneOf
|> Fuzz.list
|> Fuzz.map String.fromList

View File

@@ -0,0 +1,16 @@
{
"version": "1.0.0",
"summary": "Test Suites",
"repository": "https://github.com/rtfeldman/elm-validate.git",
"license": "BSD-3-Clause",
"source-directories": [
"../src",
"."
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "5.0.0 <= v < 6.0.0",
"elm-community/elm-test": "4.0.0 <= v < 5.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}

View File

@@ -0,0 +1,4 @@
{
"root": "../src",
"tests": [ "Validate" ]
}

View File

@@ -0,0 +1,4 @@
# elm-package generated files
elm-stuff/
# elm-repl generated files
repl-temp-*

View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2017, Richard Feldman
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,5 @@
# SelectList
A `SelectList` is a nonempty list which always has exactly one element selected.
It is an example of a list [zipper](https://en.wikipedia.org/wiki/Zipper_(data_structure)).

View File

@@ -0,0 +1,16 @@
{
"version": "1.0.0",
"summary": "A nonempty list in which exactly one element is always selected.",
"repository": "https://github.com/rtfeldman/selectlist.git",
"license": "BSD-3-Clause",
"source-directories": [
"src"
],
"exposed-modules": [
"SelectList"
],
"dependencies": {
"elm-lang/core": "5.0.0 <= v < 6.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}

View File

@@ -0,0 +1,272 @@
module SelectList
exposing
( SelectList
, map
, mapBy
, Position(..)
, fromLists
, select
, prepend
, append
, before
, after
, selected
, toList
, singleton
)
{-| A `SelectList` is a nonempty list which always has exactly one element selected.
It is an example of a list [zipper](https://en.wikipedia.org/wiki/Zipper_(data_structure)).
@docs SelectList, fromLists, singleton
## Reading
@docs toList, before, selected, after
## Transforming
@docs map, mapBy, Position, select, append, prepend
-}
{-| A nonempty list which always has exactly one element selected.
Create one using [`fromLists`](#fromLists).
-}
type SelectList a
= SelectList (List a) a (List a)
{-| Return the elements before the selected element.
import SelectList
SelectList.fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
|> SelectList.before
== [ 1, 2 ]
-}
before : SelectList a -> List a
before (SelectList beforeSel _ _) =
beforeSel
{-| Return the elements after the selected element.
import SelectList
SelectList.fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
|> SelectList.after
== [ 3, 4, 5 ]
-}
after : SelectList a -> List a
after (SelectList _ _ afterSel) =
afterSel
{-| Return the selected element.
import SelectList
SelectList.fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
|> SelectList.selected
== 3
-}
selected : SelectList a -> a
selected (SelectList _ sel _) =
sel
{-| A `SelectList` containing exactly one element.
import SelectList
SelectList.singleton "foo"
== SelectList.fromLists [] "foo" []
-}
singleton : a -> SelectList a
singleton sel =
SelectList [] sel []
{-| Transform each element of the `SelectList`. The transform
function receives a `Position` which is `Selected` if it was passed
the SelectList's selected element, `BeforeSelected` if it was passed an element
before the selected element, and `AfterSelected` otherwise.
import SelectList exposing (Position(..))
doubleOrNegate position num =
if position == Selected then
num * -1
else
num * 2
SelectList.fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
|> SelectList.mapBy doubleOrNegate
== SelectList.fromLists [ 2, 4 ] -3 [ 8, 10, 12 ]
-}
mapBy : (Position -> a -> b) -> SelectList a -> SelectList b
mapBy transform (SelectList beforeSel sel afterSel) =
SelectList
(List.map (transform BeforeSelected) beforeSel)
(transform Selected sel)
(List.map (transform AfterSelected) afterSel)
{-| Used with [`mapBy`](#mapBy).
-}
type Position
= BeforeSelected
| Selected
| AfterSelected
{-| Transform each element of the `SelectList`.
import SelectList
SelectList.fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
|> SelectList.map (\num -> num * 2)
== SelectList.fromLists [ 2, 4 ] 6 [ 8, 10, 12 ]
-}
map : (a -> b) -> SelectList a -> SelectList b
map transform (SelectList beforeSel sel afterSel) =
SelectList
(List.map transform beforeSel)
(transform sel)
(List.map transform afterSel)
{-| Returns a `SelectList`.
import SelectList
SelectList.fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
|> SelectList.selected
== 3
-}
fromLists : List a -> a -> List a -> SelectList a
fromLists =
SelectList
{-| Change the selected element to the first one which passes a
predicate function. If no elements pass, the `SelectList` is unchanged.
import SelectList
isEven num =
num % 2 == 0
SelectList.fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
|> SelectList.select isEven
== SelectList.fromLists [ 1 ] 2 [ 3, 4, 5, 6 ]
-}
select : (a -> Bool) -> SelectList a -> SelectList a
select isSelectable ((SelectList beforeSel sel afterSel) as original) =
case selectHelp isSelectable beforeSel sel afterSel of
Nothing ->
original
Just ( newBefore, newSel, newAfter ) ->
SelectList newBefore newSel newAfter
selectHelp : (a -> Bool) -> List a -> a -> List a -> Maybe ( List a, a, List a )
selectHelp isSelectable beforeList selectedElem afterList =
case ( beforeList, afterList ) of
( [], [] ) ->
Nothing
( [], first :: rest ) ->
if isSelectable selectedElem then
Just ( beforeList, selectedElem, afterList )
else if isSelectable first then
Just ( beforeList ++ [ selectedElem ], first, rest )
else
case selectHelp isSelectable [] first rest of
Nothing ->
Nothing
Just ( newBefore, newSelected, newAfter ) ->
Just ( selectedElem :: newBefore, newSelected, newAfter )
( first :: rest, _ ) ->
if isSelectable first then
Just ( [], first, (rest ++ selectedElem :: afterList) )
else
case selectHelp isSelectable rest selectedElem afterList of
Nothing ->
Nothing
Just ( newBefore, newSelected, newAfter ) ->
Just ( first :: newBefore, newSelected, newAfter )
{-| Add elements to the end of a `SelectList`.
import SelectList
SelectList.fromLists [ 1, 2 ] 3 [ 4 ]
|> SelectList.append [ 5, 6 ]
== SelectList.fromLists [ 1 ] 2 [ 3, 4, 5, 6 ]
-}
append : List a -> SelectList a -> SelectList a
append list (SelectList beforeSel sel afterSel) =
SelectList beforeSel sel (afterSel ++ list)
{-| Add elements to the beginning of a `SelectList`.
import SelectList
SelectList.fromLists [ 3 ] 4 [ 5, 6 ]
|> SelectList.prepend [ 1, 2 ]
== SelectList.fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
-}
prepend : List a -> SelectList a -> SelectList a
prepend list (SelectList beforeSel sel afterSel) =
SelectList (list ++ beforeSel) sel afterSel
{-| Return a `List` containing the elements in a `SelectList`.
import SelectList
SelectList.fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
|> SelectList.toList
== [ 1, 2, 3, 4, 5, 6 ]
-}
toList : SelectList a -> List a
toList (SelectList beforeSel sel afterSel) =
beforeSel ++ sel :: afterSel

View File

@@ -0,0 +1,164 @@
module Tests exposing (..)
import Test exposing (..)
import Expect exposing (Expectation)
import Fuzz exposing (list, int, tuple, string)
import SelectList exposing (Position(..))
import List.Extra
access : Test
access =
describe "before, selected, after"
[ fuzzSegments "before" <|
\beforeSel sel afterSel ->
SelectList.fromLists beforeSel sel afterSel
|> SelectList.before
|> Expect.equal beforeSel
, fuzzSegments "selected" <|
\beforeSel sel afterSel ->
SelectList.fromLists beforeSel sel afterSel
|> SelectList.selected
|> Expect.equal sel
, fuzzSegments "after" <|
\beforeSel sel afterSel ->
SelectList.fromLists beforeSel sel afterSel
|> SelectList.after
|> Expect.equal afterSel
, fuzzSegments "toList" <|
\beforeSel sel afterSel ->
SelectList.fromLists beforeSel sel afterSel
|> SelectList.toList
|> Expect.equal (beforeSel ++ sel :: afterSel)
]
transforming : Test
transforming =
describe "transforming" <|
[ fuzzSegments "append" <|
\beforeSel sel afterSel ->
SelectList.fromLists beforeSel sel afterSel
|> SelectList.append beforeSel
|> Expect.equal (SelectList.fromLists beforeSel sel (afterSel ++ beforeSel))
, fuzzSegments "prepend" <|
\beforeSel sel afterSel ->
SelectList.fromLists beforeSel sel afterSel
|> SelectList.prepend afterSel
|> Expect.equal (SelectList.fromLists (afterSel ++ beforeSel) sel afterSel)
, describe "mapBy"
[ fuzzSegments "mapBy transforms every element" <|
\beforeSel sel afterSel ->
SelectList.fromLists beforeSel sel afterSel
|> SelectList.mapBy (\_ num -> num * sel)
|> Expect.equal
(SelectList.fromLists
(List.map (\num -> num * sel) beforeSel)
(sel * sel)
(List.map (\num -> num * sel) afterSel)
)
, fuzzSegments "mapBy passes the selected elem" <|
\beforeSel sel afterSel ->
SelectList.fromLists beforeSel sel afterSel
|> SelectList.mapBy (\isSelected _ -> isSelected)
|> Expect.equal
(SelectList.fromLists
(List.map (\_ -> BeforeSelected) beforeSel)
Selected
(List.map (\_ -> AfterSelected) afterSel)
)
]
, describe "select"
[ fuzzSegments "is a no-op when trying to select what's already selected" <|
\beforeSel sel afterSel ->
let
original =
-- make only the selected one negative
SelectList.fromLists beforeSel sel afterSel
|> SelectList.mapBy
(\position elem ->
if position == Selected then
negate elem
else
elem
)
in
original
|> SelectList.select (\num -> num < 0)
|> Expect.equal original
, fuzzSegments "is a no-op when the predicate fails every time" <|
\beforeSel sel afterSel ->
let
original =
SelectList.fromLists beforeSel sel afterSel
in
original
|> SelectList.select (\num -> num < 0)
|> Expect.equal original
, fuzzSegments "selects the first one it finds" <|
\beforeSel sel afterSel ->
let
predicate num =
num > 5
firstInList =
(beforeSel ++ sel :: afterSel)
|> List.Extra.find predicate
|> Maybe.withDefault sel
in
SelectList.fromLists beforeSel sel afterSel
|> SelectList.select predicate
|> SelectList.selected
|> Expect.equal firstInList
, describe "selects the first one it finds in a hardcoded list"
[ test "where it's the beginning of the `before` list" <|
\() ->
SelectList.fromLists [ 1, 2, 3 ] 4 [ 5, 2, 1 ]
|> SelectList.select (\num -> num > 0)
|> Expect.equal (SelectList.fromLists [] 1 [ 2, 3, 4, 5, 2, 1 ])
, test "where it's the middle of the `before` list" <|
\() ->
SelectList.fromLists [ 1, 2, 3 ] 4 [ 5, 2, 1 ]
|> SelectList.select (\num -> num > 1)
|> Expect.equal (SelectList.fromLists [ 1 ] 2 [ 3, 4, 5, 2, 1 ])
, test "where it's the end of the `before` list" <|
\() ->
SelectList.fromLists [ 1, 2, 3 ] 4 [ 5, 2, 1 ]
|> SelectList.select (\num -> num > 2)
|> Expect.equal (SelectList.fromLists [ 1, 2 ] 3 [ 4, 5, 2, 1 ])
, test "where it's the selected element in the list" <|
\() ->
SelectList.fromLists [ 1, 2, 3 ] 4 [ 5, 2, 1 ]
|> SelectList.select (\num -> num > 3)
|> Expect.equal (SelectList.fromLists [ 1, 2, 3 ] 4 [ 5, 2, 1 ])
, test "where it's the beginning of the `after` list" <|
\() ->
SelectList.fromLists [ 1, 2, 3 ] 4 [ 5, 2, 5, 1 ]
|> SelectList.select (\num -> num > 4)
|> Expect.equal (SelectList.fromLists [ 1, 2, 3, 4 ] 5 [ 2, 5, 1 ])
, test "where it's the middle of the `after` list" <|
\() ->
SelectList.fromLists [ 1, 2, 3 ] 4 [ 5, 2, 5, 6, 1, 6, 7 ]
|> SelectList.select (\num -> num > 5)
|> Expect.equal (SelectList.fromLists [ 1, 2, 3, 4, 5, 2, 5 ] 6 [ 1, 6, 7 ])
, test "where it's the end of the `after` list" <|
\() ->
SelectList.fromLists [ 1, 2, 3 ] 4 [ 5, 2, 5, 6, 1, 6, 7 ]
|> SelectList.select (\num -> num > 6)
|> Expect.equal (SelectList.fromLists [ 1, 2, 3, 4, 5, 2, 5, 6, 1, 6 ] 7 [])
]
]
]
{-| Choose positive ints so that we can throw a negative one in there and
detect it later.
-}
fuzzSegments : String -> (List Int -> Int -> List Int -> Expectation) -> Test
fuzzSegments =
fuzz3 (list positiveInt) positiveInt (list positiveInt)
positiveInt : Fuzz.Fuzzer Int
positiveInt =
Fuzz.map abs int

View File

@@ -0,0 +1,17 @@
{
"version": "1.0.0",
"summary": "Test Suites",
"repository": "https://github.com/elm-community/elm-test.git",
"license": "BSD-3-Clause",
"source-directories": [
"../src",
"."
],
"exposed-modules": [],
"dependencies": {
"elm-community/elm-test": "4.0.0 <= v < 5.0.0",
"elm-community/list-extra": "6.0.0 <= v < 7.0.0",
"elm-lang/core": "5.0.0 <= v < 6.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}