{-# LANGUAGE RankNTypes, OverloadedStrings #-} module WildBind.BindingSpec (main, spec) where import Control.Applicative ((<$>), (<*>), pure) import Control.Monad (void) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Trans.Class (lift) import qualified Control.Monad.Trans.Reader as Reader import qualified Control.Monad.Trans.State as State import Data.Maybe (isNothing, fromJust) import Data.Monoid (mempty, (<>), mconcat) import Data.IORef (IORef, modifyIORef, newIORef, readIORef, writeIORef) import qualified Lens.Micro as Lens import Test.Hspec import Test.QuickCheck (Gen, Arbitrary(arbitrary), property, listOf, sample') import qualified WildBind.Binding as WB import WildBind.ForTest ( SampleInput(..), SampleState(..), SampleBackState(..), inputAll, execAll, evalStateEmpty, boundDescs, boundDescs', checkBoundDescs, withRefChecker ) main :: IO () main = hspec spec data BiggerSampleBackState = BSB { _lSB :: SampleBackState, _rSB :: SampleBackState } deriving (Show, Eq, Ord) lSB :: Lens.Lens' BiggerSampleBackState SampleBackState lSB = Lens.lens _lSB (\bsb sb -> bsb { _lSB = sb }) rSB :: Lens.Lens' BiggerSampleBackState SampleBackState rSB = Lens.lens _rSB (\bsb sb -> bsb { _rSB = sb }) -- 'view' is since microlens-0.3.5.0 view :: Lens.Lens' s a -> s -> a view l s = s Lens.^. l newStrRef :: MonadIO m => m (IORef String) newStrRef = liftIO $ newIORef [] withStrRef :: MonadIO m => (IORef String -> (String -> m ()) -> m ()) -> m () withStrRef = withRefChecker [] outOn :: MonadIO m => IORef [a] -> i -> a -> (i, WB.Action m ()) outOn out_ref input out_elem = (input, WB.Action "" $ liftIO $ modifyIORef out_ref (++ [out_elem])) outOnS :: MonadIO m => IORef [a] -> i -> a -> (s -> s) -> (i, WB.Action (State.StateT s m) ()) outOnS out_ref input out_elem modifier = (,) input $ WB.Action "" $ do State.modify modifier liftIO $ modifyIORef out_ref (++ [out_elem]) genStatelessBinding :: Arbitrary a => IORef [a] -> Gen (WB.Binding s SampleInput) genStatelessBinding out_list = WB.binding <$> (listOf $ (,) <$> arbitrary <*> (WB.Action "" <$> outputRandomElem)) where outputRandomElem = do out_elem <- arbitrary return $ modifyIORef out_list (out_elem :) generate :: Gen a -> IO a generate = fmap head . sample' execAll' :: Ord i => [i] -> State.StateT (WB.Binding SampleState i) IO () execAll' = execAll (SS "") mempty_stateless :: WB.Binding SampleState SampleInput mempty_stateless = mempty checkMappend :: (WB.Binding SampleState SampleInput -> WB.Binding SampleState SampleInput) -> IO () checkMappend append_op = do out_ref <- newStrRef rand_binding <- generate $ genStatelessBinding out_ref let execute b = void $ inputAll b (SS "") =<< generate (listOf arbitrary) execute rand_binding out_orig <- readIORef out_ref writeIORef out_ref [] execute (append_op rand_binding) readIORef out_ref `shouldReturn` out_orig actRun :: Maybe (WB.Action IO a) -> IO () actRun = void . WB.actDo . fromJust checkInputsS :: (Show i, Eq i) => s -> [i] -> State.StateT (WB.Binding s i) IO () checkInputsS state exp_in = State.get >>= \b -> lift $ WB.boundInputs b state `shouldMatchList` exp_in checkInputsS' :: (Show i, Eq i) => [i] -> State.StateT (WB.Binding SampleState i) IO () checkInputsS' = checkInputsS (SS "") spec :: Spec spec = do spec_stateless spec_conversions spec_convBack spec_stateful spec_extend spec_conditionBoth spec_monadic spec_reader spec_revise spec_stateless :: Spec spec_stateless = do describe "Binding (Monoid instances)" $ do it "mempty returns empty binding" $ property ( isNothing <$> (WB.boundAction mempty_stateless <$> arbitrary <*> arbitrary) ) it "mempty `mappend` random == mempty" $ do checkMappend (mempty <>) it "random `mappend` mempty == mempty" $ do checkMappend (<> mempty) describe "binding" $ do it "returns a stateless Binding" $ withStrRef $ \out checkOut -> do let b = WB.binding [outOn out SIa 'A', outOn out SIb 'B'] WB.boundInputs b (SS "") `shouldMatchList` [SIa, SIb] WB.boundAction b (SS "") SIc `shouldSatisfy` isNothing actRun $ WB.boundAction b (SS "") SIa checkOut "A" actRun $ WB.boundAction b (SS "") SIb checkOut "AB" it "prefers the latter action if multiple actions are bound to the same input" $ withStrRef $ \out checkOut -> do let b = WB.binding [ outOn out SIa '1', outOn out SIa '2', outOn out SIa '3' ] actRun $ WB.boundAction b (SS "") SIa checkOut "3" describe "whenFront" $ do it "adds a condition on the front-end state" $ withStrRef $ \out checkOut -> do let b = WB.whenFront (\(SS s) -> s == "hoge") $ WB.binding [outOn out SIa 'A'] WB.boundInputs b (SS "") `shouldMatchList` [] WB.boundAction b (SS "") SIa `shouldSatisfy` isNothing WB.boundInputs b (SS "foobar") `shouldMatchList` [] WB.boundAction b (SS "foobar") SIa `shouldSatisfy` isNothing WB.boundInputs b (SS "hoge") `shouldMatchList` [SIa] actRun $ WB.boundAction b (SS "hoge") SIa checkOut "A" it "is AND condition" $ withStrRef $ \out checkOut -> do let raw_b = WB.binding [outOn out SIa 'A'] b = WB.whenFront ((<= 5) . length . unSS) $ WB.whenFront ((3 <=) . length . unSS) $ raw_b WB.boundInputs b (SS "ho") `shouldMatchList` [] WB.boundAction b (SS "ho") SIa `shouldSatisfy` isNothing WB.boundInputs b (SS "hogehoge") `shouldMatchList` [] WB.boundAction b (SS "hogehoge") SIa `shouldSatisfy` isNothing WB.boundInputs b (SS "hoge") `shouldMatchList` [SIa] actRun $ WB.boundAction b (SS "hoge") SIa checkOut "A" it "should be effective for derived Bindings" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let raw_b = WB.binding [outOn out SIa 'A'] State.put $ WB.whenFront (\(SS s) -> s == "foobar") $ raw_b checkInputsS (SS "hoge") [] checkInputsS (SS "foobar") [SIa] execAll (SS "foobar") [SIa] checkOut "A" checkInputsS (SS "hoge") [] checkInputsS (SS "foobar") [SIa] describe "ifFront" $ do it "chooses from independent Bindings" $ withStrRef $ \out checkOut -> do let b = WB.ifFront (\(SS s) -> length s <= 5) (WB.binding [outOn out SIa 'A']) (WB.binding [outOn out SIb 'B']) WB.boundInputs b (SS "hoge") `shouldMatchList` [SIa] WB.boundInputs b (SS "foobar") `shouldMatchList` [SIb] actRun $ WB.boundAction b (SS "foobar") SIb checkOut "B" it "adds AND conditions when nested" $ withStrRef $ \out checkOut -> do let b1 = WB.ifFront (\(SS s) -> length s <= 5) (WB.binding [outOn out SIa 'A']) (WB.binding [outOn out SIb 'B']) b = WB.ifFront (\(SS s) -> length s >= 3) b1 $ WB.binding [outOn out SIc 'C'] WB.boundInputs b (SS "") `shouldMatchList` [SIc] WB.boundInputs b (SS "foo") `shouldMatchList` [SIa] WB.boundInputs b (SS "hoge") `shouldMatchList` [SIa] WB.boundInputs b (SS "foobar") `shouldMatchList` [SIb] actRun $ WB.boundAction b (SS "ho") SIc checkOut "C" describe "Binding (mappend)" $ do it "combines two stateless Bindings" $ withStrRef $ \out checkOut -> do let b1 = WB.binding [outOn out SIa 'A'] b2 = WB.binding [outOn out SIb 'B'] b = b1 <> b2 WB.boundInputs b (SS "") `shouldMatchList` [SIa, SIb] void $ inputAll b (SS "") [SIa, SIb] checkOut "AB" it "front-end conditions are preserved" $ withStrRef $ \out _ -> do let b1 = WB.whenFront ((3 <=) . length . unSS) $ WB.binding [outOn out SIa 'A'] b2 = WB.whenFront ((<= 5) . length . unSS) $ WB.binding [outOn out SIb 'B'] b = b1 <> b2 WB.boundInputs b (SS "aa") `shouldMatchList` [SIb] WB.boundInputs b (SS "aabb") `shouldMatchList` [SIa, SIb] WB.boundInputs b (SS "aabbcc") `shouldMatchList` [SIa] it "prefers the latter Binding" $ withStrRef $ \out checkOut -> do let b1 = WB.binding [outOn out SIa '1', outOn out SIb 'B'] b2 = WB.binding [outOn out SIa '2'] b = b1 <> b2 WB.boundInputs b (SS "") `shouldMatchList` [SIa, SIb] actRun $ WB.boundAction b (SS "") SIa checkOut "2" it "preserves implicit back-end states" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let b1 = WB.startFrom (SB 0) $ WB.ifBack (== (SB 0)) (WB.binding' [outOnS out SIa '0' (\_ -> SB 1)]) $ WB.ifBack (== (SB 1)) (WB.binding' [outOnS out SIa '1' (\_ -> SB 0)]) $ mempty b2 = WB.startFrom (SB 0) $ WB.ifBack (== (SB 0)) (WB.binding' [outOnS out SIb '2' (\_ -> SB 1)]) $ WB.ifBack (== (SB 1)) (WB.binding' [outOnS out SIb '3' (\_ -> SB 0)]) $ mempty State.put (b1 <> b2) checkInputsS (SS "") [SIa, SIb] execAll (SS "") [SIa] checkOut "0" checkInputsS (SS "") [SIa, SIb] execAll (SS "") [SIb] checkOut "02" checkInputsS (SS "") [SIa, SIb] execAll (SS "") [SIa] checkOut "021" checkInputsS (SS "") [SIa, SIb] execAll (SS "") [SIb] checkOut "0213" checkInputsS (SS "") [SIa, SIb] execAll (SS "") [SIa] checkOut "02130" spec_conversions :: Spec spec_conversions = do describe "convFront" $ do it "converts front-end state" $ withStrRef $ \out checkOut -> do let orig_b = WB.whenFront (("hoge" ==) . unSS) $ WB.binding [outOn out SIa 'A'] b = WB.convFront SS orig_b WB.boundInputs b "" `shouldMatchList` [] WB.boundInputs b "hoge" `shouldMatchList` [SIa] actRun $ WB.boundAction b "hoge" SIa checkOut "A" describe "convInput" $ do it "converts input symbols" $ withStrRef $ \out checkOut -> do let orig_b = WB.binding [outOn out SIa 'A'] b = WB.convInput show orig_b WB.boundInputs b (SS "") `shouldMatchList` ["SIa"] actRun $ WB.boundAction b (SS "") "SIa" checkOut "A" describe "advice" $ do it "converts all actions in Binder" $ withStrRef $ \out checkOut -> do let convert_action a = a { WB.actDescription = WB.actDescription a <> "!!", WB.actDo = WB.actDo a >> (modifyIORef out (++ "!")) } b = WB.binds $ WB.advice convert_action $ do WB.on SIa `WB.as` "action a" `WB.run` modifyIORef out (++ "A") WB.on SIb `WB.as` "action b" `WB.run` modifyIORef out (++ "B") (WB.actDescription <$> WB.boundAction b () SIa) `shouldBe` Just "action a!!" (WB.actDescription <$> WB.boundAction b () SIb) `shouldBe` Just "action b!!" void $ inputAll b () [SIa] checkOut "A!" void $ inputAll b () [SIb] checkOut "A!B!" it "preserves the order of binding." $ withStrRef $ \out checkOut -> do let b = WB.binds $ WB.advice (WB.before $ modifyIORef out (++ "A")) $ do WB.on SIa `WB.run` modifyIORef out (++ "1") WB.on SIa `WB.run` modifyIORef out (++ "2") WB.on SIa `WB.run` modifyIORef out (++ "3") void $ inputAll b () [SIa] checkOut "A3" it "can nest" $ withStrRef $ \out checkOut -> do let b = WB.binds $ do WB.on SIa `WB.run` modifyIORef out (++ "1") WB.advice (WB.before $ modifyIORef out (++ "*")) $ do WB.on SIb `WB.run` modifyIORef out (++ "3") WB.advice (WB.after $ modifyIORef out (++ "@")) $ do WB.on SIa `WB.run` modifyIORef out (++ "4") WB.on SIc `WB.run` modifyIORef out (++ "5") WB.advice (WB.after $ modifyIORef out (++ "#")) $ do WB.on SIb `WB.run` modifyIORef out (++ "6") WB.on SIc `WB.run` modifyIORef out (++ "7") WB.on SIa `WB.run` modifyIORef out (++ "8") void $ inputAll b () [SIa] checkOut "*8" void $ inputAll b () [SIb] checkOut "*8*6#" void $ inputAll b () [SIc] checkOut "*8*6#*7#" describe "before" $ do it "prepends a monadic action" $ withStrRef $ \out checkOut -> do let act = WB.Action { WB.actDescription = "desc", WB.actDo = modifyIORef out (++ "ORIG") } got = WB.before (modifyIORef out (++ "before")) act WB.actDescription got `shouldBe` "desc" WB.actDo got checkOut "beforeORIG" describe "after" $ do it "appends a monadic action" $ withStrRef $ \out checkOut -> do let act = WB.Action { WB.actDescription = "desc", WB.actDo = modifyIORef out (++ "ORIG") } got = WB.after (modifyIORef out (++ "after")) act WB.actDescription got `shouldBe` "desc" WB.actDo got checkOut "ORIGafter" spec_convBack :: Spec spec_convBack = do describe "convBack" $ do it "can convert the back-end state by isomorphism" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let act = do out_elem <- head <$> show <$> unSB <$> State.get liftIO $ modifyIORef out (++ [out_elem]) State.modify succ orig_b = WB.binding' [(SIa, WB.Action "" act)] b = WB.convBack (\s _-> unSB s) SB orig_b State.put $ WB.startFrom 0 b checkInputsS' [SIa] execAll' [SIa] checkOut "0" execAll' [SIa] checkOut "01" execAll' [SIa] checkOut "012" it "can convert the back-end state by a lens" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let bl = WB.ifBack (== (SB 0)) (WB.binding' [outOnS out SIa '0' (\_ -> SB 1)]) $ WB.whenBack (== (SB 1)) (WB.binding' [outOnS out SIa '1' (\_ -> SB 0)]) br = WB.ifBack (== (SB 0)) (WB.binding' [outOnS out SIb '2' (\_ -> SB 1)]) $ WB.whenBack(== (SB 1)) (WB.binding' [outOnS out SIb '3' (\_ -> SB 0)]) bg = WB.whenBack (== (BSB (SB 0) (SB 0))) $ WB.binding' [outOnS out SIc '4' (\_ -> BSB (SB 1) (SB 1))] convBackByLens :: Lens.Lens' s a -> WB.Binding' a f i -> WB.Binding' s f i convBackByLens l = WB.convBack (Lens.set l) (view l) b = (convBackByLens lSB bl) <> (convBackByLens rSB br) <> bg State.put $ WB.startFrom (BSB (SB 0) (SB 0)) b checkInputsS' [SIa, SIb, SIc] execAll' [SIa] checkOut "0" checkInputsS' [SIa, SIb] execAll' [SIb] checkOut "02" checkInputsS' [SIa, SIb] execAll' [SIb] checkOut "023" checkInputsS' [SIa, SIb] execAll' [SIa] checkOut "0231" checkInputsS' [SIa, SIb, SIc] execAll' [SIc] checkOut "02314" checkInputsS' [SIa, SIb] execAll' [SIa, SIb] checkOut "0231413" spec_stateful :: Spec spec_stateful = do describe "binding'" $ do it "returns a stateful Binding" $ withStrRef $ \out checkOut -> do let act = do out_elem <- head <$> show <$> unSB <$> State.get liftIO $ modifyIORef out (++ [out_elem]) State.modify succ b = WB.binding' [(SIa, WB.Action "" act)] WB.boundInputs' b (SB 0) (SS "") `shouldBe` [SIa] WB.boundInputs' b (SB 10) (SS "hoge") `shouldBe` [SIa] void $ inputAll (WB.startFrom (SB 0) b) (SS "") $ replicate 12 SIa checkOut "012345678911" it "prefers the latter action if multiple actions are bound to the same input" $ withStrRef $ \out checkOut -> do let b = WB.startFrom (SB 0) $ WB.binding' [ outOn out SIa '1', outOn out SIa '2', outOn out SIa '3' ] actRun $ WB.boundAction b (SS "") SIa checkOut "3" it "can create a stateful Binding with different bound inputs for different back-end state" $ evalStateEmpty $ withStrRef $ \out checkOut -> do State.put $ WB.startFrom (SB 0) $ WB.ifBack (== (SB 0)) (WB.binding' [outOnS out SIa 'A' (\_ -> SB 1)]) $ WB.ifBack (== (SB 1)) (WB.binding' [outOnS out SIb 'B' (\_ -> SB 2)]) $ WB.ifBack (== (SB 2)) (WB.binding' [outOnS out SIc 'C' (\_ -> SB 0)]) $ mempty checkOut "" checkInputsS (SS "") [SIa] execAll (SS "") [SIa] checkOut "A" checkInputsS (SS "") [SIb] execAll (SS "") [SIb] checkOut "AB" checkInputsS (SS "") [SIc] execAll (SS "") [SIc] checkOut "ABC" checkInputsS (SS "") [SIa] describe "Binding (mappend, stateful)" $ do it "shares the explicit back-end state" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let b1 = WB.ifBack (== (SB 0)) (WB.binding' [outOnS out SIa 'A' (\_ -> SB 1)]) $ WB.ifBack (== (SB 1)) ( WB.binding' [outOnS out SIb 'B' (\_ -> SB 2), outOnS out SIc 'b' (\_ -> SB 2)] ) $ WB.ifBack (== (SB 2)) (WB.binding' [outOnS out SIc 'C' (\_ -> SB 0)]) $ mempty b2 = WB.whenBack (== (SB 1)) $ WB.binding' [outOnS out SIb 'D' (\_ -> SB 0)] b = b1 <> b2 State.put $ WB.startFrom (SB 0) b checkInputsS (SS "") [SIa] execAll (SS "") [SIa] checkOut "A" checkInputsS (SS "") [SIb, SIc] execAll (SS "") [SIb] checkOut "AD" checkInputsS (SS "") [SIa] execAll (SS "") [SIa, SIc] checkOut "ADAb" checkInputsS (SS "") [SIc] execAll (SS "") [SIc] checkOut "ADAbC" checkInputsS (SS "") [SIa] describe "ifBack" $ do it "chooses from unconditional bindings" $ withStrRef $ \out checkOut -> do let b = WB.ifBack (\(SB sb) -> sb < 5) (WB.binding [outOn out SIa 'A']) (WB.binding [outOn out SIb 'B']) ba = WB.startFrom (SB 4) b bb = WB.startFrom (SB 5) b WB.boundInputs ba (SS "") `shouldMatchList` [SIa] actRun $ WB.boundAction ba (SS "") SIa checkOut "A" WB.boundInputs bb (SS "") `shouldMatchList` [SIb] actRun $ WB.boundAction bb (SS "") SIb checkOut "AB" it "combines an extended stateless binding with a stateful binding" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let b_stateless = WB.binding [outOn out SIa 'A'] b = WB.ifBack (\(SB sb) -> sb < 5) (b_stateless <> WB.binding' [outOnS out SIb 'B' $ const (SB 10)]) $ WB.binding' [outOnS out SIc 'C' $ const (SB 3)] State.put $ WB.startFrom (SB 0) b checkInputsS' [SIa, SIb] execAll' [SIa] checkOut "A" checkInputsS' [SIa, SIb] execAll' [SIb] checkOut "AB" checkInputsS' [SIc] execAll' [SIc] checkOut "ABC" checkInputsS' [SIa, SIb] it "combines implicit stateful binding with a binding with newly introduced states" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let b1 = WB.startFrom (SB 0) $ WB.ifBack (== (SB 0)) (WB.binding' [outOnS out SIa 'A' $ const (SB 1)]) $ WB.binding' [outOnS out SIb 'B' $ const (SB 0)] b = WB.startFrom (SB 0) $ WB.ifBack (== (SB 0)) (WB.binding' [outOnS out SIa 'a' $ const (SB 1)]) $ WB.extend b1 <> WB.binding' [outOnS out SIc 'c' $ const (SB 0)] State.put b checkInputsS' [SIa] execAll' [SIa] checkOut "a" checkInputsS' [SIa, SIc] execAll' [SIa] checkOut "aA" checkInputsS' [SIb, SIc] execAll' [SIb] checkOut "aAB" checkInputsS' [SIa, SIc] execAll' [SIc] checkOut "aABc" checkInputsS' [SIa] describe "whenBack" $ do it "adds a condition to the back-end state" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let raw_b = WB.ifBack (== (SB 0)) (WB.binding' [outOnS out SIa '0' (\_ -> SB 1)]) $ WB.ifBack (== (SB 1)) (WB.binding' [outOnS out SIb '1' (\_ -> SB 0)]) $ mempty b = WB.whenBack (== SB 0) $ raw_b State.put $ WB.startFrom (SB 0) b checkInputsS' [SIa] execAll' [SIa] checkOut "0" checkInputsS' [] spec_extend :: Spec spec_extend = do describe "extend" $ do it "extends a stateless Binding" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let bl :: WB.Binding SampleState SampleInput bl = WB.binding [ outOn out SIa 'a', outOn out SIb 'b', outOn out SIc 'c'] bs = WB.ifBack (== (SB 0)) (WB.binding' [outOnS out SIa 'A' (\_ -> SB 1)]) $ WB.ifBack (== (SB 1)) (WB.binding' [outOnS out SIb 'B' (\_ -> SB 2)]) $ WB.ifBack (== (SB 2)) (WB.binding' [outOnS out SIc 'C' (\_ -> SB 0)]) $ mempty State.put $ WB.startFrom (SB 0) $ (WB.extend bl <> bs) checkInputsS' [SIa, SIb, SIc] execAll' [SIb, SIc, SIa] checkOut "bcA" checkInputsS' [SIa, SIb, SIc] execAll' [SIa, SIc, SIb] checkOut "bcAacB" checkInputsS' [SIa, SIb, SIc] execAll' [SIa, SIb, SIc] checkOut "bcAacBabC" spec_conditionBoth :: Spec spec_conditionBoth = do describe "ifBoth" $ do it "chooses bindings according to front-end and back-end states" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let b = WB.ifBoth (\ _ (SS fs) -> fs == "hoge") ( WB.binding' [ outOnS out SIa 'a' (SB . succ . unSB), outOnS out SIb 'b' (const $ SB 0) ] ) $ WB.ifBoth (\ (SB bs) (SS fs) -> length fs < bs) (WB.binding' [ outOnS out SIc 'c' (SB . pred . unSB) ]) (WB.binding' [ outOnS out SIb 'B' (SB . succ . unSB) ]) State.put $ WB.startFrom (SB 10) $ b checkInputsS (SS "hoge") [SIa, SIb] checkInputsS (SS "") [SIc] checkInputsS (SS "foooooobaaaaaa") [SIb] execAll (SS "hoge") [SIb] checkOut "b" checkInputsS (SS "") [SIb] execAll (SS "") [SIb] checkOut "bB" checkInputsS (SS "hoge") [SIa, SIb] checkInputsS (SS "") [SIc] checkInputsS (SS "a") [SIb] execAll (SS "") [SIc] checkOut "bBc" checkInputsS (SS "hoge") [SIa, SIb] checkInputsS (SS "") [SIb] checkInputsS (SS "a") [SIb] execAll (SS "hoge") $ replicate 5 SIa checkOut "bBcaaaaa" checkInputsS (SS "hoge") [SIa, SIb] checkInputsS (SS "fooo") [SIc] checkInputsS (SS "foooo") [SIb] describe "whenBoth" $ do let incr' out ret = outOnS out SIa ret (\(SB num) -> SB (num + 1)) decr' out ret = outOnS out SIb ret (\(SB num) -> SB (num - 1)) it "adds a condition to both front-end and back-end states" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let incr = incr' out decr = decr' out raw_b = WB.ifBack (== (SB 0)) (WB.binding' [incr '+']) (WB.binding' [incr '+', decr '-']) b = WB.whenBoth (\(SB num) (SS str) -> length str == num) $ raw_b State.put $ WB.startFrom (SB 0) $ b checkInputsS (SS "hoge") [] checkInputsS (SS "") [SIa] execAll (SS "") [SIa] checkOut "+" checkInputsS (SS "") [] checkInputsS (SS "e") [SIa, SIb] execAll (SS "e") [SIa] checkOut "++" checkInputsS (SS "e") [] checkInputsS (SS "eg") [SIa, SIb] execAll (SS "eg") [SIb] checkOut "++-" checkInputsS (SS "e") [SIa, SIb] it "creates independent conditions when combined with <>" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let incr = incr' out decr = decr' out bn = WB.ifBack (== (SB 0)) (WB.binding' [incr '+']) (WB.binding' [incr '+', decr '-']) bn' = WB.whenBoth (\(SB num) (SS str) -> length str == num) bn ba = WB.ifBack (== (SB 0)) (WB.binding' [incr 'p']) (WB.binding' [incr 'p', decr 'm']) ba' = WB.whenBoth (\(SB num) (SS str) -> read str == num) ba State.put $ WB.startFrom (SB 1) (bn' <> ba') checkInputsS (SS "10") [] checkInputsS (SS "4") [SIa, SIb] execAll (SS "4") [SIa] checkOut "+" checkInputsS (SS "2") [SIa, SIb] execAll (SS "2") [SIa] checkOut "+p" checkInputsS (SS "342") [SIa, SIb] execAll (SS "342") [SIb] checkOut "+p-" execAll (SS "2") [SIb] checkOut "+p-m" checkInputsS (SS "1") [SIa, SIb] execAll (SS "1") [SIb] checkOut "+p-mm" spec_monadic :: Spec spec_monadic = describe "Monadic construction of Binding" $ do describe "binds" $ do it "constructs stateless Binding" $ withStrRef $ \out checkOut -> do let putOut c = modifyIORef out (++ [c]) b = WB.binds $ do WB.on SIa `WB.run` putOut 'a' WB.on SIb `WB.run` do putOut 'b' putOut 'B' actRun $ WB.boundAction b (SS "") SIa checkOut "a" actRun $ WB.boundAction b (SS "") SIb checkOut "abB" it "prefers the latter action if multiple actions are bound to the same input" $ withStrRef $ \out checkOut -> do let b = WB.binds $ do WB.on SIa `WB.run` modifyIORef out (++ "1") WB.on SIa `WB.run` modifyIORef out (++ "2") WB.on SIa `WB.run` modifyIORef out (++ "3") actRun $ WB.boundAction b (SS "") SIa checkOut "3" describe "Binder" $ do it "can bind actions with different result types" $ withStrRef $ \out checkOut -> do let ret_b :: String ret_b = "return by b" b = WB.binds $ do -- it's ok if it compiles.. WB.on SIa `WB.run` do modifyIORef out (++ "a") return () WB.on SIb `WB.run` do modifyIORef out (++ "b") return ret_b actRun $ WB.boundAction b (SS "") SIb checkOut "b" describe "binds'" $ do it "constructs stateful Binding" $ evalStateEmpty $ withStrRef $ \out checkOut -> do State.put $ WB.startFrom (SB 0) $ WB.binds' $ do WB.on SIa `WB.run` (State.modify $ \(SB v) -> SB (v + 1)) WB.on SIb `WB.run` (State.modify $ \(SB v) -> SB (v - 1)) WB.on SIc `WB.run` do (SB cur) <- State.get liftIO $ modifyIORef out (++ show cur) execAll' [SIa, SIa, SIa] checkOut "" execAll' [SIc] checkOut "3" execAll' [SIb, SIb] checkOut "3" execAll' [SIc] checkOut "31" it "prefers the latter action if multiple actions are bound to the same input" $ withStrRef $ \out checkOut -> do let b = WB.startFrom (SB 0) $ WB.binds' $ do WB.on SIa `WB.run` (liftIO $ modifyIORef out (++ "1")) WB.on SIa `WB.run` (liftIO $ modifyIORef out (++ "2")) WB.on SIa `WB.run` (liftIO $ modifyIORef out (++ "3")) actRun $ WB.boundAction b (SS "") SIa checkOut "3" describe "as" $ do it "sets ActionDescription" $ do let b = WB.binds $ do WB.on SIa `WB.as` "action for a" `WB.run` return () WB.on SIb `WB.as` "action for b" `WB.run` return () (WB.actDescription <$> WB.boundAction b (SS "") SIa) `shouldBe` Just "action for a" (WB.actDescription <$> WB.boundAction b (SS "") SIb) `shouldBe` Just "action for b" (WB.actDescription <$> WB.boundAction b (SS "") SIc) `shouldBe` Nothing spec_reader :: Spec spec_reader = describe "binding with ReaderT action" $ do describe "bindsF" $ do it "allows actions to access front-end state" $ withStrRef $ \out checkOut -> do let b = WB.bindsF $ do WB.on SIa `WB.run` do fs <- Reader.ask liftIO $ modifyIORef out (++ unSS fs) actRun $ WB.boundAction b (SS "hoge") SIa checkOut "hoge" actRun $ WB.boundAction b (SS "_foobar") SIa checkOut "hoge_foobar" describe "bindsF'" $ do it "allows stateful actions to access front-end state" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let ba = WB.bindsF' $ do WB.on SIa `WB.run` do (SB bs) <- State.get (SS fs) <- lift $ Reader.ask if bs >= 0 then liftIO $ modifyIORef out (++ fs) else liftIO $ modifyIORef out (++ reverse fs) bb = WB.binds' $ do WB.on SIb `WB.run` State.modify (\(SB bs) -> SB (bs + 1)) bc = WB.binds' $ do WB.on SIc `WB.run` State.modify (\(SB bs) -> SB (bs - 1)) b = WB.startFrom (SB 0) (bb <> ba <> bc) State.put b execAll (SS "abc") [SIa, SIb, SIb] checkOut "abc" execAll (SS "123") [SIc, SIc, SIc, SIc, SIa] checkOut "abc321" execAll (SS "xyz") [SIa, SIb, SIb, SIa] checkOut "abc321zyxxyz" spec_revise :: Spec spec_revise = do describe "revise" $ do it "should allow unbinding" $ do let b = WB.binds $ do WB.on SIa `WB.as` "a" `WB.run` return () WB.on SIb `WB.as` "b" `WB.run` return () rev () _ i act = if i == SIa then Nothing else Just act got = WB.revise rev b boundDescs got (SS "") `shouldMatchList` [(SIb, "b")] it "should allow revising description" $ do let b = WB.binds $ do WB.on SIa `WB.as` "a" `WB.run` return () WB.on SIb `WB.as` "b" `WB.run` return () rev () _ _ act = Just $ act { WB.actDescription = mconcat $ replicate 3 $ WB.actDescription act } got = WB.revise rev b boundDescs got (SS "") `shouldMatchList` [(SIa, "aaa"), (SIb, "bbb")] it "should revise conditionally on front-end state" $ do let b = WB.binds $ do WB.on SIa `WB.as` "a" `WB.run` return () WB.on SIb `WB.as` "b" `WB.run` return () rev () (SS fs) i act = if i == SIa && length fs >= 3 then Nothing else Just act got = WB.revise rev b boundDescs got (SS "") `shouldMatchList` [(SIa, "a"), (SIb, "b")] boundDescs got (SS "xx") `shouldMatchList` [(SIa, "a"), (SIb, "b")] boundDescs got (SS "xxx") `shouldMatchList` [(SIb, "b")] boundDescs got (SS "xxxx") `shouldMatchList` [(SIb, "b")] it "should revise conditionally on back-end state" $ do let b = WB.binds $ do WB.on SIa `WB.as` "a" `WB.run` return () WB.on SIb `WB.as` "b" `WB.run` return () rev (SB bs) _ i act = if bs >= 5 && i == SIb then Nothing else Just act got = WB.revise rev $ WB.extend b boundDescs' got (SB 3) (SS "") `shouldMatchList` [(SIa, "a"), (SIb, "b")] boundDescs' got (SB 5) (SS "") `shouldMatchList` [(SIa, "a")] boundDescs' got (SB 7) (SS "") `shouldMatchList` [(SIa, "a")] it "should allow modifying the action" $ withStrRef $ \out checkOut -> do let b = WB.binds' $ do WB.on SIa `WB.as` "a" `WB.run` do (SB bs) <- State.get liftIO $ modifyIORef out (++ show bs) rev (SB bs) (SS fs) _ = WB.justBefore bf . WB.after af where bf = modifyIORef out (++ replicate bs 'X') af = modifyIORef out (++ fs) got = WB.revise rev b actRun $ WB.boundAction' got (SB 4) (SS "FF") SIa checkOut "XXXX4FF" it "should be effective after change of back-end state" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let b = WB.startFrom (SB 0) $ WB.binds' $ do WB.on SIa `WB.run` do (SB bs) <- State.get liftIO $ modifyIORef out (++ show bs) State.put (SB $ bs + 1) rev _ _ _ = WB.justAfter $ modifyIORef out (++ "X") got = WB.revise rev b State.put got execAll (SS "") [SIa] checkOut "0X" execAll (SS "") [SIa] checkOut "0X1X" execAll (SS "") [SIa] checkOut "0X1X2X" describe "revise'" $ do it "should allow modifying the back-end state" $ evalStateEmpty $ withStrRef $ \out checkOut -> do let b = WB.binds' $ do WB.on SIa `WB.as` "a" `WB.run` do (SB bs) <- State.get liftIO $ modifyIORef out (++ show bs) rev _ (SS fs) _ = WB.justAfter af . WB.before bf where bf = State.modify (\(SB s) -> SB (s + 1)) af = State.put $ SB $ length fs got = WB.startFrom (SB 0) $ WB.revise' rev b State.put got execAll (SS "abc") [SIa] checkOut "1" execAll (SS "a") [SIa] checkOut "14" execAll (SS "") [SIa] checkOut "142" it "should allow unbind conditionally" $ evalStateEmpty $ do let b = WB.binds' $ do WB.on SIa `WB.as` "a" `WB.run` State.modify (\(SB bs) -> SB $ bs + 1) WB.on SIb `WB.as` "b" `WB.run` return () rev (SB bs) (SS fs) i orig = if i == SIb && bs >= 3 && bs <= 6 && fs /= "XXX" then Nothing else Just orig got = WB.startFrom (SB 0) $ WB.revise' rev b State.put got checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")] execAll (SS "") [SIa, SIa] checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")] execAll (SS "") [SIa] checkBoundDescs (SS "") [(SIa, "a")] checkBoundDescs (SS "XXX") [(SIa, "a"), (SIb, "b")] execAll (SS "") [SIa, SIa, SIa] checkBoundDescs (SS "") [(SIa, "a")] execAll (SS "") [SIa] checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]