genvalidity-mergeless 0.1.0.0 → 0.2.0.0
raw patch · 7 files changed
+287/−247 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.ServerStore i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.ClientStore i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.ServerStore i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.SyncRequest i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.SyncResponse i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.ClientStore i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.SyncRequest i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.SyncResponse i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.ClientStore i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.ServerStore i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.SyncRequest i a)
- Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.SyncResponse i a)
+ Data.GenValidity.Mergeless.Collection: genClientStoreFromSet :: (Show ci, Ord ci, Ord si, GenValid ci, GenValid v) => Set si -> Gen (ClientStore ci si v)
+ Data.GenValidity.Mergeless.Collection: genServerStoreFromSet :: (Ord si, GenValid v) => Set si -> Gen (ServerStore si v)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked ci, Data.GenValidity.GenUnchecked si, Data.GenValidity.GenUnchecked a, GHC.Show.Show ci, GHC.Show.Show si, GHC.Show.Show a, Data.GenValidity.GenInvalid ci, Data.GenValidity.GenInvalid si, Data.GenValidity.GenInvalid a, GHC.Classes.Ord ci, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.ClientStore ci si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked ci, Data.GenValidity.GenUnchecked si, Data.GenValidity.GenUnchecked a, GHC.Show.Show ci, GHC.Show.Show si, GHC.Show.Show a, Data.GenValidity.GenInvalid ci, Data.GenValidity.GenInvalid si, Data.GenValidity.GenInvalid a, GHC.Classes.Ord ci, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.SyncRequest ci si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked ci, Data.GenValidity.GenUnchecked si, Data.GenValidity.GenUnchecked a, GHC.Show.Show ci, GHC.Show.Show si, GHC.Show.Show a, Data.GenValidity.GenInvalid ci, Data.GenValidity.GenInvalid si, Data.GenValidity.GenInvalid a, GHC.Classes.Ord ci, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.SyncResponse ci si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked ci, Data.GenValidity.GenUnchecked si, Data.GenValidity.GenUnchecked a, GHC.Show.Show ci, GHC.Show.Show si, GHC.Show.Show a, GHC.Classes.Ord ci, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.ClientStore ci si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked ci, Data.GenValidity.GenUnchecked si, Data.GenValidity.GenUnchecked a, GHC.Show.Show ci, GHC.Show.Show si, GHC.Show.Show a, GHC.Classes.Ord ci, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.SyncRequest ci si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked ci, Data.GenValidity.GenUnchecked si, Data.GenValidity.GenUnchecked a, GHC.Show.Show ci, GHC.Show.Show si, GHC.Show.Show a, GHC.Classes.Ord ci, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.SyncResponse ci si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked si, Data.GenValidity.GenUnchecked a, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.ServerStore si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked si, Data.GenValidity.GenUnchecked a, GHC.Show.Show si, GHC.Show.Show a, Data.GenValidity.GenInvalid si, Data.GenValidity.GenInvalid a, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.ServerStore si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid ci, Data.GenValidity.GenValid si, Data.GenValidity.GenValid a, GHC.Show.Show ci, GHC.Show.Show si, GHC.Show.Show a, GHC.Classes.Ord ci, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.ClientStore ci si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid ci, Data.GenValidity.GenValid si, Data.GenValidity.GenValid a, GHC.Show.Show ci, GHC.Show.Show si, GHC.Show.Show a, GHC.Classes.Ord ci, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.SyncRequest ci si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid ci, Data.GenValidity.GenValid si, Data.GenValidity.GenValid a, GHC.Show.Show ci, GHC.Show.Show si, GHC.Show.Show a, GHC.Classes.Ord ci, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.SyncResponse ci si a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid si, Data.GenValidity.GenValid a, GHC.Show.Show si, GHC.Show.Show a, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.ServerStore si a)
- Data.GenValidity.Mergeless.Collection: genUnsyncedStore :: forall i a. (Ord i, Ord a, GenValid i, GenValid a) => Gen (ClientStore i a)
+ Data.GenValidity.Mergeless.Collection: genUnsyncedStore :: forall ci si a. (Show ci, Ord ci, Ord si, Ord a, GenValid ci, GenValid si, GenValid a) => Gen (ClientStore ci si a)
Files
- bench/Bench.hs +12/−21
- genvalidity-mergeless.cabal +3/−3
- src/Data/GenValidity/Mergeless.hs +3/−2
- src/Data/GenValidity/Mergeless/Collection.hs +52/−35
- src/Data/GenValidity/Mergeless/Item.hs +0/−1
- test/Data/Mergeless/CollectionSpec.hs +184/−160
- test/Data/Mergeless/ItemSpec.hs +33/−25
bench/Bench.hs view
@@ -1,14 +1,12 @@ {-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-} module Main where import Criterion.Main as Criterion- import Data.GenValidity.Criterion import Data.GenValidity.Mergeless ()- import Data.Mergeless.Collection import Data.Mergeless.Item @@ -16,25 +14,18 @@ main = Criterion.defaultMain [ bgroup- "Utils"- [ genValidBench @(Added Bool)- , genValidBench @(Synced Bool)- , genValidBench @(ClientAddition Int)- ]- , bgroup "Item"- [ genValidBench @(ClientItem Bool)- , genValidBench @(ItemSyncRequest Bool)- , genValidBench @(ItemSyncResponse Bool)- , genValidBench @(ServerItem Bool)- ]- , bgroup+ [ genValidBench @(ClientItem Bool),+ genValidBench @(ItemSyncRequest Bool),+ genValidBench @(ItemSyncResponse Bool),+ genValidBench @(ServerItem Bool)+ ],+ bgroup "Collection"- [ genValidBench @ClientId- , genValidBench @(ClientStore Int Bool)- , genValidBench @(SyncRequest Int Bool)- , genValidBench @(ClientAddition Int)- , genValidBench @(SyncResponse Int Bool)- , genValidBench @(ServerStore Int Bool)+ [ genValidBench @ClientId,+ genValidBench @(ClientStore ClientId Int Bool),+ genValidBench @(SyncRequest ClientId Int Bool),+ genValidBench @(SyncResponse ClientId Int Bool),+ genValidBench @(ServerStore Int Bool) ] ]
genvalidity-mergeless.cabal view
@@ -4,16 +4,16 @@ -- -- see: https://github.com/sol/hpack ----- hash: 13b8526e5aa6ded49e51ebf12a1008ee6f2e8f428b081a7766bfc17ddeef7848+-- hash: 0fb32a64505d66389249b91ab33990a95c7b3f63032e84f0dc094e1439aec358 name: genvalidity-mergeless-version: 0.1.0.0+version: 0.2.0.0 description: Please see the README on GitHub at <https://github.com/NorfairKing/mergeless#readme> homepage: https://github.com/NorfairKing/mergeless#readme bug-reports: https://github.com/NorfairKing/mergeless/issues author: Tom Sydney Kerckhove maintainer: syd.kerckhove@gmail.com-copyright: Copyright: (c) 2018 Tom Sydney Kerckhove+copyright: Copyright: (c) 2018-2020 Tom Sydney Kerckhove license: MIT build-type: Simple extra-source-files:
src/Data/GenValidity/Mergeless.hs view
@@ -1,7 +1,8 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} module Data.GenValidity.Mergeless- ( module Data.GenValidity.Mergeless.Collection- ) where+ ( module Data.GenValidity.Mergeless.Collection,+ )+where import Data.GenValidity.Mergeless.Collection
src/Data/GenValidity/Mergeless/Collection.hs view
@@ -4,20 +4,17 @@ module Data.GenValidity.Mergeless.Collection where -import qualified Data.Map as M-import Data.Map (Map)-import qualified Data.Set as S-import Data.Set (Set)- import Control.Monad- import Data.GenValidity import Data.GenValidity.Containers ()-import Data.GenValidity.Time ()-import Test.QuickCheck- import Data.GenValidity.Mergeless.Item ()+import Data.GenValidity.Time ()+import qualified Data.Map as M+import Data.Map (Map) import Data.Mergeless+import qualified Data.Set as S+import Data.Set (Set)+import Test.QuickCheck instance GenUnchecked ClientId @@ -25,10 +22,11 @@ genValid = genValidStructurallyWithoutExtraChecking shrinkValid = shrinkValidStructurallyWithoutExtraFiltering -instance (GenUnchecked i, GenUnchecked a, Show i, Show a, Ord i, Ord a) =>- GenUnchecked (ClientStore i a)+instance+ (GenUnchecked ci, GenUnchecked si, GenUnchecked a, Show ci, Show si, Show a, Ord ci, Ord si, Ord a) =>+ GenUnchecked (ClientStore ci si a) -instance (GenValid i, GenValid a, Show i, Show a, Ord i, Ord a) => GenValid (ClientStore i a) where+instance (GenValid ci, GenValid si, GenValid a, Show ci, Show si, Show a, Ord ci, Ord si, Ord a) => GenValid (ClientStore ci si a) where genValid = sized $ \n -> do (a, b, c) <- genSplit3 n@@ -39,13 +37,15 @@ pure ClientStore {..} shrinkValid = shrinkValidStructurally -instance (GenUnchecked i, GenUnchecked a, Show i, Show a, GenInvalid i, GenInvalid a, Ord i, Ord a) =>- GenInvalid (ClientStore i a)+instance+ (GenUnchecked ci, GenUnchecked si, GenUnchecked a, Show ci, Show si, Show a, GenInvalid ci, GenInvalid si, GenInvalid a, Ord ci, Ord si, Ord a) =>+ GenInvalid (ClientStore ci si a) -instance (GenUnchecked i, GenUnchecked a, Show i, Show a, Ord i, Ord a) =>- GenUnchecked (SyncRequest i a)+instance+ (GenUnchecked ci, GenUnchecked si, GenUnchecked a, Show ci, Show si, Show a, Ord ci, Ord si, Ord a) =>+ GenUnchecked (SyncRequest ci si a) -instance (GenValid i, GenValid a, Show i, Show a, Ord i, Ord a) => GenValid (SyncRequest i a) where+instance (GenValid ci, GenValid si, GenValid a, Show ci, Show si, Show a, Ord ci, Ord si, Ord a) => GenValid (SyncRequest ci si a) where genValid = sized $ \n -> do (a, b, c) <- genSplit3 n@@ -56,22 +56,25 @@ pure SyncRequest {..} shrinkValid = shrinkValidStructurally -instance (GenUnchecked i, GenUnchecked a, Show i, Show a, GenInvalid i, GenInvalid a, Ord i, Ord a) =>- GenInvalid (SyncRequest i a)+instance+ (GenUnchecked ci, GenUnchecked si, GenUnchecked a, Show ci, Show si, Show a, GenInvalid ci, GenInvalid si, GenInvalid a, Ord ci, Ord si, Ord a) =>+ GenInvalid (SyncRequest ci si a) -instance (GenUnchecked i, GenUnchecked a, Show i, Show a, Ord i, Ord a) =>- GenUnchecked (SyncResponse i a)+instance+ (GenUnchecked ci, GenUnchecked si, GenUnchecked a, Show ci, Show si, Show a, Ord ci, Ord si, Ord a) =>+ GenUnchecked (SyncResponse ci si a) -instance (GenValid i, GenValid a, Show i, Show a, Ord i, Ord a) => GenValid (SyncResponse i a) where+instance (GenValid ci, GenValid si, GenValid a, Show ci, Show si, Show a, Ord ci, Ord si, Ord a) => GenValid (SyncResponse ci si a) where genValid = do (s1, s2) <- genValid >>= splitSet (s3, s4) <- splitSet s1 (s5, s6) <- splitSet s2 syncResponseClientAdded <-- fmap M.fromList $- forM (S.toList s3) $ \i -> do- cid <- genValid -- TODO maybe we can find a way to not generate duplicate client ids and speed up this generator, but it's fine for now.- pure (cid, i)+ fmap M.fromList+ $ forM (S.toList s3)+ $ \i -> do+ cid <- genValid -- TODO maybe we can find a way to not generate duplicate client ids and speed up this generator, but it's fine for now.+ pure (cid, i) let syncResponseClientDeleted = s4 syncResponseServerAdded <- mapWithIds s5 let syncResponseServerDeleted = s6@@ -87,23 +90,37 @@ pure $ S.split a s mapWithIds :: (Ord i, GenValid a) => Set i -> Gen (Map i a)-mapWithIds s = fmap M.fromList $ forM (S.toList s) $ \i -> (,) i <$> genValid+mapWithIds = traverse id . M.fromSet (const genValid) -instance (GenUnchecked i, GenUnchecked a, Show i, Show a, GenInvalid i, GenInvalid a, Ord i, Ord a) =>- GenInvalid (SyncResponse i a)+instance+ (GenUnchecked ci, GenUnchecked si, GenUnchecked a, Show ci, Show si, Show a, GenInvalid ci, GenInvalid si, GenInvalid a, Ord ci, Ord si, Ord a) =>+ GenInvalid (SyncResponse ci si a) -instance (GenUnchecked i, GenUnchecked a, Ord i, Ord a) => GenUnchecked (ServerStore i a)+instance (GenUnchecked si, GenUnchecked a, Ord si, Ord a) => GenUnchecked (ServerStore si a) -instance (GenValid i, GenValid a, Show i, Show a, Ord i, Ord a) => GenValid (ServerStore i a) where+instance (GenValid si, GenValid a, Show si, Show a, Ord si, Ord a) => GenValid (ServerStore si a) where genValid = genValidStructurallyWithoutExtraChecking shrinkValid = shrinkValidStructurallyWithoutExtraFiltering -instance (GenUnchecked i, GenUnchecked a, Show i, Show a, GenInvalid i, GenInvalid a, Ord i, Ord a) =>- GenInvalid (ServerStore i a)+instance+ (GenUnchecked si, GenUnchecked a, Show si, Show a, GenInvalid si, GenInvalid a, Ord si, Ord a) =>+ GenInvalid (ServerStore si a) +genServerStoreFromSet :: (Ord si, GenValid v) => Set si -> Gen (ServerStore si v)+genServerStoreFromSet s = ServerStore <$> mapWithIds s+ genUnsyncedStore ::- forall i a. (Ord i, Ord a, GenValid i, GenValid a)- => Gen (ClientStore i a)+ forall ci si a.+ (Show ci, Ord ci, Ord si, Ord a, GenValid ci, GenValid si, GenValid a) =>+ Gen (ClientStore ci si a) genUnsyncedStore = do as <- genValid pure $ emptyClientStore {clientStoreAdded = as}++genClientStoreFromSet :: (Show ci, Ord ci, Ord si, GenValid ci, GenValid v) => Set si -> Gen (ClientStore ci si v)+genClientStoreFromSet s = do+ (s1, s2) <- splitSet s+ clientStoreAdded <- genValid+ clientStoreSynced <- mapWithIds s1+ let clientStoreDeleted = s2+ pure ClientStore {..}
src/Data/GenValidity/Mergeless/Item.hs view
@@ -6,7 +6,6 @@ import Data.GenValidity import Data.GenValidity.Containers () import Data.GenValidity.Time ()- import Data.Mergeless.Item instance GenUnchecked a => GenUnchecked (ClientItem a)
test/Data/Mergeless/CollectionSpec.hs view
@@ -5,108 +5,122 @@ {-# LANGUAGE TypeApplications #-} module Data.Mergeless.CollectionSpec- ( spec- ) where+ ( spec,+ )+where +import Control.Monad.State+import Data.GenValidity.Mergeless.Collection ()+import Data.GenValidity.UUID () import Data.Int (Int) import Data.List import qualified Data.Map.Strict as M+import Data.Mergeless.Collection import Data.Ord import qualified Data.Set as S+import Data.UUID import GHC.Generics (Generic) import System.Random--import Control.Monad.State- import Test.Hspec import Test.QuickCheck import Test.Validity import Test.Validity.Aeson -import Data.GenValidity.Mergeless.Collection ()-import Data.GenValidity.UUID ()-import Data.Mergeless.Collection-import Data.UUID- {-# ANN module ("HLint: ignore Reduce duplication" :: String) #-} spec :: Spec spec = do- genValidSpec @(ClientStore Int Int)- jsonSpecOnValid @(ClientStore Int Int)- genValidSpec @(SyncRequest Int Int)- jsonSpecOnValid @(SyncRequest Int Int)- genValidSpec @(SyncResponse Int Int)- jsonSpecOnValid @(SyncResponse Int Int)+ genValidSpec @(ClientStore ClientId Int Int)+ jsonSpecOnValid @(ClientStore ClientId Int Int)+ genValidSpec @(SyncRequest ClientId Int Int)+ jsonSpecOnValid @(SyncRequest ClientId Int Int)+ genValidSpec @(SyncResponse ClientId Int Int)+ jsonSpecOnValid @(SyncResponse ClientId Int Int) genValidSpec @(ServerStore Int Int) jsonSpecOnValid @(ServerStore Int Int)- describe "emptyStore" $ it "is valid" $ shouldBeValid (emptyClientStore @Int @Int)+ describe "emptyStore" $ it "is valid" $ shouldBeValid (emptyClientStore @Int @Int @Int) describe "storeSize" $ do- it "does not crash" $ producesValidsOnValids (storeSize @Int @Int)- specify "adding an item makes the store bigger" $- forAllValid $ \store ->+ it "does not crash" $ producesValidsOnValids (storeSize @Int @Int @Int)+ specify "adding an item makes the store bigger"+ $ forAllValid+ $ \store -> forAllValid $ \added -> do- let size1 = storeSize (store :: ClientStore Int Int)+ let size1 = storeSize (store :: ClientStore Int Int Int) let store' = addItemToClientStore added store let size2 = storeSize store' size2 `shouldBe` (size1 + 1)- specify "deleting an unsynced item after adding it leaves the store with the original size" $- forAllValid $ \store ->+ specify "deleting an unsynced item after adding it leaves the store with the original size"+ $ forAllValid+ $ \store -> forAllValid $ \added ->- let size1 = storeSize (store :: ClientStore Int Int)+ let size1 = storeSize (store :: ClientStore Int Int Int) store' = addItemToClientStore added store in case sortOn (Down . fst) $ M.toList (clientStoreAdded store') of [] -> expectationFailure "Expected a nonempty list"- (i, _):_ ->+ (i, _) : _ -> let store'' = deleteUnsyncedFromClientStore i store' size2 = storeSize store'' in size2 `shouldBe` size1- specify "deleting a synced item after adding it leaves the store with the original size" $- forAllValid $ \store ->+ specify "deleting a synced item after adding it leaves the store with the original size"+ $ forAllValid+ $ \store -> forAll (genValid `suchThat` (\uuid -> not $ M.member uuid $ clientStoreSynced store)) $ \uuid -> forAllValid $ \synced ->- let size1 = storeSize (store :: ClientStore Int Int)+ let size1 = storeSize (store :: ClientStore Int Int Int) store' = store {clientStoreSynced = M.insert uuid synced $ clientStoreSynced store} store'' = deleteSyncedFromClientStore uuid store' size2 = storeSize store'' in size2 `shouldBe` size1- describe "addItemToClientStore" $- it "produces valid stores" $ producesValidsOnValids2 (addItemToClientStore @Int @Int)- describe "deleteUnsyncedFromClientStore" $- it "produces valid stores" $ producesValidsOnValids2 (deleteUnsyncedFromClientStore @Int @Int)- describe "deleteSyncedFromClientStore" $- it "produces valid stores" $ producesValidsOnValids2 (deleteSyncedFromClientStore @Int @Int)- describe "makeSyncRequest" $- it "produces valid sync requests" $ producesValidsOnValids (makeSyncRequest @Int @Int)+ describe "addItemToClientStore"+ $ it "produces valid stores"+ $ producesValidsOnValids2 (addItemToClientStore @Int @Int @Int)+ describe "deleteUnsyncedFromClientStore"+ $ it "produces valid stores"+ $ producesValidsOnValids2 (deleteUnsyncedFromClientStore @Int @Int @Int)+ describe "deleteSyncedFromClientStore"+ $ it "produces valid stores"+ $ producesValidsOnValids2 (deleteSyncedFromClientStore @Int @Int @Int)+ describe "emptySyncRequest"+ $ it "is valid"+ $ shouldBeValid (emptySyncRequest @Int @Int @Int)+ describe "makeSyncRequest"+ $ it "produces valid sync requests"+ $ producesValidsOnValids (makeSyncRequest @Int @Int @Int) describe "mergeSyncResponse" $ do- it "produces valid sync stores" $ producesValidsOnValids2 (mergeSyncResponse @Int @Int)- it "adds the single item that the server tells it to add to an empty client store" $- forAllValid $ \cid ->+ it "produces valid sync stores" $ producesValidsOnValids2 (mergeSyncResponse @Int @Int @Int)+ it "adds the single item that the server tells it to add to an empty client store"+ $ forAllValid+ $ \cid -> forAllValid $ \a -> forAllValid $ \u -> do- let cstore1 = emptyClientStore {clientStoreAdded = M.singleton cid (a :: Int)}+ let cstore1 = emptyClientStore {clientStoreAdded = M.singleton (cid :: ClientId) (a :: Int)} resp = emptySyncResponse {syncResponseClientAdded = M.singleton cid (u :: Int)} cstore2 = mergeSyncResponse cstore1 resp clientStoreSynced cstore2 `shouldBe` M.singleton u a- it "deletes items that the server instructed to be deleted" $- forAllValid $ \cs ->+ it "deletes items that the server instructed to be deleted"+ $ forAllValid+ $ \cs -> forAllValid $ \sr -> do- let cs' = mergeSyncResponse @Int @Int cs sr- clientStoreDeleted cs' `shouldBe`- (clientStoreDeleted cs `S.difference` syncResponseClientDeleted sr)- describe "processServerSync" $- describe "deterministic UUIDs" $ serverSyncSpec @Int evalDM $ processServerSync genD+ let cs' = mergeSyncResponse @ClientId @Int @Int cs sr+ clientStoreDeleted cs'+ `shouldBe` (clientStoreDeleted cs `S.difference` syncResponseClientDeleted sr)+ describe "processServerSync"+ $ describe "deterministic UUIDs"+ $ serverSyncSpec @Int evalDM+ $ processServerSync genD serverSyncSpec ::- forall a i m. (Show i, Ord i, GenValid i, Show a, Ord a, GenValid a, MonadIO m)- => (forall r. m r -> IO r)- -> (ServerStore i a -> SyncRequest i a -> m (SyncResponse i a, ServerStore i a))- -> Spec+ forall a si m.+ (Show si, Ord si, GenValid si, Show a, Ord a, GenValid a, MonadIO m) =>+ (forall r. m r -> IO r) ->+ (ServerStore si a -> SyncRequest ClientId si a -> m (SyncResponse ClientId si a, ServerStore si a)) ->+ Spec serverSyncSpec eval func = do describe "Single client" $ do describe "Single-item" $ do- it "succesfully downloads a single item from the server for an empty client" $- forAllValid $ \u ->+ it "succesfully downloads a single item from the server for an empty client"+ $ forAllValid+ $ \u -> forAllValid $ \i -> eval $ do let sstore1 = emptyServerStore {serverStoreItems = M.singleton u i}@@ -117,8 +131,9 @@ liftIO $ do sstore2 `shouldBe` sstore1 clientStoreSynced cstore2 `shouldBe` serverStoreItems sstore2- it "succesfully uploads a single item to the server for an empty server" $- forAllValid $ \c ->+ it "succesfully uploads a single item to the server for an empty server"+ $ forAllValid+ $ \c -> forAllValid $ \i -> eval $ do let cstore1 = emptyClientStore {clientStoreAdded = M.singleton c i}@@ -128,11 +143,12 @@ let cstore2 = mergeSyncResponse cstore1 resp liftIO $ do clientStoreSynced cstore2 `shouldBe` serverStoreItems sstore2- sort (M.elems (clientStoreSynced cstore2)) `shouldBe`- sort (M.elems $ M.singleton c i)+ sort (M.elems (clientStoreSynced cstore2))+ `shouldBe` sort (M.elems $ M.singleton c i) describe "Multi-item" $ do- it "succesfully downloads everything from the server for an empty client" $- forAllValid $ \sstore1 ->+ it "succesfully downloads everything from the server for an empty client"+ $ forAllValid+ $ \sstore1 -> eval $ do let cstore1 = emptyClientStore let req = makeSyncRequest cstore1@@ -141,8 +157,9 @@ liftIO $ do sstore2 `shouldBe` sstore1 clientStoreSynced cstore2 `shouldBe` serverStoreItems sstore2- it "succesfully uploads everything to the server for an empty server" $- forAllValid $ \items ->+ it "succesfully uploads everything to the server for an empty server"+ $ forAllValid+ $ \items -> eval $ do let cstore1 = emptyClientStore {clientStoreAdded = items} let sstore1 = emptyServerStore@@ -152,8 +169,9 @@ liftIO $ do clientStoreSynced cstore2 `shouldBe` serverStoreItems sstore2 sort (M.elems (clientStoreSynced cstore2)) `shouldBe` sort (M.elems items)- it "is idempotent with one client" $- forAllValid $ \cstore1 ->+ it "is idempotent with one client"+ $ forAllValid+ $ \cstore1 -> forAllValid $ \sstore1 -> eval $ do let req1 = makeSyncRequest cstore1@@ -167,227 +185,236 @@ sstore2 `shouldBe` sstore3 describe "Multiple clients" $ do describe "Single-item" $ do- it "successfully syncs an addition accross to a second client" $- forAllValid $ \i ->+ it "successfully syncs an addition accross to a second client"+ $ forAllValid+ $ \i -> eval $ do let cAstore1 = emptyClientStore {clientStoreAdded = M.singleton (ClientId 0) i}- -- Client B is empty+ -- Client B is empty let cBstore1 = emptyClientStore- -- The server is empty+ -- The server is empty let sstore1 = emptyServerStore- -- Client A makes sync request 1+ -- Client A makes sync request 1 let req1 = makeSyncRequest cAstore1- -- The server processes sync request 1+ -- The server processes sync request 1 (resp1, sstore2) <- func sstore1 req1 let addedItems = syncResponseClientAdded resp1 case M.toList addedItems of [(ClientId 0, clientAdditionId)] -> do let items = M.singleton clientAdditionId i liftIO $ sstore2 `shouldBe` (ServerStore {serverStoreItems = items})- -- Client A merges the response+ -- Client A merges the response let cAstore2 = mergeSyncResponse cAstore1 resp1 liftIO $ cAstore2 `shouldBe` (emptyClientStore {clientStoreSynced = items})- -- Client B makes sync request 2+ -- Client B makes sync request 2 let req2 = makeSyncRequest cBstore1- -- The server processes sync request 2+ -- The server processes sync request 2 (resp2, sstore3) <- func sstore2 req2 liftIO $ do resp2 `shouldBe` (emptySyncResponse {syncResponseServerAdded = items}) sstore3 `shouldBe` sstore2 -- pPrint cBstore2- -- Client B merges the response+ -- Client B merges the response let cBstore2 = mergeSyncResponse cBstore1 resp2 liftIO $ cBstore2 `shouldBe` (emptyClientStore {clientStoreSynced = items})- -- Client A and Client B now have the same store+ -- Client A and Client B now have the same store liftIO $ cAstore2 `shouldBe` cBstore2 _ -> liftIO $ expectationFailure "Should have found exactly one added item."- it "succesfully syncs a deletion across to a second client" $- forAllValid $ \uuid ->+ it "succesfully syncs a deletion across to a second client"+ $ forAllValid+ $ \uuid -> forAllValid $ \i -> eval $ do let cAstore1 = emptyClientStore {clientStoreSynced = M.singleton uuid i}- -- Client A has a synced item.- -- Client B had synced that same item, but has since deleted it.+ -- Client A has a synced item.+ -- Client B had synced that same item, but has since deleted it. let cBstore1 = emptyClientStore {clientStoreDeleted = S.singleton uuid}- -- The server still has the undeleted item+ -- The server still has the undeleted item let sstore1 = ServerStore {serverStoreItems = M.singleton uuid i}- -- Client B makes sync request 1+ -- Client B makes sync request 1 let req1 = makeSyncRequest cBstore1- -- The server processes sync request 1+ -- The server processes sync request 1 (resp1, sstore2) <- func sstore1 req1 liftIO $ do resp1 `shouldBe` emptySyncResponse {syncResponseClientDeleted = S.singleton uuid} sstore2 `shouldBe` emptyServerStore- -- Client B merges the response+ -- Client B merges the response let cBstore2 = mergeSyncResponse cBstore1 resp1 liftIO $ cBstore2 `shouldBe` emptyClientStore- -- Client A makes sync request 2+ -- Client A makes sync request 2 let req2 = makeSyncRequest cAstore1- -- The server processes sync request 2+ -- The server processes sync request 2 (resp2, sstore3) <- func sstore2 req2 liftIO $ do resp2 `shouldBe` emptySyncResponse {syncResponseServerDeleted = S.singleton uuid} sstore3 `shouldBe` sstore2- -- Client A merges the response+ -- Client A merges the response let cAstore2 = mergeSyncResponse cAstore1 resp2 liftIO $ cAstore2 `shouldBe` emptyClientStore- -- Client A and Client B now have the same store+ -- Client A and Client B now have the same store liftIO $ cAstore2 `shouldBe` cBstore2- it "does not run into a conflict if two clients both try to sync a deletion" $- forAllValid $ \uuid ->+ it "does not run into a conflict if two clients both try to sync a deletion"+ $ forAllValid+ $ \uuid -> forAllValid $ \i -> eval $ do let cAstore1 = emptyClientStore {clientStoreDeleted = S.singleton uuid}- -- Both client a and client b delete an item.+ -- Both client a and client b delete an item. let cBstore1 = emptyClientStore {clientStoreDeleted = S.singleton uuid}- -- The server still has the undeleted item+ -- The server still has the undeleted item let sstore1 = ServerStore {serverStoreItems = M.singleton uuid i}- -- Client A makes sync request 1+ -- Client A makes sync request 1 let req1 = makeSyncRequest cAstore1- -- The server processes sync request 1+ -- The server processes sync request 1 (resp1, sstore2) <- func sstore1 req1 liftIO $ do resp1 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = S.singleton uuid}) sstore2 `shouldBe` (ServerStore {serverStoreItems = M.empty})- -- Client A merges the response+ -- Client A merges the response let cAstore2 = mergeSyncResponse cAstore1 resp1 liftIO $ cAstore2 `shouldBe` emptyClientStore- -- Client B makes sync request 2+ -- Client B makes sync request 2 let req2 = makeSyncRequest cBstore1- -- The server processes sync request 2+ -- The server processes sync request 2 (resp2, sstore3) <- func sstore2 req2 liftIO $ do resp2 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = S.singleton uuid}) sstore3 `shouldBe` sstore2- -- Client B merges the response+ -- Client B merges the response let cBstore2 = mergeSyncResponse cBstore1 resp2 liftIO $ do cBstore2 `shouldBe` emptyClientStore- -- Client A and Client B now have the same store+ -- Client A and Client B now have the same store cAstore2 `shouldBe` cBstore2 describe "Multiple items" $ do it- "makes no change if the sync request reflects the same local state with an empty sync response" $- forAllValid $ \sis -> do+ "makes no change if the sync request reflects the same local state with an empty sync response"+ $ forAllValid+ $ \sis -> do let cs = ServerStore sis (sr, cs') <-- eval $- func cs $- SyncRequest- { syncRequestAdded = M.empty- , syncRequestSynced = M.keysSet sis- , syncRequestDeleted = S.empty- }+ eval+ $ func cs+ $ SyncRequest+ { syncRequestAdded = M.empty,+ syncRequestSynced = M.keysSet sis,+ syncRequestDeleted = S.empty+ } cs' `shouldBe` cs- sr `shouldBe`- SyncResponse- { syncResponseClientAdded = M.empty- , syncResponseClientDeleted = S.empty- , syncResponseServerAdded = M.empty- , syncResponseServerDeleted = S.empty+ sr+ `shouldBe` SyncResponse+ { syncResponseClientAdded = M.empty,+ syncResponseClientDeleted = S.empty,+ syncResponseServerAdded = M.empty,+ syncResponseServerDeleted = S.empty }- it "successfully syncs additions accross to a second client" $- forAllValid $ \is ->+ it "successfully syncs additions accross to a second client"+ $ forAllValid+ $ \is -> eval $ do let cAstore1 = emptyClientStore {clientStoreAdded = is}- -- Client B is empty+ -- Client B is empty let cBstore1 = emptyClientStore- -- The server is empty+ -- The server is empty let sstore1 = emptyServerStore- -- Client A makes sync request 1+ -- Client A makes sync request 1 let req1 = makeSyncRequest cAstore1- -- The server processes sync request 1+ -- The server processes sync request 1 (resp1, sstore2) <- func sstore1 req1- -- Client A merges the response+ -- Client A merges the response let cAstore2 = mergeSyncResponse cAstore1 resp1 let items = clientStoreSynced cAstore2 liftIO $ do clientStoreAdded cAstore2 `shouldBe` M.empty sstore2 `shouldBe` (ServerStore {serverStoreItems = items}) liftIO $ cAstore2 `shouldBe` (emptyClientStore {clientStoreSynced = items})- -- Client B makes sync request 2+ -- Client B makes sync request 2 let req2 = makeSyncRequest cBstore1- -- The server processes sync request 2+ -- The server processes sync request 2 (resp2, sstore3) <- func sstore2 req2 liftIO $ do resp2 `shouldBe` (emptySyncResponse {syncResponseServerAdded = items}) sstore3 `shouldBe` sstore2- -- Client B merges the response+ -- Client B merges the response let cBstore2 = mergeSyncResponse cBstore1 resp2 liftIO $ cBstore2 `shouldBe` (emptyClientStore {clientStoreSynced = items})- -- Client A and Client B now have the same store+ -- Client A and Client B now have the same store liftIO $ cAstore2 `shouldBe` cBstore2- it "succesfully syncs deletions across to a second client" $- forAllValid $ \syncedItems ->+ it "succesfully syncs deletions across to a second client"+ $ forAllValid+ $ \syncedItems -> eval $ do let itemIds = M.keysSet syncedItems let cAstore1 = emptyClientStore {clientStoreSynced = syncedItems}- -- Client A has synced items- -- Client B had synced the same items, but has since deleted them.+ -- Client A has synced items+ -- Client B had synced the same items, but has since deleted them. let cBstore1 = emptyClientStore {clientStoreDeleted = itemIds}- -- The server still has the undeleted item+ -- The server still has the undeleted item let sstore1 = ServerStore {serverStoreItems = syncedItems}- -- Client B makes sync request 1+ -- Client B makes sync request 1 let req1 = makeSyncRequest cBstore1- -- The server processes sync request 1+ -- The server processes sync request 1 (resp1, sstore2) <- func sstore1 req1 liftIO $ do resp1 `shouldBe` emptySyncResponse {syncResponseClientDeleted = itemIds} sstore2 `shouldBe` emptyServerStore- -- Client B merges the response+ -- Client B merges the response let cBstore2 = mergeSyncResponse cBstore1 resp1 liftIO $ cBstore2 `shouldBe` emptyClientStore- -- Client A makes sync request 2+ -- Client A makes sync request 2 let req2 = makeSyncRequest cAstore1- -- The server processes sync request 2+ -- The server processes sync request 2 (resp2, sstore3) <- func sstore2 req2 liftIO $ do resp2 `shouldBe` emptySyncResponse {syncResponseServerDeleted = itemIds} sstore3 `shouldBe` sstore2- -- Client A merges the response+ -- Client A merges the response let cAstore2 = mergeSyncResponse cAstore1 resp2 liftIO $ cAstore2 `shouldBe` emptyClientStore- -- Client A and Client B now have the same store+ -- Client A and Client B now have the same store liftIO $ cAstore2 `shouldBe` cBstore2- it "does not run into a conflict if two clients both try to sync a deletion" $- forAllValid $ \items ->+ it "does not run into a conflict if two clients both try to sync a deletion"+ $ forAllValid+ $ \items -> eval $ do let cAstore1 = emptyClientStore {clientStoreDeleted = M.keysSet items}- -- Both client a and client b delete their items.+ -- Both client a and client b delete their items. let cBstore1 = emptyClientStore {clientStoreDeleted = M.keysSet items}- -- The server still has the undeleted items+ -- The server still has the undeleted items let sstore1 = ServerStore {serverStoreItems = items}- -- Client A makes sync request 1+ -- Client A makes sync request 1 let req1 = makeSyncRequest cAstore1- -- The server processes sync request 1+ -- The server processes sync request 1 (resp1, sstore2) <- func sstore1 req1 liftIO $ do resp1 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = M.keysSet items}) sstore2 `shouldBe` (ServerStore {serverStoreItems = M.empty}) -- TODO will probably need some sort of tombstoning.- -- Client A merges the response+ -- Client A merges the response let cAstore2 = mergeSyncResponse cAstore1 resp1 liftIO $ cAstore2 `shouldBe` emptyClientStore- -- Client B makes sync request 2+ -- Client B makes sync request 2 let req2 = makeSyncRequest cBstore1- -- The server processes sync request 2+ -- The server processes sync request 2 (resp2, sstore3) <- func sstore2 req2 liftIO $ do resp2 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = M.keysSet items}) sstore3 `shouldBe` sstore2- -- Client B merges the response+ -- Client B merges the response let cBstore2 = mergeSyncResponse cBstore1 resp2 liftIO $ do cBstore2 `shouldBe` emptyClientStore- -- Client A and Client B now have the same store+ -- Client A and Client B now have the same store cAstore2 `shouldBe` cBstore2 describe "General properties" $ do- it "produces valid results" $- forAllValid $ \cs ->+ it "produces valid results"+ $ forAllValid+ $ \cs -> forAllValid $ \sr -> do res <- eval $ func cs sr shouldBeValid res- it "successfully syncs two clients using a central store" $- forAllValid $ \store1 ->+ it "successfully syncs two clients using a central store"+ $ forAllValid+ $ \store1 -> eval $ do let central = ServerStore M.empty let store2 = emptyClientStore@@ -401,8 +428,9 @@ (sresp3, _) <- func central'' sreq3 let store1'' = mergeSyncResponse store1' sresp3 liftIO $ store1'' `shouldBe` store2'- it "ensures that syncing is idempotent" $- forAllValid $ \central1 ->+ it "ensures that syncing is idempotent"+ $ forAllValid+ $ \central1 -> forAllValid $ \local1 -> eval $ do let sreq1 = makeSyncRequest local1@@ -415,16 +443,12 @@ local2 `shouldBe` local3 central2 `shouldBe` central3 -newtype D m a =- D- { unD :: StateT StdGen m a- }+newtype D m a+ = D+ { unD :: StateT StdGen m a+ } deriving (Generic, Functor, Applicative, Monad, MonadState StdGen, MonadTrans, MonadIO) --- evalD :: D Identity a -> a--- evalD = runIdentity . evalDM--- runD :: D Identity a -> StdGen -> (a, StdGen)--- runD = runState . unD evalDM :: Functor m => D m a -> m a evalDM d = fst <$> runDM d (mkStdGen 42)
test/Data/Mergeless/ItemSpec.hs view
@@ -1,19 +1,18 @@ {-# LANGUAGE TypeApplications #-} module Data.Mergeless.ItemSpec- ( spec- ) where+ ( spec,+ )+where +import Data.GenValidity.Mergeless.Item ()+import Data.GenValidity.UUID () import Data.Int (Int)-+import Data.Mergeless.Item import Test.Hspec import Test.Validity import Test.Validity.Aeson -import Data.GenValidity.Mergeless.Item ()-import Data.GenValidity.UUID ()-import Data.Mergeless.Item- {-# ANN module ("HLint: ignore Reduce duplication" :: String) #-} spec :: Spec@@ -30,12 +29,15 @@ eqSpecOnValid @(ServerItem Int) genValidSpec @(ServerItem Int) jsonSpecOnValid @(ServerItem Int)- describe "makeItemSyncrequest" $- it "produces valid requests" $ producesValidsOnValids (makeItemSyncRequest @Int)- describe "mergeItemSyncResponse" $- it "produces valid client items" $ producesValidsOnValids2 (mergeItemSyncResponse @Int)- describe "processItemSync" $- it "produces valid tuples" $ producesValidsOnValids2 (processServerItemSync @Int)+ describe "makeItemSyncrequest"+ $ it "produces valid requests"+ $ producesValidsOnValids (makeItemSyncRequest @Int)+ describe "mergeItemSyncResponse"+ $ it "produces valid client items"+ $ producesValidsOnValids2 (mergeItemSyncResponse @Int)+ describe "processItemSync"+ $ it "produces valid tuples"+ $ producesValidsOnValids2 (processServerItemSync @Int) describe "processItemSync" $ do describe "Interaction" $ do it "makes no changes if the sync request reflects the state of the empty server" $ do@@ -44,31 +46,35 @@ let (resp, store2) = processServerItemSync @Int store1 req store2 `shouldBe` store1 resp `shouldBe` ItemSyncResponseInSyncEmpty- it "makes no changes if the sync request reflects the state of the full server" $- forAllValid $ \s -> do+ it "makes no changes if the sync request reflects the state of the full server"+ $ forAllValid+ $ \s -> do let store1 = ServerItemFull s req = ItemSyncRequestKnown let (resp, store2) = processServerItemSync @Int store1 req store2 `shouldBe` store1 resp `shouldBe` ItemSyncResponseInSyncFull describe "Client changes" $ do- it "adds the item that the client tells the server to add" $- forAllValid $ \a -> do+ it "adds the item that the client tells the server to add"+ $ forAllValid+ $ \a -> do let store1 = ServerItemEmpty req = ItemSyncRequestNew a let (resp, store2) = processServerItemSync @Int store1 req store2 `shouldBe` ServerItemFull a resp `shouldBe` ItemSyncResponseClientAdded- it "deletes the item that the client tells the server to delete" $- forAllValid $ \s -> do+ it "deletes the item that the client tells the server to delete"+ $ forAllValid+ $ \s -> do let store1 = ServerItemFull s req = ItemSyncRequestDeleted let (resp, store2) = processServerItemSync @Int store1 req store2 `shouldBe` ServerItemEmpty resp `shouldBe` ItemSyncResponseClientDeleted describe "Server changes" $ do- it "tells the client that there is a new item at the server side" $- forAllValid $ \s -> do+ it "tells the client that there is a new item at the server side"+ $ forAllValid+ $ \s -> do let store1 = ServerItemFull s req = ItemSyncRequestPoll let (resp, store2) = processServerItemSync @Int store1 req@@ -81,8 +87,9 @@ store2 `shouldBe` store1 resp `shouldBe` ItemSyncResponseServerDeleted describe "High level properties" $ do- it "it always possible to add an item from scratch" $- forAllValid $ \a -> do+ it "it always possible to add an item from scratch"+ $ forAllValid+ $ \a -> do let cstore1 = ClientAdded (a :: Int) let sstore1 = ServerItemEmpty let req1 = makeItemSyncRequest cstore1@@ -91,8 +98,9 @@ resp1 `shouldBe` ItemSyncResponseClientAdded sstore2 `shouldBe` ServerItemFull a cstore2 `shouldBe` ClientSynced a- it "is idempotent with one client" $- forAllValid $ \si1 ->+ it "is idempotent with one client"+ $ forAllValid+ $ \si1 -> forAllValid $ \ci1 -> do let req1 = makeItemSyncRequest @Int ci1 (resp1, si2) = processServerItemSync si1 req1