packages feed

ribosome-test-0.4.0.0: test/Ribosome/Test/MenuTest.hs

module Ribosome.Test.MenuTest where

import Conduit (ConduitT, yield, yieldMany)
import Control.Concurrent.MVar.Lifted (modifyMVar_)
import Control.Lens (view)
import Control.Monad.Trans.Resource (ResourceT, runResourceT)
import qualified Data.Map.Strict as Map (fromList)
import Hedgehog ((===))
import Test.Tasty (TestTree, testGroup)

import Ribosome.Control.StrictRibosome (StrictRibosome)
import Ribosome.Menu.Action (menuContinue, menuExecute, menuQuit)
import Ribosome.Menu.Data.FilteredMenuItem (FilteredMenuItem(FilteredMenuItem))
import qualified Ribosome.Menu.Data.FilteredMenuItem as FilteredMenuItem (item)
import Ribosome.Menu.Data.Menu (Menu(Menu), MenuFilter(MenuFilter))
import qualified Ribosome.Menu.Data.Menu as Menu (items)
import Ribosome.Menu.Data.MenuAction (MenuAction)
import Ribosome.Menu.Data.MenuConfig (MenuConfig(MenuConfig))
import Ribosome.Menu.Data.MenuConsumer (MenuConsumer(MenuConsumer))
import Ribosome.Menu.Data.MenuConsumerAction (MenuConsumerAction)
import qualified Ribosome.Menu.Data.MenuEvent as MenuEvent (MenuEvent(..))
import Ribosome.Menu.Data.MenuItem (MenuItem(MenuItem), simpleMenuItem)
import qualified Ribosome.Menu.Data.MenuItem as MenuItem (MenuItem(_text), text)
import Ribosome.Menu.Data.MenuRenderEvent (MenuRenderEvent)
import qualified Ribosome.Menu.Data.MenuRenderEvent as MenuRenderEvent (MenuRenderEvent(..))
import Ribosome.Menu.Data.MenuUpdate (MenuUpdate(MenuUpdate))
import Ribosome.Menu.Prompt.Data.Prompt (Prompt(Prompt))
import Ribosome.Menu.Prompt.Data.PromptConfig (PromptConfig(PromptConfig), PromptFlag(StartInsert))
import Ribosome.Menu.Prompt.Data.PromptEvent (PromptEvent)
import qualified Ribosome.Menu.Prompt.Data.PromptEvent as PromptEvent (PromptEvent(..))
import qualified Ribosome.Menu.Prompt.Data.PromptState as PromptState (PromptState(..))
import Ribosome.Menu.Prompt.Run (basicTransition, noPromptRenderer)
import Ribosome.Menu.Run (runMenu)
import Ribosome.Menu.Simple (
  basicMenu,
  defaultMenu,
  deleteByFilteredIndex,
  fuzzyMenuItemMatcher,
  markedMenuItems,
  markedMenuItemsOnly,
  selectedMenuItem,
  simpleMenu,
  )
import Ribosome.System.Time (sleep)
import Ribosome.Test.Run (UnitTest, unitTest)

promptInput ::
  MonadIO m =>
  [Text] ->
  ConduitT () PromptEvent m ()
promptInput chars = do
  lift $ sleep 0.1
  yieldMany (PromptEvent.Character <$> chars)

menuItems ::
  Monad m =>
  [Text] ->
  ConduitT () [MenuItem Text] m ()
menuItems =
  yield . fmap (simpleMenuItem "name")

storePrompt ::
  MonadBaseControl IO m =>
  MVar [Prompt] ->
  MenuUpdate m a Text ->
  m (MenuConsumerAction m a, Menu Text)
storePrompt prompts (MenuUpdate event menu) =
  check event
  where
    check (MenuEvent.PromptChange prompt) =
      store prompt
    check (MenuEvent.Mapping _ prompt) =
      store prompt
    check (MenuEvent.Quit _) =
      menuQuit menu
    check _ =
      menuContinue menu
    store prompt =
      modifyMVar_ prompts (return . (prompt :)) *> menuContinue menu

