0

For our Hakyll codebase, I've written a few helper methods and have started adding some HSpec unit tests around newer ones e.g.:

-- | Reject an item unless @fieldName@ is set to "true"
unlessEnabled :: MonadMetadata m
              => String
              -> Item a
              -> m Bool
unlessEnabled fieldName item = do
    maybeValue <- getMetadataBool (itemIdentifier item) fieldName
    return $ maybe True not maybeValue

-- | Try to look up a boolean field ("true" maps to @Just True@)
getMetadataBool :: MonadMetadata m
                => Identifier
                -> String
                -> m (Maybe Bool)
getMetadataBool ident name = do
    maybeString <- getMetadataField ident name
    return $ ((== "true") . map toLower) <$> maybeString

Now creating an Item or Identifier for testing is easy enough, but I'm not sure where to go with the MonadMetadata when running an Hspec.

I've seen testCompiler which feels like it might be copyable / useful (Compiler has a MonadMetadata instance), but I'm out of my Haskell-depth here...

4

1 回答 1

1

Eventually (and thanks to @Bergi's suggestions) I got something working, and then tidied up:

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

type KeyedValuesOf a = [(String, a)]

-- | Create Metadata from basic list of key-values -- stolen from Hakyll itself.
meta :: Yaml.ToJSON a => KeyedValuesOf a -> Metadata
meta pairs = HMS.fromList [(T.pack k, Yaml.toJSON v) | (k, v) <- pairs]

-- | A 'Reader' class of our own, with a specialised environment
--   for key-value metadata pairs.
newtype MonadMetadataReader a =
    MonadMetadataReader {runMonadMetadataReader :: KeyedValuesOf String -> a}
        deriving (Functor, Applicative, Monad)

-- | Simple 'Metadata' holder.
-- There are never any matches found,
-- and all metadata (for all items)
-- comes from from the Reader-like environment at setup.
instance MonadMetadata MonadMetadataReader where
    getMetadata identifier = MonadMetadataReader meta

    getMatches pattern = return []

Which allowed simple unit tests like:

it "enables for a value of \"false\"" $ do
    let enabled = runMonadMetadataReader (unlessEnabled "key" item) [("key", "false")]
    enabled `shouldBe` True

it "disables for a value of \"true\"" $ do
    let enabled = runMonadMetadataReader (unlessEnabled "key" item) [("key", "true")]
    enabled `shouldBe` False

it "enables if no metadata found" $ do
    let enabled = runMonadMetadataReader (unlessEnabled "key" item) []
    enabled `shouldBe` True
于 2018-11-04T18:10:42.690 回答