@@ -1,1871 +1,16 @@
11{-# LANGUAGE OverloadedStrings #-}
22module Tests.Readers.Org (tests) where
33
4−import Data.List (intersperse)
5−import Data.Text (Text)
6−import qualified Data.Text as T
7−import Test.Tasty
8−import Tests.Helpers
9−import Text.Pandoc
10−import Text.Pandoc.Builder
11−import Text.Pandoc.Shared (underlineSpan)
12−
13−org :: Text -> Pandoc
14−org = purely $ readOrg def{ readerExtensions = getDefaultExtensions "org" }
15−
16−orgSmart :: Text -> Pandoc
17−orgSmart = purely $ readOrg def { readerExtensions =
18− enableExtension Ext_smart $ getDefaultExtensions "org" }
19−
20−infix 4 =:
21−(=:) :: ToString c
22− => String -> (Text, c) -> TestTree
23−(=:) = test org
24−
25−spcSep :: [Inlines] -> Inlines
26−spcSep = mconcat . intersperse space
27−
28−simpleTable' :: Int
29− -> [Blocks]
30− -> [[Blocks]]
31− -> Blocks
32−simpleTable' n = table "" (replicate n (AlignDefault, 0.0))
33−
34−-- | Create a span for the given tag.
35−tagSpan :: String -> Inlines
36−tagSpan t = spanWith ("", ["tag"], [("tag-name", t)]) . smallcaps $ str t
4+import Test.Tasty (TestTree, testGroup)
5+import qualified Tests.Readers.Org.Block as Block
6+import qualified Tests.Readers.Org.Directive as Directive
7+import qualified Tests.Readers.Org.Inline as Inline
8+import qualified Tests.Readers.Org.Meta as Meta
379
3810tests :: [TestTree]
3911tests =
40− [ testGroup "Inlines" $
41− [ "Plain String" =:
42− "Hello, World" =?>
43− para (spcSep [ "Hello,", "World" ])
44−
45− , "Emphasis" =:
46− "/Planet Punk/" =?>
47− para (emph . spcSep $ ["Planet", "Punk"])
48−
49− , "Strong" =:
50− "*Cider*" =?>
51− para (strong "Cider")
52−
53− , "Strong Emphasis" =:
54− "/*strength*/" =?>
55− para (emph . strong $ "strength")
56−
57− , "Emphasized Strong preceded by space" =:
58− " */super/*" =?>
59− para (strong . emph $ "super")
60−
61− , "Underline" =:
62− "_underline_" =?>
63− para (underlineSpan $ "underline")
64−
65− , "Strikeout" =:
66− "+Kill Bill+" =?>
67− para (strikeout . spcSep $ [ "Kill", "Bill" ])
68−
69− , "Verbatim" =:
70− "=Robot.rock()=" =?>
71− para (code "Robot.rock()")
72−
73− , "Code" =:
74− "~word for word~" =?>
75− para (code "word for word")
76−
77− , "Math $..$" =:
78− "$E=mc^2$" =?>
79− para (math "E=mc^2")
80−
81− , "Math $$..$$" =:
82− "$$E=mc^2$$" =?>
83− para (displayMath "E=mc^2")
84−
85− , "Math \\[..\\]" =:
86− "\\[E=ℎν\\]" =?>
87− para (displayMath "E=ℎν")
88−
89− , "Math \\(..\\)" =:
90− "\\(σ_x σ_p ≥ \\frac{ℏ}{2}\\)" =?>
91− para (math "σ_x σ_p ≥ \\frac{ℏ}{2}")
92−
93− , "Symbol" =:
94− "A * symbol" =?>
95− para (str "A" <> space <> str "*" <> space <> "symbol")
96−
97− , "Superscript simple expression" =:
98− "2^-λ" =?>
99− para (str "2" <> superscript "-λ")
100−
101− , "Superscript multi char" =:
102− "2^{n-1}" =?>
103− para (str "2" <> superscript "n-1")
104−
105− , "Subscript simple expression" =:
106− "a_n" =?>
107− para (str "a" <> subscript "n")
108−
109− , "Subscript multi char" =:
110− "a_{n+1}" =?>
111− para (str "a" <> subscript "n+1")
112−
113− , "Linebreak" =:
114− "line \\\\ \nbreak" =?>
115− para ("line" <> linebreak <> "break")
116−
117− , "Inline note" =:
118− "[fn::Schreib mir eine E-Mail]" =?>
119− para (note $ para "Schreib mir eine E-Mail")
120−
121− , "Markup-chars not occuring on word break are symbols" =:
122− T.unlines [ "this+that+ +so+on"
123− , "seven*eight* nine*"
124− , "+not+funny+"
125− ] =?>
126− para ("this+that+ +so+on" <> softbreak <>
127− "seven*eight* nine*" <> softbreak <>
128− strikeout "not+funny")
129−
130− , "No empty markup" =:
131− "// ** __ <> == ~~ $$" =?>
132− para (spcSep [ "//", "**", "__", "<>", "==", "~~", "$$" ])
133−
134− , "Adherence to Org's rules for markup borders" =:
135− "/t/& a/ / ./r/ (*l*) /e/! /b/." =?>
136− para (spcSep [ emph $ "t/&" <> space <> "a"
137− , "/"
138− , "./r/"
139− , "(" <> strong "l" <> ")"
140− , emph "e" <> "!"
141− , emph "b" <> "."
142− ])
143−
144− , "Quotes are allowed border chars" =:
145− "/'yep/ *sure\"*" =?>
146− para (emph "'yep" <> space <> strong "sure\"")
147−
148− , "Spaces are forbidden border chars" =:
149− "/nada /" =?>
150− para "/nada /"
151−
152− , "Markup should work properly after a blank line" =:
153− T.unlines ["foo", "", "/bar/"] =?>
154− (para $ text "foo") <> (para $ emph $ text "bar")
155−
156− , "Inline math must stay within three lines" =:
157− T.unlines [ "$a", "b", "c$", "$d", "e", "f", "g$" ] =?>
158− para ((math "a\nb\nc") <> softbreak <>
159− "$d" <> softbreak <> "e" <> softbreak <>
160− "f" <> softbreak <> "g$")
161−
162− , "Single-character math" =:
163− "$a$ $b$! $c$?" =?>
164− para (spcSep [ math "a"
165− , "$b$!"
166− , (math "c") <> "?"
167− ])
168−
169− , "Markup may not span more than two lines" =:
170− "/this *is +totally\nnice+ not*\nemph/" =?>
171− para ("/this" <> space <>
172− strong ("is" <> space <>
173− strikeout ("totally" <>
174− softbreak <> "nice") <>
175− space <> "not") <>
176− softbreak <> "emph/")
177−
178− , "Sub- and superscript expressions" =:
179− T.unlines [ "a_(a(b)(c)d)"
180− , "e^(f(g)h)"
181− , "i_(jk)l)"
182− , "m^()n"
183− , "o_{p{q{}r}}"
184− , "s^{t{u}v}"
185− , "w_{xy}z}"
186− , "1^{}2"
187− , "3_{{}}"
188− , "4^(a(*b(c*)d))"
189− ] =?>
190− para (mconcat $ intersperse softbreak
191− [ "a" <> subscript "(a(b)(c)d)"
192− , "e" <> superscript "(f(g)h)"
193− , "i" <> (subscript "(jk)") <> "l)"
194− , "m" <> (superscript "()") <> "n"
195− , "o" <> subscript "p{q{}r}"
196− , "s" <> superscript "t{u}v"
197− , "w" <> (subscript "xy") <> "z}"
198− , "1" <> (superscript "") <> "2"
199− , "3" <> subscript "{}"
200− , "4" <> superscript ("(a(" <> strong "b(c" <> ")d))")
201− ])
202− , "Verbatim text can contain equal signes (=)" =:
203− "=is_subst = True=" =?>
204− para (code "is_subst = True")
205−
206− , testGroup "Images"
207− [ "Image" =:
208− "[[./sunset.jpg]]" =?>
209− (para $ image "./sunset.jpg" "" "")
210−
211− , "Image with explicit file: prefix" =:
212− "[[file:sunrise.jpg]]" =?>
213− (para $ image "sunrise.jpg" "" "")
214−
215− , "Multiple images within a paragraph" =:
216− T.unlines [ "[[file:sunrise.jpg]]"
217− , "[[file:sunset.jpg]]"
218− ] =?>
219− (para $ (image "sunrise.jpg" "" "")
220− <> softbreak
221− <> (image "sunset.jpg" "" ""))
222−
223− , "Image with html attributes" =:
224− T.unlines [ "#+ATTR_HTML: :width 50%"
225− , "[[file:guinea-pig.gif]]"
226− ] =?>
227− (para $ imageWith ("", [], [("width", "50%")]) "guinea-pig.gif" "" "")
228− ]
229−
230− , "Explicit link" =:
231− "[[http://zeitlens.com/][pseudo-random /nonsense/]]" =?>
232− (para $ link "http://zeitlens.com/" ""
233− ("pseudo-random" <> space <> emph "nonsense"))
234−
235− , "Self-link" =:
236− "[[http://zeitlens.com/]]" =?>
237− (para $ link "http://zeitlens.com/" "" "http://zeitlens.com/")
238−
239− , "Absolute file link" =:
240− "[[/url][hi]]" =?>
241− (para $ link "file:///url" "" "hi")
242−
243− , "Link to file in parent directory" =:
244− "[[../file.txt][moin]]" =?>
245− (para $ link "../file.txt" "" "moin")
246−
247− , "Empty link (for gitit interop)" =:
248− "[[][New Link]]" =?>
249− (para $ link "" "" "New Link")
250−
251− , "Image link" =:
252− "[[sunset.png][file:dusk.svg]]" =?>
253− (para $ link "sunset.png" "" (image "dusk.svg" "" ""))
254−
255− , "Image link with non-image target" =:
256− "[[http://example.com][./logo.png]]" =?>
257− (para $ link "http://example.com" "" (image "./logo.png" "" ""))
258−
259− , "Plain link" =:
260− "Posts on http://zeitlens.com/ can be funny at times." =?>
261− (para $ spcSep [ "Posts", "on"
262− , link "http://zeitlens.com/" "" "http://zeitlens.com/"
263− , "can", "be", "funny", "at", "times."
264− ])
265−
266− , "Angle link" =:
267− "Look at <http://moltkeplatz.de> for fnords." =?>
268− (para $ spcSep [ "Look", "at"
269− , link "http://moltkeplatz.de" "" "http://moltkeplatz.de"
270− , "for", "fnords."
271− ])
272−
273− , "Absolute file link" =:
274− "[[file:///etc/passwd][passwd]]" =?>
275− (para $ link "file:///etc/passwd" "" "passwd")
276−
277− , "File link" =:
278− "[[file:target][title]]" =?>
279− (para $ link "target" "" "title")
280−
281− , "Anchor" =:
282− "<<anchor>> Link here later." =?>
283− (para $ spanWith ("anchor", [], []) mempty <>
284− "Link" <> space <> "here" <> space <> "later.")
285−
286− , "Inline code block" =:
287− "src_emacs-lisp{(message \"Hello\")}" =?>
288− (para $ codeWith ( ""
289− , [ "commonlisp" ]
290− , [ ("org-language", "emacs-lisp") ])
291− "(message \"Hello\")")
292−
293− , "Inline code block with arguments" =:
294− "src_sh[:export both :results output]{echo 'Hello, World'}" =?>
295− (para $ codeWith ( ""
296− , [ "bash" ]
297− , [ ("org-language", "sh")
298− , ("export", "both")
299− , ("results", "output")
300− ]
301− )
302− "echo 'Hello, World'")
303−
304− , "Inline code block with toggle" =:
305− "src_sh[:toggle]{echo $HOME}" =?>
306− (para $ codeWith ( ""
307− , [ "bash" ]
308− , [ ("org-language", "sh")
309− , ("toggle", "yes")
310− ]
311− )
312− "echo $HOME")
313−
314− , "Citation" =:
315− "[@nonexistent]" =?>
316− let citation = Citation
317− { citationId = "nonexistent"
318− , citationPrefix = []
319− , citationSuffix = []
320− , citationMode = NormalCitation
321− , citationNoteNum = 0
322− , citationHash = 0}
323− in (para $ cite [citation] "[@nonexistent]")
324−
325− , "Citation containing text" =:
326− "[see @item1 p. 34-35]" =?>
327− let citation = Citation
328− { citationId = "item1"
329− , citationPrefix = [Str "see"]
330− , citationSuffix = [Space ,Str "p.",Space,Str "34-35"]
331− , citationMode = NormalCitation
332− , citationNoteNum = 0
333− , citationHash = 0}
334− in (para $ cite [citation] "[see @item1 p. 34-35]")
335−
336− , "Org-ref simple citation" =:
337− "cite:pandoc" =?>
338− let citation = Citation
339− { citationId = "pandoc"
340− , citationPrefix = mempty
341− , citationSuffix = mempty
342− , citationMode = AuthorInText
343− , citationNoteNum = 0
344− , citationHash = 0
345− }
346− in (para $ cite [citation] "cite:pandoc")
347−
348− , "Org-ref simple citation with underscores" =:
349− "cite:pandoc_org_ref" =?>
350− let citation = Citation
351− { citationId = "pandoc_org_ref"
352− , citationPrefix = mempty
353− , citationSuffix = mempty
354− , citationMode = AuthorInText
355− , citationNoteNum = 0
356− , citationHash = 0
357− }
358− in (para $ cite [citation] "cite:pandoc_org_ref")
359−
360− , "Org-ref simple citation succeeded by comma" =:
361− "cite:pandoc," =?>
362− let citation = Citation
363− { citationId = "pandoc"
364− , citationPrefix = mempty
365− , citationSuffix = mempty
366− , citationMode = AuthorInText
367− , citationNoteNum = 0
368− , citationHash = 0
369− }
370− in (para $ cite [citation] "cite:pandoc" <> str ",")
371−
372− , "Org-ref simple citation succeeded by dot" =:
373− "cite:pandoc." =?>
374− let citation = Citation
375− { citationId = "pandoc"
376− , citationPrefix = mempty
377− , citationSuffix = mempty
378− , citationMode = AuthorInText
379− , citationNoteNum = 0
380− , citationHash = 0
381− }
382− in (para $ cite [citation] "cite:pandoc" <> str ".")
383−
384− , "Org-ref simple citation succeeded by colon" =:
385− "cite:pandoc:" =?>
386− let citation = Citation
387− { citationId = "pandoc"
388− , citationPrefix = mempty
389− , citationSuffix = mempty
390− , citationMode = AuthorInText
391− , citationNoteNum = 0
392− , citationHash = 0
393− }
394− in (para $ cite [citation] "cite:pandoc" <> str ":")
395−
396− , "Org-ref simple citep citation" =:
397− "citep:pandoc" =?>
398− let citation = Citation
399− { citationId = "pandoc"
400− , citationPrefix = mempty
401− , citationSuffix = mempty
402− , citationMode = NormalCitation
403− , citationNoteNum = 0
404− , citationHash = 0
405− }
406− in (para $ cite [citation] "citep:pandoc")
407−
408− , "Org-ref extended citation" =:
409− "[[citep:Dominik201408][See page 20::, for example]]" =?>
410− let citation = Citation
411− { citationId = "Dominik201408"
412− , citationPrefix = toList "See page 20"
413− , citationSuffix = toList ", for example"
414− , citationMode = NormalCitation
415− , citationNoteNum = 0
416− , citationHash = 0
417− }
418− in (para $ cite [citation] "[[citep:Dominik201408][See page 20::, for example]]")
419−
420− , testGroup "Berkeley-style citations" $
421− let pandocCite = Citation
422− { citationId = "Pandoc"
423− , citationPrefix = mempty
424− , citationSuffix = mempty
425− , citationMode = NormalCitation
426− , citationNoteNum = 0
427− , citationHash = 0
428− }
429− pandocInText = pandocCite { citationMode = AuthorInText }
430− dominikCite = Citation
431− { citationId = "Dominik201408"
432− , citationPrefix = mempty
433− , citationSuffix = mempty
434− , citationMode = NormalCitation
435− , citationNoteNum = 0
436− , citationHash = 0
437− }
438− dominikInText = dominikCite { citationMode = AuthorInText }
439− in [
440− "Berkeley-style in-text citation" =:
441− "See @Dominik201408." =?>
442− (para $ "See "
443− <> cite [dominikInText] "@Dominik201408"
444− <> ".")
445−
446− , "Berkeley-style parenthetical citation list" =:
447− "[(cite): see; @Dominik201408;also @Pandoc; and others]" =?>
448− let pandocCite' = pandocCite {
449− citationPrefix = toList "also"
450− , citationSuffix = toList "and others"
451− }
452− dominikCite' = dominikCite {
453− citationPrefix = toList "see"
454− }
455− in (para $ cite [dominikCite', pandocCite'] "")
456−
457− , "Berkeley-style plain citation list" =:
458− "[cite: See; @Dominik201408; and @Pandoc; and others]" =?>
459− let pandocCite' = pandocInText {
460− citationPrefix = toList "and"
461− }
462− in (para $ "See "
463− <> cite [dominikInText] ""
464− <> "," <> space
465− <> cite [pandocCite'] ""
466− <> "," <> space <> "and others")
467− ]
468−
469− , "Inline LaTeX symbol" =:
470− "\\dots" =?>
471− para "…"
472−
473− , "Inline LaTeX command" =:
474− "\\textit{Emphasised}" =?>
475− para (emph "Emphasised")
476−
477− , "Inline LaTeX command with spaces" =:
478− "\\emph{Emphasis mine}" =?>
479− para (emph "Emphasis mine")
480−
481− , "Inline LaTeX math symbol" =:
482− "\\tau" =?>
483− para (emph "τ")
484−
485− , "Unknown inline LaTeX command" =:
486− "\\notacommand{foo}" =?>
487− para (rawInline "latex" "\\notacommand{foo}")
488−
489− , "Export snippet" =:
490− "@@html:<kbd>M-x org-agenda</kbd>@@" =?>
491− para (rawInline "html" "<kbd>M-x org-agenda</kbd>")
492−
493− , "MathML symbol in LaTeX-style" =:
494− "There is a hackerspace in Lübeck, Germany, called nbsp (unicode symbol: '\\nbsp')." =?>
495− para ("There is a hackerspace in Lübeck, Germany, called nbsp (unicode symbol: ' ').")
496−
497− , "MathML symbol in LaTeX-style, including braces" =:
498− "\\Aacute{}stor" =?>
499− para "Ástor"
500−
501− , "MathML copy sign" =:
502− "\\copy" =?>
503− para "©"
504−
505− , "MathML symbols, space separated" =:
506− "\\ForAll \\Auml" =?>
507− para "∀ Ä"
508−
509− , "LaTeX citation" =:
510− "\\cite{Coffee}" =?>
511− let citation = Citation
512− { citationId = "Coffee"
513− , citationPrefix = []
514− , citationSuffix = []
515− , citationMode = NormalCitation
516− , citationNoteNum = 0
517− , citationHash = 0}
518− in (para . cite [citation] $ rawInline "latex" "\\cite{Coffee}")
519−
520− , "Macro" =:
521− T.unlines [ "#+MACRO: HELLO /Hello, $1/"
522− , "{{{HELLO(World)}}}"
523− ] =?>
524− para (emph "Hello, World")
525−
526− , "Macro repeting its argument" =:
527− T.unlines [ "#+MACRO: HELLO $1$1"
528− , "{{{HELLO(moin)}}}"
529− ] =?>
530− para "moinmoin"
531−
532− , "Macro called with too few arguments" =:
533− T.unlines [ "#+MACRO: HELLO Foo $1 $2 Bar"
534− , "{{{HELLO()}}}"
535− ] =?>
536− para "Foo Bar"
537− ]
538−
539− , testGroup "Meta Information" $
540− [ "Comment" =:
541− "# Nothing to see here" =?>
542− (mempty::Blocks)
543−
544− , "Not a comment" =:
545− "#-tag" =?>
546− para "#-tag"
547−
548− , "Comment surrounded by Text" =:
549− T.unlines [ "Before"
550− , "# Comment"
551− , "After"
552− ] =?>
553− mconcat [ para "Before"
554− , para "After"
555− ]
556−
557− , "Title" =:
558− "#+TITLE: Hello, World" =?>
559− let titleInline = toList $ "Hello," <> space <> "World"
560− meta = setMeta "title" (MetaInlines titleInline) $ nullMeta
561− in Pandoc meta mempty
562−
563− , "Author" =:
564− "#+author: Albert /Emacs-Fanboy/ Krewinkel" =?>
565− let author = toList . spcSep $ [ "Albert", emph "Emacs-Fanboy", "Krewinkel" ]
566− meta = setMeta "author" (MetaList [MetaInlines author]) $ nullMeta
567− in Pandoc meta mempty
568−
569− , "Multiple authors" =:
570− "#+author: James Dewey Watson, Francis Harry Compton Crick " =?>
571− let watson = MetaInlines $ toList "James Dewey Watson"
572− crick = MetaInlines $ toList "Francis Harry Compton Crick"
573− meta = setMeta "author" (MetaList [watson, crick]) $ nullMeta
574− in Pandoc meta mempty
575−
576− , "Date" =:
577− "#+Date: Feb. *28*, 2014" =?>
578− let date = toList . spcSep $ [ "Feb.", (strong "28") <> ",", "2014" ]
579− meta = setMeta "date" (MetaInlines date) $ nullMeta
580− in Pandoc meta mempty
581−
582− , "Description" =:
583− "#+DESCRIPTION: Explanatory text" =?>
584− let description = "Explanatory text"
585− meta = setMeta "description" (MetaString description) $ nullMeta
586− in Pandoc meta mempty
587−
588− , "Properties drawer" =:
589− T.unlines [ " :PROPERTIES:"
590− , " :setting: foo"
591− , " :END:"
592− ] =?>
593− (mempty::Blocks)
594−
595− , "LaTeX_headers options are translated to header-includes" =:
596− "#+LaTeX_header: \\usepackage{tikz}" =?>
597− let latexInlines = rawInline "latex" "\\usepackage{tikz}"
598− inclList = MetaList [MetaInlines (toList latexInlines)]
599− meta = setMeta "header-includes" inclList nullMeta
600− in Pandoc meta mempty
601−
602− , "LaTeX_class option is translated to documentclass" =:
603− "#+LATEX_CLASS: article" =?>
604− let meta = setMeta "documentclass" (MetaString "article") nullMeta
605− in Pandoc meta mempty
606−
607− , "LaTeX_class_options is translated to classoption" =:
608− "#+LATEX_CLASS_OPTIONS: [a4paper]" =?>
609− let meta = setMeta "classoption" (MetaString "a4paper") nullMeta
610− in Pandoc meta mempty
611−
612− , "LaTeX_class_options is translated to classoption" =:
613− "#+html_head: <meta/>" =?>
614− let html = rawInline "html" "<meta/>"
615− inclList = MetaList [MetaInlines (toList html)]
616− meta = setMeta "header-includes" inclList nullMeta
617− in Pandoc meta mempty
618−
619− , "later meta definitions take precedence" =:
620− T.unlines [ "#+AUTHOR: this will not be used"
621− , "#+author: Max"
622− ] =?>
623− let author = MetaInlines [Str "Max"]
624− meta = setMeta "author" (MetaList [author]) $ nullMeta
625− in Pandoc meta mempty
626−
627− , "Logbook drawer" =:
628− T.unlines [ " :LogBook:"
629− , " - State \"DONE\" from \"TODO\" [2014-03-03 Mon 11:00]"
630− , " :END:"
631− ] =?>
632− (mempty::Blocks)
633−
634− , "Drawer surrounded by text" =:
635− T.unlines [ "Before"
636− , ":PROPERTIES:"
637− , ":END:"
638− , "After"
639− ] =?>
640− para "Before" <> para "After"
641−
642− , "Drawer markers must be the only text in the line" =:
643− T.unlines [ " :LOGBOOK: foo"
644− , " :END: bar"
645− ] =?>
646− para (":LOGBOOK: foo" <> softbreak <> ":END: bar")
647−
648− , "Drawers can be arbitrary" =:
649− T.unlines [ ":FOO:"
650− , "/bar/"
651− , ":END:"
652− ] =?>
653− divWith (mempty, ["FOO", "drawer"], mempty) (para $ emph "bar")
654−
655− , "Anchor reference" =:
656− T.unlines [ "<<link-here>> Target."
657− , ""
658− , "[[link-here][See here!]]"
659− ] =?>
660− (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
661− para (link "#link-here" "" ("See" <> space <> "here!")))
662−
663− , "Search links are read as emph" =:
664− "[[Wally][Where's Wally?]]" =?>
665− (para (emph $ "Where's" <> space <> "Wally?"))
666−
667− , "Link to nonexistent anchor" =:
668− T.unlines [ "<<link-here>> Target."
669− , ""
670− , "[[link$here][See here!]]"
671− ] =?>
672− (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
673− para (emph ("See" <> space <> "here!")))
674−
675− , "Link abbreviation" =:
676− T.unlines [ "#+LINK: wp https://en.wikipedia.org/wiki/%s"
677− , "[[wp:Org_mode][Wikipedia on Org-mode]]"
678− ] =?>
679− (para (link "https://en.wikipedia.org/wiki/Org_mode" ""
680− ("Wikipedia" <> space <> "on" <> space <> "Org-mode")))
681−
682− , "Link abbreviation, defined after first use" =:
683− T.unlines [ "[[zl:non-sense][Non-sense articles]]"
684− , "#+LINK: zl http://zeitlens.com/tags/%s.html"
685− ] =?>
686− (para (link "http://zeitlens.com/tags/non-sense.html" ""
687− ("Non-sense" <> space <> "articles")))
688−
689− , "Link abbreviation, URL encoded arguments" =:
690− T.unlines [ "#+link: expl http://example.com/%h/foo"
691− , "[[expl:Hello, World!][Moin!]]"
692− ] =?>
693− (para (link "http://example.com/Hello%2C%20World%21/foo" "" "Moin!"))
694−
695− , "Link abbreviation, append arguments" =:
696− T.unlines [ "#+link: expl http://example.com/"
697− , "[[expl:foo][bar]]"
698− ] =?>
699− (para (link "http://example.com/foo" "" "bar"))
700−
701−
702− , testGroup "export options"
703−
704− [ "disable simple sub/superscript syntax" =:
705− T.unlines [ "#+OPTIONS: ^:nil"
706− , "a^b"
707− ] =?>
708− para "a^b"
709−
710− , "directly select drawers to be exported" =:
711− T.unlines [ "#+OPTIONS: d:(\"IMPORTANT\")"
712− , ":IMPORTANT:"
713− , "23"
714− , ":END:"
715− , ":BORING:"
716− , "very boring"
717− , ":END:"
718− ] =?>
719− divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "23")
720−
721− , "exclude drawers from being exported" =:
722− T.unlines [ "#+OPTIONS: d:(not \"BORING\")"
723− , ":IMPORTANT:"
724− , "5"
725− , ":END:"
726− , ":BORING:"
727− , "very boring"
728− , ":END:"
729− ] =?>
730− divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "5")
731−
732− , "don't include archive trees" =:
733− T.unlines [ "#+OPTIONS: arch:nil"
734− , "* old :ARCHIVE:"
735− ] =?>
736− (mempty ::Blocks)
737−
738− , "include complete archive trees" =:
739− T.unlines [ "#+OPTIONS: arch:t"
740− , "* old :ARCHIVE:"
741− , " boring"
742− ] =?>
743− mconcat [ headerWith ("old", [], mempty) 1
744− ("old" <> space <> tagSpan "ARCHIVE")
745− , para "boring"
746− ]
747−
748− , "include archive tree header only" =:
749− T.unlines [ "#+OPTIONS: arch:headline"
750− , "* old :ARCHIVE:"
751− , " boring"
752− ] =?>
753− headerWith ("old", [], mempty) 1 ("old" <> space <> tagSpan "ARCHIVE")
754−
755− , "limit headline depth" =:
756− T.unlines [ "#+OPTIONS: H:2"
757− , "* top-level section"
758− , "** subsection"
759− , "*** list item 1"
760− , "*** list item 2"
761− ] =?>
762− mconcat [ headerWith ("top-level-section", [], []) 1 "top-level section"
763− , headerWith ("subsection", [], []) 2 "subsection"
764− , orderedList [ para "list item 1", para "list item 2" ]
765− ]
766−
767− , "turn all headlines into lists" =:
768− T.unlines [ "#+OPTIONS: H:0"
769− , "first block"
770− , "* top-level section 1"
771− , "** subsection"
772− , "* top-level section 2"
773− ] =?>
774− mconcat [ para "first block"
775− , orderedList
776− [ (para "top-level section 1" <>
777− orderedList [ para "subsection" ])
778− , para "top-level section 2" ]
779− ]
780−
781− , "preserve linebreaks as hard breaks" =:
782− T.unlines [ "#+OPTIONS: \\n:t"
783− , "first"
784− , "second"
785− ] =?>
786− para ("first" <> linebreak <> "second")
787−
788− , "disable author export" =:
789− T.unlines [ "#+OPTIONS: author:nil"
790− , "#+AUTHOR: ShyGuy"
791− ] =?>
792− Pandoc nullMeta mempty
793−
794− , "disable creator export" =:
795− T.unlines [ "#+OPTIONS: creator:nil"
796− , "#+creator: The Architect"
797− ] =?>
798− Pandoc nullMeta mempty
799−
800− , "disable email export" =:
801− T.unlines [ "#+OPTIONS: email:nil"
802− , "#+email: no-mail-please@example.com"
803− ] =?>
804− Pandoc nullMeta mempty
805−
806− , "disable inclusion of todo keywords" =:
807− T.unlines [ "#+OPTIONS: todo:nil"
808− , "** DONE todo export"
809− ] =?>
810− headerWith ("todo-export", [], []) 2 "todo export"
811−
812− , "remove tags from headlines" =:
813− T.unlines [ "#+OPTIONS: tags:nil"
814− , "* Headline :hello:world:"
815− ] =?>
816− headerWith ("headline", [], mempty) 1 "Headline"
817− ]
818− ]
819−
820− , testGroup "Basic Blocks" $
821− [ "Paragraph" =:
822− "Paragraph\n" =?>
823− para "Paragraph"
824−
825− , testGroup "headers" $
826− [ "First Level Header" =:
827− "* Headline\n" =?>
828− headerWith ("headline", [], []) 1 "Headline"
829−
830− , "Third Level Header" =:
831− "*** Third Level Headline\n" =?>
832− headerWith ("third-level-headline", [], [])
833− 3
834− ("Third" <> space <> "Level" <> space <> "Headline")
835−
836− , "Compact Headers with Paragraph" =:
837− T.unlines [ "* First Level"
838− , "** Second Level"
839− , " Text"
840− ] =?>
841− mconcat [ headerWith ("first-level", [], [])
842− 1
843− ("First" <> space <> "Level")
844− , headerWith ("second-level", [], [])
845− 2
846− ("Second" <> space <> "Level")
847− , para "Text"
848− ]
849−
850− , "Separated Headers with Paragraph" =:
851− T.unlines [ "* First Level"
852− , ""
853− , "** Second Level"
854− , ""
855− , " Text"
856− ] =?>
857− mconcat [ headerWith ("first-level", [], [])
858− 1
859− ("First" <> space <> "Level")
860− , headerWith ("second-level", [], [])
861− 2
862− ("Second" <> space <> "Level")
863− , para "Text"
864− ]
865−
866− , "Headers not preceded by a blank line" =:
867− T.unlines [ "** eat dinner"
868− , "Spaghetti and meatballs tonight."
869− , "** walk dog"
870− ] =?>
871− mconcat [ headerWith ("eat-dinner", [], [])
872− 2
873− ("eat" <> space <> "dinner")
874− , para $ spcSep [ "Spaghetti", "and", "meatballs", "tonight." ]
875− , headerWith ("walk-dog", [], [])
876− 2
877− ("walk" <> space <> "dog")
878− ]
879−
880− , testGroup "Todo keywords"
881− [ "Header with known todo keyword" =:
882− "* TODO header" =?>
883− let todoSpan = spanWith ("", ["todo", "TODO"], []) "TODO"
884− in headerWith ("header", [], []) 1 (todoSpan <> space <> "header")
885−
886− , "Header marked as done" =:
887− "* DONE header" =?>
888− let todoSpan = spanWith ("", ["done", "DONE"], []) "DONE"
889− in headerWith ("header", [], []) 1 (todoSpan <> space <> "header")
890−
891− , "Header with unknown todo keyword" =:
892− "* WAITING header" =?>
893− headerWith ("waiting-header", [], []) 1 "WAITING header"
894−
895− , "Custom todo keywords" =:
896− T.unlines [ "#+TODO: WAITING CANCELLED"
897− , "* WAITING compile"
898− , "* CANCELLED lunch"
899− ] =?>
900− let todoSpan = spanWith ("", ["todo", "WAITING"], []) "WAITING"
901− doneSpan = spanWith ("", ["done", "CANCELLED"], []) "CANCELLED"
902− in headerWith ("compile", [], []) 1 (todoSpan <> space <> "compile")
903− <> headerWith ("lunch", [], []) 1 (doneSpan <> space <> "lunch")
904−
905− , "Custom todo keywords with multiple done-states" =:
906− T.unlines [ "#+TODO: WAITING | DONE CANCELLED "
907− , "* WAITING compile"
908− , "* CANCELLED lunch"
909− , "* DONE todo-feature"
910− ] =?>
911− let waiting = spanWith ("", ["todo", "WAITING"], []) "WAITING"
912− cancelled = spanWith ("", ["done", "CANCELLED"], []) "CANCELLED"
913− done = spanWith ("", ["done", "DONE"], []) "DONE"
914− in headerWith ("compile", [], []) 1 (waiting <> space <> "compile")
915− <> headerWith ("lunch", [], []) 1 (cancelled <> space <> "lunch")
916− <> headerWith ("todo-feature", [], []) 1 (done <> space <> "todo-feature")
917− ]
918−
919− , "Tagged headers" =:
920− T.unlines [ "* Personal :PERSONAL:"
921− , "** Call Mom :@PHONE:"
922− , "** Call John :@PHONE:JOHN: "
923− ] =?>
924− mconcat [ headerWith ("personal", [], [])
925− 1
926− ("Personal " <> tagSpan "PERSONAL")
927− , headerWith ("call-mom", [], [])
928− 2
929− ("Call Mom " <> tagSpan "@PHONE")
930− , headerWith ("call-john", [], [])
931− 2
932− ("Call John " <> tagSpan "@PHONE" <> "\160" <> tagSpan "JOHN")
933− ]
934−
935− , "Untagged header containing colons" =:
936− "* This: is not: tagged" =?>
937− headerWith ("this-is-not-tagged", [], []) 1 "This: is not: tagged"
938−
939− , "Header starting with strokeout text" =:
940− T.unlines [ "foo"
941− , ""
942− , "* +thing+ other thing"
943− ] =?>
944− mconcat [ para "foo"
945− , headerWith ("thing-other-thing", [], [])
946− 1
947− ((strikeout "thing") <> " other thing")
948− ]
949−
950− , "Comment Trees" =:
951− T.unlines [ "* COMMENT A comment tree"
952− , " Not much going on here"
953− , "** This will be dropped"
954− , "* Comment tree above"
955− ] =?>
956− headerWith ("comment-tree-above", [], []) 1 "Comment tree above"
957−
958− , "Nothing but a COMMENT header" =:
959− "* COMMENT Test" =?>
960− (mempty::Blocks)
961−
962− , "Tree with :noexport:" =:
963− T.unlines [ "* Should be ignored :archive:noexport:old:"
964− , "** Old stuff"
965− , " This is not going to be exported"
966− ] =?>
967− (mempty::Blocks)
968−
969− , "Subtree with :noexport:" =:
970− T.unlines [ "* Exported"
971− , "** This isn't exported :noexport:"
972− , "*** This neither"
973− , "** But this is"
974− ] =?>
975− mconcat [ headerWith ("exported", [], []) 1 "Exported"
976− , headerWith ("but-this-is", [], []) 2 "But this is"
977− ]
978−
979− , "Preferences are treated as header attributes" =:
980− T.unlines [ "* foo"
981− , " :PROPERTIES:"
982− , " :custom_id: fubar"
983− , " :bar: baz"
984− , " :END:"
985− ] =?>
986− headerWith ("fubar", [], [("bar", "baz")]) 1 "foo"
987−
988−
989− , "Headers marked with a unnumbered property get a class of the same name" =:
990− T.unlines [ "* Not numbered"
991− , " :PROPERTIES:"
992− , " :UNNUMBERED: t"
993− , " :END:"
994− ] =?>
995− headerWith ("not-numbered", ["unnumbered"], []) 1 "Not numbered"
996− ]
997− , "Paragraph starting with an asterisk" =:
998− "*five" =?>
999− para "*five"
1000−
1001− , "Paragraph containing asterisk at beginning of line" =:
1002− T.unlines [ "lucky"
1003− , "*star"
1004− ] =?>
1005− para ("lucky" <> softbreak <> "*star")
1006−
1007− , "Example block" =:
1008− T.unlines [ ": echo hello"
1009− , ": echo dear tester"
1010− ] =?>
1011− codeBlockWith ("", ["example"], []) "echo hello\necho dear tester\n"
1012−
1013− , "Example block surrounded by text" =:
1014− T.unlines [ "Greetings"
1015− , ": echo hello"
1016− , ": echo dear tester"
1017− , "Bye"
1018− ] =?>
1019− mconcat [ para "Greetings"
1020− , codeBlockWith ("", ["example"], [])
1021− "echo hello\necho dear tester\n"
1022− , para "Bye"
1023− ]
1024−
1025− , "Horizontal Rule" =:
1026− T.unlines [ "before"
1027− , "-----"
1028− , "after"
1029− ] =?>
1030− mconcat [ para "before"
1031− , horizontalRule
1032− , para "after"
1033− ]
1034−
1035− , "Not a Horizontal Rule" =:
1036− "----- em and en dash" =?>
1037− para "\8212\8211 em and en dash"
1038−
1039− , "Comment Block" =:
1040− T.unlines [ "#+BEGIN_COMMENT"
1041− , "stuff"
1042− , "bla"
1043− , "#+END_COMMENT"] =?>
1044− (mempty::Blocks)
1045−
1046− , testGroup "Figures" $
1047− [ "Figure" =:
1048− T.unlines [ "#+caption: A very courageous man."
1049− , "#+name: goodguy"
1050− , "[[file:edward.jpg]]"
1051− ] =?>
1052− para (image "edward.jpg" "fig:goodguy" "A very courageous man.")
1053−
1054− , "Figure with no name" =:
1055− T.unlines [ "#+caption: I've been through the desert on this"
1056− , "[[file:horse.png]]"
1057− ] =?>
1058− para (image "horse.png" "fig:" "I've been through the desert on this")
1059−
1060− , "Figure with `fig:` prefix in name" =:
1061− T.unlines [ "#+caption: Used as a metapher in evolutionary biology."
1062− , "#+name: fig:redqueen"
1063− , "[[./the-red-queen.jpg]]"
1064− ] =?>
1065− para (image "./the-red-queen.jpg" "fig:redqueen"
1066− "Used as a metapher in evolutionary biology.")
1067−
1068− , "Figure with HTML attributes" =:
1069− T.unlines [ "#+CAPTION: mah brain just explodid"
1070− , "#+NAME: lambdacat"
1071− , "#+ATTR_HTML: :style color: blue :role button"
1072− , "[[file:lambdacat.jpg]]"
1073− ] =?>
1074− let kv = [("style", "color: blue"), ("role", "button")]
1075− name = "fig:lambdacat"
1076− caption = "mah brain just explodid"
1077− in para (imageWith (mempty, mempty, kv) "lambdacat.jpg" name caption)
1078−
1079− , "Labelled figure" =:
1080− T.unlines [ "#+CAPTION: My figure"
1081− , "#+LABEL: fig:myfig"
1082− , "[[file:blub.png]]"
1083− ] =?>
1084− let attr = ("fig:myfig", mempty, mempty)
1085− in para (imageWith attr "blub.png" "fig:" "My figure")
1086−
1087− , "Figure with empty caption" =:
1088− T.unlines [ "#+CAPTION:"
1089− , "[[file:guess.jpg]]"
1090− ] =?>
1091− para (image "guess.jpg" "fig:" "")
1092− ]
1093−
1094− , testGroup "Footnotes"
1095− [ "Footnote" =:
1096− T.unlines [ "A footnote[1]"
1097− , ""
1098− , "[1] First paragraph"
1099− , ""
1100− , "second paragraph"
1101− ] =?>
1102− para (mconcat
1103− [ "A", space, "footnote"
1104− , note $ mconcat [ para ("First" <> space <> "paragraph")
1105− , para ("second" <> space <> "paragraph")
1106− ]
1107− ])
1108−
1109− , "Two footnotes" =:
1110− T.unlines [ "Footnotes[fn:1][fn:2]"
1111− , ""
1112− , "[fn:1] First note."
1113− , ""
1114− , "[fn:2] Second note."
1115− ] =?>
1116− para (mconcat
1117− [ "Footnotes"
1118− , note $ para ("First" <> space <> "note.")
1119− , note $ para ("Second" <> space <> "note.")
1120− ])
1121−
1122− , "Emphasized text before footnote" =:
1123− T.unlines [ "/text/[fn:1]"
1124− , ""
1125− , "[fn:1] unicorn"
1126− ] =?>
1127− para (mconcat
1128− [ emph "text"
1129− , note . para $ "unicorn"
1130− ])
1131−
1132− , "Footnote that starts with emphasized text" =:
1133− T.unlines [ "text[fn:1]"
1134− , ""
1135− , "[fn:1] /emphasized/"
1136− ] =?>
1137− para (mconcat
1138− [ "text"
1139− , note . para $ emph "emphasized"
1140− ])
1141−
1142− , "Footnote followed by header" =:
1143− T.unlines [ "Another note[fn:yay]"
1144− , ""
1145− , "[fn:yay] This is great!"
1146− , ""
1147− , "** Headline"
1148− ] =?>
1149− mconcat
1150− [ para (mconcat
1151− [ "Another", space, "note"
1152− , note $ para ("This" <> space <> "is" <> space <> "great!")
1153− ])
1154− , headerWith ("headline", [], []) 2 "Headline"
1155− ]
1156−
1157− , "Footnote followed by two blank lines" =:
1158− T.unlines [ "footnote[fn:blanklines]"
1159− , ""
1160− , "[fn:blanklines] followed by blank lines"
1161− , ""
1162− , ""
1163− , "next"
1164− ] =?>
1165− mconcat
1166− [ para ("footnote" <> note (para "followed by blank lines"))
1167− , para "next"
1168− ]
1169− ]
1170− ]
1171−
1172− , testGroup "Lists" $
1173− [ "Simple Bullet Lists" =:
1174− ("- Item1\n" <>
1175− "- Item2\n") =?>
1176− bulletList [ plain "Item1"
1177− , plain "Item2"
1178− ]
1179−
1180− , "Indented Bullet Lists" =:
1181− (" - Item1\n" <>
1182− " - Item2\n") =?>
1183− bulletList [ plain "Item1"
1184− , plain "Item2"
1185− ]
1186−
1187− , "Unindented *" =:
1188− ("- Item1\n" <>
1189− "* Item2\n") =?>
1190− bulletList [ plain "Item1"
1191− ] <>
1192− headerWith ("item2", [], []) 1 "Item2"
1193−
1194− , "Multi-line Bullet Lists" =:
1195− ("- *Fat\n" <>
1196− " Tony*\n" <>
1197− "- /Sideshow\n" <>
1198− " Bob/") =?>
1199− bulletList [ plain $ strong ("Fat" <> softbreak <> "Tony")
1200− , plain $ emph ("Sideshow" <> softbreak <> "Bob")
1201− ]
1202−
1203− , "Nested Bullet Lists" =:
1204− ("- Discovery\n" <>
1205− " + One More Time\n" <>
1206− " + Harder, Better, Faster, Stronger\n" <>
1207− "- Homework\n" <>
1208− " + Around the World\n"<>
1209− "- Human After All\n" <>
1210− " + Technologic\n" <>
1211− " + Robot Rock\n") =?>
1212− bulletList [ mconcat
1213− [ plain "Discovery"
1214− , bulletList [ plain ("One" <> space <>
1215− "More" <> space <>
1216− "Time")
1217− , plain ("Harder," <> space <>
1218− "Better," <> space <>
1219− "Faster," <> space <>
1220− "Stronger")
1221− ]
1222− ]
1223− , mconcat
1224− [ plain "Homework"
1225− , bulletList [ plain ("Around" <> space <>
1226− "the" <> space <>
1227− "World")
1228− ]
1229− ]
1230− , mconcat
1231− [ plain ("Human" <> space <> "After" <> space <> "All")
1232− , bulletList [ plain "Technologic"
1233− , plain ("Robot" <> space <> "Rock")
1234− ]
1235− ]
1236− ]
1237−
1238− , "Bullet List with Decreasing Indent" =:
1239− (" - Discovery\n\
1240− \ - Human After All\n") =?>
1241− mconcat [ bulletList [ plain "Discovery" ]
1242− , bulletList [ plain ("Human" <> space <> "After" <> space <> "All")]
1243− ]
1244−
1245− , "Header follows Bullet List" =:
1246− (" - Discovery\n\
1247− \ - Human After All\n\
1248− \* Homework") =?>
1249− mconcat [ bulletList [ plain "Discovery"
1250− , plain ("Human" <> space <> "After" <> space <> "All")
1251− ]
1252− , headerWith ("homework", [], []) 1 "Homework"
1253− ]
1254−
1255− , "Bullet List Unindented with trailing Header" =:
1256− ("- Discovery\n\
1257− \- Homework\n\
1258− \* NotValidListItem") =?>
1259− mconcat [ bulletList [ plain "Discovery"
1260− , plain "Homework"
1261− ]
1262− , headerWith ("notvalidlistitem", [], []) 1 "NotValidListItem"
1263− ]
1264−
1265− , "Empty bullet points" =:
1266− T.unlines [ "-"
1267− , "- "
1268− ] =?>
1269− bulletList [ plain "", plain "" ]
1270−
1271− , "Simple Ordered List" =:
1272− ("1. Item1\n" <>
1273− "2. Item2\n") =?>
1274− let listStyle = (1, DefaultStyle, DefaultDelim)
1275− listStructure = [ plain "Item1"
1276− , plain "Item2"
1277− ]
1278− in orderedListWith listStyle listStructure
1279−
1280− , "Simple Ordered List with Parens" =:
1281− ("1) Item1\n" <>
1282− "2) Item2\n") =?>
1283− let listStyle = (1, DefaultStyle, DefaultDelim)
1284− listStructure = [ plain "Item1"
1285− , plain "Item2"
1286− ]
1287− in orderedListWith listStyle listStructure
1288−
1289− , "Indented Ordered List" =:
1290− (" 1. Item1\n" <>
1291− " 2. Item2\n") =?>
1292− let listStyle = (1, DefaultStyle, DefaultDelim)
1293− listStructure = [ plain "Item1"
1294− , plain "Item2"
1295− ]
1296− in orderedListWith listStyle listStructure
1297−
1298− , "Empty ordered list item" =:
1299− T.unlines [ "1."
1300− , "3. "
1301− ] =?>
1302− orderedList [ plain "", plain "" ]
1303−
1304− , "Nested Ordered Lists" =:
1305− ("1. One\n" <>
1306− " 1. One-One\n" <>
1307− " 2. One-Two\n" <>
1308− "2. Two\n" <>
1309− " 1. Two-One\n"<>
1310− " 2. Two-Two\n") =?>
1311− let listStyle = (1, DefaultStyle, DefaultDelim)
1312− listStructure = [ mconcat
1313− [ plain "One"
1314− , orderedList [ plain "One-One"
1315− , plain "One-Two"
1316− ]
1317− ]
1318− , mconcat
1319− [ plain "Two"
1320− , orderedList [ plain "Two-One"
1321− , plain "Two-Two"
1322− ]
1323− ]
1324− ]
1325− in orderedListWith listStyle listStructure
1326−
1327− , "Ordered List in Bullet List" =:
1328− ("- Emacs\n" <>
1329− " 1. Org\n") =?>
1330− bulletList [ (plain "Emacs") <>
1331− (orderedList [ plain "Org"])
1332− ]
1333−
1334− , "Bullet List in Ordered List" =:
1335− ("1. GNU\n" <>
1336− " - Freedom\n") =?>
1337− orderedList [ (plain "GNU") <> bulletList [ (plain "Freedom") ] ]
1338−
1339− , "Definition List" =:
1340− T.unlines [ "- PLL :: phase-locked loop"
1341− , "- TTL ::"
1342− , " transistor-transistor logic"
1343− , "- PSK :: phase-shift keying"
1344− , ""
1345− , " a digital modulation scheme"
1346− ] =?>
1347− definitionList [ ("PLL", [ plain $ "phase-locked" <> space <> "loop" ])
1348− , ("TTL", [ plain $ "transistor-transistor" <> space <>
1349− "logic" ])
1350− , ("PSK", [ mconcat
1351− [ para $ "phase-shift" <> space <> "keying"
1352− , para $ spcSep [ "a", "digital"
1353− , "modulation", "scheme" ]
1354− ]
1355− ])
1356− ]
1357− , "Definition list with multi-word term" =:
1358− " - Elijah Wood :: He plays Frodo" =?>
1359− definitionList [ ("Elijah" <> space <> "Wood", [plain $ "He" <> space <> "plays" <> space <> "Frodo"])]
1360− , "Compact definition list" =:
1361− T.unlines [ "- ATP :: adenosine 5' triphosphate"
1362− , "- DNA :: deoxyribonucleic acid"
1363− , "- PCR :: polymerase chain reaction"
1364− , ""
1365− ] =?>
1366− definitionList
1367− [ ("ATP", [ plain $ spcSep [ "adenosine", "5'", "triphosphate" ] ])
1368− , ("DNA", [ plain $ spcSep [ "deoxyribonucleic", "acid" ] ])
1369− , ("PCR", [ plain $ spcSep [ "polymerase", "chain", "reaction" ] ])
1370− ]
1371−
1372− , "Definition List With Trailing Header" =:
1373− "- definition :: list\n\
1374− \- cool :: defs\n\
1375− \* header" =?>
1376− mconcat [ definitionList [ ("definition", [plain "list"])
1377− , ("cool", [plain "defs"])
1378− ]
1379− , headerWith ("header", [], []) 1 "header"
1380− ]
1381−
1382− , "Definition lists double-colon markers must be surrounded by whitespace" =:
1383− "- std::cout" =?>
1384− bulletList [ plain "std::cout" ]
1385−
1386− , "Loose bullet list" =:
1387− T.unlines [ "- apple"
1388− , ""
1389− , "- orange"
1390− , ""
1391− , "- peach"
1392− ] =?>
1393− bulletList [ para "apple"
1394− , para "orange"
1395− , para "peach"
1396− ]
1397−
1398− , "Recognize preceding paragraphs in non-list contexts" =:
1399− T.unlines [ "CLOSED: [2015-10-19 Mon 15:03]"
1400− , "- Note taken on [2015-10-19 Mon 13:24]"
1401− ] =?>
1402− mconcat [ para "CLOSED: [2015-10-19 Mon 15:03]"
1403− , bulletList [ plain "Note taken on [2015-10-19 Mon 13:24]" ]
1404− ]
1405− ]
1406−
1407− , testGroup "Tables"
1408− [ "Single cell table" =:
1409− "|Test|" =?>
1410− simpleTable' 1 mempty [[plain "Test"]]
1411−
1412− , "Multi cell table" =:
1413− "| One | Two |" =?>
1414− simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
1415−
1416− , "Multi line table" =:
1417− T.unlines [ "| One |"
1418− , "| Two |"
1419− , "| Three |"
1420− ] =?>
1421− simpleTable' 1 mempty
1422− [ [ plain "One" ]
1423− , [ plain "Two" ]
1424− , [ plain "Three" ]
1425− ]
1426−
1427− , "Empty table" =:
1428− "||" =?>
1429− simpleTable' 1 mempty [[mempty]]
1430−
1431− , "Glider Table" =:
1432− T.unlines [ "| 1 | 0 | 0 |"
1433− , "| 0 | 1 | 1 |"
1434− , "| 1 | 1 | 0 |"
1435− ] =?>
1436− simpleTable' 3 mempty
1437− [ [ plain "1", plain "0", plain "0" ]
1438− , [ plain "0", plain "1", plain "1" ]
1439− , [ plain "1", plain "1", plain "0" ]
1440− ]
1441−
1442− , "Table between Paragraphs" =:
1443− T.unlines [ "Before"
1444− , "| One | Two |"
1445− , "After"
1446− ] =?>
1447− mconcat [ para "Before"
1448− , simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
1449− , para "After"
1450− ]
1451−
1452− , "Table with Header" =:
1453− T.unlines [ "| Species | Status |"
1454− , "|--------------+--------------|"
1455− , "| cervisiae | domesticated |"
1456− , "| paradoxus | wild |"
1457− ] =?>
1458− simpleTable [ plain "Species", plain "Status" ]
1459− [ [ plain "cervisiae", plain "domesticated" ]
1460− , [ plain "paradoxus", plain "wild" ]
1461− ]
1462−
1463− , "Table with final hline" =:
1464− T.unlines [ "| cervisiae | domesticated |"
1465− , "| paradoxus | wild |"
1466− , "|--------------+--------------|"
1467− ] =?>
1468− simpleTable' 2 mempty
1469− [ [ plain "cervisiae", plain "domesticated" ]
1470− , [ plain "paradoxus", plain "wild" ]
1471− ]
1472−
1473− , "Table in a box" =:
1474− T.unlines [ "|---------|---------|"
1475− , "| static | Haskell |"
1476− , "| dynamic | Lisp |"
1477− , "|---------+---------|"
1478− ] =?>
1479− simpleTable' 2 mempty
1480− [ [ plain "static", plain "Haskell" ]
1481− , [ plain "dynamic", plain "Lisp" ]
1482− ]
1483−
1484− , "Table with empty cells" =:
1485− "|||c|" =?>
1486− simpleTable' 3 mempty [[mempty, mempty, plain "c"]]
1487−
1488− , "Table with empty rows" =:
1489− T.unlines [ "| first |"
1490− , "| |"
1491− , "| third |"
1492− ] =?>
1493− simpleTable' 1 mempty [[plain "first"], [mempty], [plain "third"]]
1494−
1495− , "Table with alignment row" =:
1496− T.unlines [ "| Numbers | Text | More |"
1497− , "| <c> | <r> | |"
1498− , "| 1 | One | foo |"
1499− , "| 2 | Two | bar |"
1500− ] =?>
1501− table "" (zip [AlignCenter, AlignRight, AlignDefault] [0, 0, 0])
1502− []
1503− [ [ plain "Numbers", plain "Text", plain "More" ]
1504− , [ plain "1" , plain "One" , plain "foo" ]
1505− , [ plain "2" , plain "Two" , plain "bar" ]
1506− ]
1507−
1508− , "Pipe within text doesn't start a table" =:
1509− "Ceci n'est pas une | pipe " =?>
1510− para (spcSep [ "Ceci", "n'est", "pas", "une", "|", "pipe" ])
1511−
1512− , "Missing pipe at end of row" =:
1513− "|incomplete-but-valid" =?>
1514− simpleTable' 1 mempty [ [ plain "incomplete-but-valid" ] ]
1515−
1516− , "Table with differing row lengths" =:
1517− T.unlines [ "| Numbers | Text "
1518− , "|-"
1519− , "| <c> | <r> |"
1520− , "| 1 | One | foo |"
1521− , "| 2"
1522− ] =?>
1523− table "" (zip [AlignCenter, AlignRight] [0, 0])
1524− [ plain "Numbers", plain "Text" ]
1525− [ [ plain "1" , plain "One" , plain "foo" ]
1526− , [ plain "2" ]
1527− ]
1528−
1529− , "Table with caption" =:
1530− T.unlines [ "#+CAPTION: Hitchhiker's Multiplication Table"
1531− , "| x | 6 |"
1532− , "| 9 | 42 |"
1533− ] =?>
1534− table "Hitchhiker's Multiplication Table"
1535− [(AlignDefault, 0), (AlignDefault, 0)]
1536− []
1537− [ [ plain "x", plain "6" ]
1538− , [ plain "9", plain "42" ]
1539− ]
1540− ]
1541−
1542− , testGroup "Blocks and fragments"
1543− [ "Source block" =:
1544− T.unlines [ " #+BEGIN_SRC haskell"
1545− , " main = putStrLn greeting"
1546− , " where greeting = \"moin\""
1547− , " #+END_SRC" ] =?>
1548− let attr' = ("", ["haskell"], [])
1549− code' = "main = putStrLn greeting\n" <>
1550− " where greeting = \"moin\"\n"
1551− in codeBlockWith attr' code'
1552−
1553− , "Source block with indented code" =:
1554− T.unlines [ " #+BEGIN_SRC haskell"
1555− , " main = putStrLn greeting"
1556− , " where greeting = \"moin\""
1557− , " #+END_SRC" ] =?>
1558− let attr' = ("", ["haskell"], [])
1559− code' = "main = putStrLn greeting\n" <>
1560− " where greeting = \"moin\"\n"
1561− in codeBlockWith attr' code'
1562−
1563− , "Source block with tab-indented code" =:
1564− T.unlines [ "\t#+BEGIN_SRC haskell"
1565− , "\tmain = putStrLn greeting"
1566− , "\t where greeting = \"moin\""
1567− , "\t#+END_SRC" ] =?>
1568− let attr' = ("", ["haskell"], [])
1569− code' = "main = putStrLn greeting\n" <>
1570− " where greeting = \"moin\"\n"
1571− in codeBlockWith attr' code'
1572−
1573− , "Empty source block" =:
1574− T.unlines [ " #+BEGIN_SRC haskell"
1575− , " #+END_SRC" ] =?>
1576− let attr' = ("", ["haskell"], [])
1577− code' = ""
1578− in codeBlockWith attr' code'
1579−
1580− , "Source block between paragraphs" =:
1581− T.unlines [ "Low German greeting"
1582− , " #+BEGIN_SRC haskell"
1583− , " main = putStrLn greeting"
1584− , " where greeting = \"Moin!\""
1585− , " #+END_SRC" ] =?>
1586− let attr' = ("", ["haskell"], [])
1587− code' = "main = putStrLn greeting\n" <>
1588− " where greeting = \"Moin!\"\n"
1589− in mconcat [ para $ spcSep [ "Low", "German", "greeting" ]
1590− , codeBlockWith attr' code'
1591− ]
1592− , "Source block with babel arguments" =:
1593− T.unlines [ "#+BEGIN_SRC emacs-lisp :exports both"
1594− , "(progn (message \"Hello, World!\")"
1595− , " (+ 23 42))"
1596− , "#+END_SRC" ] =?>
1597− let classes = [ "commonlisp" ] -- as kate doesn't know emacs-lisp syntax
1598− params = [ ("org-language", "emacs-lisp")
1599− , ("exports", "both")
1600− ]
1601− code' = unlines [ "(progn (message \"Hello, World!\")"
1602− , " (+ 23 42))" ]
1603− in codeBlockWith ("", classes, params) code'
1604−
1605− , "Source block with results and :exports both" =:
1606− T.unlines [ "#+BEGIN_SRC emacs-lisp :exports both"
1607− , "(progn (message \"Hello, World!\")"
1608− , " (+ 23 42))"
1609− , "#+END_SRC"
1610− , ""
1611− , "#+RESULTS:"
1612− , ": 65"] =?>
1613− let classes = [ "commonlisp" ]
1614− params = [ ("org-language", "emacs-lisp")
1615− , ("exports", "both")
1616− ]
1617− code' = unlines [ "(progn (message \"Hello, World!\")"
1618− , " (+ 23 42))" ]
1619− results' = "65\n"
1620− in codeBlockWith ("", classes, params) code'
1621− <>
1622− codeBlockWith ("", ["example"], []) results'
1623−
1624− , "Source block with results and :exports code" =:
1625− T.unlines [ "#+BEGIN_SRC emacs-lisp :exports code"
1626− , "(progn (message \"Hello, World!\")"
1627− , " (+ 23 42))"
1628− , "#+END_SRC"
1629− , ""
1630− , "#+RESULTS:"
1631− , ": 65" ] =?>
1632− let classes = [ "commonlisp" ]
1633− params = [ ("org-language", "emacs-lisp")
1634− , ("exports", "code")
1635− ]
1636− code' = unlines [ "(progn (message \"Hello, World!\")"
1637− , " (+ 23 42))" ]
1638− in codeBlockWith ("", classes, params) code'
1639−
1640− , "Source block with results and :exports results" =:
1641− T.unlines [ "#+BEGIN_SRC emacs-lisp :exports results"
1642− , "(progn (message \"Hello, World!\")"
1643− , " (+ 23 42))"
1644− , "#+END_SRC"
1645− , ""
1646− , "#+RESULTS:"
1647− , ": 65" ] =?>
1648− let results' = "65\n"
1649− in codeBlockWith ("", ["example"], []) results'
1650−
1651− , "Source block with results and :exports none" =:
1652− T.unlines [ "#+BEGIN_SRC emacs-lisp :exports none"
1653− , "(progn (message \"Hello, World!\")"
1654− , " (+ 23 42))"
1655− , "#+END_SRC"
1656− , ""
1657− , "#+RESULTS:"
1658− , ": 65" ] =?>
1659− (mempty :: Blocks)
1660−
1661− , "Source block with toggling header arguments" =:
1662− T.unlines [ "#+BEGIN_SRC sh :noeval"
1663− , "echo $HOME"
1664− , "#+END_SRC"
1665− ] =?>
1666− let classes = [ "bash" ]
1667− params = [ ("org-language", "sh"), ("noeval", "yes") ]
1668− in codeBlockWith ("", classes, params) "echo $HOME\n"
1669−
1670− , "Source block with line number switch" =:
1671− T.unlines [ "#+BEGIN_SRC sh -n 10"
1672− , ":() { :|:& };:"
1673− , "#+END_SRC"
1674− ] =?>
1675− let classes = [ "bash", "numberLines" ]
1676− params = [ ("org-language", "sh"), ("startFrom", "10") ]
1677− in codeBlockWith ("", classes, params) ":() { :|:& };:\n"
1678−
1679− , "Source block with multi-word parameter values" =:
1680− T.unlines [ "#+BEGIN_SRC dot :cmdline -Kdot -Tpng "
1681− , "digraph { id [label=\"ID\"] }"
1682− , "#+END_SRC"
1683− ] =?>
1684− let classes = [ "dot" ]
1685− params = [ ("cmdline", "-Kdot -Tpng") ]
1686− in codeBlockWith ("", classes, params) "digraph { id [label=\"ID\"] }\n"
1687−
1688− , "Example block" =:
1689− T.unlines [ "#+begin_example"
1690− , "A chosen representation of"
1691− , "a rule."
1692− , "#+eND_exAMPle"
1693− ] =?>
1694− codeBlockWith ("", ["example"], [])
1695− "A chosen representation of\na rule.\n"
1696−
1697− , "HTML block" =:
1698− T.unlines [ "#+BEGIN_HTML"
1699− , "<aside>HTML5 is pretty nice.</aside>"
1700− , "#+END_HTML"
1701− ] =?>
1702− rawBlock "html" "<aside>HTML5 is pretty nice.</aside>\n"
1703−
1704− , "Quote block" =:
1705− T.unlines [ "#+BEGIN_QUOTE"
1706− , "/Niemand/ hat die Absicht, eine Mauer zu errichten!"
1707− , "#+END_QUOTE"
1708− ] =?>
1709− blockQuote (para (spcSep [ emph "Niemand", "hat", "die", "Absicht,"
1710− , "eine", "Mauer", "zu", "errichten!"
1711− ]))
1712−
1713− , "Verse block" =:
1714− T.unlines [ "The first lines of Goethe's /Faust/:"
1715− , "#+begin_verse"
1716− , "Habe nun, ach! Philosophie,"
1717− , "Juristerei und Medizin,"
1718− , "Und leider auch Theologie!"
1719− , "Durchaus studiert, mit heißem Bemühn."
1720− , "#+end_verse"
1721− ] =?>
1722− mconcat
1723− [ para $ spcSep [ "The", "first", "lines", "of"
1724− , "Goethe's", emph "Faust" <> ":"]
1725− , lineBlock
1726− [ "Habe nun, ach! Philosophie,"
1727− , "Juristerei und Medizin,"
1728− , "Und leider auch Theologie!"
1729− , "Durchaus studiert, mit heißem Bemühn."
1730− ]
1731− ]
1732−
1733− , "Verse block with blank lines" =:
1734− T.unlines [ "#+BEGIN_VERSE"
1735− , "foo"
1736− , ""
1737− , "bar"
1738− , "#+END_VERSE"
1739− ] =?>
1740− lineBlock [ "foo", mempty, "bar" ]
1741−
1742− , "Verse block with varying indentation" =:
1743− T.unlines [ "#+BEGIN_VERSE"
1744− , " hello darkness"
1745− , "my old friend"
1746− , "#+END_VERSE"
1747− ] =?>
1748− lineBlock [ "\160\160hello darkness", "my old friend" ]
1749−
1750− , "Raw block LaTeX" =:
1751− T.unlines [ "#+BEGIN_LaTeX"
1752− , "The category $\\cat{Set}$ is adhesive."
1753− , "#+END_LaTeX"
1754− ] =?>
1755− rawBlock "latex" "The category $\\cat{Set}$ is adhesive.\n"
1756−
1757− , "Raw LaTeX line" =:
1758− "#+LATEX: \\let\\foo\\bar" =?>
1759− rawBlock "latex" "\\let\\foo\\bar"
1760−
1761− , "Raw Beamer line" =:
1762− "#+beamer: \\pause" =?>
1763− rawBlock "beamer" "\\pause"
1764−
1765− , "Raw HTML line" =:
1766− "#+HTML: <aside>not important</aside>" =?>
1767− rawBlock "html" "<aside>not important</aside>"
1768−
1769− , "Export block HTML" =:
1770− T.unlines [ "#+BEGIN_export html"
1771− , "<samp>Hello, World!</samp>"
1772− , "#+END_export"
1773− ] =?>
1774− rawBlock "html" "<samp>Hello, World!</samp>\n"
1775−
1776− , "LaTeX fragment" =:
1777− T.unlines [ "\\begin{equation}"
1778− , "X_i = \\begin{cases}"
1779− , " G_{\\alpha(i)} & \\text{if }\\alpha(i-1) = \\alpha(i)\\\\"
1780− , " C_{\\alpha(i)} & \\text{otherwise}"
1781− , " \\end{cases}"
1782− , "\\end{equation}"
1783− ] =?>
1784− rawBlock "latex"
1785− (unlines [ "\\begin{equation}"
1786− , "X_i = \\begin{cases}"
1787− , " G_{\\alpha(i)} & \\text{if }\\alpha(i-1) =" <>
1788− " \\alpha(i)\\\\"
1789− , " C_{\\alpha(i)} & \\text{otherwise}"
1790− , " \\end{cases}"
1791− , "\\end{equation}"
1792− ])
1793−
1794− , "Code block with caption" =:
1795− T.unlines [ "#+CAPTION: Functor laws in Haskell"
1796− , "#+NAME: functor-laws"
1797− , "#+BEGIN_SRC haskell"
1798− , "fmap id = id"
1799− , "fmap (p . q) = (fmap p) . (fmap q)"
1800− , "#+END_SRC"
1801− ] =?>
1802− divWith
1803− nullAttr
1804− (mappend
1805− (plain $ spanWith ("", ["label"], [])
1806− (spcSep [ "Functor", "laws", "in", "Haskell" ]))
1807− (codeBlockWith ("functor-laws", ["haskell"], [])
1808− (unlines [ "fmap id = id"
1809− , "fmap (p . q) = (fmap p) . (fmap q)"
1810− ])))
1811−
1812− , "Convert blank lines in blocks to single newlines" =:
1813− T.unlines [ "#+begin_html"
1814− , ""
1815− , "<span>boring</span>"
1816− , ""
1817− , "#+end_html"
1818− ] =?>
1819− rawBlock "html" "\n<span>boring</span>\n\n"
1820−
1821− , "Accept `ATTR_HTML` attributes for generic block" =:
1822− T.unlines [ "#+ATTR_HTML: :title hello, world :id test :class fun code"
1823− , "#+BEGIN_TEST"
1824− , "nonsense"
1825− , "#+END_TEST"
1826− ] =?>
1827− let attr = ("test", ["fun", "code", "TEST"], [("title", "hello, world")])
1828− in divWith attr (para "nonsense")
1829−
1830− , "Non-letter chars in source block parameters" =:
1831− T.unlines [ "#+BEGIN_SRC C :tangle xxxx.c :city Zürich"
1832− , "code body"
1833− , "#+END_SRC"
1834− ] =?>
1835− let params = [ ("org-language", "C")
1836− , ("tangle", "xxxx.c")
1837− , ("city", "Zürich")
1838− ]
1839− in codeBlockWith ( "", ["c"], params) "code body\n"
1840− ]
1841−
1842− , testGroup "Smart punctuation"
1843− [ test orgSmart "quote before ellipses"
1844− ("'...hi'"
1845− =?> para (singleQuoted "…hi"))
1846−
1847− , test orgSmart "apostrophe before emph"
1848− ("D'oh! A l'/aide/!"
1849− =?> para ("D’oh! A l’" <> emph "aide" <> "!"))
1850−
1851− , test orgSmart "apostrophe in French"
1852− ("À l'arrivée de la guerre, le thème de l'«impossibilité du socialisme»"
1853− =?> para "À l’arrivée de la guerre, le thème de l’«impossibilité du socialisme»")
1854−
1855− , test orgSmart "Quotes cannot occur at the end of emphasized text"
1856− ("/say \"yes\"/" =?>
1857− para ("/say" <> space <> doubleQuoted "yes" <> "/"))
1858−
1859− , test orgSmart "Dashes are allowed at the borders of emphasis'"
1860− ("/foo---/" =?>
1861− para (emph "foo—"))
1862−
1863− , test orgSmart "Single quotes can be followed by emphasized text"
1864− ("Singles on the '/meat market/'" =?>
1865− para ("Singles on the " <> (singleQuoted $ emph "meat market")))
1866−
1867− , test orgSmart "Double quotes can be followed by emphasized text"
1868− ("Double income, no kids: \"/DINK/\"" =?>
1869− para ("Double income, no kids: " <> (doubleQuoted $ emph "DINK")))
1870− ]
12+ [ testGroup "Inlines" Inline.tests
13+ , testGroup "Basic Blocks" Block.tests
14+ , testGroup "Meta Information" Meta.tests
15+ , testGroup "Directives" Directive.tests
187116 ]