render ::
  MonadIO m =>
  MonadBaseControl IO m =>
  MVar [[FilteredMenuItem Text]] ->
  MenuRenderEvent m a Text ->
  m ()
render varItems (MenuRenderEvent.Render _ (Menu _ items _ _ _ _)) = do
  modifyMVar_ varItems (return . (items :))
  sleep 0.01
render _ (MenuRenderEvent.Quit _) =
  return ()

type TestM = StateT (StrictRibosome ()) (ResourceT IO)

menuTest ::
  (MenuUpdate TestM a Text -> TestM (MenuAction TestM a, Menu Text)) ->
  [Text] ->
  [Text] ->
  IO [[FilteredMenuItem Text]]
menuTest handler items chars = do
  itemsVar <- newMVar []
  void $ runResourceT $ runMenu (conf itemsVar) `execStateT` def
  readMVar itemsVar
  where
    conf itemsVar =
      MenuConfig (menuItems items) (MenuConsumer handler) (render itemsVar) promptConfig def
    promptConfig =
      PromptConfig (promptInput chars) basicTransition noPromptRenderer [StartInsert]

promptTest :: [Text] -> [Text] -> IO ([[FilteredMenuItem Text]], [Prompt])
promptTest items chars = do
  prompts <- newMVar []
  itemsResult <- menuTest (basicMenu fuzzyMenuItemMatcher (storePrompt prompts)) items chars
  (itemsResult,) <$> readMVar prompts

promptsTarget1 :: [Prompt]
promptsTarget1 =
  uncurry one' <$> [
    (1, PromptState.Insert),
    (0, PromptState.Normal),
    (0, PromptState.Normal),
    (1, PromptState.Insert)
    ]
  where
    one' c s = Prompt c s "i"

items1 :: [Text]
items1 =
  [
    "i1",
    "j1",
    "i2",
    "i3",
    "j2",
    "i4"
    ]

chars1 :: [Text]
chars1 =
  [
    "i",
    "esc",
    "k",
    "a",
    "2"
    ]

itemsTarget1 :: [[MenuItem Text]]
itemsTarget1 =
  [
    item <$> ["i2"],
    item <$> ["i1", "i2", "i3", "i4"]
    ]
  where
    item =
      simpleMenuItem "name"

test_pureMenuModeChange :: UnitTest
test_pureMenuModeChange = do
  (items, prompts) <- liftIO (promptTest items1 chars1)
  itemsTarget1 === (view FilteredMenuItem.item <$$> take 2 items)
  promptsTarget1 === (take 4 $ reverse prompts)

chars2 :: [Text]
chars2 =
  ["l", "o", "n", "g", "-", "i", "t", "e", "m"]

items2 :: [Text]
items2 =
  [
    "long",
    "short",
    "long-item",
    "longitem"
    ]

itemsTarget :: [MenuItem Text]
itemsTarget =
  [simpleMenuItem "name" "long-item"]

test_pureMenuFilter :: UnitTest
test_pureMenuFilter = do
  items <- liftIO (fst <$> promptTest items2 chars2)
  [itemsTarget] === (view FilteredMenuItem.item <$$> take 1 items)

chars3 :: [Text]
chars3 =
  ["i", "esc", "cr"]

items3 :: [Text]
items3 =
  [
    "item1",
    "item2"
    ]

exec ::
  MonadIO m =>
  MVar [Text] ->
  Menu Text ->
  Prompt ->
  m (MenuConsumerAction m a, Menu Text)
exec var m@(Menu _ items _ _ _ _) _ =
  swapMVar var (view (FilteredMenuItem.item . MenuItem.text) <$> items) *> menuQuit m

test_pureMenuExecute :: UnitTest
test_pureMenuExecute = do
  var <- newMVar []
  _ <- liftIO (menuTest (simpleMenu (Map.fromList [("cr", exec var)])) items3 chars3)
  (items3 ===) =<< readMVar var

