packages feed

wild-bind-0.1.2.9: test/WildBind/SeqSpec.hs

{-# LANGUAGE OverloadedStrings #-}
module WildBind.SeqSpec
    ( main
    , spec
    ) where

import           Control.Applicative       ((<*>))
import           Control.Monad             (forM_)
import           Control.Monad.IO.Class    (liftIO)
import qualified Control.Monad.Trans.State as State
import           Data.IORef                (modifyIORef, newIORef, readIORef)
import           Data.Monoid               ((<>))
import           Test.Hspec

import           WildBind.Binding          (Binding, actDescription, as, binds, boundActions,
                                            boundInputs, justBefore, on, run)
import           WildBind.Description      (ActionDescription)
import           WildBind.Seq              (fromSeq, prefix, reviseSeq, toSeq, withCancel,
                                            withPrefix)

import           WildBind.ForTest          (SampleInput (..), SampleState (..), boundDescs,
                                            checkBoundDesc, checkBoundDescs, checkBoundInputs,
                                            curBoundDesc, curBoundDescs, curBoundInputs,
                                            evalStateEmpty, execAll, withRefChecker)

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
  spec_prefix
  spec_SeqBinding
  spec_reviseSeq

spec_prefix :: Spec
spec_prefix = describe "prefix" $ do
  let base_b = binds $ do
        on SIa `as` "a" `run` return ()
        on SIb `as` "b" `run` return ()
  specify "no prefix" $ do
    let b = prefix [] [] base_b
    boundDescs b (SS "") `shouldMatchList`
      [ (SIa, "a"),
        (SIb, "b")
      ]
  specify "one prefix" $ evalStateEmpty $ do
    State.put $ prefix [] [SIc] base_b
    checkBoundInputs (SS "") [SIc]
    execAll (SS "") [SIc]
    checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]
    execAll (SS "") [SIc]
    checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]
    execAll (SS "") [SIa]
    checkBoundInputs (SS "") [SIc]
  specify "two prefixes" $ evalStateEmpty $ do
    State.put $ prefix [] [SIc, SIb] base_b
    checkBoundInputs (SS "") [SIc]
    execAll (SS "") [SIc]
    checkBoundInputs (SS "") [SIb]
    execAll (SS "") [SIb]
    checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]
    execAll (SS "") [SIa]
    checkBoundInputs (SS "") [SIc]
  specify "cancel binding" $ evalStateEmpty $ do
    State.put $ prefix [SIa] [SIc, SIb] base_b
    checkBoundInputs (SS "") [SIc]
    execAll (SS "") [SIc] -- there is no cancel binding at the top level.
    checkBoundInputs (SS "") [SIa, SIb]
    checkBoundDesc (SS "") SIa "cancel"
    execAll (SS "") [SIa]
    checkBoundInputs (SS "") [SIc]
    execAll (SS "") [SIc, SIb]
    checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]  -- cancel binding should be weak and overridden.
    execAll (SS "") [SIb]
    checkBoundInputs (SS "") [SIc]
    execAll (SS "") [SIc, SIb]
    checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]
    execAll (SS "") [SIa]
    checkBoundInputs (SS "") [SIc]


