packages feed

generic-diff-0.1.0.0: test/Generics/Diff/UnitTestsSpec.hs

{-# OPTIONS_GHC -Wno-partial-fields #-}

module Generics.Diff.UnitTestsSpec where

import Data.Foldable
import Data.SOP
import qualified Data.Text as T
import qualified GHC.Generics as G
import Generics.Diff
import Generics.Diff.Instances ()
import Generics.SOP
import Generics.SOP.Arbitrary (garbitrary)
import qualified Test.Hspec as H
import qualified Test.Hspec.QuickCheck as H
import Test.QuickCheck as Q
import Util

spec :: H.Spec
spec =
  H.describe "Unit tests using hand-rolled datatypes" $
    traverse_ specTestSet testSets

specTestSet :: (Diff a, Show a) => TestSet a -> H.Spec
specTestSet TestSet {..} =
  H.prop (T.unpack setName) $
    propDiffResult leftValue rightValue expectedDiffResult

------------------------------------------------------------
-- test type

data CustomType
  = -- | Normal constructor
    Con1 (Either Int String) (Maybe Char)
  | -- | Record constructor
    Con2 {c11 :: Maybe String, c12 :: Bool}
  | -- | Infix constructor
    (Char, Int, ()) `Con3` [Maybe Int]
  | -- | Recursive
    Con4 CustomType CustomType
  deriving (Show, Eq, G.Generic)

instance Generic CustomType

instance HasDatatypeInfo CustomType

instance Diff CustomType

instance Q.Arbitrary CustomType where
  arbitrary = garbitrary
  shrink = Q.genericShrink

-- bunch of 'ConstructorInfo's, for convenience

c1Info :: ConstructorInfo '[Either Int String, Maybe Char]
c2Info :: ConstructorInfo '[Maybe String, Bool]
c3Info :: ConstructorInfo '[(Char, Int, ()), [Maybe Int]]
c4Info :: ConstructorInfo '[CustomType, CustomType]
c1Info :* c2Info :* c3Info :* c4Info :* _ = constructorInfo $ datatypeInfo $ Proxy @CustomType

nothingInfo :: ConstructorInfo '[]
justInfo :: forall a. ConstructorInfo '[a]
nothingInfo :* justInfo :* _ = constructorInfo $ datatypeInfo (Proxy :: Proxy (Maybe a))

data TestSet a = TestSet
  { setName :: T.Text
  , leftValue :: a
  , rightValue :: a
  , expectedDiffResult :: DiffResult a
  }
  deriving (Show)

-- | some pairs of 'CustomType's, with the 'DiffResult' we expect to get for each of them using 'gdiff'.
testSets :: [TestSet CustomType]
testSets =
  [ TestSet
      { setName = "Equal - Con1"
      , leftValue = value1
      , rightValue = value1
      , expectedDiffResult = Equal
      }
  , TestSet
      { setName = "Diff, WrongConstructor, Con3 and Con1"
      , leftValue = leftValue2
      , rightValue = rightValue2
      , expectedDiffResult = Error error2
      }
  , TestSet
      { setName = "Diff, WrongConstructor, Con3 and Con1"
      , leftValue = ('a', 5, ()) `Con3` [Just 1]
      , rightValue = Con1 (Left 0) (Just 'a')
      , expectedDiffResult = Error (Nested $ WrongConstructor (S (S (Z c3Info))) (Z c1Info))
      }
  , TestSet
      { setName = "Diff, FieldMismatch, normal Constructor, nested"
      , leftValue = Con1 (Left 0) Nothing
      , rightValue = Con1 (Left 0) (Just 'a')
      , expectedDiffResult = Error (Nested $ FieldMismatch (DiffAtField (Z (c1Info :*: S (Z $ Nested (WrongConstructor (Z nothingInfo) (S (Z justInfo))))))))
      }
  , TestSet
      { setName = "Diff, FieldMismatch, Infix constructor, right side, nested"
      , leftValue = ('a', 5, ()) `Con3` [Just 1]
      , rightValue = ('a', 5, ()) `Con3` [Nothing, Just 1]
      , expectedDiffResult = Error (Nested $ FieldMismatch (DiffAtField (S (S (Z (c3Info :*: S (Z $ DiffSpecial (DiffAtIndex 0 (Nested (WrongConstructor (S (Z justInfo)) (Z nothingInfo)))))))))))
      }
  , TestSet
      { setName = "Diff, FieldMismatch, Infix constructor, left side, nested"
      , leftValue = ('a', 4, ()) `Con3` [Just 1]
      , rightValue = ('a', 5, ()) `Con3` [Nothing, Just 1]
      , expectedDiffResult = Error (Nested $ FieldMismatch (DiffAtField (S (S (Z (c3Info :*: Z (Nested $ FieldMismatch $ DiffAtField $ Z (Constructor "(,,)" :*: S (Z TopLevelNotEqual)))))))))
      }
  , TestSet
      { setName = "Diff, FieldMismatch, recursive"
      , leftValue = Con4 value1 leftValue2
      , rightValue = Con4 value1 rightValue2
      , expectedDiffResult = Error (Nested $ FieldMismatch (DiffAtField (S (S (S (Z (c4Info :*: S (Z error2))))))))
      }
  ]
  where
    value1 = Con1 (Left 0) Nothing

    leftValue2 = Con1 (Left 0) Nothing
    rightValue2 = Con2 Nothing False
    error2 = Nested $ WrongConstructor (Z c1Info) (S (Z c2Info))

-- | The expected 'DiffResult's of 'testSets'
expectedGDiffResults :: [DiffResult CustomType]
expectedGDiffResults = expectedDiffResult <$> testSets

-- | The actual 'DiffResult's of 'testSets' - from calling 'gdiff'.
actualGDiffResults :: [DiffResult CustomType]
actualGDiffResults = [gdiff leftValue rightValue | TestSet {..} <- testSets]