atelier-core-0.1.0.0: test/Unit/Atelier/Effects/YieldSpec.hs
module Unit.Atelier.Effects.YieldSpec (spec_Yield) where
import Effectful (runEff, runPureEff)
import Effectful.Concurrent (runConcurrent)
import Effectful.State.Static.Shared (execState, modify)
import Effectful.Writer.Static.Shared (execWriter, tell)
import Test.Hspec (Spec, describe, it, shouldBe, shouldMatchList)
import Atelier.Effects.Chan (runChan)
import Atelier.Effects.Conc (runConc)
import Atelier.Effects.Await qualified as Await
import Atelier.Effects.Yield qualified as Yield
spec_Yield :: Spec
spec_Yield = do
describe "yieldToList" testYieldToList
describe "yieldToReverseList" testYieldToReverseList
describe "forEach" testForEach
describe "ignoreYield" testIgnoreYield
describe "inFoldable" testInFoldable
describe "cycleToYield" testCycleToYield
describe "withYieldToList" testWithYieldToList
describe "enumerate" testEnumerate
describe "enumerateFrom" testEnumerateFrom
describe "map" testMap
describe "mapMaybe" testMapMaybe
describe "catMaybes" testCatMaybes
describe "filter" testFilter
describe "changes" testChanges
testYieldToList :: Spec
testYieldToList = do
it "collects yields in order" do
let ((), xs) = runPureEff $ Yield.yieldToList @Int do
Yield.yield 1
Yield.yield 2
Yield.yield 3
xs `shouldMatchList` [1, 2, 3]
it "returns empty list when nothing is yielded" do
let (_, xs) = runPureEff $ Yield.yieldToList @Int $ pure ()
xs `shouldMatchList` []
it "also returns the result of the computation" do
let (r :: Text, xs) = runPureEff $ Yield.yieldToList do
Yield.yield (1 :: Int)
pure "result"
r `shouldBe` "result"
xs `shouldMatchList` [1]
testYieldToReverseList :: Spec
testYieldToReverseList = do
it "collects yields in reverse order" do
let ((), xs) = runPureEff $ Yield.yieldToReverseList @Int do
Yield.yield 1
Yield.yield 2
Yield.yield 3
xs `shouldBe` [3, 2, 1]
testForEach :: Spec
testForEach = do
it "calls the action for each yielded value" do
let xs = runPureEff
$ execState @[Int] mempty
$ Yield.forEach (\x -> modify (x :)) do
Yield.yield 1
Yield.yield 2
Yield.yield 3
xs `shouldBe` [3, 2, 1]
it "can discard values" do
let xs = runPureEff $ Yield.forEach @Int (const $ pure ()) do
Yield.yield 1
xs `shouldBe` ()
testIgnoreYield :: Spec
testIgnoreYield = do
it "discards all yielded values" do
let x = runPureEff $ Yield.ignoreYield @Int do
Yield.yield 1
Yield.yield 2
x `shouldBe` ()
testInFoldable :: Spec
testInFoldable = do
it "yields all elements of a list in order" do
let ((), xs) = runPureEff $ Yield.yieldToList $ Yield.inFoldable @Int [1, 2, 3]
xs `shouldBe` [1, 2, 3]
it "yields nothing for an empty list" do
let ((), xs) = runPureEff $ Yield.yieldToList $ Yield.inFoldable @Int []
xs `shouldBe` []
testCycleToYield :: Spec
testCycleToYield = do
it "yields elements of a list repeatedly in order" do
xs <-
runTest
$ Await.awaitYield
(Yield.cycleToYield @Int [1, 2, 3])
(replicateM_ 7 (Await.await >>= \x -> tell [x]))
xs `shouldBe` [1, 2, 3, 1, 2, 3, 1]
where
runTest = runEff . runConcurrent . runConc . runChan . execWriter
testWithYieldToList :: Spec
testWithYieldToList = do
it "passes the collected yields to the returned function" do
let result = runPureEff $ Yield.withYieldToList @Int do
Yield.yield 1
Yield.yield 2
Yield.yield 3
pure length
result `shouldBe` 3
it "passes yields in order to the function" do
let result = runPureEff $ Yield.withYieldToList @Int do
Yield.yield 1
Yield.yield 2
Yield.yield 3
pure id
result `shouldBe` [1, 2, 3]
it "passes an empty list when nothing is yielded" do
let result = runPureEff $ Yield.withYieldToList @Int do
pure null
result `shouldBe` True
testEnumerate :: Spec
testEnumerate = do
it "pairs each value with its zero-based index" do
let ((), xs) = runPureEff $ Yield.yieldToList $ Yield.enumerate do
Yield.yield 'a'
Yield.yield 'b'
Yield.yield 'c'
xs `shouldBe` [(0, 'a'), (1, 'b'), (2, 'c')]
testEnumerateFrom :: Spec
testEnumerateFrom = do
it "pairs each value with its index starting from the given value" do
let ((), xs) = runPureEff $ Yield.yieldToList $ Yield.enumerateFrom 5 do
Yield.yield 'x'
Yield.yield 'y'
xs `shouldBe` [(5, 'x'), (6, 'y')]
testMap :: Spec
testMap = do
it "transforms each yielded value" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.map @Int (* 2) do
Yield.yield 1
Yield.yield 2
Yield.yield 3
xs `shouldBe` [2, 4, 6]
it "preserves order" do
let (_, xs :: [Text]) = runPureEff $ Yield.yieldToList $ Yield.map show do
Yield.yield @Int 1
Yield.yield 2
xs `shouldBe` ["1", "2"]
testMapMaybe :: Spec
testMapMaybe = do
describe "when the function returns Just" $ it "yields transformed values" do
let ((), xs) = runPureEff $ Yield.yieldToList $ Yield.mapMaybe (\x -> if even x then Just (x * 10) else Nothing) do
Yield.yield @Int 1
Yield.yield 2
Yield.yield 3
Yield.yield 4
xs `shouldMatchList` [20, 40]
describe "when the function always returns Nothing" $ it "yields nothing" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.mapMaybe @Int @Int (const Nothing) do
Yield.yield 1
xs `shouldMatchList` []
testCatMaybes :: Spec
testCatMaybes = do
it "unwraps Just values and drops Nothings" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.catMaybes do
Yield.yield (Just 1)
Yield.yield Nothing
Yield.yield (Just 2)
xs `shouldBe` [1 :: Int, 2]
it "yields nothing when all values are Nothing" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.catMaybes do
Yield.yield (Nothing :: Maybe Int)
Yield.yield Nothing
xs `shouldBe` []
testFilter :: Spec
testFilter = do
it "passes values satisfying the predicate" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.filter even do
Yield.yield 1
Yield.yield 2
Yield.yield 3
Yield.yield 4
xs `shouldBe` [2 :: Int, 4]
it "drops all values when predicate is always false" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.filter (const False) do
Yield.yield (1 :: Int)
xs `shouldBe` []
it "passes all values when predicate is always true" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.filter (const True) do
Yield.yield 1
Yield.yield 2
xs `shouldBe` [1 :: Int, 2]
testChanges :: Spec
testChanges = do
it "suppresses yields equal to the initial value" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.changes 0 do
Yield.yield 0
Yield.yield 1
xs `shouldBe` [1 :: Int]
it "passes values that differ from the initial value" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.changes 0 do
Yield.yield 1
Yield.yield 2
xs `shouldBe` [1 :: Int, 2]
it "suppresses initial value interspersed with other values" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.changes 0 do
Yield.yield 0
Yield.yield 1
Yield.yield 0
Yield.yield 2
Yield.yield 0
Yield.yield 3
xs `shouldBe` [1 :: Int, 2, 3]
it "does not suppress non-initial values even if they repeat" do
let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.changes 0 do
Yield.yield 1
Yield.yield 1
Yield.yield 2
xs `shouldBe` [1 :: Int, 1, 2]