packages feed

restman-0.7.2.2: test/Props/SkylightingProps.hs

module Props.SkylightingProps (tests) where

-- base
import Data.Bits ((.&.))
import Data.Word (Word8)

-- hedgehog
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range

-- tasty / tasty-hedgehog
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testProperty)

-- vty
import qualified Graphics.Vty.Attributes as VA

-- skylighting
import Skylighting.Types (ANSIColorLevel(..), Color(..))

-- restman
import Skylighting.Format.Vty
  (attrColor, attrStyle, colorDistance, vtyStyleAttr)

tests :: TestTree
tests = testGroup "Props.Skylighting.Format.Vty"
  [ colorDistanceProps
  , attrStyleProps
  , attrColorProps
  , vtyStyleAttrProps
  ]

-- ---------------------------------------------------------------------------
-- Generators
-- ---------------------------------------------------------------------------

genWord8 :: Gen Word8
genWord8 = Gen.word8 Range.linearBounded

genColor :: Gen Color
genColor = RGB
  <$> genWord8
  <*> genWord8
  <*> genWord8

genColorLevel :: Gen ANSIColorLevel
genColorLevel = Gen.element [ANSITrueColor, ANSI256Color, ANSI16Color]

-- ---------------------------------------------------------------------------
-- colorDistance properties
--
-- colorDistance :: Color -> Color -> Int16
-- Defined as the L1 (Manhattan) distance over RGB channels.
-- ---------------------------------------------------------------------------

-- Distance from a color to itself is zero.
prop_colorDistance_self_zero :: Property
prop_colorDistance_self_zero = property $ do
  c <- forAll genColor
  colorDistance c c === 0

-- Distance is symmetric: d(a,b) == d(b,a).
prop_colorDistance_symmetric :: Property
prop_colorDistance_symmetric = property $ do
  a <- forAll genColor
  b <- forAll genColor
  colorDistance a b === colorDistance b a

-- Distance is always non-negative.
prop_colorDistance_nonneg :: Property
prop_colorDistance_nonneg = property $ do
  a <- forAll genColor
  b <- forAll genColor
  assert (colorDistance a b >= 0)

-- Triangle inequality: d(a,c) <= d(a,b) + d(b,c).
prop_colorDistance_triangle :: Property
prop_colorDistance_triangle = property $ do
  a <- forAll genColor
  b <- forAll genColor
  c <- forAll genColor
  assert (colorDistance a c <= colorDistance a b + colorDistance b c)

colorDistanceProps :: TestTree
colorDistanceProps = testGroup "colorDistance"
  [ testProperty "self distance is zero"   prop_colorDistance_self_zero
  , testProperty "symmetric"               prop_colorDistance_symmetric
  , testProperty "non-negative"            prop_colorDistance_nonneg
  , testProperty "triangle inequality"     prop_colorDistance_triangle
  ]

-- ---------------------------------------------------------------------------
-- attrStyle properties
--
-- attrStyle :: Bool -> Bool -> Bool -> VA.Style
-- Each Bool selects whether bold, italic, or underline bits are set.
-- ---------------------------------------------------------------------------

-- When bold=True the bold bit is set; when False it is clear.
prop_attrStyle_bold_bit :: Property
prop_attrStyle_bold_bit = property $ do
  b <- forAll Gen.bool
  i <- forAll Gen.bool
  u <- forAll Gen.bool
  let s = attrStyle b i u
  (s .&. VA.bold /= 0) === b

-- When italic=True the italic bit is set; when False it is clear.
prop_attrStyle_italic_bit :: Property
prop_attrStyle_italic_bit = property $ do
  b <- forAll Gen.bool
  i <- forAll Gen.bool
  u <- forAll Gen.bool
  let s = attrStyle b i u
  (s .&. VA.italic /= 0) === i

-- When underline=True the underline bit is set; when False it is clear.
prop_attrStyle_underline_bit :: Property
prop_attrStyle_underline_bit = property $ do
  b <- forAll Gen.bool
  i <- forAll Gen.bool
  u <- forAll Gen.bool
  let s = attrStyle b i u
  (s .&. VA.underline /= 0) === u

-- All flags False gives the defaultStyleMask (no bits set by us).
prop_attrStyle_all_false_is_default :: Property
prop_attrStyle_all_false_is_default = property $
  attrStyle False False False === VA.defaultStyleMask

-- Flags are independent: toggling one Bool does not affect the other bits.
prop_attrStyle_flags_independent :: Property
prop_attrStyle_flags_independent = property $ do
  i <- forAll Gen.bool
  u <- forAll Gen.bool
  -- flip bold only
  let withBold = attrStyle True  i u
      withoutBold = attrStyle False i u
  -- italic and underline bits should be the same in both
  (withBold    .&. VA.italic)     === (withoutBold .&. VA.italic)
  (withBold    .&. VA.underline)  === (withoutBold .&. VA.underline)

attrStyleProps :: TestTree
attrStyleProps = testGroup "attrStyle"
  [ testProperty "bold flag controls bold bit"        prop_attrStyle_bold_bit
  , testProperty "italic flag controls italic bit"    prop_attrStyle_italic_bit
  , testProperty "underline flag controls underline"  prop_attrStyle_underline_bit
  , testProperty "all False gives defaultStyleMask"   prop_attrStyle_all_false_is_default
  , testProperty "flags are independent"              prop_attrStyle_flags_independent
  ]

