packages feed

interpolator-1.1.0: test/Data/Interpolation/THSpec.hs

module Data.Interpolation.THSpec where

import Prelude

import Control.Monad.Reader (runReader)
import Data.Containers (mapFromList)
import Data.Either.Validation (validationToEither)
import Data.Profunctor.Product.Default (def)
import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
import Data.Text (Text, toUpper)
import Test.Hspec (Spec, describe, it, shouldBe)

-- the modules being tested
import Data.Interpolation
import Data.Interpolation.TH

-- |A simple newtype with a derived instance for 'FromTemplateValue'; that instance will be in scope
-- when the type is referred to later in this file.
newtype BarName = BarName { unBarName :: Text }
  deriving (Eq, Ord, Show, FromTemplateValue, ToTemplateValue)

-- |A simple non-interpolatable type (no 'FromTemplateValue' instance.)
data Quux
  = QuuxFuzzy
  | QuuxSmooth
  deriving (Eq, Ord, Show)

withUninterpolated [d|
  data Bar = Bar
    { barA :: BarName
      -- ^Because BarName has 'FromTemplateValue', it will be 'Uninterpolated BarName' in the generated type.
    , barB :: Maybe Int
      -- ^No instance for 'Maybe Int', so this becomes 'Maybe (Uninterpolated Int)'.
    , barC :: [Bool]
      -- ^Similarly, this becomes '[Uninterpolated Bool]'.
    , barD :: Quux
      -- ^No 'FromTemplateValue' instance at all, so this is left as just 'Quux'.
    } deriving (Eq, Ord, Show)
  |]

-- |A type that will have its FromTemplateValue instance defined manually. For demonstration
-- purposes, this is just a string that is upper-cased when it is interpolated from a template.
newtype Upcased = Upcased { unUpcased :: Text }
  deriving (Eq, Show)

-- |Note: want the uninterpolated type for this sum to account for the late-defined
-- `FromTemplateValue Upcased` instance, so derive only the polymorphic type now:
withPolymorphic [d|
  data Foo
    = FooBar Bar
    | FooInt Int
    | FooBool Bool
    | FooUpcased Upcased
    | FooNone
    deriving (Eq, Ord, Show)
  |]

makeAdaptorAndInstance "pBar" ''Bar'
makeInterpolatorSumInstance ''Foo'

instance FromTemplateValue Upcased where
  parseTemplateValue = Just . Upcased . toUpper . unTemplateValue

-- Finally, derive the uninterpolated type for Foo, with the instance for Upcased defined.
deriveUninterpolated ''Foo

-- --
-- -- Another newtype, this time with a custom FromTemplateValue instance which is defined after the
-- -- type, and a record type that uses it.
-- --

-- newtype BazName = BazName { unBazName :: Text }
--   deriving (Eq, Ord, Show)

-- withPolymorphic [d|
--   data Baz = Baz
--     { bazName :: BazName
--     } deriving (Eq, Ord, Show)
--   |]

key1, key2, key3, key4, key5 :: TemplateKey
key1 = TemplateKey "key1"
key2 = TemplateKey "key2"
key3 = TemplateKey "key3"
key4 = TemplateKey "key4"
key5 = TemplateKey "key5"

run :: UninterpolatedFoo -> Either [InterpolationFailure] Foo
run = validationToEither . flip runReader defaultContext . runInterpolator fooInterpolator
  where
    fooInterpolator :: Interpolator UninterpolatedFoo Foo
    fooInterpolator = def

    defaultContext :: InterpolationContext
    defaultContext = InterpolationContext . mapFromList $
      [ (key1, TemplateValue "asdf")
      , (key2, TemplateValue "1")
      , (key3, TemplateValue "2")
      , (key4, TemplateValue "true")
      , (key5, TemplateValue "Wow")
      ]

spec :: Spec
spec = describe "Shared.Interpolation.THSpec" $ do
  it "if it compiles, it worked" True

  it "interpolates over all branches" $ do
    run (FooBar (Bar (Templated $ Template key1 Nothing) (Just . Templated $ Template key2 Nothing) [] QuuxFuzzy))
      `shouldBe` Right (FooBar $ Bar (BarName "asdf") (Just 1) [] QuuxFuzzy)
    run (FooInt (Templated $ Template key3 Nothing)) `shouldBe` Right (FooInt 2)
    run (FooBool (Templated $ Template key4 Nothing)) `shouldBe` Right (FooBool True)
    run (FooUpcased (Templated $ Template key5 Nothing)) `shouldBe` Right (FooUpcased (Upcased "WOW"))
    run FooNone `shouldBe` Right FooNone