{-# LANGUAGE
OverloadedStrings,
RecordWildCards,
ViewPatterns,
NoImplicitPrelude
#-}
module Main where
import BasePrelude
-- Trees
import qualified Data.Tree as Tree
-- Text
import qualified Data.Text as T
import Data.Text (Text)
-- Tests
import Test.Hspec
import Test.QuickCheck
import Test.Hspec.QuickCheck
-- Markdown
import CMark
import CMark.Sections
main :: IO ()
main = hspec $ do
let mkSect level heading content ns =
Tree.Node Section{headingAnn = (),contentAnn = (),..} ns
describe "converting:" $ do
it "empty document" $ do
let src = ""
prefaceAnn = ()
preface = mempty
sections = []
nodesToDocument (commonmarkToAnnotatedNodes [] src)
`shouldBe` Document{..}
it "spaces" $ do
let src = " \n\n \n"
prefaceAnn = ()
preface = Ann " \n\n \n" []
sections = []
nodesToDocument (commonmarkToAnnotatedNodes [] src)
`shouldBe` Document{..}
it "paragraph" $ do
let src = "x"
prefaceAnn = ()
preface = Ann "x" [
Node (Just (PosInfo 1 1 1 1)) PARAGRAPH [text "x"] ]
sections = []
nodesToDocument (commonmarkToAnnotatedNodes [] src)
`shouldBe` Document{..}
it "3 paragraphs" $ do
let src = T.unlines ["","x","","","y","","z",""]
prefaceAnn = ()
preface = Ann "\nx\n\n\ny\n\nz\n\n" [
Node (Just (PosInfo 2 1 2 1)) PARAGRAPH [text "x"],
Node (Just (PosInfo 5 1 5 1)) PARAGRAPH [text "y"],
Node (Just (PosInfo 7 1 7 1)) PARAGRAPH [text "z"] ]
sections = []
nodesToDocument (commonmarkToAnnotatedNodes [] src)
`shouldBe` Document{..}
it "headers" $ do
let src = T.unlines ["# 1", "", "## 2", "", "## 3"]
prefaceAnn = ()
preface = mempty
sections = [
mkSect 1 (Ann "# 1\n\n" [text "1"]) mempty [
mkSect 2 (Ann "## 2\n\n" [text "2"]) mempty [],
mkSect 2 (Ann "## 3\n" [text "3"]) mempty [] ] ]
nodesToDocument (commonmarkToAnnotatedNodes [] src)
`shouldBe` Document{..}
it "headers+content" $ do
let src = T.unlines ["# 1", "", "## 2", "test", "## 3"]
prefaceAnn = ()
preface = mempty
sections = [
mkSect 1 (Ann "# 1\n\n" [text "1"]) mempty [
mkSect 2 (Ann "## 2\n" [text "2"])
(Ann "test\n" [Node (Just (PosInfo 4 1 4 4)) PARAGRAPH
[text "test"]]) [],
mkSect 2 (Ann "## 3\n" [text "3"]) mempty [] ] ]
nodesToDocument (commonmarkToAnnotatedNodes [] src)
`shouldBe` Document{..}
it "preface+headers" $ do
let src = T.unlines ["blah", "# 1", "", "## 2", "", "## 3"]
prefaceAnn = ()
preface = commonmarkToAnnotatedNodes [] "blah\n"
sections = [
mkSect 1 (Ann "# 1\n\n" [text "1"]) mempty [
mkSect 2 (Ann "## 2\n\n" [text "2"]) mempty [],
mkSect 2 (Ann "## 3\n" [text "3"]) mempty [] ] ]
nodesToDocument (commonmarkToAnnotatedNodes [] src)
`shouldBe` Document{..}
describe "reconstruction:" $ do
it "paragraph + ###-header" $
fromToDoc "foo\n\n# bar\n"
it "paragraph + ===-header" $
fromToDoc "foo\n\nbar\n===\n"
it "no blank line after header" $
fromToDoc "# header\n# header\n"
it "header + blockquote" $
fromToDoc "# header\n\n> a blockquote\n"
it "header + list" $
fromToDoc "# header\n * item\n"
it "blanks + header" $
fromToDoc "\n\n\n# header\n"
modifyMaxSize (*20) $ modifyMaxSuccess (*10) $
prop "QuickCheck" $
forAllShrink mdGen shrinkMD $ \(T.concat -> src) ->
let md1 = commonmarkToAnnotatedNodes [] src
md2 = flattenDocument . nodesToDocument $ md1
err = printf "%s: %s /= %s" (show src) (show md1) (show md2)
in counterexample err (compareMD md1 md2)
text :: Text -> Node
text t = Node Nothing (TEXT t) []
fromToDoc :: Text -> Expectation
fromToDoc src =
flattenDocument (nodesToDocument (commonmarkToAnnotatedNodes [] src))
`shouldBeMD` commonmarkToAnnotatedNodes [] src
shouldBeMD :: Annotated [Node] -> Annotated [Node] -> Expectation
shouldBeMD x y = x `shouldSatisfy` (compareMD y)
-- | Check that pieces of Markdown are equivalent (modulo trailing newline
-- and position info).
compareMD :: Annotated [Node] -> Annotated [Node] -> Bool
compareMD x y =
map (\(Node _ a b) -> Node Nothing a b) (annValue x) ==
map (\(Node _ a b) -> Node Nothing a b) (annValue y)
&&
or [annSource x == annSource y,
and [not (T.isSuffixOf "\n" (annSource x)),
T.isSuffixOf "\n" (annSource y),
annSource x == T.init (annSource y)],
and [not (T.isSuffixOf "\n" (annSource y)),
T.isSuffixOf "\n" (annSource x),
annSource y == T.init (annSource x)] ]
-- | Try to shrink Markdown.
shrinkMD :: [Text] -> [[Text]]
shrinkMD = shrinkList shrinkNothing
-- | Generate random Markdown.
mdGen :: Gen [Text]
mdGen = do
ls <- listOf $ elements [
-- ###-headers
"# header 1\n", "# header 1 #\n",
"## header 2\n",
"### header 3\n",
"#### header 4\n",
"##### header 5\n",
"###### header 6\n",
" # header 1\n", " # header 1\n",
" ## header 2\n", " ## header 2\n",
-- ===-headers
"header 1\n======\n", "header 2\n------\n",
"multiline\nheader 1\n======\n", "multiline\nheader 2\n------\n",
-- blocks with headers inside
"> # header\n", "* # header\n",
-- lists
"* item\n", " * item\n",
"+ item 1\n+ item 2\n",
-- links and link references
"[link][link]\n",
"[link]: http://google.com\n",
"> [link]: http://google.com\n",
-- blockquotes
"> blockquote\n> 1\n", ">> blockquote\n>> 2\n",
-- other blocks
" a *paragraph*\n",
"*multiline*\nparagraph\n",
"~~~\ncode block\n~~~\n", " code\n",
"---\n", "* * *\n", " * * * \n",
-- other things
"", " ", " ", "\n", "\n\n",
"`", "``", "```"]
let randomNL = T.replicate <$> choose (0, 3) <*> pure "\n"
concat <$> mapM (\x -> do nl <- randomNL; return [x, nl]) ls