Skip to content

Instantly share code, notes, and snippets.

@rupertlssmith
Created September 17, 2024 10:22
Show Gist options
  • Save rupertlssmith/c140437602e5ef2d6e8864473c7aa0c7 to your computer and use it in GitHub Desktop.
Save rupertlssmith/c140437602e5ef2d6e8864473c7aa0c7 to your computer and use it in GitHub Desktop.
Modal groups and tests for them in Elm
module ModalGroup exposing (..)
{-| A ModalGroup is a collection of items, zero or one of which can be active at a time.
It is intended for modelling the behaviour of modal dialog boxes, where at most one dialog box can
be open at a time.
-}
import Dict exposing (Dict)
type ModalGroup k comparable a
= Group
{ active : Maybe k
, group : Dict comparable a
, keyFn : k -> comparable
}
{-| Empty grouping.
Map the key type of the group with a function, so that non-comparable keys can be used.
-}
empty : (k -> comparableKey) -> ModalGroup k comparableKey a
empty keyFn =
Group { active = Nothing, group = Dict.empty, keyFn = keyFn }
{-| Clears any active item.
-}
reset : ModalGroup k comparableKey a -> ModalGroup k comparableKey a
reset (Group record) =
Group { record | active = Nothing }
{-| Adds a new item to the group by its key.
-}
add : k -> a -> ModalGroup k comparableKey a -> ModalGroup k comparableKey a
add key value (Group record) =
let
compKey =
record.keyFn key
newGroup =
Dict.insert compKey value record.group
in
Group { record | group = newGroup }
{-| Sets the active item in the group. If no matching item can be found any active item will be cleared.
-}
active : k -> ModalGroup k comparableKey a -> ( ModalGroup k comparableKey a, Maybe a )
active key (Group record) =
let
compKey =
record.keyFn key
maybeValue =
Dict.get compKey record.group
newActive =
maybeValue
|> Maybe.map (always key)
newRecord =
Group { record | active = newActive }
in
( newRecord, maybeValue )
module ModalGroupTest exposing (..)
import Dict exposing (Dict)
import ModalGroup exposing (..)
import Expect
import Test exposing (..)
{-| Test that an empty ModalGroup has no active item and no items in the group.
-}
test_emptyModalGroup : Test
test_emptyModalGroup =
test "Empty ModalGroup has no active item and no items" <|
\_ ->
let
modalGroup =
empty identity
in
case modalGroup of
Group record ->
Expect.all
[ \_ -> Expect.equal record.active Nothing
, \_ -> Expect.equal (Dict.size record.group) 0
]
()
{-| Test that adding an item to the ModalGroup increases the group size.
-}
test_addItemToModalGroup : Test
test_addItemToModalGroup =
test "Add an item to ModalGroup" <|
\_ ->
let
modalGroup =
empty identity
|> add 1 "Item 1"
in
case modalGroup of
Group record ->
Expect.equal (Dict.size record.group) 1
{-| Test that adding multiple items to the ModalGroup works correctly.
-}
test_addMultipleItemsToModalGroup : Test
test_addMultipleItemsToModalGroup =
test "Add multiple items to ModalGroup" <|
\_ ->
let
modalGroup =
empty identity
|> add 1 "Item 1"
|> add 2 "Item 2"
in
case modalGroup of
Group record ->
Expect.equal (Dict.size record.group) 2
{-| Test activating an existing item updates the active item and returns the correct item.
-}
test_activateExistingItem : Test
test_activateExistingItem =
test "Activate an existing item" <|
\_ ->
let
modalGroup =
empty identity
|> add 1 "Item 1"
|> add 2 "Item 2"
( updatedGroup, maybeItem ) =
active 1 modalGroup
in
case updatedGroup of
Group record ->
Expect.all
[ \_ -> Expect.equal record.active (Just 1)
, \_ -> Expect.equal maybeItem (Just "Item 1")
]
()
{-| Test that activating a non-existing item resets the active item to Nothing.
-}
test_activateNonExistingItem : Test
test_activateNonExistingItem =
test "Activate a non-existing item resets active" <|
\_ ->
let
modalGroup =
empty identity
|> add 1 "Item 1"
|> add 2 "Item 2"
( updatedGroup, maybeItem ) =
active 3 modalGroup
in
case updatedGroup of
Group record ->
Expect.all
[ \_ -> Expect.equal record.active Nothing
, \_ -> Expect.equal maybeItem Nothing
]
()
{-| Test that resetting the ModalGroup clears any active item.
-}
test_resetClearsActiveItem : Test
test_resetClearsActiveItem =
test "Reset clears active item" <|
\_ ->
let
modalGroup =
empty identity
|> add 1 "Item 1"
|> (\mg -> Tuple.first (active 1 mg))
|> reset
in
case modalGroup of
Group record ->
Expect.equal record.active Nothing
{-| Test that activating another item changes the active item.
-}
test_activateAnotherItem : Test
test_activateAnotherItem =
test "Active item changes when activating another item" <|
\_ ->
let
modalGroup =
empty identity
|> add 1 "Item 1"
|> add 2 "Item 2"
|> (\mg -> Tuple.first (active 1 mg))
( updatedGroup, maybeItem ) =
active 2 modalGroup
in
case updatedGroup of
Group record ->
Expect.all
[ \_ -> Expect.equal record.active (Just 2)
, \_ -> Expect.equal maybeItem (Just "Item 2")
]
()
type CustomKey
= One
| Two
| Three
customKeyFn : CustomKey -> Int
customKeyFn ck =
case ck of
One ->
1
Two ->
2
Three ->
3
{-| Test adding items with a custom key function using a custom type.
-}
test_addItemWithCustomKeyFunction : Test
test_addItemWithCustomKeyFunction =
test "Adding items with a custom key function using CustomKey type" <|
\_ ->
let
modalGroup =
empty customKeyFn
|> add One "Item One"
|> add Three "Item Three"
( updatedGroup, maybeItem ) =
active Three modalGroup
in
case updatedGroup of
Group record ->
Expect.all
[ \_ -> Expect.equal record.active (Just Three)
, \_ -> Expect.equal maybeItem (Just "Item Three")
, \_ -> Expect.equal (Dict.size record.group) 2
]
()
{-| Test that items can be activated after resetting.
-}
test_activateAfterReset : Test
test_activateAfterReset =
test "Activating an item after reset" <|
\_ ->
let
modalGroup =
empty identity
|> add 1 "Item 1"
|> (\mg -> Tuple.first (active 1 mg))
|> reset
( updatedGroup, maybeItem ) =
active 1 modalGroup
in
case updatedGroup of
Group record ->
Expect.all
[ \_ -> Expect.equal record.active (Just 1)
, \_ -> Expect.equal maybeItem (Just "Item 1")
]
()
{-| Test that adding an item with a duplicate key overwrites the existing item.
-}
test_addDuplicateKeyOverwritesItem : Test
test_addDuplicateKeyOverwritesItem =
test "Add duplicate keys overwrites the item" <|
\_ ->
let
modalGroup =
empty identity
|> add 1 "Item 1"
|> add 1 "Item 1 Updated"
maybeItem =
case modalGroup of
Group record ->
Dict.get 1 record.group
in
Expect.equal maybeItem (Just "Item 1 Updated")
{-| Collect all tests into a test suite.
-}
tests : Test
tests =
describe "ModalGroup Tests"
[ test_emptyModalGroup
, test_addItemToModalGroup
, test_addMultipleItemsToModalGroup
, test_activateExistingItem
, test_activateNonExistingItem
, test_resetClearsActiveItem
, test_activateAnotherItem
, test_addItemWithCustomKeyFunction
, test_activateAfterReset
, test_addDuplicateKeyOverwritesItem
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment