packages feed

hspec-effectful 1.0.1 → 1.1.0

raw patch · 3 files changed

+86/−86 lines, 3 filesdep +QuickCheckPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck

API changes (from Hackage documentation)

- Effectful.Hspec: type HasCallStack = ?callStack :: CallStack
- Effectful.Hspec: type Spec = SpecWith ()
+ Effectful.Hspec: fprop :: forall prop (es :: [Effect]). (HasCallStack, Testable prop, Hspec :> es) => String -> prop -> Eff es ()
+ Effectful.Hspec: prop :: forall prop (es :: [Effect]). (HasCallStack, Testable prop, Hspec :> es) => String -> prop -> Eff es ()
+ Effectful.Hspec: runHUnit :: forall (es :: [Effect]) a. IOE :> es => Eff (HUnit ': es) a -> Eff es a
+ Effectful.Hspec: xprop :: forall prop (es :: [Effect]). (HasCallStack, Testable prop, Hspec :> es) => String -> prop -> Eff es ()

Files

CHANGELOG.md view
@@ -5,6 +5,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/). +## [1.1.0] - 2026-07-21++### Added++- `prop`, `fprop`, `xprop` combinators+- `runHUnit` re-export++### Removed++- `HasCallStack` and `Spec` re-exports+ ## [1.0.1] - 2026-07-13  ### Added
hspec-effectful.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: hspec-effectful-version: 1.0.1+version: 1.1.0 synopsis: Effectful driver for Hspec category: Test description:@@ -61,6 +61,7 @@   import: common   hs-source-dirs: src   build-depends:+    QuickCheck >=2.12 && <3,     hspec >=2.11 && <2.12,     hspec-core >=2.11 && <2.12,     hspec-expectations >=0.8 && <0.9,
src/Effectful/Hspec.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE Trustworthy #-} {-# OPTIONS_GHC -Wno-missing-role-annotations #-} {-# OPTIONS_GHC -Wno-orphans #-}+{-# OPTIONS_GHC -Wno-term-variable-capture #-}  -- | -- Module      : Effectful.Hspec@@ -36,7 +37,7 @@ -- >             put $ funds - amount -- >             pure True ----- Use 'describe' to group related examples, 'it' to state individual expectations,+-- Use 'describe' to group related examples, 'it' to state individual expectations -- and setup/teardown combinators such as 'before_', 'after_', or 'around_' to -- run the examples from a known initial state: --@@ -65,6 +66,7 @@     , runHspecWith     , runHspec'     , runHspec+    , runHUnit        -- * Spec construction     , describe@@ -112,10 +114,15 @@     , shouldNotReturn     , shouldThrow     , anyException++      -- * Properties+    , prop+    , xprop+    , fprop++      -- * Config     , Config (..)     , defaultConfig-    , Hspec.Spec-    , HasCallStack     ) where @@ -131,8 +138,10 @@ import Test.Hspec.Core.Spec (ResultStatus (..)) import Test.Hspec.Expectations (Selector, anyException) import Test.Hspec.Expectations.Pretty.Matcher (matchList)+import Test.Hspec.QuickCheck qualified as Hspec import Test.Hspec.Runner (Config (..), defaultConfig) import Test.Hspec.Runner qualified as Hspec+import Test.QuickCheck qualified as QuickCheck import Prelude  data Hspec :: Effect@@ -174,160 +183,139 @@     Hspec{unlift} <- getStaticRep     unsafeEff_ . unlift . inject $ action +appendSpec :: (Hspec :> es) => Hspec.Spec -> Eff es ()+appendSpec spec = stateStaticRep \hspec -> ((), hspec{spec = hspec.spec >> spec})+ mapSpec :: (HasCallStack, Hspec :> es) => (Hspec.Spec -> Hspec.Spec) -> Eff es a -> Eff es a mapSpec f action =     stateStaticRepM \before -> do         putStaticRep before{spec = pure ()}         result <- action-        after <- getStaticRep-        pure (result, before{spec = spec before >> f (spec after)})+        after <- getStaticRep @Hspec+        pure (result, before{spec = before.spec >> f after.spec}) --- | Lifted 'Hspec.describe'+-- | Lifted 'Hspec.describe'. describe :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a describe = mapSpec . Hspec.describe --- | Lifted 'Hspec.it'+-- | Lifted 'Hspec.it'. it :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es () it label action = do     ioAction <-         unsafeConcUnliftIO Ephemeral Unlimited \runInIO ->             pure (runInIO action)-    stateStaticRep \hspec -> ((), hspec{spec = spec hspec >> Hspec.it label ioAction})+    appendSpec $ Hspec.it label ioAction --- | Lifted 'Hspec.specify'+-- | Lifted 'Hspec.specify'. specify :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es () specify = it --- | Lifted 'Hspec.context'+-- | Lifted 'Hspec.context'. context :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a context = describe --- | Lifted 'Hspec.pending'+-- | Lifted 'Hspec.pending'. pending :: (HasCallStack, Hspec :> es) => Eff es () pending = getStaticRep @Hspec >> unsafeEff_ Hspec.pending  pending_ :: Eff es () pending_ = throwIO $ Pending Nothing Nothing --- | Lifted 'Hspec.pendingWith'+-- | Lifted 'Hspec.pendingWith'. pendingWith :: (HasCallStack, Hspec :> es) => String -> Eff es () pendingWith = (getStaticRep @Hspec >>) . unsafeEff_ . Hspec.pendingWith --- | Lifted 'Hspec.xit'+-- | Lifted 'Hspec.xit'. xit :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es () xit = (before_ pending_ .) . it --- | Lifted 'Hspec.xspecify'+-- | Lifted 'Hspec.xspecify'. xspecify :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es () xspecify = xit --- | Lifted 'Hspec.xdescribe'+-- | Lifted 'Hspec.xdescribe'. xdescribe :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a xdescribe = (before_ pending_ .) . describe --- | Lifted 'Hspec.xcontext'+-- | Lifted 'Hspec.xcontext'. xcontext :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a xcontext = xdescribe --- | Lifted 'Hspec.focus'+-- | Lifted 'Hspec.focus'. focus :: (HasCallStack, Hspec :> es) => Eff es a -> Eff es a focus = mapSpec Hspec.focus --- | Lifted 'Hspec.fit'+-- | Lifted 'Hspec.fit'. fit :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es () fit = fmap focus . it --- | Lifted 'Hspec.fspecify'+-- | Lifted 'Hspec.fspecify'. fspecify :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es () fspecify = fit --- | Lifted 'Hspec.fdescribe'+-- | Lifted 'Hspec.fdescribe'. fdescribe :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a fdescribe = fmap focus . describe --- | Lifted 'Hspec.fcontext'+-- | Lifted 'Hspec.fcontext'. fcontext :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a fcontext = fdescribe --- | Lifted 'Hspec.parallel'+-- | Lifted 'Hspec.parallel'. parallel :: (HasCallStack, Hspec :> es) => Eff es a -> Eff es a parallel = mapSpec Hspec.parallel --- | Lifted 'Hspec.sequential'+-- | Lifted 'Hspec.sequential'. sequential :: (HasCallStack, Hspec :> es) => Eff es a -> Eff es a sequential = mapSpec Hspec.sequential --- | Lifted 'Hspec.before_'+-- | Lifted 'Hspec.before_'. before_ :: (HasCallStack, Hspec :> es) => Eff es () -> Eff es a -> Eff es a before_ setup action = do     ioSetup <-         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->             pure (unlift setup)-    stateStaticRepM \prev -> do-        putStaticRep prev{spec = pure ()}-        result <- action-        after <- getStaticRep-        pure (result, prev{spec = spec prev >> Hspec.before_ ioSetup (spec after)})+    mapSpec (Hspec.before_ ioSetup) action --- | Lifted 'Hspec.beforeAll_'+-- | Lifted 'Hspec.beforeAll_'. beforeAll_ :: (HasCallStack, Hspec :> es) => Eff es () -> Eff es a -> Eff es a beforeAll_ setup action = do     ioSetup <-         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->             pure (unlift setup)-    stateStaticRepM \prev -> do-        putStaticRep prev{spec = pure ()}-        result <- action-        after <- getStaticRep-        pure (result, prev{spec = spec prev >> Hspec.beforeAll_ ioSetup (spec after)})+    mapSpec (Hspec.beforeAll_ ioSetup) action --- | Lifted 'Hspec.after_'+-- | Lifted 'Hspec.after_'. after_ :: (HasCallStack, Hspec :> es) => Eff es () -> Eff es a -> Eff es a after_ teardown action = do     ioTeardown <-         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->             pure (unlift teardown)-    stateStaticRepM \prev -> do-        putStaticRep prev{spec = pure ()}-        result <- action-        after <- getStaticRep-        pure (result, prev{spec = spec prev >> Hspec.after_ ioTeardown (spec after)})+    mapSpec (Hspec.after_ ioTeardown) action --- | Lifted 'Hspec.afterAll_'+-- | Lifted 'Hspec.afterAll_'. afterAll_ :: (HasCallStack, Hspec :> es) => Eff es () -> Eff es a -> Eff es a afterAll_ teardown action = do     ioTeardown <-         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->             pure (unlift teardown)-    stateStaticRepM \prev -> do-        putStaticRep prev{spec = pure ()}-        result <- action-        after <- getStaticRep-        pure (result, prev{spec = spec prev >> Hspec.afterAll_ ioTeardown (spec after)})+    mapSpec (Hspec.afterAll_ ioTeardown) action --- | Lifted 'Hspec.around_'+-- | Lifted 'Hspec.around_'. around_ :: (HasCallStack, Hspec :> es) => (Eff es () -> Eff es ()) -> Eff es a -> Eff es a around_ wrapper action = do     ioWrapper <-         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->             pure (unlift . wrapper . unsafeEff_)-    stateStaticRepM \prev -> do-        putStaticRep prev{spec = pure ()}-        result <- action-        after <- getStaticRep-        pure (result, prev{spec = spec prev >> Hspec.around_ ioWrapper (spec after)})+    mapSpec (Hspec.around_ ioWrapper) action --- | Lifted 'Hspec.aroundAll_'+-- | Lifted 'Hspec.aroundAll_'. aroundAll_ :: (HasCallStack, Hspec :> es) => (Eff es () -> Eff es ()) -> Eff es a -> Eff es a aroundAll_ wrapper action = do     ioWrapper <-         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->             pure (unlift . wrapper . unsafeEff_)-    stateStaticRepM \prev -> do-        putStaticRep prev{spec = pure ()}-        result <- action-        after <- getStaticRep-        pure (result, prev{spec = spec prev >> Hspec.aroundAll_ ioWrapper (spec after)})+    mapSpec (Hspec.aroundAll_ ioWrapper) action  expectationFailure :: (HasCallStack, Hspec :> es) => String -> Expectation es expectationFailure msg = hunit $ HUnit.assertFailure msg@@ -350,14 +338,12 @@  infix 1 `shouldNotBe`, `shouldNotSatisfy`, `shouldNotContain`, `shouldNotReturn` --- |--- @actual \`shouldBe\` expected@ sets the expectation that @actual@ is equal+-- | @actual \`shouldBe\` expected@ sets the expectation that @actual@ is equal -- to @expected@. shouldBe :: (HasCallStack, Show a, Eq a, Hspec :> es) => a -> a -> Expectation es actual `shouldBe` expected = hunit $ actual @?= expected --- |--- @v \`shouldSatisfy\` p@ sets the expectation that @p v@ is @True@.+-- | @v \`shouldSatisfy\` p@ sets the expectation that @p v@ is @True@. shouldSatisfy :: (HasCallStack, Show a, Hspec :> es) => a -> (a -> Bool) -> Expectation es v `shouldSatisfy` p = expectTrue ("predicate failed on: " ++ show v) (p v) @@ -372,55 +358,46 @@   where     errorMsg = show result ++ " " ++ errorDesc ++ " " ++ show expected --- |--- @list \`shouldStartWith\` prefix@ sets the expectation that @list@ starts with @prefix@,+-- | @list \`shouldStartWith\` prefix@ sets the expectation that @list@ starts with @prefix@. shouldStartWith :: (HasCallStack, Show a, Eq a, Hspec :> es) => [a] -> [a] -> Expectation es shouldStartWith = compareWith List.isPrefixOf "does not start with" --- |--- @list \`shouldEndWith\` suffix@ sets the expectation that @list@ ends with @suffix@,+-- | @list \`shouldEndWith\` suffix@ sets the expectation that @list@ ends with @suffix@. shouldEndWith :: (HasCallStack, Show a, Eq a, Hspec :> es) => [a] -> [a] -> Expectation es shouldEndWith = compareWith List.isSuffixOf "does not end with" --- |--- @list \`shouldContain\` sublist@ sets the expectation that @sublist@ is contained,+-- | @list \`shouldContain\` sublist@ sets the expectation that @sublist@ is contained, -- wholly and intact, anywhere in @list@. shouldContain :: (HasCallStack, Show a, Eq a, Hspec :> es) => [a] -> [a] -> Expectation es shouldContain = compareWith List.isInfixOf "does not contain" --- |--- @xs \`shouldMatchList\` ys@ sets the expectation that @xs@ has the same--- elements that @ys@ has, possibly in another order+-- | @xs \`shouldMatchList\` ys@ sets the expectation that @xs@ has the same+-- elements that @ys@ has, possibly in another order. shouldMatchList :: (HasCallStack, Show a, Eq a, Hspec :> es) => [a] -> [a] -> Expectation es xs `shouldMatchList` ys = maybe (return ()) expectationFailure (matchList xs ys) --- |--- @action \`shouldReturn\` expected@ sets the expectation that @action@+-- | @action \`shouldReturn\` expected@ sets the expectation that @action@ -- returns @expected@. shouldReturn :: (HasCallStack, Show a, Eq a, Hspec :> es) => Eff es a -> a -> Expectation es action `shouldReturn` expected = action >>= (`shouldBe` expected) --- |--- @actual \`shouldNotBe\` notExpected@ sets the expectation that @actual@ is not--- equal to @notExpected@+-- | @actual \`shouldNotBe\` notExpected@ sets the expectation that @actual@ is not+-- equal to @notExpected@. shouldNotBe :: (HasCallStack, Show a, Eq a, Hspec :> es) => a -> a -> Expectation es actual `shouldNotBe` notExpected = expectTrue ("not expected: " ++ show actual) (actual /= notExpected) --- |--- @v \`shouldNotSatisfy\` p@ sets the expectation that @p v@ is @False@.+-- | @v \`shouldNotSatisfy\` p@ sets the expectation that @p v@ is @False@. shouldNotSatisfy :: (HasCallStack, Show a, Hspec :> es) => a -> (a -> Bool) -> Expectation es v `shouldNotSatisfy` p = expectTrue ("predicate succeeded on: " ++ show v) ((not . p) v) --- |--- @list \`shouldNotContain\` sublist@ sets the expectation that @sublist@ is not+-- | @list \`shouldNotContain\` sublist@ sets the expectation that @sublist@ is not -- contained anywhere in @list@. shouldNotContain :: (HasCallStack, Show a, Eq a, Hspec :> es) => [a] -> [a] -> Expectation es list `shouldNotContain` sublist = expectTrue errorMsg (not $ sublist `List.isInfixOf` list)   where     errorMsg = show list ++ " does contain " ++ show sublist --- |--- @action \`shouldNotReturn\` notExpected@ sets the expectation that @action@+-- | @action \`shouldNotReturn\` notExpected@ sets the expectation that @action@ -- does not return @notExpected@. shouldNotReturn     :: (HasCallStack, Show a, Eq a, Hspec :> es)@@ -429,8 +406,7 @@     -> Expectation es action `shouldNotReturn` notExpected = action >>= (`shouldNotBe` notExpected) --- |--- @action \`shouldThrow\` selector@ sets the expectation that @action@ throws+-- | @action \`shouldThrow\` selector@ sets the expectation that @action@ throws -- an exception.  The precise nature of the expected exception is described -- with a 'Selector'. shouldThrow@@ -453,3 +429,15 @@       where         instanceOf :: Selector a -> a         instanceOf _ = error "Effectful.Hspec.shouldThrow: broken Typeable instance"++-- | Lifted 'Hspec.prop'.+prop :: (HasCallStack, QuickCheck.Testable prop, Hspec :> es) => String -> prop -> Eff es ()+prop = (appendSpec .) . Hspec.prop++-- | Lifted 'Hspec.xprop'.+xprop :: (HasCallStack, QuickCheck.Testable prop, Hspec :> es) => String -> prop -> Eff es ()+xprop = (appendSpec .) . Hspec.xprop++-- | Lifted 'Hspec.fprop'.+fprop :: (HasCallStack, QuickCheck.Testable prop, Hspec :> es) => String -> prop -> Eff es ()+fprop = (appendSpec .) . Hspec.fprop