packages feed

circular-enum-0.2.0.0: test/Main.hs

module Main (main) where

import Data.Enum.Circular
import Data.Function
import Data.List
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck

data Dir = N | E | S | W deriving (Show, Eq, Enum, Bounded)

-- Enumeration of all members of the direction enum
allDirs :: [Dir]
allDirs = enumFrom minBound :: [Dir]

instance Arbitrary Dir where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary a => Arbitrary (Circular a) where
  arbitrary = Circular <$> arbitrary

-- compare the first 2 times 'Dir'-Enum-Size items
startShouldBe :: (Show a, Eq a) => [a] -> [a] -> Expectation
startShouldBe = shouldBe `on` take (length allDirs * 2)

main :: IO ()
main = hspec $ do

  describe "Circular directions" $ do

    describe "Boundaries" $ do
      it "North after West"   $ csucc W `shouldBe` N
      it "West before North"  $ cpred N `shouldBe` W

    describe "Compatible with Enum instance" $ do
      it "Successors" $
        iterate csucc minBound `startShouldBe` cycle allDirs
      it "Predecessors" $
        iterate cpred maxBound `startShouldBe` cycle (reverse allDirs)

    describe "Reversibility" $ do
      prop "csucc . cpred = id" $ \dir ->
        (csucc . cpred) (dir :: Dir) `shouldBe` dir
      prop "cpred . csucc = id" $ \dir ->
        (cpred . csucc) (dir :: Dir) `shouldBe` dir

  describe "Circular newtype" $ do

    describe "Boundaries" $ do
      it "North after West"   $ succ (Circular W) `shouldBe` Circular N
      it "West before North"  $ pred (Circular N) `shouldBe` Circular W

    describe "Reversibility" $ do
      prop "succ o pred = id" $ \dir ->
        (succ . pred) (Circular dir) `shouldBe` Circular (dir :: Dir)
      prop "pred o succ = id" $ \dir ->
        (pred . succ) (Circular dir) `shouldBe` Circular (dir :: Dir)

    describe "Compatible with inner Enum instance" $ do
      prop "Successors" $ \dir -> dir /= (maxBound :: Dir) ==>
        succ (Circular dir) `shouldBe` Circular (succ dir)
      prop "Predecessors" $ \dir -> dir /= (minBound :: Dir) ==>
        pred (Circular dir) `shouldBe` Circular (pred dir)

    describe "to/fromEnum" $ do
      let len = length allDirs
      it "Upper example" $
        toEnum 4 `shouldBe` Circular N
      it "Lower example" $
        toEnum (-1) `shouldBe` Circular W
      prop "Cycle down" $ \n -> n >= fromEnum (maxBound :: Dir) ==>
        toEnum n `shouldBe` (toEnum (n `mod` len) :: Circular Dir)
      prop "Cycle up" $ \n -> n < 0 ==>
        toEnum n `shouldBe` (toEnum (n `mod` len) :: Circular Dir)
      prop "toEnum Truncation" $ \n ->
        fromEnum (toEnum n :: Circular Dir) `shouldBe` n `mod` len
      prop "toEnum: repeating series" $ \n -> n >= 0 ==>
        let toEnums   = toEnum <$> [0..]
            cAllDirs  = Circular <$> cycle allDirs
        in  toEnums !! n `shouldBe` cAllDirs !! n
      prop "Roundtrip toEnum . fromEnum" $ \cd ->
        toEnum (fromEnum cd) `shouldBe` (cd :: Circular Dir)
      prop "'Roundtrip' fromEnum . toEnum . fromEnum" $ \cd ->
        let i = fromEnum (cd :: Circular Dir)
        in  fromEnum (toEnum i :: Circular Dir) `shouldBe` i

    describe "enum[From][Then][To] circularity" $ do

      describe "Examples" $ do
        it "enumFromThen: stepped Enum iteration" $
          enumFromThen (Circular N) (Circular S)
            `startShouldBe` cycle (fmap Circular [N, S])
        it "enumFromTo: enumeration wrapping" $
          enumFromTo (Circular S) (Circular E)
            `shouldBe` fmap Circular [S, W, N, E]
          -- forward iteration
        it "enumFromTo: singleton" $
          enumFromTo (Circular N) (Circular N)
            `shouldBe` [Circular N]
        it "enumFromThenTo: target exactly hit" $
          enumFromThenTo (Circular N) (Circular S) (Circular S)
            `shouldBe` fmap Circular [N, S]
        it "enumFromThenTo: start = next -> infinite" $
          enumFromThenTo (Circular N) (Circular N) (Circular S)
            `startShouldBe` repeat (Circular N)
        it "enumFromThenTO: enumeration stepped wrapping" $
          enumFromThenTo (Circular N) (Circular S) (Circular E)
            `shouldBe` [Circular N]
          -- consistent with [1,3..2]
        it "enumFrom: infinity" $
          enumFrom (Circular E)
            `startShouldBe` cycle (fmap Circular [E, S, W, N])

      describe "General properties" $ do

        describe "enumFromThen" $ do
          prop "Correct start: first" $ \cd cd' ->
            head (enumFromThen cd cd') `shouldBe` (cd :: Circular Dir)
          prop "Correct start: second" $ \cd cd' ->
            enumFromThen cd cd' !! 1 `shouldBe` (cd' :: Circular Dir)
          prop "Start = next -> infinite" $ \cd ->
            enumFromThen cd cd `startShouldBe` repeat (cd :: Circular Dir)
          prop "Correct step size" $ \cd n -> n /= 0 ==>
            let cd' = toEnum (fromEnum (cd :: Circular Dir) + n)
                ds  = enumFromThen cd cd'
                shouldBeModLen = shouldBe `on` (`mod` length allDirs)
            in  forAll (sized $ \s -> choose (0, s)) $ \i ->
                  let cd1 = ds !! i
                      cd2 = ds !! (i+1)
                  in  (fromEnum cd2 - fromEnum cd1) `shouldBeModLen` n

        describe "enumFrom: enumFromThen with succ" $ do
          prop "Same infinite list start" $ \cd ->
            enumFrom cd
              `startShouldBe` enumFromThen (cd :: Circular Dir) (succ cd)

        describe "enumFromThenTo: finite enumFromThen" $ do
          prop "Start = end -> singleton" $ \cd cd' -> cd' /= cd ==>
            enumFromThenTo cd cd' cd `shouldBe` [cd :: Circular Dir]
          prop "Start = next -> infinite" $ \cd target -> cd /= target ==>
            enumFromThenTo cd cd target
              `startShouldBe` repeat (cd :: Circular Dir)
          prop "Prefix" $ \cd cd' target -> cd /= cd' ==>
            enumFromThenTo cd cd' target
              `isPrefixOf` enumFromThen cd (cd' :: Circular Dir)

        describe "enumFromTo: enumFromThenTo with succ" $ do
          prop "Same list" $ \cd target ->
            enumFromTo cd target
              `shouldBe` enumFromThenTo cd (succ cd :: Circular Dir) target