packages feed

atelier-core-0.7.3.0: test/Unit/Atelier/Effects/YieldSpec.hs

module Unit.Atelier.Effects.YieldSpec (test_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.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, (@?=))

import Atelier.Effects.Chan (runChan)
import Atelier.Effects.Conc (runConc)

import Atelier.Effects.Await qualified as Await
import Atelier.Effects.Yield qualified as Yield


test_Yield :: TestTree
test_Yield =
    testGroup
        "Yield"
        [ testGroup "yieldToList" testYieldToList
        , testGroup "yieldToReverseList" testYieldToReverseList
        , testGroup "forEach" testForEach
        , testGroup "ignoreYield" testIgnoreYield
        , testGroup "inFoldable" testInFoldable
        , testGroup "cycleToYield" testCycleToYield
        , testGroup "withYieldToList" testWithYieldToList
        , testGroup "enumerate" testEnumerate
        , testGroup "enumerateFrom" testEnumerateFrom
        , testGroup "map" testMap
        , testGroup "mapMaybe" testMapMaybe
        , testGroup "catMaybes" testCatMaybes
        , testGroup "filter" testFilter
        , testGroup "changes" testChanges
        ]


testYieldToList :: [TestTree]
testYieldToList =
    [ testCase "collects yields in order" do
        let ((), xs) = runPureEff $ Yield.yieldToList @Int do
                Yield.yield 1
                Yield.yield 2
                Yield.yield 3
        xs @?= [1, 2, 3]
    , testCase "returns empty list when nothing is yielded" do
        let (_, xs) = runPureEff $ Yield.yieldToList @Int $ pure ()
        xs @?= []
    , testCase "also returns the result of the computation" do
        let (r :: Text, xs) = runPureEff $ Yield.yieldToList do
                Yield.yield (1 :: Int)
                pure "result"
        r @?= "result"
        xs @?= [1]
    ]


testYieldToReverseList :: [TestTree]
testYieldToReverseList =
    [ testCase "collects yields in reverse order" do
        let ((), xs) = runPureEff $ Yield.yieldToReverseList @Int do
                Yield.yield 1
                Yield.yield 2
                Yield.yield 3
        xs @?= [3, 2, 1]
    ]


testForEach :: [TestTree]
testForEach =
    [ testCase "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 @?= [3, 2, 1]
    , testCase "can discard values" do
        let xs = runPureEff $ Yield.forEach @Int (const $ pure ()) do
                Yield.yield 1
        xs @?= ()
    ]


testIgnoreYield :: [TestTree]
testIgnoreYield =
    [ testCase "discards all yielded values" do
        let x = runPureEff $ Yield.ignoreYield @Int do
                Yield.yield 1
                Yield.yield 2
        x @?= ()
    ]


testInFoldable :: [TestTree]
testInFoldable =
    [ testCase "yields all elements of a list in order" do
        let ((), xs) = runPureEff $ Yield.yieldToList $ Yield.inFoldable @Int [1, 2, 3]
        xs @?= [1, 2, 3]
    , testCase "yields nothing for an empty list" do
        let ((), xs) = runPureEff $ Yield.yieldToList $ Yield.inFoldable @Int []
        xs @?= []
    ]


testCycleToYield :: [TestTree]
testCycleToYield =
    [ testCase "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 @?= [1, 2, 3, 1, 2, 3, 1]
    ]
  where
    runTest = runEff . runConcurrent . runConc . runChan . execWriter


testWithYieldToList :: [TestTree]
testWithYieldToList =
    [ testCase "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 @?= 3
    , testCase "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 @?= [1, 2, 3]
    , testCase "passes an empty list when nothing is yielded" do
        let result = runPureEff $ Yield.withYieldToList @Int do
                pure null
        result @?= True
    ]


testEnumerate :: [TestTree]
testEnumerate =
    [ testCase "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 @?= [(0, 'a'), (1, 'b'), (2, 'c')]
    ]


testEnumerateFrom :: [TestTree]
testEnumerateFrom =
    [ testCase "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 @?= [(5, 'x'), (6, 'y')]
    ]


testMap :: [TestTree]
testMap =
    [ testCase "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 @?= [2, 4, 6]
    , testCase "preserves order" do
        let (_, xs :: [Text]) = runPureEff $ Yield.yieldToList $ Yield.map show do
                Yield.yield @Int 1
                Yield.yield 2
        xs @?= ["1", "2"]
    ]


testMapMaybe :: [TestTree]
testMapMaybe =
    [ testGroup
        "when the function returns Just"
        [ testCase "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 @?= [20, 40]
        ]
    , testGroup
        "when the function always returns Nothing"
        [ testCase "yields nothing" do
            let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.mapMaybe @Int @Int (const Nothing) do
                    Yield.yield 1
            xs @?= []
        ]
    ]


testCatMaybes :: [TestTree]
testCatMaybes =
    [ testCase "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 @?= [1 :: Int, 2]
    , testCase "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 @?= []
    ]


testFilter :: [TestTree]
testFilter =
    [ testCase "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 @?= [2 :: Int, 4]
    , testCase "drops all values when predicate is always false" do
        let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.filter (const False) do
                Yield.yield (1 :: Int)
        xs @?= []
    , testCase "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 @?= [1 :: Int, 2]
    ]


testChanges :: [TestTree]
testChanges =
    [ testCase "suppresses yields equal to the initial value" do
        let (_, xs) = runPureEff $ Yield.yieldToList $ Yield.changes 0 do
                Yield.yield 0
                Yield.yield 1
        xs @?= [1 :: Int]
    , testCase "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 @?= [1 :: Int, 2]
    , testCase "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 @?= [1 :: Int, 2, 3]
    , testCase "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 @?= [1 :: Int, 1, 2]
    ]