packages feed

large-anon-0.2: test/Test/Sanity/Generics.hs

{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators    #-}

{-# OPTIONS_GHC -fplugin=Data.Record.Anon.Plugin #-}
{-# OPTIONS_GHC -Wno-orphans #-} -- for the ToJSON/FromJSON instances

module Test.Sanity.Generics (tests) where

import Data.Aeson
import Data.Record.Generic

import Data.Record.Anon
import Data.Record.Anon.Advanced (Record)
import qualified Data.Record.Anon.Advanced as Anon

import Test.Tasty
import Test.Tasty.HUnit

import Test.Infra.Generics

import qualified Test.Sanity.Named.Record1 as R1
import qualified Test.Sanity.Named.Record2 as R2

-- add test with non-I functor

tests :: TestTree
tests = testGroup "Test.Sanity.Generics" [
      testCase "Show"           test_Show
    , testCase "Eq"             test_Eq
    , testCase "Ord"            test_Ord
    , testCase "describeRecord" test_describeRecord
    , testCase "JSON"           test_JSON
    ]

{-------------------------------------------------------------------------------
  Example values
-------------------------------------------------------------------------------}

type TypeRecord1 = Record I [ "x" := Bool, "y" := Char, "z" := () ]

record1 :: Record I [ "x" := Bool, "y" := Char, "z" := () ]
record1 =
      Anon.insert #x (I True)
    $ Anon.insert #y (I 'a')
    $ Anon.insert #z (I ())
    $ Anon.empty

-- | Example where the fields do not appear in alphabetical order
--
-- Ordering matters in the 'Generic' instance.
record2 :: Record I [ "y" := Char, "x" := Bool ]
record2 =
      Anon.insert #y (I 'a')
    $ Anon.insert #x (I True)
    $ Anon.empty

-- | Example that doesn't use I as the functor
record3 :: Record (K ()) [ "y" := Char, "x" := Bool ]
record3 =
      Anon.insert #y (K ())
    $ Anon.insert #x (K ())
    $ Anon.empty

{-------------------------------------------------------------------------------
  Tests proper
-------------------------------------------------------------------------------}

test_Show :: Assertion
test_Show = do
    assertEqual "R1" (show (R1.ANON_F (I True) (I 'a') (I ()))) $ show record1
    assertEqual "R2" (show (R2.ANON_F (I 'a')  (I True)))       $ show record2
    assertEqual "R3" (show (R2.ANON_F (K ())   (K ())))         $ show record3

test_Eq :: Assertion
test_Eq = do
    assertEqual "equal" True $
      record1 == record1
    assertEqual "not equal" False $
      record1 == (Anon.set #x (I False) record1)

test_Ord :: Assertion
test_Ord = do
    assertEqual "R1" (compare (R1.ANON_F (I True) (I 'a') (I ())) (R1.ANON_F (I False) (I 'a') (I ()))) $
      compare record1 (Anon.set #x (I False) record1)
    assertEqual "R2" (compare (R2.ANON_F (I 'a') (I True)) (R2.ANON_F (I 'a') (I False))) $
      compare record2 (Anon.set #x (I False) record2)

-- Test 'describeRecord'
--
-- The primary motivation for 'test_describeRecord' is actually not to test the
-- function itself, but to verify that constraint resolution is working ok.
-- Specifically, that the implicit kind argument to 'Typeable' is handled by ghc
-- and does not need to be taken into account by the @large-anon@ plugin.
test_describeRecord :: Assertion
test_describeRecord = do
    assertEqual "" expected $ describeRecord (Proxy @TypeRecord1)
  where
    expected :: String
    expected = "Record {x :: I Bool, y :: I Char, z :: I ()}"

test_JSON :: Assertion
test_JSON = do
    assertEqual "R1" (Just record1) $ decode (encode record1)
    assertEqual "R2" (Just record2) $ decode (encode record2)

{-------------------------------------------------------------------------------
  Auxiliary
-------------------------------------------------------------------------------}

instance FromJSON a => FromJSON (I a) where
  parseJSON = fmap I . parseJSON

instance ToJSON a => ToJSON (I a) where
  toJSON = toJSON . unI