charsMulti :: [Text]
charsMulti =
  ["esc", "k", "k", "space", "space", "space", "space", "j", "space", "cr"]

itemsMulti :: [Text]
itemsMulti =
  [
    "item1",
    "item2",
    "item3",
    "item4",
    "item5",
    "item6"
    ]

execMulti ::
  MonadIO m =>
  MVar (Maybe (NonEmpty Text)) ->
  Menu Text ->
  Prompt ->
  m (MenuConsumerAction m a, Menu Text)
execMulti var m _ =
  swapMVar var (MenuItem._text <$$> markedMenuItems m) *> menuQuit m

test_menuMultiMark :: UnitTest
test_menuMultiMark = do
  var <- newMVar Nothing
  _ <- liftIO (menuTest (defaultMenu (Map.fromList [("cr", execMulti var)])) itemsMulti charsMulti)
  (Just ("item3" :| ["item4", "item5"]) ===) =<< readMVar var

charsToggle :: [Text]
charsToggle =
  ["esc", "k", "space", "*", "i", "a", "b", "cr"]

itemsToggle :: [Text]
itemsToggle =
  [
    "a",
    "ab",
    "abc"
    ]

execToggle ::
  MonadIO m =>
  MVar (Maybe (NonEmpty Text)) ->
  Menu Text ->
  Prompt ->
  m (MenuConsumerAction m a, Menu Text)
execToggle var m _ =
  swapMVar var (MenuItem._text <$$> markedMenuItemsOnly m) *> menuQuit m

test_menuToggle :: UnitTest
test_menuToggle = do
  var <- newMVar Nothing
  _ <- liftIO (menuTest (defaultMenu (Map.fromList [("cr", execToggle var)])) itemsToggle charsToggle)
  (Nothing ===) =<< readMVar var

charsExecuteThunk :: [Text]
charsExecuteThunk =
  ["esc", "cr", "k", "cr", "esc"]

itemsExecuteThunk :: [Text]
itemsExecuteThunk =
  [
    "a",
    "b"
    ]

execExecuteThunk ::
  MonadIO m =>
  MonadBaseControl IO m =>
  MVar [Text] ->
  Menu Text ->
  Prompt ->
  m (MenuConsumerAction m a, Menu Text)
execExecuteThunk var m _ =
  menuExecute (modifyMVar_ var prepend) m
  where
    prepend a =
      pure $ a ++ maybeToList (MenuItem._text <$> selectedMenuItem m)

test_menuExecuteThunk :: UnitTest
test_menuExecuteThunk = do
  var <- newMVar []
  _ <- liftIO (menuTest (defaultMenu (Map.fromList [("cr", execExecuteThunk var)])) itemsExecuteThunk charsExecuteThunk)
  (["a", "b"] ===) =<< readMVar var

test_menuDeleteByFilteredIndex :: UnitTest
test_menuDeleteByFilteredIndex =
  (target ===) . fmap (view MenuItem.text) . view Menu.items . deleteByFilteredIndex [1, 2] $ menu
  where
    target =
      ["1", "2", "3", "5", "7", "8"]
    menu =
      Menu items filtered 0 [] (MenuFilter "") Nothing
    items =
      simpleMenuItem () <$> ["1", "2", "3", "4", "5", "6", "7", "8"]
    filtered =
      uncurry FilteredMenuItem . second menuItem <$> [(1, "2"), (3, "4"), (5, "6"), (7, "8")]
    menuItem t =
      MenuItem () t t

test_menu :: TestTree
test_menu =
  testGroup "menu" [
    unitTest "change mode" test_pureMenuModeChange,
    unitTest "filter items" test_pureMenuFilter,
    unitTest "execute an action" test_pureMenuExecute,
    unitTest "mark multiple items" test_menuMultiMark,
    unitTest "toggle marked items" test_menuToggle,
    unitTest "execute a thunk action" test_menuExecuteThunk,
    unitTest "delete by filtered index" test_menuDeleteByFilteredIndex
    ]