packages feed

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

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

import           Control.Lens            (Getter, to, view, views)
import           Data.Map.Strict         ((!))
import qualified Data.Map.Strict         as Map
import           Data.Sequence           (Seq)
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 = arbitrary
            `suchThat` lastLineOnScreen
            `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 >>= pure . \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 >>= pure . \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 (region . to regionHeight)

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

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 <- views region regionHeight
    linesOnScreen <- numberOfLinesOnScreen
    pure $ counterexample
        (show linesOnScreen ++ " > " ++ show height)
        (linesOnScreen <= height)

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