module Quasar.Observable.ObservableHashMapSpec (spec) where import Control.Monad (void) import Data.HashMap.Strict qualified as HM import Data.IORef import Quasar.Awaitable import Quasar.Disposable import Quasar.Observable import Quasar.Observable.Delta import Quasar.Observable.ObservableHashMap qualified as OM import Quasar.Prelude import Quasar.ResourceManager import Test.Hspec shouldReturnM :: (Eq a, Show a, MonadIO m) => m a -> a -> m () shouldReturnM action expected = do result <- action liftIO $ result `shouldBe` expected spec :: Spec spec = parallel $ do describe "retrieve" $ do it "returns the contents of the map" $ io $ withRootResourceManager do om :: OM.ObservableHashMap String String <- OM.new (retrieve om >>= await) `shouldReturnM` HM.empty -- Evaluate unit for coverage () <- OM.insert "key" "value" om (retrieve om >>= await) `shouldReturnM` HM.singleton "key" "value" OM.insert "key2" "value2" om (retrieve om >>= await) `shouldReturnM` HM.fromList [("key", "value"), ("key2", "value2")] describe "subscribe" $ do it "calls the callback with the contents of the map" $ io $ withRootResourceManager do lastCallbackValue <- liftIO $ newIORef unreachableCodePath om :: OM.ObservableHashMap String String <- OM.new subscriptionHandle <- captureDisposable_ $ observe om $ liftIO . writeIORef lastCallbackValue let lastCallbackShouldBe expected = liftIO do (ObservableUpdate update) <- readIORef lastCallbackValue update `shouldBe` expected lastCallbackShouldBe HM.empty OM.insert "key" "value" om lastCallbackShouldBe (HM.singleton "key" "value") OM.insert "key2" "value2" om lastCallbackShouldBe (HM.fromList [("key", "value"), ("key2", "value2")]) dispose subscriptionHandle lastCallbackShouldBe (HM.fromList [("key", "value"), ("key2", "value2")]) OM.insert "key3" "value3" om lastCallbackShouldBe (HM.fromList [("key", "value"), ("key2", "value2")]) describe "subscribeDelta" $ do it "calls the callback with changes to the map" $ io $ withRootResourceManager do lastDelta <- liftIO $ newIORef unreachableCodePath om :: OM.ObservableHashMap String String <- OM.new subscriptionHandle <- subscribeDelta om $ writeIORef lastDelta let lastDeltaShouldBe = liftIO . (readIORef lastDelta `shouldReturn`) lastDeltaShouldBe $ Reset HM.empty OM.insert "key" "value" om lastDeltaShouldBe $ Insert "key" "value" OM.insert "key" "changed" om lastDeltaShouldBe $ Insert "key" "changed" OM.insert "key2" "value2" om lastDeltaShouldBe $ Insert "key2" "value2" dispose subscriptionHandle lastDeltaShouldBe $ Insert "key2" "value2" OM.insert "key3" "value3" om lastDeltaShouldBe $ Insert "key2" "value2" void $ subscribeDelta om $ writeIORef lastDelta lastDeltaShouldBe $ Reset $ HM.fromList [("key", "changed"), ("key2", "value2"), ("key3", "value3")] OM.delete "key2" om lastDeltaShouldBe $ Delete "key2" OM.lookupDelete "key" om `shouldReturnM` Just "changed" lastDeltaShouldBe $ Delete "key" (retrieve om >>= await) `shouldReturnM` HM.singleton "key3" "value3" describe "observeKey" $ do it "calls key callbacks with the correct value" $ io $ withRootResourceManager do value1 <- liftIO $ newIORef undefined value2 <- liftIO $ newIORef undefined om :: OM.ObservableHashMap String String <- OM.new void $ observe (OM.observeKey "key1" om) (liftIO . writeIORef value1) let v1ShouldBe expected = liftIO do (ObservableUpdate update) <- readIORef value1 update `shouldBe` expected v1ShouldBe $ Nothing OM.insert "key1" "value1" om v1ShouldBe $ Just "value1" OM.insert "key2" "value2" om v1ShouldBe $ Just "value1" handle2 <- captureDisposable_ $ observe (OM.observeKey "key2" om) (liftIO . writeIORef value2) let v2ShouldBe expected = liftIO do (ObservableUpdate update) <- readIORef value2 update `shouldBe` expected v1ShouldBe $ Just "value1" v2ShouldBe $ Just "value2" OM.insert "key2" "changed" om v1ShouldBe $ Just "value1" v2ShouldBe $ Just "changed" OM.delete "key1" om v1ShouldBe $ Nothing v2ShouldBe $ Just "changed" -- Delete again (should have no effect) OM.delete "key1" om v1ShouldBe $ Nothing v2ShouldBe $ Just "changed" (retrieve om >>= await) `shouldReturnM` HM.singleton "key2" "changed" dispose handle2 OM.lookupDelete "key2" om `shouldReturnM` Just "changed" v2ShouldBe $ Just "changed" OM.lookupDelete "key2" om `shouldReturnM` Nothing OM.lookupDelete "key1" om `shouldReturnM` Nothing v1ShouldBe $ Nothing (retrieve om >>= await) `shouldReturnM` HM.empty