{-# LANGUAGE ScopedTypeVariables #-} module Main (main) where import Test.QuickCheck.Gen as Gen import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck as QC import Test.Tasty.QuickCheck.Laws import Control.Applicative import Data.Aeson import Data.Aeson.Possible import Data.Typeable import GHC.Generics (Generic) main :: IO () main = defaultMain tests tests :: TestTree tests = testGroup "Tests" [laws, unitTests] newtype A_Possible a = A_Possible {unwrap :: Possible a} deriving stock (Show, Generic, Functor) deriving newtype (Eq, Applicative, Alternative) instance (Arbitrary a) => Arbitrary (A_Possible a) where arbitrary = A_Possible <$> Gen.oneof [pure Missing, pure HaveNull, HaveData <$> arbitrary] shrink (A_Possible v) = A_Possible <$> genericShrink v laws :: TestTree laws = testGroup "Laws" [ testEqLaws (Proxy :: Proxy (A_Possible Bool)) , testFunctorLaws (Proxy :: Proxy (A_Possible)) (Proxy :: Proxy Int) (Proxy :: Proxy Int) (Proxy :: Proxy Int) (Proxy :: Proxy Int) (\_ fu1 fu2 -> fu1 == fu2) , testApplicativeLaws (Proxy :: Proxy (A_Possible)) (Proxy :: Proxy Int) (Proxy :: Proxy Int) (Proxy :: Proxy Int) (Proxy :: Proxy Int) (\_ fu1 fu2 -> fu1 == fu2) , testGroup "Alternative is as documented" -- Should not need testing as it is quite simple, but just to be sure -- and guard against regressions [ QC.testProperty "PATCH new data" $ \(new :: Int) (old :: A_Possible Int) -> let newP = A_Possible (HaveData new) in (newP <|> old) === newP , QC.testProperty "PATCH sets null" $ \(old :: A_Possible Bool) -> let aNull = A_Possible HaveNull in (aNull <|> old) === aNull , QC.testProperty "PATCH did not change data" $ \(old :: A_Possible Bool) -> (A_Possible Missing <|> old) === old ] ] data TestData = TestData { a :: Possible Bool , b :: Possible Bool , c :: Possible Bool } deriving stock (Show, Generic, Eq) instance FromJSON TestData where parseJSON = genericParseJSON $ defaultOptions{omitNothingFields = True} instance ToJSON TestData where toJSON = genericToJSON $ defaultOptions{omitNothingFields = True} unitTests :: TestTree unitTests = testGroup "Unit Tests" [ let objJson = "{\"b\":null,\"c\":true}" objValue = TestData{a = Missing, b = HaveNull, c = HaveData True} in testGroup "Object encoding and decoding" [ testCase "Decoding works as expected" $ eitherDecode objJson @?= Right objValue , testCase "Encoding works as expected" $ encode objValue @?= objJson ] , -- Encoding in non-objects leads to "null" as it must encode to something! -- Encoding other constructors are covered by object tests testGroup "Encoding not in an object" [ testCase "Bare `Missing`" $ encode (Missing :: Possible Int) @?= "null" , testCase "List of Possible values" $ encode ([HaveNull, Missing, HaveData True]) @?= "[null,null,true]" ] , testGroup "Bare decoding" [ testCase "Decode null" $ eitherDecode "null" @?= Right (HaveNull :: Possible Bool) , testCase "Decode value" $ eitherDecode "true" @?= Right (HaveData True) ] ]