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)