packages feed

vgrep-0.2.2.0: test/Test/Vgrep/Widget/Results.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase       #-}
module Test.Vgrep.Widget.Results (test) where

import           Control.Lens.Compat     (Getter, over, to, view, (<&>), _1)
import           Control.Monad           (void)
import           Data.Map.Strict         ((!))
import qualified Data.Map.Strict         as Map
import           Data.Monoid             ((<>))
import           Data.Sequence           (Seq, ViewR (..))
import qualified Data.Sequence           as Seq
import           Test.Case
import           Test.QuickCheck
import           Test.QuickCheck.Monadic

import Vgrep.Widget.Results.Testable

test :: TestTree
test = runTestCases "Results widget"
    [ TestInvariant
        { description = "Scrolling one line down and up keeps selected line"
        , testData = arbitrary `suchThat` (not . lastLine)
        , testCase = run (nextLine >> prevLine)
        , invariant = selectedLine
        }
    , TestInvariant
        { description = "Scrolling one page down and up keeps selected line"
        , testData = fmap moveToLastLineOnScreen arbitrary
            `suchThat` \(results, env) -> linesBelowCurrent (> screenHeight env) results
        , testCase = run (pageDown >> pageUp)
        , invariant = selectedLine
        }
    , TestProperty
        { description = "Scrolling one page down jumps to end of screen"
        , testData = arbitrary
        , testCase = run pageDown
        , assertion = const $ get <&> \case
            EmptyResults       -> True
            Results _ _ _ ds _ -> null ds
        }
    , TestProperty
        { description = "Scrolling one page up jumps to start of screen"
        , testData = arbitrary
        , testCase = run pageUp
        , assertion = const $ get <&> \case
            EmptyResults       -> True
            Results _ bs _ _ _ -> null bs
        }
    , TestProperty
        { description = "Number of lines on screen is bounded by screen height after resizing"
        , testData = arbitrary
        , testCase = run resizeToWindow
        , assertion = const assertWidgetFitsOnScreen
        }
    , TestProperty
        { description = "Number of lines on screen is bounded by screen height after each action"
        , testData = arbitrary
        , testCase = do
            run (void resizeToWindow)
                -- ^ Precondition: widget is resized to display height
            run =<< arbitraryAction
        , assertion = const assertWidgetFitsOnScreen
        }
    , TestInvariant
        { description = "Results do not change order"
        , testData = arbitrary
        , testCase = run =<< arbitraryAction
        , invariant = resultsAsList
        }
    ]

selectedLine :: Getter Results (Maybe FileLineReference)
selectedLine = to $ \case
    EmptyResults      -> Nothing
    Results _ _ c _ _ -> Just c

linesBelowCurrent :: (Int -> Bool) -> Results -> Bool
linesBelowCurrent p = \case
    EmptyResults        -> p 0
    Results _ _ _ ds es -> p (length ds + length es)

screenHeight :: Environment -> Int
screenHeight = view viewportHeight

moveToLastLineOnScreen :: (Results, Environment) -> (Results, Environment)
moveToLastLineOnScreen = over _1 $ \case
    EmptyResults          -> EmptyResults
    Results as bs c ds es -> case Seq.viewr ds of
        EmptyR   -> Results as bs c ds es
        ds' :> d -> Results as (Seq.reverse ds' <> pure c <> bs) d Seq.empty es

lastLine :: (Results, Environment) -> Bool
lastLine (results, _env) = case results of
    EmptyResults        -> True
    Results _ _ _ ds es -> null ds && null es

resultsAsList :: Getter Results (Seq FileLineReference)
resultsAsList = to $ \case
    EmptyResults -> mempty
    Results as bs c ds es -> mconcat
        [ Seq.reverse as, Seq.reverse bs, pure c, ds, es ]

arbitraryAction ::  Monad m => PropertyM m (Vgrep Results ())
arbitraryAction = do
    let actions = Map.fromList
            [ ("pageUp",   pageUp)
            , ("pageDown", pageDown)
            , ("prevLine", prevLine)
            , ("nextLine", nextLine) ]
    actionName <- pick (elements ["pageUp", "pageDown", "prevLine", "nextLine"])
    pure (actions ! actionName)

assertWidgetFitsOnScreen
    :: (MonadState Results m, MonadReader Environment m)
    => m Property
assertWidgetFitsOnScreen = do
    height <- view viewportHeight
    linesOnScreen <- numberOfLinesOnScreen
    pure $ counterexample
        (show linesOnScreen ++ " > " ++ show height)
        (linesOnScreen <= height)

numberOfLinesOnScreen :: MonadState Results m => m Int
numberOfLinesOnScreen = get <&> \case
    EmptyResults        -> 0
    Results _ bs c ds _ -> length (mconcat [bs, pure c, ds])