packages feed

slug-0.1.7: tests/Main.hs

{-# LANGUAGE CPP              #-}
{-# OPTIONS -fno-warn-orphans #-}

module Main (main) where

import Control.Monad ((>=>))
import Data.Char (isAlphaNum)
import Data.Function (on)
import Data.Maybe (isJust, isNothing)
import Data.Semigroup
import Data.Text (Text)
import Test.Hspec
import Test.QuickCheck
import Web.HttpApiData
import Web.PathPieces
import Web.Slug
import qualified Data.Text as T

#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
#endif

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
  describe "slug properties" $ do
    it "cannot be empty" $
      property $ \slug ->
        unSlug slug `shouldNotSatisfy` T.null
    it "contains only dashes and alpha-numeric characters" $
      property $ \slug ->
        let f x = isAlphaNum x || x == '-'
        in unSlug slug `shouldSatisfy` T.all f
    it "does not begin with a dash" $
      property $ \slug ->
        T.head (unSlug slug) `shouldNotBe` '-'
    it "does not end with a dash" $
      property $ \slug ->
        T.last (unSlug slug) `shouldNotBe` '-'
    it "does not contain empty words between dashes" $
      property $ \slug ->
        T.splitOn "-" (unSlug slug) `shouldNotSatisfy` any T.null
    -- it "no upper-cased chars found in slugs" $
    --   property $ \slug ->
    --     unSlug slug `shouldNotSatisfy` T.any isUpper
    it "showed Slug looks the same as its inner Text" $
      property $ \slug ->
        show slug === show (unSlug slug)
    it "showed Slug can be read back again" $
      property $ \slug ->
        read (show slug) === (slug :: Slug)
    it "incorrect Slug won't be read successfully" $
      property $ \x -> isNothing (parseSlug x) ==>
        (reads (show x) :: [(Slug, String)]) === []
    it "valid Slug text is a valid path piece" $
      property $ \slug ->
        fromPathPiece (unSlug slug) === Just slug
    it "valid Slug text is a valid HTTP API data" $
      property $ \slug ->
        parseUrlPiece (toUrlPiece slug) === Right (slug :: Slug)
  describe "Semigroup instance of Slug" $
    it "the (<>) operation produces valid slugs in all cases" $
      property $ \x y -> do
        let slug = unSlug (x <> y)
        slug' <- unSlug <$> parseSlug slug
        slug' `shouldBe` slug
  describe "mkSlug" $ do
    it "Slug transformation in idempotent" $
      property $ \x ->
        let f = mkSlug
            g = mkSlug >=> mkSlug . unSlug
        in f x ==== g x
    it "text containing at least one alpha-num char is Sluggable" $ do
      let hasAlphaNum = isJust . T.find isAlphaNum
      property $ \x -> hasAlphaNum x ==>
        isJust (mkSlug x) `shouldBe` True
  describe "parseSlug" $ do
    it "succeeds on valid input" $
      property $ \slug ->
        parseSlug (unSlug slug) `shouldReturn` slug
    it "fails on invalid input" $
      property $ \x ->
        (unSlug <$> mkSlug x) `notElem` [Nothing, Just x] ==>
          parseSlug x `shouldThrow` (== InvalidSlug x)
  describe "truncateSlug" $ do
    context "when required length is less than 0" $
      it "throws InvalidLength" $
        property $ \n slug -> (n < 1) ==>
          truncateSlug n slug `shouldThrow` (== InvalidLength n)
    context "when required length is OK" $
      it "truncates to this length or one less" $
        property $ \n slug -> (n > 0) ==> do
          t <- truncateSlug n slug
          T.length (unSlug t) `shouldSatisfy` (<= n)

----------------------------------------------------------------------------
-- Helpers

infix 4 ====

(====) :: (Show a, Show b, Eq b) => Either a b -> Either a b -> Property
(====) = (===) `on` displayLeft

displayLeft :: Show a => Either a b -> Either String b
displayLeft = either (Left . show) Right

instance Arbitrary Text where
  arbitrary = T.pack <$> arbitrary