reflex-vty-1.3.0.0: test/Reflex/Vty/Widget/ScrollSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Reflex.Vty.Widget.ScrollSpec (spec) where
import Control.Monad (forM_)
import qualified Graphics.Vty as V
import Test.Hspec
import Reflex.Vty.Test.Snapshot
import Reflex.Vty.Widget.Scroll
( ScrollPos (..)
, ScrollbarVisibility (..)
, scrollByLines
, scrollToLine
, scrollbarImages
)
-- | Specified normalization of an already-clamped target line.
expectedPosFromClamped :: Int -> Int -> Int -> ScrollPos
expectedPosFromClamped total height line
| total <= height = ScrollPos_Top
| line <= 0 = ScrollPos_Top
| line + height >= total = ScrollPos_Bottom
| otherwise = ScrollPos_Line line
-- | Specified normalization of a raw (possibly out-of-range) request.
expectedPos :: Int -> Int -> Int -> ScrollPos
expectedPos total height req = expectedPosFromClamped total height (max 0 req)
spec :: Spec
spec = describe "Reflex.Vty.Widget.Scroll" $ do
describe "scrollbarImages" $ do
let attr = V.defAttr
it "returns empty when hidden" $ do
scrollbarImages ScrollbarHidden attr 10 50 0 20 `shouldBe` []
it "returns empty when content fits viewport" $ do
scrollbarImages ScrollbarAlways attr 10 5 0 20 `shouldBe` []
it "returns empty when viewport too narrow" $ do
scrollbarImages ScrollbarAlways attr 10 50 0 1 `shouldBe` []
it "produces a thumb and gutter for ScrollbarAlways" $ do
let imgs = scrollbarImages ScrollbarAlways attr 10 50 0 20
length imgs `shouldBe` 2 -- gutter + thumb
it "produces only a thumb for ScrollbarThumbOnly" $ do
let imgs = scrollbarImages ScrollbarThumbOnly attr 10 50 0 20
length imgs `shouldBe` 1 -- thumb only
it "produces only a thumb for ScrollbarWhileScrolling" $ do
let imgs = scrollbarImages ScrollbarWhileScrolling attr 10 50 0 20
length imgs `shouldBe` 1 -- thumb only
it "places thumb at top when scrollLine is 0" $ do
let imgs = scrollbarImages ScrollbarThumbOnly attr 10 50 0 20
rows = imageText (V.vertCat imgs)
-- Thumb should be at the top of the rightmost column
firstChar [] = ' '
firstChar (c : _) = c
case rows of
(r : _) -> last r `shouldBe` '█'
[] -> expectationFailure "expected at least one rendered row"
case rows of
(r1 : _) -> firstChar r1 `shouldBe` ' '
[] -> expectationFailure "expected at least one rendered row"
it "places thumb lower when scrolled down" $ do
let imgsAtTop = scrollbarImages ScrollbarThumbOnly attr 10 50 0 20
imgsScrolled = scrollbarImages ScrollbarThumbOnly attr 10 50 20 20
topRow = imageText (V.vertCat imgsAtTop)
scrolledRows = imageText (V.vertCat imgsScrolled)
case (topRow, scrolledRows) of
((top : _), (scrolled : _)) -> do
last top `shouldBe` '█'
-- When scrolled, the first row should not have the thumb
last scrolled `shouldSatisfy` (/= '█')
_ -> expectationFailure "expected rendered rows"
describe "scrollToLine" $ do
it "normalizes every small-domain request exactly" $ do
-- Exhaustive over totalLines 0..6, height 0..4, request -2..8,
-- against the specified semantics: content that fits is Top;
-- nonpositive requests are Top; requests at/past the last page
-- are Bottom; anything else is the exact line.
forM_ [(0 :: Int) .. 6] $ \total ->
forM_ [0 .. 4] $ \height ->
forM_ [(-2) .. 8] $ \req ->
scrollToLine total height req
`shouldBe` expectedPos total height req
describe "scrollByLines" $ do
it "clamps and normalizes relative movement from every starting form" $ do
forM_ [ScrollPos_Top, ScrollPos_Bottom, ScrollPos_Line 0, ScrollPos_Line 2, ScrollPos_Line 5] $ \sp ->
forM_ [(0 :: Int) .. 6] $ \total ->
forM_ [0 .. 4] $ \height ->
forM_ [(-3), (-1), 0, 1, 4] $ \delta -> do
-- The starting line is used literally; stored positions
-- are always normalized in production, so Top is 0 and
-- Bottom is the last page.
let start = case sp of
ScrollPos_Top -> 0
ScrollPos_Bottom -> total - height
ScrollPos_Line n -> n
target = min total (max 0 (start + delta))
scrollByLines sp total height delta
`shouldBe` (if total <= height then ScrollPos_Top else expectedPosFromClamped total height target)
it "thumb size is proportional to viewport/total ratio" $ do
let attr' = V.defAttr
-- vpHeight=10, totalLines=100 → thumbLen = max 1 (100/100) = 1
imgs1 = scrollbarImages ScrollbarThumbOnly attr' 10 100 0 20
-- vpHeight=10, totalLines=20 → thumbLen = max 1 (100/20) = 5
imgs2 = scrollbarImages ScrollbarThumbOnly attr' 10 20 0 20
heightOf [] = 0
heightOf (img : _) = V.imageHeight img
heightOf imgs1 `shouldBe` 1
heightOf imgs2 `shouldBe` 5