-- ---------------------------------------------------------------------------
-- attrColor properties
--
-- attrColor :: ANSIColorLevel -> Maybe Color -> Maybe VA.Color
-- Nothing input always gives Nothing output (color level irrelevant).
-- Just input always gives Just output.
-- ---------------------------------------------------------------------------

-- Nothing maps to Nothing for all color levels.
prop_attrColor_nothing_to_nothing :: Property
prop_attrColor_nothing_to_nothing = property $ do
  clv <- forAll genColorLevel
  attrColor clv Nothing === Nothing

-- Just a color always produces Just a vty color (never Nothing).
prop_attrColor_just_to_just :: Property
prop_attrColor_just_to_just = property $ do
  clv <- forAll genColorLevel
  col <- forAll genColor
  case attrColor clv (Just col) of
    Nothing -> failure
    Just _ -> success

-- For TrueColor and 256-color, identical RGB inputs yield identical outputs.
prop_attrColor_same_rgb_same_result :: Property
prop_attrColor_same_rgb_same_result = property $ do
  clv <- forAll genColorLevel
  col <- forAll genColor
  attrColor clv (Just col) === attrColor clv (Just col)

attrColorProps :: TestTree
attrColorProps = testGroup "attrColor"
  [ testProperty "Nothing -> Nothing for all levels"  prop_attrColor_nothing_to_nothing
  , testProperty "Just -> Just for all levels"        prop_attrColor_just_to_just
  , testProperty "same input gives same output"       prop_attrColor_same_rgb_same_result
  ]

-- ---------------------------------------------------------------------------
-- vtyStyleAttr properties
--
-- vtyStyleAttr :: ANSIColorLevel -> Maybe Color -> Maybe Color
--              -> Bool -> Bool -> Bool -> VA.Attr
-- ---------------------------------------------------------------------------

-- fg Nothing -> attrForeColor is Default.
prop_vtyStyleAttr_fg_nothing_default :: Property
prop_vtyStyleAttr_fg_nothing_default = property $ do
  clv <- forAll genColorLevel
  bgc <- forAll (Gen.maybe genColor)
  b   <- forAll Gen.bool
  i   <- forAll Gen.bool
  u   <- forAll Gen.bool
  VA.attrForeColor (vtyStyleAttr clv Nothing bgc b i u) === VA.Default

-- fg Just -> attrForeColor is SetTo.
prop_vtyStyleAttr_fg_just_set :: Property
prop_vtyStyleAttr_fg_just_set = property $ do
  clv <- forAll genColorLevel
  fgc <- forAll genColor
  bgc <- forAll (Gen.maybe genColor)
  b   <- forAll Gen.bool
  i   <- forAll Gen.bool
  u   <- forAll Gen.bool
  case VA.attrForeColor (vtyStyleAttr clv (Just fgc) bgc b i u) of
    VA.SetTo _ -> success
    _ -> failure

-- bg Nothing -> attrBackColor is Default.
prop_vtyStyleAttr_bg_nothing_default :: Property
prop_vtyStyleAttr_bg_nothing_default = property $ do
  clv <- forAll genColorLevel
  fgc <- forAll (Gen.maybe genColor)
  b   <- forAll Gen.bool
  i   <- forAll Gen.bool
  u   <- forAll Gen.bool
  VA.attrBackColor (vtyStyleAttr clv fgc Nothing b i u) === VA.Default

-- bg Just -> attrBackColor is SetTo.
prop_vtyStyleAttr_bg_just_set :: Property
prop_vtyStyleAttr_bg_just_set = property $ do
  clv <- forAll genColorLevel
  fgc <- forAll (Gen.maybe genColor)
  bgc <- forAll genColor
  b   <- forAll Gen.bool
  i   <- forAll Gen.bool
  u   <- forAll Gen.bool
  case VA.attrBackColor (vtyStyleAttr clv fgc (Just bgc) b i u) of
    VA.SetTo _ -> success
    _ -> failure

-- attrURL is always Default.
prop_vtyStyleAttr_url_always_default :: Property
prop_vtyStyleAttr_url_always_default = property $ do
  clv <- forAll genColorLevel
  fgc <- forAll (Gen.maybe genColor)
  bgc <- forAll (Gen.maybe genColor)
  b   <- forAll Gen.bool
  i   <- forAll Gen.bool
  u   <- forAll Gen.bool
  VA.attrURL (vtyStyleAttr clv fgc bgc b i u) === VA.Default

vtyStyleAttrProps :: TestTree
vtyStyleAttrProps = testGroup "vtyStyleAttr"
  [ testProperty "fg Nothing -> Default foreground"    prop_vtyStyleAttr_fg_nothing_default
  , testProperty "fg Just -> SetTo foreground"         prop_vtyStyleAttr_fg_just_set
  , testProperty "bg Nothing -> Default background"    prop_vtyStyleAttr_bg_nothing_default
  , testProperty "bg Just -> SetTo background"         prop_vtyStyleAttr_bg_just_set
  , testProperty "attrURL is always Default"           prop_vtyStyleAttr_url_always_default
  ]