packages feed

IntFormats-0.1.0.0: test/Text/IntFormatsSpec.hs

module Text.IntFormatsSpec (spec) where

import Data.Char (isAlpha, isDigit)
import Test.Hspec
import Text.Read (readEither)
import Test.QuickCheck

import Text.IntFormats

showInt' :: Bool -> IntFormat -> Int -> String
showInt' = showInt

parseInt' :: String -> Either String Int
parseInt' = parseInt

spec :: Spec
spec = do
  describe "showInt" $ do
    it "separates digits in base 16" $
      showInt' False Hexadecimal 0x98765430 `shouldBe` "98765430"
    it "prints digits >9 in lowercase" $
      showInt' True Hexadecimal 0xafbecd `shouldBe` "0xafbecd"
    it "prints digits >9 in uppercase" $
      showInt' True HexUpper 0xafbecd `shouldBe` "0xAFBECD"
    it "only uses uppercase when needed" $
      showInt' False HexUpper 0x400 `shouldBe` "400"
    it "separates digits in base 10" $
      showInt' False Decimal 1234567890 `shouldBe` "1234567890"
    it "has correct decimal prefix" $
      showInt' True Decimal 321 `shouldBe` "0d321"
    it "separates digits in base 8" $
      showInt' False Octal 0o72546310 `shouldBe` "72546310"
    it "has correct octal prefix" $
      showInt' True Octal 0o1234 `shouldBe` "0o1234"
    it "separates digits in base 2" $
      showInt' False Binary 1000 `shouldBe` "1111101000"
    it "has correct binary prefix" $
      showInt' True Binary 100 `shouldBe` "0b1100100"
    it "extends show" $ property $
      equivalentFuncs (showInt' False Decimal) show
    it "works with negative numbers" $ property $
      \(p, m, n) -> showInt' p m (-(abs n)) === (if n == 0 then "" else "-") ++ showInt' p m (abs n)
    it "shows zero" $
      showInt' False Decimal 0 `shouldBe` "0"
  describe "parseInt" $ do
    it "hexadecimal with lowercase prefix" $
      parseInt' "0xaBcDEf01" `shouldBe` Right 0xabcdef01
    it "hexadecimal with uppercase prefix" $
      parseInt' "0X29876FF3" `shouldBe` Right 0x29876ff3
    it "decimal with lowercase prefix" $
      parseInt' "0d9087654321" `shouldBe` Right 9087654321
    it "decimal with uppercase prefix" $
      parseInt' "0D12345" `shouldBe` Right 12345
    it "octal with lowercase prefix" $
      parseInt' "0o71062543" `shouldBe` Right 0o71062543
    it "octal with uppercase prefix" $
      parseInt' "0O11111" `shouldBe` Right 0o11111
    it "binary with lowercase prefix" $
      parseInt' "0b1110" `shouldBe` Right 14
    it "binary with uppercase prefix" $
      parseInt' "0B101010" `shouldBe` Right 42
    it "zero with prefix" $
      parseInt' "0b0" `shouldBe` Right 0
    it "inverts showInt" $ property $
      \(m, n) -> (parseInt' . showInt True m) (abs n) === Right (abs n)
    it "extends read" $ property $
      \s -> parseInt' s `equalRight` readEither s
    it "zero without prefix" $
      parseInt' "0" `shouldBe` Right 0
    it "doesn't require prefix for base 10" $ property $
      equivalentFuncs (parseInt' . show . abs) (Right . abs)
    it "fails on leading alphabetical characters" $ property $
      null . parseInt' . filter isAlpha
    it "fails on empty string" $
      parseInt' "" `shouldSatisfy` null
    it "fails on 8 or 9 in octal" $ property $
      \n -> '8' `elem` show (abs n) || '9' `elem` show (abs n) ==> (null . parseInt') ("0o" ++ show (abs n :: Int))
    it "accepts leading zeros" $ property $
      \(n, m) -> m `mod` 5 /= 0 ==> Right (abs n) === parseInt' (replicate (m `mod` 5) '0' ++ show (abs n))
    it "fails on 2 to 9 in binary" $ property $
      \n -> length (show (abs n)) /= length (filter (\x->x=='0'||x=='1') (show (abs n))) ==> (null . parseInt') ("0b" ++ show (abs n :: Int))
    it "fails on a to f in decimal" $ property $
      \n -> length (showInt' False Hexadecimal (abs n)) /= length (filter isDigit (showInt' False Hexadecimal (abs n))) ==>
              (null . parseInt') (showInt False Hexadecimal (abs n))
    it "fails on trailing '.'" $
      parseInt' "12." `shouldSatisfy` null
    it "stops at separator" $
      parseInt' "12 34" `shouldBe` Right 12
    it "fails on invalid prefix" $ property $
      \c -> isAlpha c && c `notElem` "xdobXDOB" ==> null (parseInt' (c:"100"))
    it "works with negative numbers" $ property $
      \n b -> equivalentFuncs (parseInt' . ('-':) . showInt' True b . abs) (Right . (0-) . abs) n

equivalentFuncs :: (Eq b, Show b) => (a -> b) -> (a -> b) -> a -> Property
equivalentFuncs = (.) (<*>) $ (<$>) (===)

equalRight :: (Eq a, Show a) => Either t a -> Either s a -> Bool
equalRight (Left _) _ = True
equalRight _ (Left _) = True
equalRight (Right a) (Right b) = a == b