packages feed

skeletest-0.3.4: src/Skeletest/Plugin.hs

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE NoFieldSelectors #-}

module Skeletest.Plugin (
  -- * Plugin
  Plugin (..),
  defaultPlugin,

  -- * Hooks
  Hooks (..),
  defaultHooks,

  -- * Re-exports

  -- ** TestResult
  X.TestResult (..),
  X.TestResultMessage (..),
  X.BoxSpec,
  X.BoxSpecContent (..),

  -- ** TestInfo
  X.TestInfo (..),

  -- ** Markers
  X.findMarker,
  X.hasMarker,
  X.hasMarkerNamed,

  -- ** SpecRegistry
  X.SpecRegistry,
  X.Spec,
  X.SpecInfo (..),
  X.SpecTree (..),
  X.SpecTest (..),
  X.getSpecTrees,
  X.withSpecTrees,
  X.mapSpecTrees,
  X.traverseSpecTrees,
  X.mapSpecTests,
  X.traverseSpecTests,
  X.filterSpecTests,
  X.mapSpecs,
  X.traverseSpecs,
) where

import Skeletest.Internal.CLI (Flag)
import Skeletest.Internal.Markers qualified as X
import Skeletest.Internal.Snapshot (SnapshotRenderer)
import Skeletest.Internal.Spec.Output qualified as X
import Skeletest.Internal.Spec.Tree (SpecRegistry)
import Skeletest.Internal.Spec.Tree qualified as X
import Skeletest.Internal.TestInfo (TestInfo (..))
import Skeletest.Internal.TestInfo qualified as X
import Skeletest.Internal.TestRunner (TestResult (..))
import Skeletest.Internal.TestRunner qualified as X
import Skeletest.Internal.TestTargets (TestTargets)

-- | A plugin for extending Skeletest.
--
-- Use 'defaultPlugin' instead of using v'Plugin' directly, to minimize
-- breaking changes.
data Plugin = Plugin
  { cliFlags :: [Flag]
  , snapshotRenderers :: [SnapshotRenderer]
  , hooks :: Hooks
  }

instance Semigroup Plugin where
  plugin1 <> plugin2 =
    Plugin
      { cliFlags = plugin1.cliFlags <> plugin2.cliFlags
      , snapshotRenderers = plugin1.snapshotRenderers <> plugin2.snapshotRenderers
      , hooks = plugin1.hooks <> plugin2.hooks
      }

instance Monoid Plugin where
  mempty = defaultPlugin

defaultPlugin :: Plugin
defaultPlugin =
  Plugin
    { cliFlags = []
    , snapshotRenderers = []
    , hooks = defaultHooks
    }

-- | Hooks for extending Skeletest.
--
-- Use 'defaultHooks' instead of using v'Hooks' directly, to minimize
-- breaking changes.
data Hooks = Hooks
  { modifySpecRegistry :: TestTargets -> (SpecRegistry -> IO SpecRegistry) -> (SpecRegistry -> IO SpecRegistry)
  -- ^ Modify all the specs in the test suite, being able to modify before/after
  -- previously registered hooks.
  --
  -- For example:
  -- @
  -- \_ modify -> pre >=> modify >=> post
  -- @
  --
  -- @since 0.3.4
  , runTest :: TestInfo -> IO TestResult -> IO TestResult
  -- ^ Modify how a test is executed
  }

instance Semigroup Hooks where
  hooks1 <> hooks2 =
    Hooks
      { modifySpecRegistry = \targets -> hooks2.modifySpecRegistry targets . hooks1.modifySpecRegistry targets
      , runTest = \testInfo -> hooks2.runTest testInfo . hooks1.runTest testInfo
      }

instance Monoid Hooks where
  mempty = defaultHooks

defaultHooks :: Hooks
defaultHooks =
  Hooks
    { modifySpecRegistry = \_ -> id
    , runTest = \_ -> id
    }