spec_SeqBinding :: Spec
spec_SeqBinding = describe "SeqBinding" $ do
  let b_a = binds $ on SIa `as` "a" `run` return ()
      b_b = binds $ on SIb `as` "b" `run` return ()
  describe "withPrefix" $ do
    it "should allow nesting" $ evalStateEmpty $ do
      State.put $ fromSeq $ withPrefix [SIb] $ withPrefix [SIc] $ withPrefix [SIa] $ toSeq (b_a <> b_b)
      checkBoundInputs (SS "") [SIb]
      execAll (SS "") [SIb]
      checkBoundInputs (SS "") [SIc]
      execAll (SS "") [SIc]
      checkBoundInputs (SS "") [SIa]
      execAll (SS "") [SIa]
      checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]
      execAll (SS "") [SIa]
      checkBoundInputs (SS "") [SIb]
  describe "mappend" $ do
    it "should be able to combine SeqBindings with different prefixes." $ evalStateEmpty $ do
      State.put $ fromSeq $ withPrefix [SIc] $ ( (withPrefix [SIa, SIc] $ toSeq $ b_a)
                                                 <> (withPrefix [SIa] $ toSeq $ b_b)
                                               )
      checkBoundInputs (SS "") [SIc]
      execAll (SS "") [SIc]
      checkBoundInputs (SS "") [SIa]
      execAll (SS "") [SIa]
      checkBoundInputs (SS "") [SIc, SIb]
      checkBoundDesc (SS "") SIb "b"
      execAll (SS "") [SIb]
      checkBoundInputs (SS "") [SIc]
      execAll (SS "") [SIc, SIa]
      checkBoundInputs (SS "") [SIc, SIb]
      execAll (SS "") [SIc]
      checkBoundDescs (SS "") [(SIa, "a")]
      execAll (SS "") [SIa]
      checkBoundInputs (SS "") [SIc]
  describe "withCancel" $ do
    it "should weakly add 'cancel' binding when at least one prefix is kept in the state." $ evalStateEmpty $ do
      State.put $ fromSeq $ withPrefix [SIa, SIc] $ withCancel [SIa, SIb, SIc] $ ( toSeq b_a
                                                                                   <> (withPrefix [SIc] $ toSeq b_b)
                                                                                 )
      let checkPrefixOne = do
            checkBoundInputs (SS "") [SIa]
            execAll (SS "") [SIa]
            checkBoundInputs (SS "") [SIa, SIb, SIc]
            forM_ [SIa, SIb] $ \c -> checkBoundDesc (SS "") c "cancel"
      checkPrefixOne
      execAll (SS "") [SIa]
      checkPrefixOne
      execAll (SS "") [SIc]
      checkBoundInputs (SS "") [SIa, SIb, SIc]
      checkBoundDesc (SS "") SIa "a"
      checkBoundDesc (SS "") SIb "cancel"
      execAll (SS "") [SIa]
      checkPrefixOne
      execAll (SS "") [SIc, SIb]
      checkPrefixOne
      execAll (SS "") [SIc, SIc]
      checkBoundDescs (SS "") [(SIa, "cancel"), (SIb, "b"), (SIc, "cancel")]
      execAll (SS "") [SIb]
      checkPrefixOne

spec_reviseSeq :: Spec
spec_reviseSeq = describe "reviseSeq" $ do
  it "should allow access to prefix keys input so far" $ evalStateEmpty $ withRefChecker [] $ \out checkOut -> do
    act_out <- liftIO $ newIORef ("" :: String)
    let sb = withCancel [SIa] $ withPrefix [SIa, SIb, SIc] $ toSeq $ base_b
        base_b = binds $ on SIb `as` "B" `run` modifyIORef act_out (++ "B executed")
        rev ps _ _ = justBefore $ modifyIORef out (++ [ps])
    State.put $ fromSeq $ reviseSeq rev sb
    execAll (SS "") [SIa, SIa]
    checkOut [[], [SIa]]
    execAll (SS "") [SIa, SIb, SIc]
    checkOut [[], [SIa], [], [SIa], [SIa, SIb]]
    liftIO $ readIORef act_out `shouldReturn` ""
    execAll (SS "") [SIb]
    checkOut [[], [SIa], [], [SIa], [SIa, SIb], [SIa, SIb, SIc]]
    liftIO $ readIORef act_out `shouldReturn` "B executed"
  it "should allow unbinding" $ evalStateEmpty $ do
    let sb = withPrefix [SIa]
             ( toSeq ba
               <> (withPrefix [SIb] $ toSeq bab)
               <> (withPrefix [SIa] $ toSeq baa)
             )
        ba  = binds $ on SIc `as` "c on a" `run` return ()
        bab = binds $ on SIc `as` "c on ab" `run` return ()
        baa = binds $ do
          on SIc `as` "c on aa" `run` return ()
          on SIb `as` "b on aa" `run` return ()
        rev ps _ i act = if (ps == [SIa] && i == SIb) || (ps == [SIa,SIa] && i == SIc)
                         then Nothing
                         else Just act
    State.put $ fromSeq $ reviseSeq rev sb
    checkBoundInputs (SS "") [SIa]
    execAll (SS "") [SIa]
    checkBoundInputs (SS "") [SIa, SIc] -- SIb should be canceled
    execAll (SS "") [SIa]
    checkBoundDescs (SS "") [(SIb, "b on aa")] -- SIc should be canceled
    execAll (SS "") [SIb]
    checkBoundInputs (SS "") [SIa]