{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Tests for Shrinking functions
--
-- You will need @TypeApplications@ to use these.
module Test.Validity.Shrinking
    ( shrinkValiditySpec
    , shrinkValidSpec
    , shrinkValidSpecWithLimit
    , shrinkInvalidSpec
    , shrinkValidPreservesValidOnGenValid
    , shrinkValidPreservesValidOnGenValidWithLimit
    , shrinkInvalidPreservesInvalidOnGenInvalid
    , shrinkPreservesValidOnGenValid
    , shrinkPreservesInvalidOnGenInvalid
    , shrinkValidPreservesValid
    , shrinkInvalidPreservesInvalid
    , shrinkingStaysValid
    , shrinkingStaysInvalid
    , shrinkingPreserves
    , shrinkUncheckedDoesNotShrinkToItself
    , shrinkUncheckedDoesNotShrinkToItselfWithLimit
    , shrinkValidDoesNotShrinkToItself
    , shrinkValidDoesNotShrinkToItselfWithLimit
    , shrinkInvalidDoesNotShrinkToItself
    , shrinkInvalidDoesNotShrinkToItselfWithLimit
    ) where

import Data.Data

import Data.GenValidity

import Control.Monad

import Test.Hspec
import Test.QuickCheck

import Test.Validity.Shrinking.Property
import Test.Validity.Utils

shrinkValiditySpec ::
       forall a. (Show a, Eq a, Typeable a, GenValid a, GenInvalid a)
    => Spec
shrinkValiditySpec = do
    shrinkValidSpec @a
    shrinkInvalidSpec @a

shrinkValidSpec ::
       forall a. (Show a, Eq a, Typeable a, GenValid a)
    => Spec
shrinkValidSpec =
    describe ("shrinkValid :: " ++ nameOf @(a -> [a])) $ do
        it "preserves validity" $
            forAll (genValid @a) $ \a -> forM_ (shrinkValid a) shouldBeValid
        it "never shrinks to itself for valid values" $
            shrinkValidDoesNotShrinkToItself @a

shrinkValidSpecWithLimit ::
       forall a. (Show a, Eq a, Typeable a, GenUnchecked a, GenValid a)
    => Int
    -> Spec
shrinkValidSpecWithLimit l =
    describe ("shrinkValid :: " ++ nameOf @(a -> [a])) $ do
        it (unwords ["preserves validity for the first", show l, "elements"]) $
            forAll (genValid @a) $ \a ->
                forM_ (take l $ shrinkValid a) shouldBeValid
        it
            (unwords
                 [ "never shrinks to itself for valid values for the first"
                 , show l
                 , "elements"
                 ]) $
            shrinkValidDoesNotShrinkToItselfWithLimit @a l

shrinkInvalidSpec ::
       forall a. (Show a, Typeable a, GenInvalid a)
    => Spec
shrinkInvalidSpec =
    describe ("shrinkInvalid :: " ++ nameOf @(a -> [a])) $ do
        it "preserves invalidity" $
            forAll (genInvalid @a) $ \a ->
                forM_ (shrinkInvalid a) shouldBeInvalid

shrinkValidPreservesValidOnGenValid ::
       forall a. (Show a, GenValid a)
    => Property
shrinkValidPreservesValidOnGenValid =
    shrinkingStaysValid @a genValid shrinkValid

shrinkValidPreservesValidOnGenValidWithLimit ::
       forall a. (Show a, GenValid a)
    => Int
    -> Property
shrinkValidPreservesValidOnGenValidWithLimit =
    shrinkingStaysValidWithLimit @a genValid shrinkValid

shrinkInvalidPreservesInvalidOnGenInvalid ::
       forall a. (Show a, GenInvalid a)
    => Property
shrinkInvalidPreservesInvalidOnGenInvalid =
    shrinkingStaysInvalid @a genInvalid shrinkInvalid

shrinkUncheckedDoesNotShrinkToItself ::
       forall a. (Show a, Eq a, GenUnchecked a)
    => Property
shrinkUncheckedDoesNotShrinkToItself =
    shrinkDoesNotShrinkToItself @a shrinkUnchecked

shrinkValidDoesNotShrinkToItself ::
       forall a. (Show a, Eq a, GenValid a)
    => Property
shrinkValidDoesNotShrinkToItself =
    shrinkDoesNotShrinkToItselfOnValid @a shrinkValid

shrinkInvalidDoesNotShrinkToItself ::
       forall a. (Show a, Eq a, GenInvalid a)
    => Property
shrinkInvalidDoesNotShrinkToItself =
    shrinkDoesNotShrinkToItselfOnInvalid @a shrinkInvalid

shrinkInvalidDoesNotShrinkToItselfWithLimit ::
       forall a. (Show a, Eq a, GenUnchecked a, GenInvalid a)
    => Int
    -> Property
shrinkInvalidDoesNotShrinkToItselfWithLimit =
    shrinkDoesNotShrinkToItselfWithLimit @a shrinkInvalid

shrinkValidDoesNotShrinkToItselfWithLimit ::
       forall a. (Show a, Eq a, GenUnchecked a, GenValid a)
    => Int
    -> Property
shrinkValidDoesNotShrinkToItselfWithLimit =
    shrinkDoesNotShrinkToItselfWithLimit @a shrinkValid

shrinkUncheckedDoesNotShrinkToItselfWithLimit ::
       forall a. (Show a, Eq a, GenUnchecked a)
    => Int
    -> Property
shrinkUncheckedDoesNotShrinkToItselfWithLimit =
    shrinkDoesNotShrinkToItselfWithLimit @a shrinkUnchecked
