@@ -0,0 +1,1262 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Muse (tests) where
4+import Data.List (intersperse)
5+import Data.Text (Text)
6+import qualified Data.Text as T
8+import Test.Tasty.QuickCheck
11+import Text.Pandoc.Arbitrary ()
12+import Text.Pandoc.Builder
13+import Text.Pandoc.Shared (underlineSpan)
14+import Text.Pandoc.Walk (walk)
16+amuse :: Text -> Pandoc
17+amuse = purely $ readMuse def { readerExtensions = extensionsFromList [Ext_amuse]}
19+emacsMuse :: Text -> Pandoc
20+emacsMuse = purely $ readMuse def { readerExtensions = emptyExtensions }
24+ => String -> (Text, c) -> TestTree
27+spcSep :: [Inlines] -> Inlines
28+spcSep = mconcat . intersperse space
30+-- Tables don't round-trip yet
32+makeRoundTrip :: Block -> Block
33+makeRoundTrip Table{} = Para [Str "table was here"]
34+makeRoundTrip (OrderedList (start, LowerAlpha, _) items) = OrderedList (start, Decimal, Period) items
35+makeRoundTrip (OrderedList (start, UpperAlpha, _) items) = OrderedList (start, Decimal, Period) items
38+-- Demand that any AST produced by Muse reader and written by Muse writer can be read back exactly the same way.
39+-- Currently we remove tables and compare first rewrite to the second.
40+roundTrip :: Block -> Bool
41+roundTrip b = d' == d''
42+ where d = walk makeRoundTrip $ Pandoc nullMeta [b]
45+ rewrite = amuse . T.pack . (++ "\n") . T.unpack .
46+ purely (writeMuse def { writerExtensions = extensionsFromList [Ext_amuse]
47+ , writerWrapText = WrapPreserve
52+ [ testGroup "Inlines"
57+ , "Muse is not XML" =: "<" =?> para "<"
61+ para (emph . spcSep $ ["Foo", "bar"])
63+ , "Comma after closing *" =:
65+ para ("Foo " <> emph "bar" <> ", baz")
67+ , "Letter after closing *" =:
69+ para "Foo *bar*x baz"
71+ , "Letter before opening *" =:
73+ para "Foo x*bar* baz"
76+ "<em>Foo bar</em>" =?>
77+ para (emph . spcSep $ ["Foo", "bar"])
81+ para (strong "Cider")
83+ , "Strong tag" =: "<strong>Strong</strong>" =?> para (strong "Strong")
85+ , "Strong Emphasis" =:
87+ para (strong . emph $ "strength")
89+ , test emacsMuse "Underline"
90+ ("_Underline_" =?> para (underlineSpan "Underline"))
92+ , "Superscript tag" =: "<sup>Superscript</sup>" =?> para (superscript "Superscript")
94+ , "Subscript tag" =: "<sub>Subscript</sub>" =?> para (subscript "Subscript")
96+ , "Strikeout tag" =: "<del>Strikeout</del>" =?> para (strikeout "Strikeout")
98+ , "Opening inline tags" =: "foo <em> bar <strong>baz" =?> para "foo <em> bar <strong>baz"
100+ , "Closing inline tags" =: "foo </em> bar </strong>baz" =?> para "foo </em> bar </strong>baz"
102+ , "Tag soup" =: "foo <em> bar </strong>baz" =?> para "foo <em> bar </strong>baz"
104+ -- Both inline tags must be within the same paragraph
105+ , "No multiparagraph inline tags" =:
106+ T.unlines [ "First line"
109+ , "Fourth line</em>"
111+ para "First line\n<em>Second line" <>
112+ para "Fourth line</em>"
114+ , "Linebreak" =: "Line <br> break" =?> para ("Line" <> linebreak <> "break")
116+ , "Trailing whitespace inside paragraph" =:
117+ T.unlines [ "First line " -- trailing whitespace here
120+ =?> para "First line\nsecond line"
122+ , "Non-breaking space" =: "Foo~~bar" =?> para "Foo\160bar"
123+ , "Single ~" =: "Foo~bar" =?> para "Foo~bar"
125+ , testGroup "Code markup"
126+ [ "Code" =: "=foo(bar)=" =?> para (code "foo(bar)")
128+ , "Not code" =: "a=b= =c=d" =?> para (text "a=b= =c=d")
130+ -- Emacs Muse 3.20 parses this as code, we follow Amusewiki
131+ , "Not code if closing = is detached" =: "=this is not a code =" =?> para "=this is not a code ="
133+ , "Not code if opening = is detached" =: "= this is not a code=" =?> para "= this is not a code="
135+ , "Code if followed by comma" =:
136+ "Foo =bar=, baz" =?>
137+ para (text "Foo " <> code "bar" <> text ", baz")
139+ , "One character code" =: "=c=" =?> para (code "c")
141+ , "Three = characters is not a code" =: "===" =?> para "==="
143+ , "Multiline code markup" =:
144+ "foo =bar\nbaz= end of code" =?>
145+ para (text "foo " <> code "bar\nbaz" <> text " end of code")
147+{- Emacs Muse 3.20 has a bug: it publishes
150+ - baz</code> foo</p>
151+ - which is displayed as one paragraph by browsers.
152+ - We follow Amusewiki here and avoid joining paragraphs.
154+ , "No multiparagraph code" =:
155+ T.unlines [ "foo =bar"
162+ , "Code at the beginning of paragraph but not first column" =:
163+ " - =foo=" =?> bulletList [ para $ code "foo" ]
166+ , "Code tag" =: "<code>foo(bar)</code>" =?> para (code "foo(bar)")
168+ , "Verbatim tag" =: "*<verbatim>*</verbatim>*" =?> para (emph "*")
170+ , "Verbatim inside code" =: "<code><verbatim>foo</verbatim></code>" =?> para (code "<verbatim>foo</verbatim>")
172+ , "Verbatim tag after text" =: "Foo <verbatim>bar</verbatim>" =?> para "Foo bar"
174+ , "Class tag" =: "<class name=\"foo\">bar</class>" =?> para (spanWith ("", ["foo"], []) "bar")
175+ , "Class tag without name" =: "<class>foobar</class>" =?> para (spanWith ("", [], []) "foobar")
177+ -- <em> tag should match with the last </em> tag, not verbatim one
178+ , "Nested \"</em>\" inside em tag" =: "<em>foo<verbatim></em></verbatim>bar</em>" =?> para (emph "foo</em>bar")
181+ [ "Link without description" =:
182+ "[[https://amusewiki.org/]]" =?>
183+ para (link "https://amusewiki.org/" "" (str "https://amusewiki.org/"))
184+ , "Link with description" =:
185+ "[[https://amusewiki.org/][A Muse Wiki]]" =?>
186+ para (link "https://amusewiki.org/" "" (text "A Muse Wiki"))
189+ para (image "image.jpg" "" mempty)
190+ , "Image with description" =:
191+ "[[image.jpg][Image]]" =?>
192+ para (image "image.jpg" "" (text "Image"))
194+ "[[URL:image.jpg]]" =?>
195+ para (link "image.jpg" "" (str "image.jpg"))
196+ , "Image link with description" =:
197+ "[[URL:image.jpg][Image]]" =?>
198+ para (link "image.jpg" "" (text "Image"))
199+ -- Implicit links are supported in Emacs Muse, but not in Amusewiki:
200+ -- https://github.com/melmothx/text-amuse/issues/18
202+ -- This test also makes sure '=' without whitespace is not treated as code markup
203+ , "No implicit links" =: "http://example.org/index.php?action=view&id=1"
204+ =?> para "http://example.org/index.php?action=view&id=1"
207+ , testGroup "Literal"
208+ [ test emacsMuse "Inline literal"
209+ ("Foo<literal style=\"html\">lit</literal>bar" =?>
210+ para (text "Foo" <> rawInline "html" "lit" <> text "bar"))
214+ , testGroup "Blocks"
215+ [ testProperty "Round trip" roundTrip,
216+ "Block elements end paragraphs" =:
217+ T.unlines [ "First paragraph"
219+ , "Second paragraph"
220+ ] =?> para (text "First paragraph") <> horizontalRule <> para (text "Second paragraph")
221+ , testGroup "Horizontal rule"
222+ [ "Less than 4 dashes is not a horizontal rule" =: "---" =?> para (text "---")
223+ , "4 dashes is a horizontal rule" =: "----" =?> horizontalRule
224+ , "5 dashes is a horizontal rule" =: "-----" =?> horizontalRule
225+ , "4 dashes with spaces is a horizontal rule" =: "---- " =?> horizontalRule
227+ , testGroup "Paragraphs"
228+ [ "Simple paragraph" =:
229+ T.unlines [ "First line"
232+ para "First line\nsecond line."
233+ , "Indented paragraph" =:
234+ T.unlines [ " First line"
237+ para "First line\nsecond line."
238+ -- Emacs Muse starts a blockquote on the second line.
239+ -- We copy Amusewiki behavior and require a blank line to start a blockquote.
240+ , "Indentation in the middle of paragraph" =:
241+ T.unlines [ "First line"
245+ para "First line\nsecond line\nthird line"
247+ " This is a quotation\n" =?>
248+ blockQuote (para "This is a quotation")
249+ , "Indentation does not indicate quote inside quote tag" =:
250+ T.unlines [ "<quote>"
251+ , " Not a nested quote"
254+ blockQuote (para "Not a nested quote")
255+ , "Multiline quote" =:
256+ T.unlines [ " This is a quotation"
257+ , " with a continuation"
259+ blockQuote (para "This is a quotation\nwith a continuation")
261+ [ "Div without id" =:
266+ divWith nullAttr (para "Foo bar")
268+ T.unlines [ "<div id=\"foo\">"
272+ divWith ("foo", [], []) (para "Foo bar")
275+ T.unlines [ "> This is"
277+ , ">" -- Emacs produces verbatim ">" here, we follow Amusewiki
287+ lineBlock [ "This is"
291+ , "\160\160Second stanza"
295+ lineBlock [ "Another verse"
296+ , "\160\160\160is here"
299+ , "Verse in list" =: " - > foo" =?> bulletList [ lineBlock [ "foo" ] ]
300+ , "Verse line starting with emphasis" =: "> *foo* bar" =?> lineBlock [ emph "foo" <> text " bar" ]
301+ , "Multiline verse in list" =:
302+ T.unlines [ " - > foo"
305+ bulletList [ lineBlock [ "foo", "bar" ] ]
306+ , "Paragraph after verse in list" =:
307+ T.unlines [ " - > foo"
310+ bulletList [ lineBlock [ "foo" ] <> para "bar" ]
311+ , "Empty quote tag" =:
312+ T.unlines [ "<quote>"
315+ =?> blockQuote mempty
317+ T.unlines [ "<quote>"
321+ =?> blockQuote (para $ text "Hello, world")
322+ , "Nested quote tag" =:
323+ T.unlines [ "<quote>"
331+ blockQuote (para "foo" <> blockQuote (para "bar") <> para "baz")
332+ , "Indented quote inside list" =:
333+ T.unlines [ " - <quote>"
337+ bulletList [ blockQuote (para "foo") ]
339+ T.unlines [ "<verse>"
347+ , text "Foo bar baz"
348+ , text "\160\160One two three"
351+ , "Verse tag with empty line inside" =:
352+ T.unlines [ "<verse>"
357+ , testGroup "Example"
358+ [ "Braces on separate lines" =:
363+ codeBlock "Example line"
364+ , "Spaces after opening braces" =:
369+ codeBlock "Example line"
370+ , "One blank line in the beginning" =:
376+ codeBlock "\nExample line"
377+ , "One blank line in the end" =:
383+ codeBlock "Example line\n"
384+ -- Amusewiki requires braces to be on separate line,
385+ -- this is an extension.
387+ "{{{Example line}}}" =?>
388+ codeBlock "Example line"
390+ , testGroup "Example tag"
391+ [ "Tags on separate lines" =:
392+ T.unlines [ "<example>"
396+ codeBlock "Example line"
398+ "<example>Example line</example>" =?>
399+ codeBlock "Example line"
400+ , "One blank line in the beginning" =:
401+ T.unlines [ "<example>"
406+ codeBlock "\nExample line"
407+ , "One blank line in the end" =:
408+ T.unlines [ "<example>"
413+ codeBlock "Example line\n"
414+ , "Example inside list" =:
415+ T.unlines [ " - <example>"
419+ bulletList [ codeBlock "foo" ]
420+ , "Empty example inside list" =:
421+ T.unlines [ " - <example>"
424+ bulletList [ codeBlock "" ]
425+ , "Example inside list with empty lines" =:
426+ T.unlines [ " - <example>"
436+ bulletList [ codeBlock "foo" <> para "bar" <> codeBlock "baz" ]
437+ , "Indented example inside list" =:
438+ T.unlines [ " - <example>"
442+ bulletList [ codeBlock "foo" ]
443+ , "Example inside definition list" =:
444+ T.unlines [ " foo :: <example>"
448+ definitionList [ ("foo", [codeBlock "bar"]) ]
449+ , "Example inside list definition with empty lines" =:
450+ T.unlines [ " term :: <example>"
460+ definitionList [ ("term", [codeBlock "foo" <> para "bar" <> codeBlock "baz"]) ]
461+ , "Example inside note" =:
462+ T.unlines [ "Foo[1]"
468+ para ("Foo" <> note (codeBlock "bar"))
470+ , testGroup "Literal blocks"
471+ [ test emacsMuse "Literal block"
472+ (T.unlines [ "<literal style=\"latex\">"
476+ rawBlock "latex" "\\newpage")
479+ T.unlines [ "<center>"
483+ para (text "Hello, world")
485+ T.unlines [ "<right>"
489+ para (text "Hello, world")
490+ , testGroup "Comments"
491+ [ "Comment tag" =: "<comment>\nThis is a comment\n</comment>" =?> (mempty::Blocks)
492+ , "Line comment" =: "; Comment" =?> (mempty::Blocks)
493+ , "Empty comment" =: ";" =?> (mempty::Blocks)
494+ , "Text after empty comment" =: ";\nfoo" =?> para "foo" -- Make sure we don't consume newline while looking for whitespace
495+ , "Not a comment (does not start with a semicolon)" =: " ; Not a comment" =?> para (text "; Not a comment")
496+ , "Not a comment (has no space after semicolon)" =: ";Not a comment" =?> para (text ";Not a comment")
498+ , testGroup "Headers"
501+ header 1 "First level"
503+ "** Second level" =?>
504+ header 2 "Second level"
506+ "*** Third level" =?>
507+ header 3 "Third level"
509+ "**** Fourth level" =?>
510+ header 4 "Fourth level"
511+ , "Subsubsection" =:
512+ "***** Fifth level" =?>
513+ header 5 "Fifth level"
514+ , "Whitespace is required after *" =: "**Not a header" =?> para "**Not a header"
515+ , "No headers in footnotes" =:
516+ T.unlines [ "Foo[1]"
520+ note (para "* Bar"))
521+ , "No headers in quotes" =:
522+ T.unlines [ "<quote>"
526+ blockQuote (para "* Hi")
527+ , "Headers consume anchors" =:
528+ T.unlines [ "** Foo"
531+ headerWith ("bar",[],[]) 2 "Foo"
532+ , "Headers don't consume anchors separated with a blankline" =:
533+ T.unlines [ "** Foo"
538+ para (spanWith ("bar", [], []) mempty)
539+ , "Headers terminate lists" =:
540+ T.unlines [ " - foo"
543+ bulletList [ para "foo" ] <>
546+ , testGroup "Directives"
548+ "#title Document title" =?>
549+ let titleInline = toList "Document title"
550+ meta = setMeta "title" (MetaInlines titleInline) nullMeta
551+ in Pandoc meta mempty
552+ -- Emacs Muse documentation says that "You can use any combination
553+ -- of uppercase and lowercase letters for directives",
554+ -- but also allows '-', which is not documented, but used for disable-tables.
555+ , test emacsMuse "Disable tables"
556+ ("#disable-tables t" =?>
557+ Pandoc (setMeta "disable-tables" (MetaInlines $ toList "t") nullMeta) mempty)
558+ , "Multiple directives" =:
559+ T.unlines [ "#title Document title"
560+ , "#subtitle Document subtitle"
562+ Pandoc (setMeta "title" (MetaInlines $ toList "Document title") $
563+ setMeta "subtitle" (MetaInlines $ toList "Document subtitle") nullMeta) mempty
564+ , "Multiline directive" =:
565+ T.unlines [ "#title Document title"
566+ , "#notes First line"
570+ Pandoc (setMeta "title" (MetaInlines $ toList "Document title") $
571+ setMeta "notes" (MetaInlines $ toList "First line\nand second line") $
572+ setMeta "author" (MetaInlines $ toList "Name") nullMeta) mempty
574+ , testGroup "Anchors"
576+ T.unlines [ "; A comment to make sure anchor is not parsed as a directive"
579+ para (spanWith ("anchor", [], []) mempty <> "Target")
580+ , "Anchor cannot start with a number" =:
581+ T.unlines [ "; A comment to make sure anchor is not parsed as a directive"
582+ , "#0notanchor Target"
584+ para "#0notanchor Target"
585+ , "Not anchor if starts with a space" =:
586+ " #notanchor Target" =?>
587+ para "#notanchor Target"
588+ , "Anchor inside a paragraph" =:
589+ T.unlines [ "Paragraph starts here"
590+ , "#anchor and ends here."
592+ para ("Paragraph starts here\n" <> spanWith ("anchor", [], []) mempty <> "and ends here.")
594+ , testGroup "Footnotes"
595+ [ "Simple footnote" =:
596+ T.unlines [ "Here is a footnote[1]."
598+ , "[1] Footnote contents"
600+ para (text "Here is a footnote" <>
601+ note (para "Footnote contents") <>
603+ , "Recursive footnote" =:
604+ T.unlines [ "Start recursion here[1]"
606+ , "[1] Recursion continues here[1]"
608+ para (text "Start recursion here" <>
609+ note (para "Recursion continues here[1]"))
610+ , "No zero footnotes" =:
611+ T.unlines [ "Here is a footnote[0]."
613+ , "[0] Footnote contents"
615+ para "Here is a footnote[0]." <>
616+ para "[0] Footnote contents"
617+ , "Footnotes can't start with zero" =:
618+ T.unlines [ "Here is a footnote[01]."
620+ , "[01] Footnote contents"
622+ para "Here is a footnote[01]." <>
623+ para "[01] Footnote contents"
624+ , testGroup "Multiparagraph footnotes"
625+ [ "Amusewiki multiparagraph footnotes" =:
626+ T.unlines [ "Multiparagraph[1] footnotes[2]"
628+ , "[1] First footnote paragraph"
630+ , " Second footnote paragraph"
631+ , "with continuation"
634+ , "[2] Second footnote"
636+ para (text "Multiparagraph" <>
637+ note (para "First footnote paragraph" <>
638+ para "Second footnote paragraph\nwith continuation") <>
639+ text " footnotes" <>
640+ note (para "Second footnote")) <>
641+ para (text "Not a note")
643+ -- Verse requires precise indentation, so it is good to test indentation requirements
644+ , "Note continuation with verse" =:
645+ T.unlines [ "Foo[1]"
651+ para ("Foo" <> note (para "Bar" <> lineBlock ["Baz"]))
652+ , test emacsMuse "Emacs multiparagraph footnotes"
654+ [ "First footnote reference[1] and second footnote reference[2]."
656+ , "[1] First footnote paragraph"
661+ , "[2] Third footnote paragraph"
663+ , "Fourth footnote paragraph"
665+ para (text "First footnote reference" <>
666+ note (para "First footnote paragraph" <>
667+ para "Second footnote\nparagraph") <>
668+ text " and second footnote reference" <>
669+ note (para "Third footnote paragraph" <>
670+ para "Fourth footnote paragraph") <>
675+ , testGroup "Tables"
676+ [ "Two cell table" =:
678+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
680+ [[plain "One", plain "Two"]]
681+ , "Table with multiple words" =:
682+ "One two | three four" =?>
683+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
685+ [[plain "One two", plain "three four"]]
688+ para (text "One| Two")
689+ , "Not a table again" =:
691+ para (text "One |Two")
692+ , "Two line table" =:
697+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
699+ [[plain "One", plain "Two"],
700+ [plain "Three", plain "Four"]]
701+ , "Table with one header" =:
706+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
707+ [plain "First", plain "Second"]
708+ [[plain "Third", plain "Fourth"]]
709+ , "Table with two headers" =:
712+ , "Second || header"
715+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
716+ [plain "First", plain "header"]
717+ [[plain "Second", plain "header"],
718+ [plain "Foo", plain "bar"]]
719+ , "Header and footer reordering" =:
725+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
726+ [plain "Baz", plain "foo"]
727+ [[plain "Bar", plain "baz"],
728+ [plain "Foo", plain "bar"]]
729+ , "Table with caption" =:
731+ [ "Foo || bar || baz"
732+ , "First | row | here"
733+ , "Second | row | there"
734+ , "|+ Table caption +|"
736+ table (text "Table caption") (replicate 3 (AlignDefault, 0.0))
737+ [plain "Foo", plain "bar", plain "baz"]
738+ [[plain "First", plain "row", plain "here"],
739+ [plain "Second", plain "row", plain "there"]]
740+ , "Caption without table" =:
741+ "|+ Foo bar baz +|" =?>
742+ table (text "Foo bar baz") [] [] []
743+ , "Table indented with space" =:
749+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
751+ [[plain "Foo", plain "bar"],
752+ [plain "Baz", plain "foo"],
753+ [plain "Bar", plain "baz"]]
761+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
762+ [plain "", plain "baz"]
763+ [[plain "", plain "Foo"],
764+ [plain "", plain ""],
765+ [plain "bar", plain ""]]
774+ bulletList [ para "Item1"
783+ orderedListWith (1, Decimal, Period) [ para "Item1"
786+ , "Ordered list with implicit numbers" =:
794+ orderedListWith (1, Decimal, Period) [ para "Item1"
798+ , "Ordered list with roman numerals" =:
805+ orderedListWith (1, LowerRoman, Period) [ para "First"
810+ , "Bullet list with empty items" =:
819+ , "Ordered list with empty items" =:
827+ orderedListWith (1, Decimal, Period) [ mempty
831+ , "Bullet list with last item empty" =:
837+ bulletList [ mempty ] <>
839+ , testGroup "Nested lists"
840+ [ "Nested bullet list" =:
841+ T.unlines [ " - Item1"
848+ bulletList [ para "Item1" <>
849+ bulletList [ para "Item2" <>
850+ bulletList [ para "Item3" ]
852+ bulletList [ para "Item5" ]
856+ , "Nested ordered list" =:
857+ T.unlines [ " 1. Item1"
864+ orderedListWith (1, Decimal, Period) [ para "Item1" <>
865+ orderedListWith (1, Decimal, Period) [ para "Item2" <>
866+ orderedListWith (1, Decimal, Period) [ para "Item3" ]
868+ orderedListWith (1, Decimal, Period) [ para "Item5" ]
872+ , "Mixed nested list" =:
882+ bulletList [ mconcat [ para "Item1"
883+ , bulletList [ para "Item2"
887+ , mconcat [ para "Item4"
888+ , orderedListWith (1, Decimal, Period) [ para "Nested"
894+ , "Text::Amuse includes only one space in list marker" =:
899+ bulletList [ para "First item" <> bulletList [ para "Nested item"]]
901+ , "List continuation" =:
909+ bulletList [ mconcat [ para "a"
914+ , "List continuation afeter nested list" =:
920+ bulletList [ bulletList [ para "foo" ] <>
923+ -- Emacs Muse allows to separate lists with two or more blank lines.
924+ -- Text::Amuse (Amusewiki engine) always creates a single list as of version 0.82.
925+ -- pandoc follows Emacs Muse behavior
926+ , testGroup "Blank lines"
927+ [ "Blank lines between list items are not required" =:
932+ bulletList [ para "Foo"
935+ , "One blank line between list items is allowed" =:
941+ bulletList [ para "Foo"
944+ , "Two blank lines separate lists" =:
951+ bulletList [ para "Foo" ] <> bulletList [ para "Bar" ]
952+ , "No blank line after multiline first item" =:
958+ bulletList [ para "Foo\nbar"
961+ , "One blank line after multiline first item" =:
968+ bulletList [ para "Foo\nbar"
971+ , "Two blank lines after multiline first item" =:
979+ bulletList [ para "Foo\nbar" ] <> bulletList [ para "Baz" ]
980+ , "No blank line after list continuation" =:
987+ bulletList [ para "Foo" <> para "bar"
990+ , "One blank line after list continuation" =:
998+ bulletList [ para "Foo" <> para "bar"
1001+ , "Two blank lines after list continuation" =:
1010+ bulletList [ para "Foo" <> para "bar" ] <> bulletList [ para "Baz" ]
1011+ , "No blank line after blockquote" =:
1018+ bulletList [ blockQuote $ para "foo", para "bar" ]
1019+ , "One blank line after blockquote" =:
1027+ bulletList [ blockQuote $ para "foo", para "bar" ]
1028+ , "Two blank lines after blockquote" =:
1037+ bulletList [ blockQuote $ para "foo" ] <> bulletList [ para "bar" ]
1038+ , "No blank line after verse" =:
1043+ bulletList [ lineBlock [ "foo" ], para "bar" ]
1044+ , "One blank line after verse" =:
1050+ bulletList [ lineBlock [ "foo" ], para "bar" ]
1051+ , "Two blank lines after verse" =:
1058+ bulletList [ lineBlock [ "foo" ] ] <> bulletList [ para "bar" ]
1060+ -- Test that definition list requires a leading space.
1061+ -- Emacs Muse does not require a space, we follow Amusewiki here.
1062+ , "Not a definition list" =:
1064+ [ "First :: second"
1067+ para "First :: second\nFoo :: bar"
1068+ , test emacsMuse "Emacs Muse definition list"
1070+ [ "First :: second"
1073+ definitionList [ ("First", [ para "second" ])
1074+ , ("Foo", [ para "bar" ])
1076+ , "Definition list" =:
1078+ [ " First :: second"
1081+ definitionList [ ("First", [ para "second" ])
1082+ , ("Foo", [ para "bar" ])
1084+ , "Definition list term cannot include newline" =:
1086+ [ " Foo" -- "Foo" is not a part of the definition list term
1090+ definitionList [ ("Bar", [ para "baz" ]) ]
1091+ , "One-line definition list" =: " foo :: bar" =?>
1092+ definitionList [ ("foo", [ para "bar" ]) ]
1093+ , "Definition list term may include single colon" =:
1094+ " foo:bar :: baz" =?>
1095+ definitionList [ ("foo:bar", [ para "baz" ]) ]
1096+ , "Definition list term with emphasis" =: " *Foo* :: bar\n" =?>
1097+ definitionList [ (emph "Foo", [ para "bar" ]) ]
1098+ , "Definition list term with :: inside code" =: " foo <code> :: </code> :: bar <code> :: </code> baz\n" =?>
1099+ definitionList [ ("foo " <> code " :: ", [ para $ "bar " <> code " :: " <> " baz" ]) ]
1100+ , "Multi-line definition lists" =:
1102+ [ " First term :: Definition of first term"
1103+ , "and its continuation."
1104+ , " Second term :: Definition of second term."
1106+ definitionList [ ("First term", [ para "Definition of first term\nand its continuation." ])
1107+ , ("Second term", [ para "Definition of second term." ])
1109+ , test emacsMuse "Multi-line definition lists from Emacs Muse manual"
1112+ , " This is a first definition"
1113+ , " And it has two lines;"
1114+ , "no, make that three."
1116+ , "Term2 :: This is a second definition"
1118+ definitionList [ ("Term1", [ para "This is a first definition\nAnd it has two lines;\nno, make that three."])
1119+ , ("Term2", [ para "This is a second definition"])
1121+ -- Text::Amuse requires indentation with one space
1122+ , "Multi-line definition lists from Emacs Muse manual with initial space" =:
1125+ , " This is a first definition"
1126+ , " And it has two lines;"
1127+ , "no, make that three."
1129+ , " Term2 :: This is a second definition"
1131+ definitionList [ ("Term1", [ para "This is a first definition\nAnd it has two lines;\nno, make that three."])
1132+ , ("Term2", [ para "This is a second definition"])
1134+ , "One-line nested definition list" =:
1135+ " Foo :: bar :: baz" =?>
1136+ definitionList [ ("Foo", [ definitionList [ ("bar", [ para "baz" ])]])]
1137+ , "Nested definition list" =:
1139+ [ " First :: Second :: Third"
1140+ , " Fourth :: Fifth :: Sixth"
1141+ , " Seventh :: Eighth"
1143+ definitionList [ ("First", [ definitionList [ ("Second", [ para "Third" ]),
1144+ ("Fourth", [ definitionList [ ("Fifth", [ para "Sixth"] ) ] ] ) ] ] )
1145+ , ("Seventh", [ para "Eighth" ])
1147+ , testGroup "Definition lists with multiple descriptions"
1148+ [ "Correctly indented second description" =:
1150+ [ " First term :: first description"
1151+ , " :: second description"
1153+ definitionList [ ("First term", [ para "first description"
1154+ , para "second description"
1157+ , "Incorrectly indented second description" =:
1159+ [ " First term :: first description"
1160+ , " :: second description"
1162+ definitionList [ ("First term", [ para "first description" ])
1163+ , ("", [ para "second description" ])
1166+ , "Two blank lines separate definition lists" =:
1171+ , " Second :: list"
1173+ definitionList [ ("First", [ para "list" ]) ] <>
1174+ definitionList [ ("Second", [ para "list" ]) ]
1175+ -- Headers in first column of list continuation are not allowed
1176+ , "No headers in list continuation" =:
1182+ bulletList [ mconcat [ para "Foo"
1186+ , "Bullet list inside a tag" =:
1196+ blockQuote (bulletList [ para "First"
1200+ , "Ordered list inside a tag" =:
1210+ blockQuote (orderedListWith (1, Decimal, Period) [ para "First"
1214+ -- Regression test for a bug caught by round-trip test
1215+ , "Do not consume whitespace while looking for end tag" =:
1221+ , " bar" -- Do not consume whitespace while looking for arbitraritly indented </quote>
1224+ blockQuote (bulletList [ blockQuote $ para "foo" ] <> para "bar")
1226+ , "Unclosed quote tag" =:
1233+ para "<quote>" <> lineBlock [ "</quote>" ]
1235+ , "Unclosed quote tag inside list" =:
1242+ bulletList [ para "<quote>" <> lineBlock [ "</quote>" ] ]
1244+ -- Allowing indented closing tags is dangerous,
1245+ -- as they may terminate lists
1246+ , "No indented closing tags" =:
1260+ para "<quote>" <> bulletList [ para "Foo" <> para "</quote>" <> para "bar" <> lineBlock [ "</quote>" ] ]