packages feed

monoidmap-0.0.1.2: src/test/Data/MonoidMap/MapSpec.hs

{- HLINT ignore "Redundant bracket" -}
{- HLINT ignore "Use camelCase" -}
{- HLINT ignore "Use null" -}

-- |
-- Copyright: © 2022–2024 Jonathan Knowles
-- License: Apache-2.0
--
module Data.MonoidMap.MapSpec
    ( spec
    ) where

import Prelude

import Control.Monad
    ( forM_ )
import Data.Bifunctor
    ( first, second )
import Data.Function
    ( (&) )
import Data.Monoid.Null
    ( MonoidNull )
import Data.MonoidMap
    ( MonoidMap, nonNullCount )
import Data.Proxy
    ( Proxy (..) )
import Test.Common
    ( Key, Test, TestType (TestType), makeSpec, property, testTypesMonoidNull )
import Test.Hspec
    ( Spec, describe, it )
import Test.QuickCheck
    ( Fun (..), Property, applyFun, applyFun2, cover, expectFailure, (===) )

import qualified Data.Foldable as F
import qualified Data.Monoid.Null as Null
import qualified Data.MonoidMap as MonoidMap
import qualified Data.Set as Set

spec :: Spec
spec = describe "Mapping" $ do

    forM_ testTypesMonoidNull $ \(TestType p) -> specFor (Proxy @Key) p

specFor :: forall k v. Test k v => Proxy k -> Proxy v -> Spec
specFor = makeSpec $ do

    it "prop_map_asList" $
        prop_map_asList
            @k @v & property
    it "prop_map_composition" $
        prop_map_composition
            @k @v & property
    it "prop_map_composition_failure" $
        prop_map_composition_failure
            @k @v & property
    it "prop_map_get" $
        prop_map_get
            @k @v & property
    it "prop_map_get_total" $
        prop_map_get_total
            @k @v & property
    it "prop_map_get_total_failure" $
        prop_map_get_total_failure
            @k @v & property
    it "prop_mapKeys_asList" $
        prop_mapKeys_asList
            @k @v & property
    it "prop_mapKeys_get" $
        prop_mapKeys_get
            @k @v & property
    it "prop_mapKeysWith_asList" $
        prop_mapKeysWith_asList
            @k @v & property

--------------------------------------------------------------------------------
-- Mapping
--------------------------------------------------------------------------------

prop_map_asList
    :: Test k v
    => Fun v v
    -> MonoidMap k v
    -> Property
prop_map_asList (applyFun -> f) m =
    n === (MonoidMap.fromList . fmap (second f) . MonoidMap.toList $ m)
    & cover 2
        (0 < nonNullCount n && nonNullCount n < nonNullCount m)
        "0 < nonNullCount n && nonNullCount n < nonNullCount m"
  where
    n = MonoidMap.map f m

prop_map_composition
    :: forall k v. Test k v
    => Fun v v
    -> Fun v v
    -> MonoidMap k v
    -> Property
prop_map_composition (applyFun -> f0) (applyFun -> g0) m =
    MonoidMap.map (f . g) m === MonoidMap.map f (MonoidMap.map g m)
    & cover 2
        (MonoidMap.nonNull m)
        "MonoidMap.nonNull m"
  where
    f = toNullPreservingFn f0
    g = g0

prop_map_composition_failure
    :: forall k v. Test k v
    => Fun v v
    -> Fun v v
    -> MonoidMap k v
    -> Property
prop_map_composition_failure (applyFun -> f) (applyFun -> g) m =
    expectFailure $
    MonoidMap.map (f . g) m === MonoidMap.map f (MonoidMap.map g m)
    & cover 1
        (MonoidMap.map (f . g) m /= MonoidMap.map f (MonoidMap.map g m))
        "MonoidMap.map (f . g) m /= MonoidMap.map f (MonoidMap.map g m)"

prop_map_get
    :: Test k v
    => Fun v v
    -> k
    -> MonoidMap k v
    -> Property
prop_map_get (applyFun -> f) k m =
    MonoidMap.get k (MonoidMap.map f m)
    ===
    (if MonoidMap.nullKey k m then mempty else f (MonoidMap.get k m))
    & cover 2
        (MonoidMap.nullKey k m)
        "MonoidMap.nullKey k m"
    & cover 2
        (MonoidMap.nonNullKey k m)
        "MonoidMap.nonNullKey k m"

prop_map_get_total
    :: forall k v. Test k v
    => Fun v v
    -> k
    -> MonoidMap k v
    -> Property
prop_map_get_total (applyFun -> f0) k m =
    MonoidMap.get k (MonoidMap.map f m) === f (MonoidMap.get k m)
    & cover 2
        (MonoidMap.nullKey k m)
        "MonoidMap.nullKey k m"
    & cover 2
        (MonoidMap.nonNullKey k m)
        "MonoidMap.nonNullKey k m"
  where
    f = toNullPreservingFn f0

prop_map_get_total_failure
    :: Test k v
    => Fun v v
    -> k
    -> MonoidMap k v
    -> Property
prop_map_get_total_failure (applyFun -> f) k m =
    expectFailure $
    MonoidMap.get k (MonoidMap.map f m) === f (MonoidMap.get k m)

prop_mapKeys_asList
    :: Test k v
    => Fun k k
    -> MonoidMap k v
    -> Property
prop_mapKeys_asList (applyFun -> f) m =
    n === (MonoidMap.fromList . fmap (first f) . MonoidMap.toList $ m)
    & cover 2
        (0 < nonNullCount n && nonNullCount n < nonNullCount m)
        "0 < nonNullCount n && nonNullCount n < nonNullCount m"
  where
    n = MonoidMap.mapKeys f m

prop_mapKeys_get
    :: Test k v
    => Fun k k
    -> k
    -> MonoidMap k v
    -> Property
prop_mapKeys_get (applyFun -> f) k m =
    MonoidMap.get k (MonoidMap.mapKeys f m)
        ===
        F.foldMap
            (`MonoidMap.get` m)
            (Set.filter ((==) k . f) (MonoidMap.nonNullKeys m))
    & cover 2
        (MonoidMap.nullKey k (MonoidMap.mapKeys f m))
        "MonoidMap.nullKey k (MonoidMap.mapKeys f m)"
    & cover 2
        (MonoidMap.nonNullKey k (MonoidMap.mapKeys f m))
        "MonoidMap.nonNullKey k (MonoidMap.mapKeys f m)"

prop_mapKeysWith_asList
    :: Test k v
    => Fun (v, v) v
    -> Fun k k
    -> MonoidMap k v
    -> Property
prop_mapKeysWith_asList (applyFun2 -> c) (applyFun -> f) m =
    n === (MonoidMap.fromListWith c . fmap (first f) . MonoidMap.toList $ m)
    & cover 2
        (0 < nonNullCount n && nonNullCount n < nonNullCount m)
        "0 < nonNullCount n && nonNullCount n < nonNullCount m"
  where
    n = MonoidMap.mapKeysWith c f m

--------------------------------------------------------------------------------
-- Utilities
--------------------------------------------------------------------------------

-- | Creates a function that never maps null values to non-null values.
--
toNullPreservingFn :: MonoidNull v => (v -> v) -> (v -> v)
toNullPreservingFn f v
    | Null.null v = v
    | otherwise = f v