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.

Revisions

  1. rupertlssmith created this gist Sep 17, 2024.
    68 changes: 68 additions & 0 deletions ModalGroup.elm
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,68 @@
    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 )
    256 changes: 256 additions & 0 deletions ModalGroupTest.elm
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,256 @@
    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
    ]