diff options
Diffstat (limited to 'test/Tests/Readers/Muse.hs')
-rw-r--r-- | test/Tests/Readers/Muse.hs | 1224 |
1 files changed, 1224 insertions, 0 deletions
diff --git a/test/Tests/Readers/Muse.hs b/test/Tests/Readers/Muse.hs new file mode 100644 index 000000000..508d79e18 --- /dev/null +++ b/test/Tests/Readers/Muse.hs @@ -0,0 +1,1224 @@ +{-# LANGUAGE OverloadedStrings #-} +module Tests.Readers.Muse (tests) where + +import Data.List (intersperse) +import Data.Text (Text) +import qualified Data.Text as T +import Test.Tasty +import Test.Tasty.QuickCheck +import Tests.Helpers +import Text.Pandoc +import Text.Pandoc.Arbitrary () +import Text.Pandoc.Builder +import Text.Pandoc.Shared (underlineSpan) +import Text.Pandoc.Walk (walk) + +amuse :: Text -> Pandoc +amuse = purely $ readMuse def { readerExtensions = extensionsFromList [Ext_amuse]} + +emacsMuse :: Text -> Pandoc +emacsMuse = purely $ readMuse def { readerExtensions = emptyExtensions } + +infix 4 =: +(=:) :: ToString c + => String -> (Text, c) -> TestTree +(=:) = test amuse + +spcSep :: [Inlines] -> Inlines +spcSep = mconcat . intersperse space + +-- Tables don't round-trip yet +-- +makeRoundTrip :: Block -> Block +makeRoundTrip Table{} = Para [Str "table was here"] +makeRoundTrip (OrderedList (start, LowerAlpha, _) items) = OrderedList (start, Decimal, Period) items +makeRoundTrip (OrderedList (start, UpperAlpha, _) items) = OrderedList (start, Decimal, Period) items +makeRoundTrip x = x + +-- Demand that any AST produced by Muse reader and written by Muse writer can be read back exactly the same way. +-- Currently we remove tables and compare third rewrite to the second. +-- First and second rewrites are not equal yet. +roundTrip :: Block -> Bool +roundTrip b = d'' == d''' + where d = walk makeRoundTrip $ Pandoc nullMeta [b] + d' = rewrite d + d'' = rewrite d' + d''' = rewrite d'' + rewrite = amuse . T.pack . (++ "\n") . T.unpack . + purely (writeMuse def { writerExtensions = extensionsFromList [Ext_amuse] + , writerWrapText = WrapPreserve + }) + +tests :: [TestTree] +tests = + [ testGroup "Inlines" + [ "Plain String" =: + "Hello, World" =?> + para "Hello, World" + + , "Muse is not XML" =: "<" =?> para "<" + + , "Emphasis" =: + "*Foo bar*" =?> + para (emph . spcSep $ ["Foo", "bar"]) + + , "Comma after closing *" =: + "Foo *bar*, baz" =?> + para ("Foo " <> emph "bar" <> ", baz") + + , "Letter after closing *" =: + "Foo *bar*x baz" =?> + para "Foo *bar*x baz" + + , "Letter before opening *" =: + "Foo x*bar* baz" =?> + para "Foo x*bar* baz" + + , "Emphasis tag" =: + "<em>Foo bar</em>" =?> + para (emph . spcSep $ ["Foo", "bar"]) + + , "Strong" =: + "**Cider**" =?> + para (strong "Cider") + + , "Strong tag" =: "<strong>Strong</strong>" =?> para (strong "Strong") + + , "Strong Emphasis" =: + "***strength***" =?> + para (strong . emph $ "strength") + + , test emacsMuse "Underline" + ("_Underline_" =?> para (underlineSpan "Underline")) + + , "Superscript tag" =: "<sup>Superscript</sup>" =?> para (superscript "Superscript") + + , "Subscript tag" =: "<sub>Subscript</sub>" =?> para (subscript "Subscript") + + , "Strikeout tag" =: "<del>Strikeout</del>" =?> para (strikeout "Strikeout") + + , "Opening inline tags" =: "foo <em> bar <strong>baz" =?> para "foo <em> bar <strong>baz" + + , "Closing inline tags" =: "foo </em> bar </strong>baz" =?> para "foo </em> bar </strong>baz" + + , "Tag soup" =: "foo <em> bar </strong>baz" =?> para "foo <em> bar </strong>baz" + + -- Both inline tags must be within the same paragraph + , "No multiparagraph inline tags" =: + T.unlines [ "First line" + , "<em>Second line" + , "" + , "Fourth line</em>" + ] =?> + para "First line\n<em>Second line" <> + para "Fourth line</em>" + + , "Linebreak" =: "Line <br> break" =?> para ("Line" <> linebreak <> "break") + + , "Trailing whitespace inside paragraph" =: + T.unlines [ "First line " -- trailing whitespace here + , "second line" + ] + =?> para "First line\nsecond line" + + , "Non-breaking space" =: "Foo~~bar" =?> para "Foo\160bar" + , "Single ~" =: "Foo~bar" =?> para "Foo~bar" + + , testGroup "Code markup" + [ "Code" =: "=foo(bar)=" =?> para (code "foo(bar)") + + , "Not code" =: "a=b= =c=d" =?> para (text "a=b= =c=d") + + -- Emacs Muse 3.20 parses this as code, we follow Amusewiki + , "Not code if closing = is detached" =: "=this is not a code =" =?> para "=this is not a code =" + + , "Not code if opening = is detached" =: "= this is not a code=" =?> para "= this is not a code=" + + , "Code if followed by comma" =: + "Foo =bar=, baz" =?> + para (text "Foo " <> code "bar" <> text ", baz") + + , "One character code" =: "=c=" =?> para (code "c") + + , "Three = characters is not a code" =: "===" =?> para "===" + + , "Multiline code markup" =: + "foo =bar\nbaz= end of code" =?> + para (text "foo " <> code "bar\nbaz" <> text " end of code") + +{- Emacs Muse 3.20 has a bug: it publishes + - <p>foo <code>bar + - + - baz</code> foo</p> + - which is displayed as one paragraph by browsers. + - We follow Amusewiki here and avoid joining paragraphs. + -} + , "No multiparagraph code" =: + T.unlines [ "foo =bar" + , "" + , "baz= foo" + ] =?> + para "foo =bar" <> + para "baz= foo" + + , "Code at the beginning of paragraph but not first column" =: + " - =foo=" =?> bulletList [ para $ code "foo" ] + ] + + , "Code tag" =: "<code>foo(bar)</code>" =?> para (code "foo(bar)") + + , "Verbatim tag" =: "*<verbatim>*</verbatim>*" =?> para (emph "*") + + , "Verbatim inside code" =: "<code><verbatim>foo</verbatim></code>" =?> para (code "<verbatim>foo</verbatim>") + + , "Verbatim tag after text" =: "Foo <verbatim>bar</verbatim>" =?> para "Foo bar" + + -- <em> tag should match with the last </em> tag, not verbatim one + , "Nested \"</em>\" inside em tag" =: "<em>foo<verbatim></em></verbatim>bar</em>" =?> para (emph ("foo</em>bar")) + + , testGroup "Links" + [ "Link without description" =: + "[[https://amusewiki.org/]]" =?> + para (link "https://amusewiki.org/" "" (str "https://amusewiki.org/")) + , "Link with description" =: + "[[https://amusewiki.org/][A Muse Wiki]]" =?> + para (link "https://amusewiki.org/" "" (text "A Muse Wiki")) + , "Image" =: + "[[image.jpg]]" =?> + para (image "image.jpg" "" mempty) + , "Image with description" =: + "[[image.jpg][Image]]" =?> + para (image "image.jpg" "" (text "Image")) + , "Image link" =: + "[[URL:image.jpg]]" =?> + para (link "image.jpg" "" (str "image.jpg")) + , "Image link with description" =: + "[[URL:image.jpg][Image]]" =?> + para (link "image.jpg" "" (text "Image")) + -- Implicit links are supported in Emacs Muse, but not in Amusewiki: + -- https://github.com/melmothx/text-amuse/issues/18 + -- + -- This test also makes sure '=' without whitespace is not treated as code markup + , "No implicit links" =: "http://example.org/index.php?action=view&id=1" + =?> para "http://example.org/index.php?action=view&id=1" + ] + + , testGroup "Literal" + [ test emacsMuse "Inline literal" + ("Foo<literal style=\"html\">lit</literal>bar" =?> + para (text "Foo" <> rawInline "html" "lit" <> text "bar")) + ] + ] + + , testGroup "Blocks" + [ testProperty "Round trip" roundTrip, + "Block elements end paragraphs" =: + T.unlines [ "First paragraph" + , "----" + , "Second paragraph" + ] =?> para (text "First paragraph") <> horizontalRule <> para (text "Second paragraph") + , testGroup "Horizontal rule" + [ "Less than 4 dashes is not a horizontal rule" =: "---" =?> para (text "---") + , "4 dashes is a horizontal rule" =: "----" =?> horizontalRule + , "5 dashes is a horizontal rule" =: "-----" =?> horizontalRule + , "4 dashes with spaces is a horizontal rule" =: "---- " =?> horizontalRule + ] + , testGroup "Paragraphs" + [ "Simple paragraph" =: + T.unlines [ "First line" + , "second line." + ] =?> + para "First line\nsecond line." + , "Indented paragraph" =: + T.unlines [ " First line" + , "second line." + ] =?> + para "First line\nsecond line." + -- Emacs Muse starts a blockquote on the second line. + -- We copy Amusewiki behavior and require a blank line to start a blockquote. + , "Indentation in the middle of paragraph" =: + T.unlines [ "First line" + , " second line" + , "third line" + ] =?> + para "First line\nsecond line\nthird line" + , "Quote" =: + " This is a quotation\n" =?> + blockQuote (para "This is a quotation") + , "Indentation does not indicate quote inside quote tag" =: + T.unlines [ "<quote>" + , " Not a nested quote" + , "</quote>" + ] =?> + blockQuote (para "Not a nested quote") + , "Multiline quote" =: + T.unlines [ " This is a quotation" + , " with a continuation" + ] =?> + blockQuote (para "This is a quotation\nwith a continuation") + , testGroup "Div" + [ "Div without id" =: + T.unlines [ "<div>" + , "Foo bar" + , "</div>" + ] =?> + divWith nullAttr (para "Foo bar") + , "Div with id" =: + T.unlines [ "<div id=\"foo\">" + , "Foo bar" + , "</div>" + ] =?> + divWith ("foo", [], []) (para "Foo bar") + ] + , "Verse" =: + T.unlines [ "> This is" + , "> First stanza" + , ">" -- Emacs produces verbatim ">" here, we follow Amusewiki + , "> And this is" + , "> Second stanza" + , ">" + , "" + , ">" + , "" + , "> Another verse" + , "> is here" + ] =?> + lineBlock [ "This is" + , "First stanza" + , "" + , "And this is" + , "\160\160Second stanza" + , "" + ] <> + lineBlock [ "" ] <> + lineBlock [ "Another verse" + , "\160\160\160is here" + ] + ] + , "Verse in list" =: " - > foo" =?> bulletList [ lineBlock [ "foo" ] ] + , "Multiline verse in list" =: + T.unlines [ " - > foo" + , " > bar" + ] =?> + bulletList [ lineBlock [ "foo", "bar" ] ] + , "Paragraph after verse in list" =: + T.unlines [ " - > foo" + , " bar" + ] =?> + bulletList [ lineBlock [ "foo" ] <> para "bar" ] + , "Empty quote tag" =: + T.unlines [ "<quote>" + , "</quote>" + ] + =?> blockQuote mempty + , "Quote tag" =: + T.unlines [ "<quote>" + , "Hello, world" + , "</quote>" + ] + =?> blockQuote (para $ text "Hello, world") + , "Nested quote tag" =: + T.unlines [ "<quote>" + , "foo" + , "<quote>" + , "bar" + , "</quote>" + , "baz" + , "</quote>" + ] =?> + blockQuote (para "foo" <> blockQuote (para "bar") <> para "baz") + , "Indented quote inside list" =: + T.unlines [ " - <quote>" + , " foo" + , " </quote>" + ] =?> + bulletList [ blockQuote (para "foo") ] + , "Verse tag" =: + T.unlines [ "<verse>" + , "" + , "Foo bar baz" + , " One two three" + , "" + , "</verse>" + ] =?> + lineBlock [ "" + , text "Foo bar baz" + , text "\160\160One two three" + , "" + ] + , "Verse tag with empty line inside" =: + T.unlines [ "<verse>" + , "" + , "</verse>" + ] =?> + lineBlock [ "" ] + , testGroup "Example" + [ "Braces on separate lines" =: + T.unlines [ "{{{" + , "Example line" + , "}}}" + ] =?> + codeBlock "Example line" + , "Spaces after opening braces" =: + T.unlines [ "{{{ " + , "Example line" + , "}}}" + ] =?> + codeBlock "Example line" + , "One blank line in the beginning" =: + T.unlines [ "{{{" + , "" + , "Example line" + , "}}}" + ] =?> + codeBlock "\nExample line" + , "One blank line in the end" =: + T.unlines [ "{{{" + , "Example line" + , "" + , "}}}" + ] =?> + codeBlock "Example line\n" + -- Amusewiki requires braces to be on separate line, + -- this is an extension. + , "One line" =: + "{{{Example line}}}" =?> + codeBlock "Example line" + ] + , testGroup "Example tag" + [ "Tags on separate lines" =: + T.unlines [ "<example>" + , "Example line" + , "</example>" + ] =?> + codeBlock "Example line" + , "One line" =: + "<example>Example line</example>" =?> + codeBlock "Example line" + , "One blank line in the beginning" =: + T.unlines [ "<example>" + , "" + , "Example line" + , "</example>" + ] =?> + codeBlock "\nExample line" + , "One blank line in the end" =: + T.unlines [ "<example>" + , "Example line" + , "" + , "</example>" + ] =?> + codeBlock "Example line\n" + , "Example inside list" =: + T.unlines [ " - <example>" + , " foo" + , " </example>" + ] =?> + bulletList [ codeBlock "foo" ] + , "Empty example inside list" =: + T.unlines [ " - <example>" + , " </example>" + ] =?> + bulletList [ codeBlock "" ] + , "Example inside list with empty lines" =: + T.unlines [ " - <example>" + , " foo" + , " </example>" + , "" + , " bar" + , "" + , " <example>" + , " baz" + , " </example>" + ] =?> + bulletList [ codeBlock "foo" <> para "bar" <> codeBlock "baz" ] + , "Indented example inside list" =: + T.unlines [ " - <example>" + , " foo" + , " </example>" + ] =?> + bulletList [ codeBlock "foo" ] + , "Example inside definition list" =: + T.unlines [ " foo :: <example>" + , " bar" + , " </example>" + ] =?> + definitionList [ ("foo", [codeBlock "bar"]) ] + , "Example inside list definition with empty lines" =: + T.unlines [ " term :: <example>" + , " foo" + , " </example>" + , "" + , " bar" + , "" + , " <example>" + , " baz" + , " </example>" + ] =?> + definitionList [ ("term", [codeBlock "foo" <> para "bar" <> codeBlock "baz"]) ] + , "Example inside note" =: + T.unlines [ "Foo[1]" + , "" + , "[1] <example>" + , " bar" + , " </example>" + ] =?> + para ("Foo" <> note (codeBlock "bar")) + ] + , testGroup "Literal blocks" + [ test emacsMuse "Literal block" + (T.unlines [ "<literal style=\"latex\">" + , "\\newpage" + , "</literal>" + ] =?> + rawBlock "latex" "\\newpage") + ] + , "Center" =: + T.unlines [ "<center>" + , "Hello, world" + , "</center>" + ] =?> + para (text "Hello, world") + , "Right" =: + T.unlines [ "<right>" + , "Hello, world" + , "</right>" + ] =?> + para (text "Hello, world") + , testGroup "Comments" + [ "Comment tag" =: "<comment>\nThis is a comment\n</comment>" =?> (mempty::Blocks) + , "Line comment" =: "; Comment" =?> (mempty::Blocks) + , "Empty comment" =: ";" =?> (mempty::Blocks) + , "Text after empty comment" =: ";\nfoo" =?> para "foo" -- Make sure we don't consume newline while looking for whitespace + , "Not a comment (does not start with a semicolon)" =: " ; Not a comment" =?> para (text "; Not a comment") + , "Not a comment (has no space after semicolon)" =: ";Not a comment" =?> para (text ";Not a comment") + ] + , testGroup "Headers" + [ "Part" =: + "* First level" =?> + header 1 "First level" + , "Chapter" =: + "** Second level" =?> + header 2 "Second level" + , "Section" =: + "*** Third level" =?> + header 3 "Third level" + , "Subsection" =: + "**** Fourth level" =?> + header 4 "Fourth level" + , "Subsubsection" =: + "***** Fifth level" =?> + header 5 "Fifth level" + , "Whitespace is required after *" =: "**Not a header" =?> para "**Not a header" + , "No headers in footnotes" =: + T.unlines [ "Foo[1]" + , "[1] * Bar" + ] =?> + para (text "Foo" <> + note (para "* Bar")) + , "No headers in quotes" =: + T.unlines [ "<quote>" + , "* Hi" + , "</quote>" + ] =?> + blockQuote (para "* Hi") + , "Headers consume anchors" =: + T.unlines [ "** Foo" + , "#bar" + ] =?> + headerWith ("bar",[],[]) 2 "Foo" + , "Headers don't consume anchors separated with a blankline" =: + T.unlines [ "** Foo" + , "" + , "#bar" + ] =?> + header 2 "Foo" <> + para (spanWith ("bar", [], []) mempty) + , "Headers terminate lists" =: + T.unlines [ " - foo" + , "* bar" + ] =?> + bulletList [ para "foo" ] <> + header 1 "bar" + ] + , testGroup "Directives" + [ "Title" =: + "#title Document title" =?> + let titleInline = toList "Document title" + meta = setMeta "title" (MetaInlines titleInline) nullMeta + in Pandoc meta mempty + -- Emacs Muse documentation says that "You can use any combination + -- of uppercase and lowercase letters for directives", + -- but also allows '-', which is not documented, but used for disable-tables. + , test emacsMuse "Disable tables" + ("#disable-tables t" =?> + Pandoc (setMeta "disable-tables" (MetaInlines $ toList "t") nullMeta) mempty) + , "Multiple directives" =: + T.unlines [ "#title Document title" + , "#subtitle Document subtitle" + ] =?> + Pandoc (setMeta "title" (MetaInlines $ toList "Document title") $ + setMeta "subtitle" (MetaInlines $ toList "Document subtitle") nullMeta) mempty + , "Multiline directive" =: + T.unlines [ "#title Document title" + , "#notes First line" + , "and second line" + , "#author Name" + ] =?> + Pandoc (setMeta "title" (MetaInlines $ toList "Document title") $ + setMeta "notes" (MetaInlines $ toList "First line\nand second line") $ + setMeta "author" (MetaInlines $ toList "Name") nullMeta) mempty + ] + , testGroup "Anchors" + [ "Anchor" =: + T.unlines [ "; A comment to make sure anchor is not parsed as a directive" + , "#anchor Target" + ] =?> + para (spanWith ("anchor", [], []) mempty <> "Target") + , "Anchor cannot start with a number" =: + T.unlines [ "; A comment to make sure anchor is not parsed as a directive" + , "#0notanchor Target" + ] =?> + para "#0notanchor Target" + , "Not anchor if starts with a space" =: + " #notanchor Target" =?> + para "#notanchor Target" + , "Anchor inside a paragraph" =: + T.unlines [ "Paragraph starts here" + , "#anchor and ends here." + ] =?> + para ("Paragraph starts here\n" <> spanWith ("anchor", [], []) mempty <> "and ends here.") + ] + , testGroup "Footnotes" + [ "Simple footnote" =: + T.unlines [ "Here is a footnote[1]." + , "" + , "[1] Footnote contents" + ] =?> + para (text "Here is a footnote" <> + note (para "Footnote contents") <> + str ".") + , "Recursive footnote" =: + T.unlines [ "Start recursion here[1]" + , "" + , "[1] Recursion continues here[1]" + ] =?> + para (text "Start recursion here" <> + note (para "Recursion continues here[1]")) + , "No zero footnotes" =: + T.unlines [ "Here is a footnote[0]." + , "" + , "[0] Footnote contents" + ] =?> + para "Here is a footnote[0]." <> + para "[0] Footnote contents" + , "Footnotes can't start with zero" =: + T.unlines [ "Here is a footnote[01]." + , "" + , "[01] Footnote contents" + ] =?> + para "Here is a footnote[01]." <> + para "[01] Footnote contents" + , testGroup "Multiparagraph footnotes" + [ "Amusewiki multiparagraph footnotes" =: + T.unlines [ "Multiparagraph[1] footnotes[2]" + , "" + , "[1] First footnote paragraph" + , "" + , " Second footnote paragraph" + , "with continuation" + , "" + , "Not a note" + , "[2] Second footnote" + ] =?> + para (text "Multiparagraph" <> + note (para "First footnote paragraph" <> + para "Second footnote paragraph\nwith continuation") <> + text " footnotes" <> + note (para "Second footnote")) <> + para (text "Not a note") + + -- Verse requires precise indentation, so it is good to test indentation requirements + , "Note continuation with verse" =: + T.unlines [ "Foo[1]" + , "" + , "[1] Bar" + , "" + , " > Baz" + ] =?> + para ("Foo" <> note (para "Bar" <> lineBlock ["Baz"])) + , test emacsMuse "Emacs multiparagraph footnotes" + (T.unlines + [ "First footnote reference[1] and second footnote reference[2]." + , "" + , "[1] First footnote paragraph" + , "" + , "Second footnote" + , "paragraph" + , "" + , "[2] Third footnote paragraph" + , "" + , "Fourth footnote paragraph" + ] =?> + para (text "First footnote reference" <> + note (para "First footnote paragraph" <> + para "Second footnote\nparagraph") <> + text " and second footnote reference" <> + note (para "Third footnote paragraph" <> + para "Fourth footnote paragraph") <> + text ".")) + ] + ] + ] + , testGroup "Tables" + [ "Two cell table" =: + "One | Two" =?> + table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)] + [] + [[plain "One", plain "Two"]] + , "Table with multiple words" =: + "One two | three four" =?> + table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)] + [] + [[plain "One two", plain "three four"]] + , "Not a table" =: + "One| Two" =?> + para (text "One| Two") + , "Not a table again" =: + "One |Two" =?> + para (text "One |Two") + , "Two line table" =: + T.unlines + [ "One | Two" + , "Three | Four" + ] =?> + table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)] + [] + [[plain "One", plain "Two"], + [plain "Three", plain "Four"]] + , "Table with one header" =: + T.unlines + [ "First || Second" + , "Third | Fourth" + ] =?> + table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)] + [plain "First", plain "Second"] + [[plain "Third", plain "Fourth"]] + , "Table with two headers" =: + T.unlines + [ "First || header" + , "Second || header" + , "Foo | bar" + ] =?> + table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)] + [plain "First", plain "header"] + [[plain "Second", plain "header"], + [plain "Foo", plain "bar"]] + , "Header and footer reordering" =: + T.unlines + [ "Foo ||| bar" + , "Baz || foo" + , "Bar | baz" + ] =?> + table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)] + [plain "Baz", plain "foo"] + [[plain "Bar", plain "baz"], + [plain "Foo", plain "bar"]] + , "Table with caption" =: + T.unlines + [ "Foo || bar || baz" + , "First | row | here" + , "Second | row | there" + , "|+ Table caption +|" + ] =?> + table (text "Table caption") (replicate 3 (AlignDefault, 0.0)) + [plain "Foo", plain "bar", plain "baz"] + [[plain "First", plain "row", plain "here"], + [plain "Second", plain "row", plain "there"]] + , "Caption without table" =: + "|+ Foo bar baz +|" =?> + table (text "Foo bar baz") [] [] [] + , "Table indented with space" =: + T.unlines + [ " Foo | bar" + , " Baz | foo" + , " Bar | baz" + ] =?> + table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)] + [] + [[plain "Foo", plain "bar"], + [plain "Baz", plain "foo"], + [plain "Bar", plain "baz"]] + , "Empty cells" =: + T.unlines + [ " | Foo" + , " |" + , " bar |" + , " || baz" + ] =?> + table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)] + [plain "", plain "baz"] + [[plain "", plain "Foo"], + [plain "", plain ""], + [plain "bar", plain ""]] + ] + , testGroup "Lists" + [ "Bullet list" =: + T.unlines + [ " - Item1" + , "" + , " - Item2" + ] =?> + bulletList [ para "Item1" + , para "Item2" + ] + , "Ordered list" =: + T.unlines + [ " 1. Item1" + , "" + , " 2. Item2" + ] =?> + orderedListWith (1, Decimal, Period) [ para "Item1" + , para "Item2" + ] + , "Ordered list with implicit numbers" =: + T.unlines + [ " 1. Item1" + , "" + , " 1. Item2" + , "" + , " 1. Item3" + ] =?> + orderedListWith (1, Decimal, Period) [ para "Item1" + , para "Item2" + , para "Item3" + ] + , "Ordered list with roman numerals" =: + T.unlines + [ " i. First" + , " ii. Second" + , " iii. Third" + , " iv. Fourth" + ] =?> + orderedListWith (1, LowerRoman, Period) [ para "First" + , para "Second" + , para "Third" + , para "Fourth" + ] + , "Bullet list with empty items" =: + T.unlines + [ " -" + , "" + , " - Item2" + ] =?> + bulletList [ mempty + , para "Item2" + ] + , "Ordered list with empty items" =: + T.unlines + [ " 1." + , "" + , " 2." + , "" + , " 3. Item3" + ] =?> + orderedListWith (1, Decimal, Period) [ mempty + , mempty + , para "Item3" + ] + , "Bullet list with last item empty" =: + T.unlines + [ " -" + , "" + , "foo" + ] =?> + bulletList [ mempty ] <> + para "foo" + , testGroup "Nested lists" + [ "Nested bullet list" =: + T.unlines [ " - Item1" + , " - Item2" + , " - Item3" + , " - Item4" + , " - Item5" + , " - Item6" + ] =?> + bulletList [ para "Item1" <> + bulletList [ para "Item2" <> + bulletList [ para "Item3" ] + , para "Item4" <> + bulletList [ para "Item5" ] + ] + , para "Item6" + ] + , "Nested ordered list" =: + T.unlines [ " 1. Item1" + , " 1. Item2" + , " 1. Item3" + , " 2. Item4" + , " 1. Item5" + , " 2. Item6" + ] =?> + orderedListWith (1, Decimal, Period) [ para "Item1" <> + orderedListWith (1, Decimal, Period) [ para "Item2" <> + orderedListWith (1, Decimal, Period) [ para "Item3" ] + , para "Item4" <> + orderedListWith (1, Decimal, Period) [ para "Item5" ] + ] + , para "Item6" + ] + , "Mixed nested list" =: + T.unlines + [ " - Item1" + , " - Item2" + , " - Item3" + , " - Item4" + , " 1. Nested" + , " 2. Ordered" + , " 3. List" + ] =?> + bulletList [ mconcat [ para "Item1" + , bulletList [ para "Item2" + , para "Item3" + ] + ] + , mconcat [ para "Item4" + , orderedListWith (1, Decimal, Period) [ para "Nested" + , para "Ordered" + , para "List" + ] + ] + ] + , "Text::Amuse includes only one space in list marker" =: + T.unlines + [ " - First item" + , " - Nested item" + ] =?> + bulletList [ para "First item" <> bulletList [ para "Nested item"]] + ] + , "List continuation" =: + T.unlines + [ " - a" + , "" + , " b" + , "" + , " c" + ] =?> + bulletList [ mconcat [ para "a" + , para "b" + , para "c" + ] + ] + , "List continuation afeter nested list" =: + T.unlines + [ " - - foo" + , "" + , " bar" + ] =?> + bulletList [ bulletList [ para "foo" ] <> + para "bar" + ] + -- Emacs Muse allows to separate lists with two or more blank lines. + -- Text::Amuse (Amusewiki engine) always creates a single list as of version 0.82. + -- pandoc follows Emacs Muse behavior + , testGroup "Blank lines" + [ "Blank lines between list items are not required" =: + T.unlines + [ " - Foo" + , " - Bar" + ] =?> + bulletList [ para "Foo" + , para "Bar" + ] + , "One blank line between list items is allowed" =: + T.unlines + [ " - Foo" + , "" + , " - Bar" + ] =?> + bulletList [ para "Foo" + , para "Bar" + ] + , "Two blank lines separate lists" =: + T.unlines + [ " - Foo" + , "" + , "" + , " - Bar" + ] =?> + bulletList [ para "Foo" ] <> bulletList [ para "Bar" ] + , "No blank line after multiline first item" =: + T.unlines + [ " - Foo" + , " bar" + , " - Baz" + ] =?> + bulletList [ para "Foo\nbar" + , para "Baz" + ] + , "One blank line after multiline first item" =: + T.unlines + [ " - Foo" + , " bar" + , "" + , " - Baz" + ] =?> + bulletList [ para "Foo\nbar" + , para "Baz" + ] + , "Two blank lines after multiline first item" =: + T.unlines + [ " - Foo" + , " bar" + , "" + , "" + , " - Baz" + ] =?> + bulletList [ para "Foo\nbar" ] <> bulletList [ para "Baz" ] + , "No blank line after list continuation" =: + T.unlines + [ " - Foo" + , "" + , " bar" + , " - Baz" + ] =?> + bulletList [ para "Foo" <> para "bar" + , para "Baz" + ] + , "One blank line after list continuation" =: + T.unlines + [ " - Foo" + , "" + , " bar" + , "" + , " - Baz" + ] =?> + bulletList [ para "Foo" <> para "bar" + , para "Baz" + ] + , "Two blank lines after list continuation" =: + T.unlines + [ " - Foo" + , "" + , " bar" + , "" + , "" + , " - Baz" + ] =?> + bulletList [ para "Foo" <> para "bar" ] <> bulletList [ para "Baz" ] + , "No blank line after blockquote" =: + T.unlines + [ " - <quote>" + , " foo" + , " </quote>" + , " - bar" + ] =?> + bulletList [ blockQuote $ para "foo", para "bar" ] + , "One blank line after blockquote" =: + T.unlines + [ " - <quote>" + , " foo" + , " </quote>" + , "" + , " - bar" + ] =?> + bulletList [ blockQuote $ para "foo", para "bar" ] + , "Two blank lines after blockquote" =: + T.unlines + [ " - <quote>" + , " foo" + , " </quote>" + , "" + , "" + , " - bar" + ] =?> + bulletList [ blockQuote $ para "foo" ] <> bulletList [ para "bar" ] + , "No blank line after verse" =: + T.unlines + [ " - > foo" + , " - bar" + ] =?> + bulletList [ lineBlock [ "foo" ], para "bar" ] + , "One blank line after verse" =: + T.unlines + [ " - > foo" + , "" + , " - bar" + ] =?> + bulletList [ lineBlock [ "foo" ], para "bar" ] + , "Two blank lines after verse" =: + T.unlines + [ " - > foo" + , "" + , "" + , " - bar" + ] =?> + bulletList [ lineBlock [ "foo" ] ] <> bulletList [ para "bar" ] + ] + -- Test that definition list requires a leading space. + -- Emacs Muse does not require a space, we follow Amusewiki here. + , "Not a definition list" =: + T.unlines + [ "First :: second" + , "Foo :: bar" + ] =?> + para "First :: second\nFoo :: bar" + , test emacsMuse "Emacs Muse definition list" + (T.unlines + [ "First :: second" + , "Foo :: bar" + ] =?> + definitionList [ ("First", [ para "second" ]) + , ("Foo", [ para "bar" ]) + ]) + , "Definition list" =: + T.unlines + [ " First :: second" + , " Foo :: bar" + ] =?> + definitionList [ ("First", [ para "second" ]) + , ("Foo", [ para "bar" ]) + ] + , "Definition list term cannot include newline" =: + T.unlines + [ " Foo" -- "Foo" is not a part of the definition list term + , " Bar :: baz" + ] =?> + para "Foo" <> + definitionList [ ("Bar", [ para "baz" ]) ] + , "One-line definition list" =: " foo :: bar" =?> + definitionList [ ("foo", [ para "bar" ]) ] + , "Definition list term may include single colon" =: + " foo:bar :: baz" =?> + definitionList [ ("foo:bar", [ para "baz" ]) ] + , "Definition list term with emphasis" =: " *Foo* :: bar\n" =?> + definitionList [ (emph "Foo", [ para "bar" ]) ] + , "Definition list term with :: inside code" =: " foo <code> :: </code> :: bar <code> :: </code> baz\n" =?> + definitionList [ ("foo " <> code " :: ", [ para $ "bar " <> code " :: " <> " baz" ]) ] + , "Multi-line definition lists" =: + T.unlines + [ " First term :: Definition of first term" + , "and its continuation." + , " Second term :: Definition of second term." + ] =?> + definitionList [ ("First term", [ para "Definition of first term\nand its continuation." ]) + , ("Second term", [ para "Definition of second term." ]) + ] + , test emacsMuse "Multi-line definition lists from Emacs Muse manual" + (T.unlines + [ "Term1 ::" + , " This is a first definition" + , " And it has two lines;" + , "no, make that three." + , "" + , "Term2 :: This is a second definition" + ] =?> + definitionList [ ("Term1", [ para "This is a first definition\nAnd it has two lines;\nno, make that three."]) + , ("Term2", [ para "This is a second definition"]) + ]) + -- Text::Amuse requires indentation with one space + , "Multi-line definition lists from Emacs Muse manual with initial space" =: + (T.unlines + [ " Term1 ::" + , " This is a first definition" + , " And it has two lines;" + , "no, make that three." + , "" + , " Term2 :: This is a second definition" + ] =?> + definitionList [ ("Term1", [ para "This is a first definition\nAnd it has two lines;\nno, make that three."]) + , ("Term2", [ para "This is a second definition"]) + ]) + , "One-line nested definition list" =: + " Foo :: bar :: baz" =?> + definitionList [ ("Foo", [ definitionList [ ("bar", [ para "baz" ])]])] + , "Nested definition list" =: + T.unlines + [ " First :: Second :: Third" + , " Fourth :: Fifth :: Sixth" + , " Seventh :: Eighth" + ] =?> + definitionList [ ("First", [ definitionList [ ("Second", [ para "Third" ]), + ("Fourth", [ definitionList [ ("Fifth", [ para "Sixth"] ) ] ] ) ] ] ) + , ("Seventh", [ para "Eighth" ]) + ] + , testGroup "Definition lists with multiple descriptions" + [ "Correctly indented second description" =: + T.unlines + [ " First term :: first description" + , " :: second description" + ] =?> + definitionList [ ("First term", [ para "first description" + , para "second description" + ]) + ] + , "Incorrectly indented second description" =: + T.unlines + [ " First term :: first description" + , " :: second description" + ] =?> + definitionList [ ("First term", [ para "first description" ]) + , ("", [ para "second description" ]) + ] + ] + , "Two blank lines separate definition lists" =: + T.unlines + [ " First :: list" + , "" + , "" + , " Second :: list" + ] =?> + definitionList [ ("First", [ para "list" ]) ] <> + definitionList [ ("Second", [ para "list" ]) ] + -- Headers in first column of list continuation are not allowed + , "No headers in list continuation" =: + T.unlines + [ " - Foo" + , "" + , " * Bar" + ] =?> + bulletList [ mconcat [ para "Foo" + , para "* Bar" + ] + ] + , "Bullet list inside a tag" =: + T.unlines + [ "<quote>" + , " - First" + , "" + , " - Second" + , "" + , " - Third" + , "</quote>" + ] =?> + blockQuote (bulletList [ para "First" + , para "Second" + , para "Third" + ]) + , "Ordered list inside a tag" =: + T.unlines + [ "<quote>" + , " 1. First" + , "" + , " 2. Second" + , "" + , " 3. Third" + , "</quote>" + ] =?> + blockQuote (orderedListWith (1, Decimal, Period) [ para "First" + , para "Second" + , para "Third" + ]) + -- Regression test for a bug caught by round-trip test + , "Do not consume whitespace while looking for end tag" =: + T.unlines + [ "<quote>" + , " - <quote>" + , " foo" + , " </quote>" + , " bar" -- Do not consume whitespace while looking for arbitraritly indented </quote> + , "</quote>" + ] =?> + blockQuote (bulletList [ blockQuote $ para "foo" ] <> para "bar") + ] + ] |