{-# OPTIONS_GHC -fno-warn-deprecations #-}
-- | Stability: unstable
module Test.Hspec.Core.Extension {-# WARNING "This API is experimental." #-} (
-- * Lifecycle of a test run
{- |
A test run goes through four distinct phases:

1. A tree of spec items and a default `Config` are constructed in `SpecM`.
2. Command-line options are parsed.  This transforms the default `Config` that was constructed in phase 1.
3. The tree of spec items is transformed in various ways, depending on specific `Config` values.
4. Each spec item is executed and its result is reported to the user.

An extension can directly influence phase 1, and indirectly influence phases 2-4.

When writing extensions the following imports are recommended:

@
import "Test.Hspec.Core.Extension"
import "Test.Hspec.Core.Extension.Config" qualified as Config
import "Test.Hspec.Core.Extension.Option" qualified as Option
import "Test.Hspec.Core.Extension.Item" qualified as Item
import "Test.Hspec.Core.Extension.Spec" qualified as Spec
import "Test.Hspec.Core.Extension.Tree" qualified as Tree
@
-}

-- ** Phase 1: Constructing a spec tree
{- |
- An extension can use @Spec.`Test.Hspec.Core.Extension.Spec.mapItems`@ to transform spec items in phase 1.

    * @Item.`setAnnotation`@ can be used to add custom metadata to a spec `Item`.

- An extension can use `modifyConfig` to modify the default config.

    * @Config.`Test.Hspec.Core.Extension.Config.setAnnotation`@ can be used to add custom metadata to the default `Config`.
-}
  runIO
, modifyConfig

-- ** Phase 2: Parsing command-line options
{- |
An extension can use `registerOptions` during phase 1 to register custom command-line options, and as a consequence indirectly influence this phase.

* Options can use @Config.`Test.Hspec.Core.Extension.Config.setAnnotation`@ to add custom metadata to the `Config`.
-}
, Option
, registerOptions

-- ** Phase 3: Transforming the spec tree
{- |
An extension can use `registerTransformation` during phase 1 to indirectly influence this phase.
-}
, registerTransformation

-- ** Phase 4: Reporting results
{- |
An extension can register a [custom formatter](https://hspec.github.io/extending-hspec-formatter.html) in phase 1 to indirectly influence this phase.
-}

-- * Types
{- |
Operations on these types are provided by the following modules respectively:

* "Test.Hspec.Core.Extension.Config"
* "Test.Hspec.Core.Extension.Item"
* "Test.Hspec.Core.Extension.Spec"
* "Test.Hspec.Core.Extension.Tree"
-}
, Config
, Item
, SpecM
, SpecWith
, SpecTree
) where

import           Prelude ()
import           Test.Hspec.Core.Compat

import qualified Data.CallStack as CallStack

import qualified GetOpt.Declarative as Declarative
import           Test.Hspec.Core.Spec (SpecM, SpecWith, runIO, modifyConfig)
import qualified Test.Hspec.Core.Spec as Core
import qualified Test.Hspec.Core.Config.Definition as Core

import           Test.Hspec.Core.Extension.Config (Config)
import qualified Test.Hspec.Core.Extension.Config.Type as Config
import           Test.Hspec.Core.Extension.Option
import           Test.Hspec.Core.Extension.Item
import           Test.Hspec.Core.Extension.Tree

registerOptions :: HasCallStack => [Option] -> SpecWith a
registerOptions = Core.modifyConfig . Core.addExtensionOptions section . map liftOption
  where
    section = "OPTIONS FOR " <> package
    package = maybe "main" (CallStack.srcLocPackage . snd) CallStack.callSite

    liftOption :: Option -> Declarative.Option Core.Config
    liftOption = Config.unOption

{- |
Register a transformation that transforms the spec tree in phase 3.

The registered transformation can use:

- @Tree.`mapItems`@ to modify spec items
- @Tree.`filterItems`@ to discard spec items
- @Config.`Test.Hspec.Core.Extension.Config.getAnnotation`@ to access custom config metadata
- @Item.`getAnnotation`@ to access custom item metadata
-}
registerTransformation :: (Config -> [SpecTree] -> [SpecTree]) -> SpecWith a
registerTransformation = modifyConfig . Config.addSpecTransformation
