packages feed

spectacle-1.0.0: test/unit-tests/Test/Laws/Lens.hs

module Test.Laws.Lens
  ( -- * Lens Laws
    laws,
    injective,
    surjective,
    idempotent,
  )
where

import Lens.Micro (Lens', set)
import Lens.Micro.Extras (view)

import Hedgehog (Gen, PropertyT, diff, forAll, property)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testProperty)

-- ---------------------------------------------------------------------------------------------------------------------

laws :: (Show a, Eq a, Show b, Eq b) => Lens' a b -> String -> Gen a -> Gen b -> TestTree
laws p desc gen1 gen0 =
  testGroup
    (if null desc then "lens laws" else desc ++ " - lens laws")
    [ testProperty "1. injectivity" $ property (injective p gen1 gen0)
    , testProperty "2. surjectivity" $ property (surjective p gen1)
    , testProperty "3. idempotency" $ property (idempotent p gen1 gen0)
    ]

-- | Setters are injective. You get back what you put in:
--
-- @
-- 'view' p ('set' p x fx) == 'set' p x fx
-- @
injective :: (Monad m, Show a, Eq b, Show b) => Lens' a b -> Gen a -> Gen b -> PropertyT m ()
injective p gen1 gen0 = do
  fx <- forAll gen1
  x <- forAll gen0
  diff (view p (set p x fx)) (==) x

-- | Setters are surjective. Putting back what you got doesn't change anything:
--
-- @
-- 'set' p ('view' p fx) fx == fx
-- @
surjective :: (Monad m, Eq a, Show a) => Lens' a b -> Gen a -> PropertyT m ()
surjective p gen1 = do
  fx <- forAll gen1
  diff (set p (view p fx) fx) (==) fx

-- | Setters are idempotent. Setting twice is the same as setting once:
--
-- @
-- 'set' p x ('set' p x fx) == 'set' p x fx
-- @
idempotent :: (Monad m, Eq a, Show a, Show b) => Lens' a b -> Gen a -> Gen b -> PropertyT m ()
idempotent p gen1 gen0 = do
  fx <- forAll gen1
  x <- forAll gen0
  diff (set p x (set p x fx)) (==) (set p x fx)