summaryrefslogtreecommitdiff
path: root/test/Tests/Readers/Muse.hs
diff options
context:
space:
mode:
Diffstat (limited to 'test/Tests/Readers/Muse.hs')
-rw-r--r--test/Tests/Readers/Muse.hs1224
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")
+ ]
+ ]