packages feed

purview-0.2.0.0: test/EventHandlingSpec.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
module EventHandlingSpec where

import Prelude hiding (div)
import Control.Concurrent.STM.TChan
import Control.Monad.STM (atomically)
import Control.Monad.IO.Class
import Test.Hspec.QuickCheck
import Test.Hspec
import Test.QuickCheck
import Data.Aeson
import Data.Aeson.TH

import TreeGenerator
import Component
import ComponentHelpers
import EventHandling
import Events
import PrepareTree
import Rendering

import Data.Typeable

type Id a = a -> a

data TestAction = Up | Down

$(deriveJSON defaultOptions ''TestAction)

data SingleConstructor = SingleConstructor

$(deriveJSON (defaultOptions{tagSingleConstructors=True}) ''SingleConstructor)

{-

Just to clean up tests a bit. I dunno if this approach would work to clean
up the main event loop as well, since it calls "runEvent" (re: applying the event)
in a non-forked fashioned.  If you try void . forkIO you end up back in m a -> IO a
hell.

-}
-- apply :: MonadIO m => TChan Event -> Event -> Purview event m -> m (Purview event m)
-- apply eventBus newStateEvent@StateChangeEvent {} component =
--   pure $ applyNewState newStateEvent component
-- apply eventBus fromEvent@Event {event=eventKind} component =
--   case eventKind of
--     "newState" -> pure $ applyNewState fromEvent component
--     _          -> do
--       events <- runEvent fromEvent component
--       liftIO $ mapM_ (atomically . writeTChan eventBus) events
--       pure component

spec :: SpecWith ()
spec = parallel $ do

  describe "applyNewState" $ do
    it "applies new state at the top level" $ do
      let
        reducer "test" st = (1, [])
        reducer _      st = (0, [])

        clickHandler :: (Int -> Purview String IO) -> Purview () IO
        clickHandler = handler' [] 0 reducer

        component = prepareTree $ clickHandler $ \state -> div [ text (show state) ]

        event = StateChangeEvent (\state -> state + 1 :: Int) (Just [])

        appliedClickHandler :: (Int -> Purview String IO) -> Purview () IO
        appliedClickHandler = handler' [] 1 reducer

        applied = prepareTree $ appliedClickHandler $ \state -> div [ text (show state) ]

      -- state should now be 1
      applyNewState event component `shouldBe` applied

    it "applies new state at a lower level" $ do
      let
        reducer "test" st = (1, [])
        reducer _      st = (0, [])

        clickHandler :: (Int -> Purview String IO) -> Purview String IO
        clickHandler = handler' [] 0 reducer

        component = prepareTree $ clickHandler $ \_ -> clickHandler $ \_ -> div []

        event = StateChangeEvent (\state -> state + 1 :: Int) (Just [0])

        appliedClickHandler :: (Int -> Purview String IO) -> Purview String IO
        appliedClickHandler = handler' [] 1 reducer

        applied = prepareTree $ clickHandler $ \_ -> appliedClickHandler $ \_ -> div []

      -- state should now be 1
      applyNewState event component `shouldBe` applied


  describe "runEvent" $ do
    it "applies an event at the top level" $ do
      let
        reducer "test" st = (1, [])
        reducer _      st = (0, [])

        clickHandler :: (Int -> Purview String IO) -> Purview () IO
        clickHandler = handler' [] (0 :: Int) reducer

        component = prepareTree $ clickHandler $ \state -> div [ text (show state) ]

        event = InternalEvent { event = "test" :: String, childId = Nothing, handlerId = Just [] }

      [stateChangeEvent] <- runEvent event component

      case stateChangeEvent of
        StateChangeEvent fn id -> case cast fn of
          Just fn' -> fn' (0 :: Int) `shouldBe` (1 :: Int)
          _        -> fail "state change fn wrong type"
        _ -> fail "didn't return a state change fn"

    it "applies an event to the lower level" $ do
      let
        reducerA "test" st = (1, [])
        reducerA _      st = (0, [])

        clickHandlerA :: (Int -> Purview String IO) -> Purview () IO
        clickHandlerA = handler' [] (0 :: Int) reducerA

        reducerB "test" st = (5, [])
        reducerB _      st = (6, [])

        clickHandlerB :: (Int -> Purview String IO) -> Purview String IO
        clickHandlerB = handler' [] (0 :: Int) reducerB

        component = prepareTree $ clickHandlerA $ \_ -> clickHandlerB $ \_ -> div []

        event = InternalEvent { event = "test" :: String, childId = Nothing, handlerId = Just [0] }

      [stateChangeEvent] <- runEvent event component

      case stateChangeEvent of
        StateChangeEvent fn id -> case cast fn of
          Just fn' -> fn' (0 :: Int) `shouldBe` (5 :: Int)
          _        -> fail "state change fn wrong type"
        _ -> fail "didn't return a state change fn"


  describe "findEvent" $ do
    it "works" $ do
      let
        reducer "test" st = (const 1, [])
        reducer _      st = (const 0, [])

        clickHandler :: (Int -> Purview String IO) -> Purview () IO
        clickHandler = handler [] (0 :: Int) reducer

        tree = clickHandler $ const $ div [ onClick "test" $ div [ text "up" ] ]
        treeWithLocations = prepareTree tree

        -- EffectHandler Just [] Just [] "0" div [  Attr On "click" Just [0,0] div [  "up" ]  ]
        event' = FromFrontendEvent { kind="click", childLocation=Just [0, 0], location=Just [], value=Nothing }

      findEvent event' treeWithLocations
        `shouldBe`
        Just (InternalEvent ("test" :: String) (Just [0, 0]) (Just []))

main :: IO ()
main = hspec spec