git.iko.soy/typeable/pandoc
on master help sign in
Man writer: Don't escape U+2019 as '.

Closes #4550.

commitd5b98c8c6ec13556911876ac5632efb63a1ce40d authorJohn MacFarlane <jgm@berkeley.edu> dateSat 14 Apr 2018, 10:38 (9 years ago) parent488320f
83 files changed+10417−22
src/Text/Pandoc/Writers/Ms.hs−1 test/Tests/Command.hs.orig+95 test/Tests/Helpers.hs.orig+138 test/Tests/Lua.hs.orig+196 test/Tests/Old.hs.orig+288 test/Tests/Readers/Creole.hs.orig+286 test/Tests/Readers/Docx.hs.orig+405 test/Tests/Readers/EPUB.hs.orig+40 test/Tests/Readers/HTML.hs.orig+54 test/Tests/Readers/JATS.hs.orig+116 test/Tests/Readers/LaTeX.hs.orig+341 test/Tests/Readers/Markdown.hs.orig+462 test/Tests/Readers/Muse.hs.orig+1262 test/Tests/Readers/Odt.hs.orig+170 test/Tests/Readers/Org.hs.orig+16 test/Tests/Readers/Org/Block.hs.orig+192 test/Tests/Readers/Org/Block/CodeBlock.hs.orig+194 test/Tests/Readers/Org/Block/Figure.hs.orig+57 test/Tests/Readers/Org/Block/Header.hs.orig+182 test/Tests/Readers/Org/Block/List.hs.orig+244 test/Tests/Readers/Org/Block/Table.hs.orig+150 test/Tests/Readers/Org/Directive.hs.orig+199 test/Tests/Readers/Org/Inline.hs.orig+352 test/Tests/Readers/Org/Inline/Citation.hs.orig+179 test/Tests/Readers/Org/Inline/Note.hs.orig+86 test/Tests/Readers/Org/Inline/Smart.hs.orig+46 test/Tests/Readers/Org/Meta.hs.orig+191 test/Tests/Readers/Org/Shared.hs.orig+29 test/Tests/Readers/RST.hs.orig+189 test/Tests/Readers/Txt2Tags.hs.orig+437 test/Tests/Shared.hs.orig+39 test/Tests/Writers/AsciiDoc.hs.orig+56 test/Tests/Writers/ConTeXt.hs.orig+149 test/Tests/Writers/Docbook.hs.orig+303 test/Tests/Writers/Docx.hs.orig+157 test/Tests/Writers/FB2.hs.orig+34 test/Tests/Writers/HTML.hs.orig+44 test/Tests/Writers/JATS.hs.orig+108 test/Tests/Writers/LaTeX.hs.orig+176 test/Tests/Writers/Markdown.hs.orig+267 test/Tests/Writers/Muse.hs.orig+410 test/Tests/Writers/Native.hs.orig+22 test/Tests/Writers/OOXML.hs.orig+184 test/Tests/Writers/Org.hs.orig+25 test/Tests/Writers/Plain.hs.orig+21 test/Tests/Writers/Powerpoint.hs.orig+93 test/Tests/Writers/RST.hs.orig+130 test/Tests/Writers/TEI.hs.orig+43 test/command/3510-src.hs.orig+1 test/command/4550.md+7 test/pandoc-test14632-86+1448 test/pandoc-test14632-89 test/pandoc-test14632-90 test/pandoc-test14632-91 test/pandoc-test14632-94 test/pandoc-test14632-95 test/pandoc-test14632-96 test/pandoc-test14632-97 test/pandoc-test77993-78 test/pandoc-test77993-79 test/pandoc-test77993-82 test/pandoc-test77993-83 test/pandoc-test77993-86 test/pandoc-test77993-87 test/pandoc-test77993-90 test/pandoc-test77993-91 test/pandoc-test88473-110 test/pandoc-test88473-111 test/pandoc-test88473-112 test/pandoc-test88473-113 test/pandoc-test88473-116 test/pandoc-test88473-117 test/pandoc-test88473-118 test/pandoc-test88473-119 test/pandoc-test94017-86 test/pandoc-test94017-87 test/pandoc-test94017-90 test/pandoc-test94017-91 test/pandoc-test94017-92 test/pandoc-test94017-93 test/tables.ms+4−4 test/test-pandoc.hs.orig+83 test/writer.ms+17−17
src/Text/Pandoc/Writers/Ms.hs−1
@@ -135,7 +135,6 @@ msEscapes = Map.fromList
135135 [ ('\160', "\\~")
136136 , ('\'', "\\[aq]")
137137 , ('`', "\\`")
138− , ('\8217', "'")
139138 , ('"', "\\[dq]")
140139 , ('\x2014', "\\[em]")
141140 , ('\x2013', "\\[en]")
test/Tests/Command.hs.orignew file+95
@@ -0,0 +1,95 @@
1+module Tests.Command (findPandoc, runTest, tests)
2+where
3+
4+import Data.Algorithm.Diff
5+import qualified Data.ByteString as BS
6+import Data.List (isSuffixOf)
7+import Prelude hiding (readFile)
8+import System.Directory
9+import System.Exit
10+import System.FilePath (joinPath, splitDirectories, takeDirectory, (</>))
11+import System.IO (hPutStr, stderr)
12+import System.IO.Unsafe (unsafePerformIO)
13+import System.Process
14+import Test.Tasty
15+import Test.Tasty.HUnit
16+import Tests.Helpers
17+import Text.Pandoc
18+import qualified Text.Pandoc.UTF8 as UTF8
19+
20+-- | Run a test with normalize function, return True if test passed.
21+runTest :: String -- ^ Title of test
22+ -> FilePath -- ^ Path to pandoc
23+ -> String -- ^ Shell command
24+ -> String -- ^ Input text
25+ -> String -- ^ Expected output
26+ -> TestTree
27+runTest testname pandocpath cmd inp norm = testCase testname $ do
28+ let findDynlibDir [] = Nothing
29+ findDynlibDir ("build":xs) = Just $ joinPath (reverse xs) </> "build"
30+ findDynlibDir (_:xs) = findDynlibDir xs
31+ let mbDynlibDir = findDynlibDir (reverse $ splitDirectories $
32+ takeDirectory $ takeWhile (/=' ') cmd)
33+ let dynlibEnv = case mbDynlibDir of
34+ Nothing -> []
35+ Just d -> [("DYLD_LIBRARY_PATH", d),
36+ ("LD_LIBRARY_PATH", d)]
37+ let env' = dynlibEnv ++ [("PATH",takeDirectory pandocpath),("TMP","."),("LANG","en_US.UTF-8"),("HOME", "./"),("pandoc_datadir", "..")]
38+ let pr = (shell cmd){ env = Just env' }
39+ (ec, out', err') <- readCreateProcessWithExitCode pr inp
40+ -- filter \r so the tests will work on Windows machines
41+ let out = filter (/= '\r') $ err' ++ out'
42+ result <- if ec == ExitSuccess
43+ then
44+ if out == norm
45+ then return TestPassed
46+ else return
47+ $ TestFailed cmd "expected"
48+ $ getDiff (lines out) (lines norm)
49+ else do
50+ hPutStr stderr err'
51+ return $ TestError ec
52+ assertBool (show result) (result == TestPassed)
53+
54+tests :: TestTree
55+{-# NOINLINE tests #-}
56+tests = unsafePerformIO $ do
57+ pandocpath <- findPandoc
58+ files <- filter (".md" `isSuffixOf`) <$>
59+ getDirectoryContents "command"
60+ let cmds = map (extractCommandTest pandocpath) files
61+ return $ testGroup "Command:" cmds
62+
63+isCodeBlock :: Block -> Bool
64+isCodeBlock (CodeBlock _ _) = True
65+isCodeBlock _ = False
66+
67+extractCode :: Block -> String
68+extractCode (CodeBlock _ code) = code
69+extractCode _ = ""
70+
71+dropPercent :: String -> String
72+dropPercent ('%':xs) = dropWhile (== ' ') xs
73+dropPercent xs = xs
74+
75+runCommandTest :: FilePath -> (Int, String) -> TestTree
76+runCommandTest pandocpath (num, code) =
77+ let codelines = lines code
78+ (continuations, r1) = span ("\\" `isSuffixOf`) codelines
79+ (cmd, r2) = (dropPercent (unwords (map init continuations ++ take 1 r1)),
80+ drop 1 r1)
81+ (inplines, r3) = break (=="^D") r2
82+ normlines = takeWhile (/=".") (drop 1 r3)
83+ input = unlines inplines
84+ norm = unlines normlines
85+ shcmd = cmd -- trimr $ takeDirectory pandocpath </> cmd
86+ in runTest ("#" ++ show num) pandocpath shcmd input norm
87+
88+extractCommandTest :: FilePath -> FilePath -> TestTree
89+extractCommandTest pandocpath fp = unsafePerformIO $ do
90+ contents <- UTF8.toText <$> BS.readFile ("command" </> fp)
91+ Pandoc _ blocks <- runIOorExplode (readMarkdown
92+ def{ readerExtensions = pandocExtensions } contents)
93+ let codeblocks = map extractCode $ filter isCodeBlock blocks
94+ let cases = map (runCommandTest pandocpath) $ zip [1..] codeblocks
95+ return $ testGroup fp cases
test/Tests/Helpers.hs.orignew file+138
@@ -0,0 +1,138 @@
1+{-# LANGUAGE FlexibleInstances #-}
2+{-# LANGUAGE TypeSynonymInstances #-}
3+-- Utility functions for the test suite.
4+
5+module Tests.Helpers ( test
6+ , TestResult(..)
7+ , showDiff
8+ , findPandoc
9+ , (=?>)
10+ , purely
11+ , ToString(..)
12+ , ToPandoc(..)
13+ )
14+ where
15+
16+import Data.Algorithm.Diff
17+import qualified Data.Map as M
18+import Data.Text (Text, unpack)
19+import System.Directory
20+import System.Environment.Executable (getExecutablePath)
21+import System.Exit
22+import System.FilePath
23+import Test.Tasty
24+import Test.Tasty.HUnit
25+import Text.Pandoc.Builder (Blocks, Inlines, doc, plain)
26+import Text.Pandoc.Class
27+import Text.Pandoc.Definition
28+import Text.Pandoc.Options
29+import Text.Pandoc.Shared (trimr)
30+import Text.Pandoc.Writers.Native (writeNative)
31+import Text.Printf
32+
33+test :: (ToString a, ToString b, ToString c)
34+ => (a -> b) -- ^ function to test
35+ -> String -- ^ name of test case
36+ -> (a, c) -- ^ (input, expected value)
37+ -> TestTree
38+test fn name (input, expected) =
39+ testCase name' $ assertBool msg (actual' == expected')
40+ where msg = nl ++ dashes "input" ++ nl ++ input' ++ nl ++
41+ dashes "result" ++ nl ++
42+ unlines (map vividize diff) ++
43+ dashes ""
44+ nl = "\n"
45+ name' = if length name > 54
46+ then take 52 name ++ "..." -- avoid wide output
47+ else name
48+ input' = toString input
49+ actual' = lines $ toString $ fn input
50+ expected' = lines $ toString expected
51+ diff = getDiff expected' actual'
52+ dashes "" = replicate 72 '-'
53+ dashes x = replicate (72 - length x - 5) '-' ++ " " ++ x ++ " ---"
54+
55+data TestResult = TestPassed
56+ | TestError ExitCode
57+ | TestFailed String FilePath [Diff String]
58+ deriving (Eq)
59+
60+instance Show TestResult where
61+ show TestPassed = "PASSED"
62+ show (TestError ec) = "ERROR " ++ show ec
63+ show (TestFailed cmd file d) = '\n' : dash ++
64+ "\n--- " ++ file ++
65+ "\n+++ " ++ cmd ++ "\n" ++ showDiff (1,1) d ++
66+ dash
67+ where dash = replicate 72 '-'
68+
69+showDiff :: (Int,Int) -> [Diff String] -> String
70+showDiff _ [] = ""
71+showDiff (l,r) (First ln : ds) =
72+ printf "+%4d " l ++ ln ++ "\n" ++ showDiff (l+1,r) ds
73+showDiff (l,r) (Second ln : ds) =
74+ printf "-%4d " r ++ ln ++ "\n" ++ showDiff (l,r+1) ds
75+showDiff (l,r) (Both _ _ : ds) =
76+ showDiff (l+1,r+1) ds
77+
78+-- | Find pandoc executable relative to test-pandoc
79+-- First, try in same directory (e.g. if both in ~/.cabal/bin)
80+-- Second, try ../pandoc (e.g. if in dist/XXX/build/test-pandoc)
81+findPandoc :: IO FilePath
82+findPandoc = do
83+ testExePath <- getExecutablePath
84+ let testExeDir = takeDirectory testExePath
85+ found <- doesFileExist (testExeDir </> "pandoc")
86+ return $ if found
87+ then testExeDir </> "pandoc"
88+ else case splitDirectories testExeDir of
89+ [] -> error "test-pandoc: empty testExeDir"
90+ xs -> joinPath (init xs) </> "pandoc" </> "pandoc"
91+
92+
93+vividize :: Diff String -> String
94+vividize (Both s _) = " " ++ s
95+vividize (First s) = "- " ++ s
96+vividize (Second s) = "+ " ++ s
97+
98+purely :: (b -> PandocPure a) -> b -> a
99+purely f = either (error . show) id . runPure . f
100+
101+infix 5 =?>
102+(=?>) :: a -> b -> (a,b)
103+x =?> y = (x, y)
104+
105+class ToString a where
106+ toString :: a -> String
107+
108+instance ToString Pandoc where
109+ toString d = unpack $
110+ purely (writeNative def{ writerTemplate = s }) $ toPandoc d
111+ where s = case d of
112+ (Pandoc (Meta m) _)
113+ | M.null m -> Nothing
114+ | otherwise -> Just "" -- need this to get meta output
115+
116+instance ToString Blocks where
117+ toString = unpack . purely (writeNative def) . toPandoc
118+
119+instance ToString Inlines where
120+ toString = trimr . unpack . purely (writeNative def) . toPandoc
121+
122+instance ToString String where
123+ toString = id
124+
125+instance ToString Text where
126+ toString = unpack
127+
128+class ToPandoc a where
129+ toPandoc :: a -> Pandoc
130+
131+instance ToPandoc Pandoc where
132+ toPandoc = id
133+
134+instance ToPandoc Blocks where
135+ toPandoc = doc
136+
137+instance ToPandoc Inlines where
138+ toPandoc = doc . plain
test/Tests/Lua.hs.orignew file+196
@@ -0,0 +1,196 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Lua ( tests ) where
3+
4+import Control.Monad (when)
5+import Data.Version (Version (versionBranch))
6+import System.FilePath ((</>))
7+import Test.Tasty (TestTree, localOption)
8+import Test.Tasty.HUnit (Assertion, assertEqual, testCase)
9+import Test.Tasty.QuickCheck (QuickCheckTests (..), ioProperty, testProperty)
10+import Text.Pandoc.Arbitrary ()
11+import Text.Pandoc.Builder (bulletList, divWith, doc, doubleQuoted, emph,
12+ header, linebreak, para, plain, rawBlock,
13+ singleQuoted, space, str, strong)
14+import Text.Pandoc.Class (runIOorExplode, setUserDataDir)
15+import Text.Pandoc.Definition (Block (BlockQuote, Div, Para), Inline (Emph, Str),
16+ Attr, Meta, Pandoc, pandocTypesVersion)
17+import Text.Pandoc.Lua (runLuaFilter, runPandocLua)
18+import Text.Pandoc.Options (def)
19+import Text.Pandoc.Shared (pandocVersion)
20+
21+import qualified Foreign.Lua as Lua
22+
23+tests :: [TestTree]
24+tests = map (localOption (QuickCheckTests 20))
25+ [ testProperty "inline elements can be round-tripped through the lua stack" $
26+ \x -> ioProperty (roundtripEqual (x::Inline))
27+
28+ , testProperty "block elements can be round-tripped through the lua stack" $
29+ \x -> ioProperty (roundtripEqual (x::Block))
30+
31+ , testProperty "meta blocks can be round-tripped through the lua stack" $
32+ \x -> ioProperty (roundtripEqual (x::Meta))
33+
34+ , testProperty "documents can be round-tripped through the lua stack" $
35+ \x -> ioProperty (roundtripEqual (x::Pandoc))
36+
37+ , testCase "macro expansion via filter" $
38+ assertFilterConversion "a '{{helloworld}}' string is expanded"
39+ "strmacro.lua"
40+ (doc . para $ str "{{helloworld}}")
41+ (doc . para . emph $ str "Hello, World")
42+
43+ , testCase "convert all plains to paras" $
44+ assertFilterConversion "plains become para"
45+ "plain-to-para.lua"
46+ (doc $ bulletList [plain (str "alfa"), plain (str "bravo")])
47+ (doc $ bulletList [para (str "alfa"), para (str "bravo")])
48+
49+ , testCase "make hello world document" $
50+ assertFilterConversion "Document contains 'Hello, World!'"
51+ "hello-world-doc.lua"
52+ (doc . para $ str "Hey!" <> linebreak <> str "What's up?")
53+ (doc . para $ str "Hello," <> space <> str "World!")
54+
55+ , testCase "implicit doc filter" $
56+ assertFilterConversion "Document contains 'Hello, World!'"
57+ "implicit-doc-filter.lua"
58+ (doc . plain $ linebreak)
59+ (doc . para $ str "Hello," <> space <> str "World!")
60+
61+ , testCase "parse raw markdown blocks" $
62+ assertFilterConversion "raw markdown block is converted"
63+ "markdown-reader.lua"
64+ (doc $ rawBlock "markdown" "*charly* **delta**")
65+ (doc . para $ emph "charly" <> space <> strong "delta")
66+
67+ , testCase "allow shorthand functions for quote types" $
68+ assertFilterConversion "single quoted becomes double quoted string"
69+ "single-to-double-quoted.lua"
70+ (doc . para . singleQuoted $ str "simple")
71+ (doc . para . doubleQuoted $ str "simple")
72+
73+ , testCase "Count inlines via metatable catch-all" $
74+ assertFilterConversion "filtering with metatable catch-all failed"
75+ "metatable-catch-all.lua"
76+ (doc . para $ "four words, three spaces")
77+ (doc . para $ str "7")
78+
79+ , testCase "Count blocks via Block-specific catch-all" $
80+ assertFilterConversion "filtering with Block catch-all failed"
81+ "block-count.lua"
82+ (doc $ para "one" <> para "two")
83+ (doc $ para "2")
84+
85+ , testCase "Convert header upper case" $
86+ assertFilterConversion "converting header to upper case failed"
87+ "uppercase-header.lua"
88+ (doc $ header 1 "les états-unis" <> para "text")
89+ (doc $ header 1 "LES ÉTATS-UNIS" <> para "text")
90+
91+ , testCase "Attribute lists are convenient to use" $
92+ let kv_before = [("one", "1"), ("two", "2"), ("three", "3")]
93+ kv_after = [("one", "eins"), ("three", "3"), ("five", "5")]
94+ in assertFilterConversion "Attr doesn't behave as expected"
95+ "attr-test.lua"
96+ (doc $ divWith ("", [], kv_before) (para "nil"))
97+ (doc $ divWith ("", [], kv_after) (para "nil"))
98+
99+ , testCase "Test module pandoc.utils" $
100+ assertFilterConversion "pandoc.utils doesn't work as expected."
101+ "test-pandoc-utils.lua"
102+ (doc $ para "doesn't matter")
103+ (doc $ mconcat [ plain (str "hierarchicalize: OK")
104+ , plain (str "normalize_date: OK")
105+ , plain (str "pipe: OK")
106+ , plain (str "failing pipe: OK")
107+ , plain (str "read: OK")
108+ , plain (str "failing read: OK")
109+ , plain (str "sha1: OK")
110+ , plain (str "stringify: OK")
111+ , plain (str "to_roman_numeral: OK")
112+ ])
113+
114+ , testCase "Script filename is set" $
115+ assertFilterConversion "unexpected script name"
116+ "script-name.lua"
117+ (doc $ para "ignored")
118+ (doc $ para (str $ "lua" </> "script-name.lua"))
119+
120+ , testCase "Pandoc version is set" . runPandocLua' $ do
121+ Lua.getglobal' "table.concat"
122+ Lua.getglobal "PANDOC_VERSION"
123+ Lua.push ("." :: String) -- seperator
124+ Lua.call 2 1
125+ Lua.liftIO . assertEqual "pandoc version is wrong" pandocVersion
126+ =<< Lua.peek Lua.stackTop
127+
128+ , testCase "Pandoc types version is set" . runPandocLua' $ do
129+ let versionNums = versionBranch pandocTypesVersion
130+ Lua.getglobal "PANDOC_API_VERSION"
131+ Lua.liftIO . assertEqual "pandoc-types version is wrong" versionNums
132+ =<< Lua.peek Lua.stackTop
133+
134+ , testCase "Allow singleton inline in constructors" . runPandocLua' $ do
135+ Lua.liftIO . assertEqual "Not the exptected Emph" (Emph [Str "test"])
136+ =<< Lua.callFunc "pandoc.Emph" (Str "test")
137+ Lua.liftIO . assertEqual "Unexpected element" (Para [Str "test"])
138+ =<< Lua.callFunc "pandoc.Para" ("test" :: String)
139+ Lua.liftIO . assertEqual "Unexptected element"
140+ (BlockQuote [Para [Str "foo"]]) =<< (
141+ do
142+ Lua.getglobal' "pandoc.BlockQuote"
143+ Lua.push (Para [Str "foo"])
144+ _ <- Lua.call 1 1
145+ Lua.peek Lua.stackTop
146+ )
147+
148+ , testCase "Elements with Attr have `attr` accessor" . runPandocLua' $ do
149+ Lua.push (Div ("hi", ["moin"], [])
150+ [Para [Str "ignored"]])
151+ Lua.getfield Lua.stackTop "attr"
152+ Lua.liftIO . assertEqual "no accessor" (("hi", ["moin"], []) :: Attr)
153+ =<< Lua.peek Lua.stackTop
154+
155+ , testCase "informative error messages" . runPandocLua' $ do
156+ Lua.pushboolean True
157+ err <- Lua.peekEither Lua.stackTop :: Lua.Lua (Either String Pandoc)
158+ case err of
159+ Left msg -> do
160+ let expectedMsg = "Could not get Pandoc value: "
161+ ++ "expected table but got boolean."
162+ Lua.liftIO $ assertEqual "unexpected error message" expectedMsg msg
163+ Right _ -> error "Getting a Pandoc element from a bool should fail."
164+ ]
165+
166+assertFilterConversion :: String -> FilePath -> Pandoc -> Pandoc -> Assertion
167+assertFilterConversion msg filterPath docIn docExpected = do
168+ docEither <- runIOorExplode $ do
169+ setUserDataDir (Just "../data")
170+ runLuaFilter def ("lua" </> filterPath) [] docIn
171+ case docEither of
172+ Left _ -> fail "lua filter failed"
173+ Right docRes -> assertEqual msg docExpected docRes
174+
175+roundtripEqual :: (Eq a, Lua.FromLuaStack a, Lua.ToLuaStack a) => a -> IO Bool
176+roundtripEqual x = (x ==) <$> roundtripped
177+ where
178+ roundtripped :: (Lua.FromLuaStack a, Lua.ToLuaStack a) => IO a
179+ roundtripped = runPandocLua' $ do
180+ oldSize <- Lua.gettop
181+ Lua.push x
182+ size <- Lua.gettop
183+ when (size - oldSize /= 1) $
184+ error ("not exactly one additional element on the stack: " ++ show size)
185+ res <- Lua.peekEither (-1)
186+ case res of
187+ Left e -> error (show e)
188+ Right y -> return y
189+
190+runPandocLua' :: Lua.Lua a -> IO a
191+runPandocLua' op = runIOorExplode $ do
192+ setUserDataDir (Just "../data")
193+ res <- runPandocLua op
194+ case res of
195+ Left e -> error (show e)
196+ Right x -> return x
test/Tests/Old.hs.orignew file+288
@@ -0,0 +1,288 @@
1+module Tests.Old (tests) where
2+
3+import Data.Algorithm.Diff
4+import Prelude hiding (readFile)
5+import System.Exit
6+import System.FilePath (joinPath, splitDirectories, (<.>), (</>))
7+import System.IO.Temp (withTempFile)
8+import System.Process (runProcess, waitForProcess)
9+import Test.Tasty (TestTree, testGroup)
10+import Test.Tasty.Golden.Advanced (goldenTest)
11+import Tests.Helpers hiding (test)
12+import qualified Text.Pandoc.UTF8 as UTF8
13+
14+tests :: [TestTree]
15+tests = [ testGroup "markdown"
16+ [ testGroup "writer"
17+ $ writerTests "markdown" ++ lhsWriterTests "markdown"
18+ , testGroup "reader"
19+ [ test "basic" ["-r", "markdown", "-w", "native", "-s"]
20+ "testsuite.txt" "testsuite.native"
21+ , test "tables" ["-r", "markdown", "-w", "native", "--columns=80"]
22+ "tables.txt" "tables.native"
23+ , test "pipe tables" ["-r", "markdown", "-w", "native", "--columns=80"]
24+ "pipe-tables.txt" "pipe-tables.native"
25+ , test "more" ["-r", "markdown", "-w", "native", "-s"]
26+ "markdown-reader-more.txt" "markdown-reader-more.native"
27+ , lhsReaderTest "markdown+lhs"
28+ ]
29+ , testGroup "citations"
30+ [ test "citations" ["-r", "markdown", "-w", "native"]
31+ "markdown-citations.txt" "markdown-citations.native"
32+ ]
33+ ]
34+ , testGroup "rst"
35+ [ testGroup "writer" (writerTests "rst" ++ lhsWriterTests "rst")
36+ , testGroup "reader"
37+ [ test "basic" ["-r", "rst+smart", "-w", "native",
38+ "-s", "--columns=80"] "rst-reader.rst" "rst-reader.native"
39+ , test "tables" ["-r", "rst", "-w", "native", "--columns=80"]
40+ "tables.rst" "tables-rstsubset.native"
41+ , lhsReaderTest "rst+lhs"
42+ ]
43+ ]
44+ , testGroup "latex"
45+ [ testGroup "writer" (writerTests "latex" ++ lhsWriterTests "latex")
46+ , testGroup "reader"
47+ [ test "basic" ["-r", "latex+raw_tex", "-w", "native", "-s"]
48+ "latex-reader.latex" "latex-reader.native"
49+ , lhsReaderTest "latex+lhs"
50+ ]
51+ ]
52+ , testGroup "html"
53+ [ testGroup "writer" (writerTests "html4" ++ writerTests "html5" ++
54+ lhsWriterTests "html")
55+ , test "reader" ["-r", "html", "-w", "native", "-s"]
56+ "html-reader.html" "html-reader.native"
57+ ]
58+ , testGroup "s5"
59+ [ s5WriterTest "basic" ["-s"] "s5"
60+ , s5WriterTest "fancy" ["-s","-m","-i"] "s5"
61+ , s5WriterTest "fragment" [] "html4"
62+ , s5WriterTest "inserts" ["-s", "-H", "insert",
63+ "-B", "insert", "-A", "insert", "-c", "main.css"] "html4"
64+ ]
65+ , testGroup "textile"
66+ [ testGroup "writer" $ writerTests "textile"
67+ , test "reader" ["-r", "textile", "-w", "native", "-s"]
68+ "textile-reader.textile" "textile-reader.native"
69+ ]
70+ , testGroup "docbook"
71+ [ testGroup "writer" $ writerTests "docbook4"
72+ , test "reader" ["-r", "docbook", "-w", "native", "-s"]
73+ "docbook-reader.docbook" "docbook-reader.native"
74+ , test "reader" ["-r", "docbook", "-w", "native", "-s"]
75+ "docbook-xref.docbook" "docbook-xref.native"
76+ ]
77+ , testGroup "docbook5"
78+ [ testGroup "writer" $ writerTests "docbook5"
79+ ]
80+ , testGroup "jats"
81+ [ testGroup "writer" $ writerTests "jats"
82+ , test "reader" ["-r", "jats", "-w", "native", "-s"]
83+ "jats-reader.xml" "jats-reader.native"
84+ ]
85+ , testGroup "native"
86+ [ testGroup "writer" $ writerTests "native"
87+ , test "reader" ["-r", "native", "-w", "native", "-s"]
88+ "testsuite.native" "testsuite.native"
89+ ]
90+ , testGroup "fb2"
91+ [ fb2WriterTest "basic" [] "fb2/basic.markdown" "fb2/basic.fb2"
92+ , fb2WriterTest "titles" [] "fb2/titles.markdown" "fb2/titles.fb2"
93+ , fb2WriterTest "images" [] "fb2/images.markdown" "fb2/images.fb2"
94+ , fb2WriterTest "images-embedded" [] "fb2/images-embedded.html" "fb2/images-embedded.fb2"
95+ , fb2WriterTest "math" [] "fb2/math.markdown" "fb2/math.fb2"
96+ , fb2WriterTest "tables" [] "tables.native" "tables.fb2"
97+ , fb2WriterTest "testsuite" [] "testsuite.native" "writer.fb2"
98+ ]
99+ , testGroup "mediawiki"
100+ [ testGroup "writer" $ writerTests "mediawiki"
101+ , test "reader" ["-r", "mediawiki", "-w", "native", "-s"]
102+ "mediawiki-reader.wiki" "mediawiki-reader.native"
103+ ]
104+ , testGroup "vimwiki"
105+ [ test "reader" ["-r", "vimwiki", "-w", "native", "-s"]
106+ "vimwiki-reader.wiki" "vimwiki-reader.native"
107+ ]
108+ , testGroup "dokuwiki"
109+ [ testGroup "writer" $ writerTests "dokuwiki"
110+ , test "inline_formatting" ["-r", "native", "-w", "dokuwiki", "-s"]
111+ "dokuwiki_inline_formatting.native" "dokuwiki_inline_formatting.dokuwiki"
112+ , test "multiblock table" ["-r", "native", "-w", "dokuwiki", "-s"]
113+ "dokuwiki_multiblock_table.native" "dokuwiki_multiblock_table.dokuwiki"
114+ , test "external images" ["-r", "native", "-w", "dokuwiki", "-s"]
115+ "dokuwiki_external_images.native" "dokuwiki_external_images.dokuwiki"
116+ ]
117+ , testGroup "opml"
118+ [ test "basic" ["-r", "native", "-w", "opml", "--columns=78", "-s"]
119+ "testsuite.native" "writer.opml"
120+ , test "reader" ["-r", "opml", "-w", "native", "-s"]
121+ "opml-reader.opml" "opml-reader.native"
122+ ]
123+ , testGroup "haddock"
124+ [ testGroup "writer" $ writerTests "haddock"
125+ , test "reader" ["-r", "haddock", "-w", "native", "-s"]
126+ "haddock-reader.haddock" "haddock-reader.native"
127+ ]
128+ , testGroup "txt2tags"
129+ [ test "reader" ["-r", "t2t", "-w", "native", "-s"]
130+ "txt2tags.t2t" "txt2tags.native" ]
131+ , testGroup "epub" [
132+ test "features" ["-r", "epub", "-w", "native"]
133+ "epub/features.epub" "epub/features.native"
134+ , test "wasteland" ["-r", "epub", "-w", "native"]
135+ "epub/wasteland.epub" "epub/wasteland.native"
136+ , test "formatting" ["-r", "epub", "-w", "native"]
137+ "epub/formatting.epub" "epub/formatting.native"
138+ ]
139+ , testGroup "twiki"
140+ [ test "reader" ["-r", "twiki", "-w", "native", "-s"]
141+ "twiki-reader.twiki" "twiki-reader.native" ]
142+ , testGroup "tikiwiki"
143+ [ test "reader" ["-r", "tikiwiki", "-w", "native", "-s"]
144+ "tikiwiki-reader.tikiwiki" "tikiwiki-reader.native" ]
145+ , testGroup "other writers" $ map (\f -> testGroup f $ writerTests f)
146+ [ "opendocument" , "context" , "texinfo", "icml", "tei"
147+ , "man" , "plain" , "rtf", "org", "asciidoc", "zimwiki"
148+ ]
149+ , testGroup "writers-lang-and-dir"
150+ [ test "latex" ["-f", "native", "-t", "latex", "-s"]
151+ "writers-lang-and-dir.native" "writers-lang-and-dir.latex"
152+ , test "context" ["-f", "native", "-t", "context", "-s"]
153+ "writers-lang-and-dir.native" "writers-lang-and-dir.context"
154+ ]
155+ , testGroup "muse"
156+ [ testGroup "writer" $ writerTests "muse"
157+ ]
158+ , testGroup "ms"
159+ [ testGroup "writer" $ writerTests "ms"
160+ ]
161+ , testGroup "creole"
162+ [ test "reader" ["-r", "creole", "-w", "native", "-s"]
163+ "creole-reader.txt" "creole-reader.native"
164+ ]
165+ , testGroup "custom writer"
166+ [ test "basic" ["-f", "native", "-t", "../data/sample.lua"]
167+ "testsuite.native" "writer.custom"
168+ , test "tables" ["-f", "native", "-t", "../data/sample.lua"]
169+ "tables.native" "tables.custom"
170+ ]
171+ ]
172+
173+-- makes sure file is fully closed after reading
174+readFile' :: FilePath -> IO String
175+readFile' f = do s <- UTF8.readFile f
176+ return $! (length s `seq` s)
177+
178+lhsWriterTests :: String -> [TestTree]
179+lhsWriterTests format
180+ = [ t "lhs to normal" format
181+ , t "lhs to lhs" (format ++ "+lhs")
182+ ]
183+ where
184+ t n f = test n ["--wrap=preserve", "-r", "native", "-s", "-w", f]
185+ "lhs-test.native" ("lhs-test" <.> f)
186+
187+lhsReaderTest :: String -> TestTree
188+lhsReaderTest format =
189+ test "lhs" ["-r", format, "-w", "native"]
190+ ("lhs-test" <.> format) norm
191+ where norm = if format == "markdown+lhs"
192+ then "lhs-test-markdown.native"
193+ else "lhs-test.native"
194+
195+writerTests :: String -> [TestTree]
196+writerTests format
197+ = [ test "basic" (opts ++ ["-s"]) "testsuite.native" ("writer" <.> format)
198+ , test "tables" opts "tables.native" ("tables" <.> format)
199+ ]
200+ where
201+ opts = ["-r", "native", "-w", format, "--columns=78",
202+ "--variable", "pandoc-version="]
203+
204+s5WriterTest :: String -> [String] -> String -> TestTree
205+s5WriterTest modifier opts format
206+ = test (format ++ " writer (" ++ modifier ++ ")")
207+ (["-r", "native", "-w", format] ++ opts)
208+ "s5.native" ("s5-" ++ modifier <.> "html")
209+
210+fb2WriterTest :: String -> [String] -> String -> String -> TestTree
211+fb2WriterTest title opts inputfile normfile =
212+ testWithNormalize (ignoreBinary . formatXML)
213+ title (["-t", "fb2"]++opts) inputfile normfile
214+ where
215+ formatXML xml = splitTags $ zip xml (drop 1 xml)
216+ splitTags [] = []
217+ splitTags [end] = fst end : snd end : []
218+ splitTags (('>','<'):rest) = ">\n" ++ splitTags rest
219+ splitTags ((c,_):rest) = c : splitTags rest
220+ ignoreBinary = unlines . filter (not . startsWith "<binary ") . lines
221+ startsWith tag str = all (uncurry (==)) $ zip tag str
222+
223+-- | Run a test without normalize function, return True if test passed.
224+test :: String -- ^ Title of test
225+ -> [String] -- ^ Options to pass to pandoc
226+ -> String -- ^ Input filepath
227+ -> FilePath -- ^ Norm (for test results) filepath
228+ -> TestTree
229+test = testWithNormalize id
230+
231+-- | Run a test with normalize function, return True if test passed.
232+testWithNormalize :: (String -> String) -- ^ Normalize function for output
233+ -> String -- ^ Title of test
234+ -> [String] -- ^ Options to pass to pandoc
235+ -> String -- ^ Input filepath
236+ -> FilePath -- ^ Norm (for test results) filepath
237+ -> TestTree
238+testWithNormalize normalizer testname opts inp norm =
239+ goldenTest testname getExpected getActual
240+ (compareValues norm options) updateGolden
241+ where getExpected = normalizer <$> readFile' norm
242+ getActual =
243+ withTempFile "." "pandoc-test" $ \outputPath hOut -> do
244+ withTempFile "." "pandoc-test" $ \errorPath hErr -> do
245+ pandocPath <- findPandoc
246+ let mbDynlibDir = findDynlibDir (reverse $
247+ splitDirectories pandocPath)
248+ let dynlibEnv = case mbDynlibDir of
249+ Nothing -> []
250+ Just d -> [("DYLD_LIBRARY_PATH", d),
251+ ("LD_LIBRARY_PATH", d)]
252+ let env = dynlibEnv ++
253+ [("TMP","."),("LANG","en_US.UTF-8"),("HOME", "./"),
254+ ("pandoc_datadir","..")]
255+ ph <- runProcess pandocPath options Nothing
256+ (Just env) Nothing (Just hOut) (Just hErr)
257+ ec <- waitForProcess ph
258+ if ec == ExitSuccess
259+ then
260+ -- filter \r so the tests will work on Windows machines
261+ (filter (/='\r') . normalizer) <$> readFile' outputPath
262+ else do
263+ errcontents <- UTF8.readFile errorPath
264+ fail $ "Pandoc failed with " ++ show ec ++
265+ if null errcontents
266+ then ""
267+ else '\n':errcontents
268+ updateGolden = UTF8.writeFile norm
269+ options = ["--quiet"] ++ [inp] ++ opts
270+
271+compareValues :: FilePath -> [String] -> String -> String -> IO (Maybe String)
272+compareValues norm options expected actual = do
273+ pandocPath <- findPandoc
274+ let cmd = pandocPath ++ " " ++ unwords options
275+ let dash = replicate 72 '-'
276+ let diff = getDiff (lines actual) (lines expected)
277+ if expected == actual
278+ then return Nothing
279+ else return $ Just $
280+ '\n' : dash ++
281+ "\n--- " ++ norm ++
282+ "\n+++ " ++ cmd ++ "\n" ++
283+ showDiff (1,1) diff ++ dash
284+
285+findDynlibDir :: [FilePath] -> Maybe FilePath
286+findDynlibDir [] = Nothing
287+findDynlibDir ("build":xs) = Just $ joinPath (reverse xs) </> "build"
288+findDynlibDir (_:xs) = findDynlibDir xs
test/Tests/Readers/Creole.hs.orignew file+286
@@ -0,0 +1,286 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Creole (tests) where
3+
4+import Data.Text (Text)
5+import qualified Data.Text as T
6+import Test.Tasty
7+import Tests.Helpers
8+import Text.Pandoc
9+import Text.Pandoc.Arbitrary ()
10+import Text.Pandoc.Builder
11+
12+creole :: Text -> Pandoc
13+creole = purely $ readCreole def{ readerStandalone = True }
14+
15+infix 4 =:
16+(=:) :: ToString c
17+ => String -> (Text, c) -> TestTree
18+(=:) = test creole
19+
20+tests :: [TestTree]
21+tests = [
22+ testGroup "Basic Text Formatting" [
23+ "bold, single line, fully delimited" =:
24+ "only **bold** is bold"
25+ =?> para ("only " <> strong "bold" <> " is bold")
26+ , "italics, single line, fully delimited" =:
27+ "only //this// is in italics"
28+ =?> para ("only " <> emph "this" <> " is in italics")
29+ , "bold in italics, fully delimited" =:
30+ "//**this**// is in bold italics"
31+ =?> para (emph (strong "this") <> " is in bold italics")
32+ , "italics in bold, fully delimited" =:
33+ "**//this//** is in bold italics"
34+ =?> para (strong (emph "this") <> " is in bold italics")
35+
36+ , "escape bold marker" =:
37+ "~**not bold" =?> para "**not bold"
38+ , "escape italics marker" =:
39+ "~//not in italics" =?> para "//not in italics"
40+
41+ , "inline nowiki, simple" =:
42+ "this is {{{**not** ~interpreted}}} at all"
43+ =?> para ("this is " <> code "**not** ~interpreted" <> " at all")
44+ , "inline nowiki, curly braces inside" =:
45+ "this is {{{{{{//including// some `}' chars}}}}}}"
46+ =?> para ("this is " <> code "{{{//including// some `}' chars}}}")
47+
48+ , "placeholder" =:
49+ "foo <<<place holder>>> bar"
50+ =?> para "foo bar"
51+ , "placeholder escaped" =:
52+ "foo ~<<<no place holder>>> bar"
53+ =?> para "foo <<<no place holder>>> bar"
54+ ]
55+ , testGroup "Headers" [
56+ "header level 1, no space, no trailing =" =:
57+ "= Top-Level Header"
58+ =?> header 1 (str "Top-Level Header")
59+ , "header level 1, leading space, trailing =" =:
60+ " = Top-Level Header = "
61+ =?> header 1 (str "Top-Level Header")
62+ , "header level 2, no space, no trailing =" =:
63+ "== Second Level"
64+ =?> header 2 (str "Second Level")
65+ , "header level 2, leading space, no trailing =" =:
66+ " == Second Level"
67+ =?> header 2 (str "Second Level")
68+ , "header level 3, no space, no trailing =" =:
69+ "=== Third"
70+ =?> header 3 (str "Third")
71+ , "header level 3, no space, > 3 trailing =" =:
72+ "=== Third ======="
73+ =?> header 3 (str "Third")
74+ , "header level 4, no space, no trailing =" =:
75+ "==== Fourth Level Heading"
76+ =?> header 4 (str "Fourth Level Heading")
77+ , "header level 4, no space, < 4 trailing =" =:
78+ "==== Fourth Level Heading =="
79+ =?> header 4 (str "Fourth Level Heading")
80+ , "header level 5, no space, no trailing =" =:
81+ "===== Fifth"
82+ =?> header 5 (str "Fifth")
83+ , "header level 6, no space, no trailing =" =:
84+ "====== Sixth"
85+ =?> header 6 (str "Sixth")
86+ ]
87+ , testGroup "Paragraphs" [
88+ "paragraphs: multiple, one line" =:
89+ "first line\n\nanother line\n"
90+ =?> para "first line" <> para "another line"
91+ ]
92+ , testGroup "Lists" [
93+ "unordered list, two entries, one separating space" =:
94+ "* foo\n* bar"
95+ =?> bulletList [ plain "foo", plain "bar" ]
96+ , "unordered list, three entries, one separating space" =:
97+ "* foo\n* bar\n* baz"
98+ =?> bulletList [ plain "foo", plain "bar", plain "baz" ]
99+ , "para followed by, unordered list, two entries, one separating space" =:
100+ "blubber\n* foo\n* bar"
101+ =?> para "blubber" <> bulletList [ plain "foo", plain "bar" ]
102+ , "nested unordered list, one separating space" =:
103+ "* foo\n** bar\n** baz\n* blubb"
104+ =?> bulletList [ plain "foo"
105+ <> bulletList [ plain "bar", plain "baz" ]
106+ , plain "blubb" ]
107+ , "nested many unordered lists, one separating space" =:
108+ ("* foo\n** bar\n*** third\n*** third two\n** baz\n*** third again\n"
109+ <> "**** fourth\n***** fith\n* blubb")
110+ =?> bulletList [ plain "foo"
111+ <> bulletList [ plain "bar"
112+ <> bulletList [ plain "third"
113+ , plain "third two"]
114+ , plain "baz"
115+ <> bulletList [ plain "third again"
116+ <> bulletList [
117+ plain "fourth"
118+ <> bulletList [
119+ plain "fith"
120+ ]
121+ ]
122+ ]
123+ ]
124+ , plain "blubb" ]
125+ , "nested unordered list, mixed separating space" =:
126+ "*foo\n ** bar\n **baz\n * blubb"
127+ =?> bulletList [ plain "foo"
128+ <> bulletList [ plain "bar", plain "baz" ]
129+ , plain "blubb" ]
130+ , "nested unordered list, one separating space, trailing space" =:
131+ "* foo \n** bar \n** baz \n* blubb "
132+ =?> bulletList [ plain "foo"
133+ <> bulletList [ plain "bar", plain "baz" ]
134+ , plain "blubb" ]
135+ , "ordered list, two entries, one separating space" =:
136+ "# foo\n# bar"
137+ =?> orderedList [ plain "foo", plain "bar" ]
138+ , "ordered list, three entries, one separating space" =:
139+ "# foo\n# bar\n# baz"
140+ =?> orderedList [ plain "foo", plain "bar", plain "baz" ]
141+ , "para followed by, ordered list, two entries, one separating space" =:
142+ "blubber\n# foo\n# bar"
143+ =?> para "blubber" <> orderedList [ plain "foo", plain "bar" ]
144+ , "nested ordered list, one separating space" =:
145+ "# foo\n## bar\n## baz\n# blubb"
146+ =?> orderedList [ plain "foo"
147+ <> orderedList [ plain "bar", plain "baz" ]
148+ , plain "blubb" ]
149+ , "nested ordered list, one separating space, trailing space" =:
150+ "# foo \n## bar \n## baz \n# blubb "
151+ =?> orderedList [ plain "foo"
152+ <> orderedList [ plain "bar", plain "baz" ]
153+ , plain "blubb" ]
154+ , "nested many ordered lists, one separating space" =:
155+ ("# foo\n## bar\n### third\n### third two\n## baz\n### third again\n"
156+ <> "#### fourth\n##### fith\n# blubb")
157+ =?> orderedList [ plain "foo"
158+ <> orderedList [ plain "bar"
159+ <> orderedList [ plain "third"
160+ , plain "third two"]
161+ , plain "baz"
162+ <> orderedList [ plain "third again"
163+ <> orderedList [
164+ plain "fourth"
165+ <> orderedList [
166+ plain "fith"
167+ ]
168+ ]
169+ ]
170+ ]
171+ , plain "blubb" ]
172+ , "nested ordered list, mixed separating space" =:
173+ "#foo\n ## bar\n ##baz\n # blubb"
174+ =?> orderedList [ plain "foo"
175+ <> orderedList [ plain "bar", plain "baz" ]
176+ , plain "blubb" ]
177+ , "mixed nested ordered and unordered lists, one separating space" =:
178+ ("# foo\n** bar\n### third\n### third two\n** baz\n### third again\n"
179+ <> "#### fourth\n***** fith\n# blubb")
180+ =?> orderedList [ plain "foo"
181+ <> bulletList [ plain "bar"
182+ <> orderedList [ plain "third"
183+ , plain "third two"]
184+ , plain "baz"
185+ <> orderedList [ plain "third again"
186+ <> orderedList [
187+ plain "fourth"
188+ <> bulletList [
189+ plain "fith"
190+ ]
191+ ]
192+ ]
193+ ]
194+ , plain "blubb" ]
195+ ]
196+ , testGroup "NoWiki" [
197+ "quoted block, simple" =:
198+ "{{{\nfoo bar\n //baz//\n}}}"
199+ =?> codeBlock "foo bar\n //baz//"
200+ , "quoted block, curly bracket exception" =:
201+ "{{{\nfoo bar\n }}}\nbaz\n }}}\n}}}"
202+ =?> codeBlock "foo bar\n }}}\nbaz\n}}}"
203+ , "forced line breaks" =:
204+ "{{{no break!\\\\here}}} but a break\\\\here!"
205+ =?> para (code "no break!\\\\here" <> " but a break"
206+ <> linebreak <> "here!"),
207+ "quoted block, after trailing white space" =:
208+ "this is a paragraph \n{{{\nfoo bar\n //baz//\n}}}"
209+ =?> para "this is a paragraph" <> codeBlock "foo bar\n //baz//"
210+ ]
211+ , testGroup "Images and Links" [
212+ "image simple" =:
213+ "{{foo.png}}" =?> para (image "foo.png" "" (str ""))
214+ , "image with alt text" =:
215+ "Image of a bar: {{/path/to/bar.png|A Bar}} look at it!"
216+ =?> para ("Image of a bar: "
217+ <> image "/path/to/bar.png" "" (str "A Bar") <> " look at it!")
218+
219+ , "auto link" =:
220+ "foo http://foo.example.com/bar/baz.html bar"
221+ =?> para ("foo "
222+ <> link "http://foo.example.com/bar/baz.html" ""
223+ (str "http://foo.example.com/bar/baz.html")
224+ <> " bar")
225+ , "escaped auto link" =:
226+ "foo ~http://foo.example.com/bar/baz.html bar"
227+ =?> para "foo http://foo.example.com/bar/baz.html bar"
228+ , "wiki link simple" =:
229+ "foo [[http://foo.example.com/foo.png]] bar"
230+ =?> para ("foo "
231+ <> link "http://foo.example.com/foo.png" ""
232+ (str "http://foo.example.com/foo.png")
233+ <> " bar")
234+ , "wiki link with name" =:
235+ "foo [[http://foo.example.com/foo.png|my link]] bar"
236+ =?> para ("foo "
237+ <> link "http://foo.example.com/foo.png" ""
238+ (str "my link")
239+ <> " bar")
240+ , "image link" =:
241+ "[[http://foo.example.com/|{{foo.png}}]]"
242+ =?> para (link "http://foo.example.com/" "" (image "foo.png" "" (str "")))
243+ ]
244+ , testGroup "Table" [
245+ "Table with Header" =:
246+ T.unlines [ "|= Foo |= Bar |= Baz |"
247+ , "| One | Two | Three |"
248+ , "| 1 | 2 | 3 |"
249+ , "| A | B | C |"
250+ ]
251+ =?> simpleTable
252+ [plain "Foo", plain "Bar" , plain "Baz"]
253+ [[plain "One", plain "Two" , plain "Three"]
254+ ,[plain "1", plain "2" , plain "3"]
255+ ,[plain "A", plain "B" , plain "C"]]
256+ , "Table without Header" =:
257+ T.unlines [ "| One | Two | Three |"
258+ , "| 1 | 2 | 3 |"
259+ , "| A | B | C |"
260+ ]
261+ =?> simpleTable [mempty]
262+ [[plain "One", plain "Two" , plain "Three"]
263+ ,[plain "1", plain "2" , plain "3"]
264+ ,[plain "A", plain "B" , plain "C"]]
265+ , "Table without Header, no markers at line ends" =:
266+ T.unlines [ "| One | Two | Three"
267+ , "| 1 | 2 | 3"
268+ , "| A | B | C "
269+ ]
270+ =?> simpleTable [mempty]
271+ [[plain "One", plain "Two" , plain "Three"]
272+ ,[plain "1", plain "2" , plain "3"]
273+ ,[plain "A", plain "B" , plain "C"]]
274+ , "Table with Header, with formatting" =:
275+ T.unlines [ "|= **Foo** |= **Bar** |= **Baz** |"
276+ , "|//one// element |//second// elt|Three |"
277+ , "| {{{1}}} | {{{{}}}} | [[link]] |"
278+ ]
279+ =?> simpleTable
280+ [plain $ strong "Foo", plain $ strong "Bar" , plain $ strong "Baz"]
281+ [[plain (emph "one" <> " element"), plain (emph "second" <> " elt")
282+ ,plain "Three"]
283+ ,[plain $ code "1", plain $ code "{}"
284+ ,plain $ link "link" "" (str "link")]]
285+ ]
286+ ]
test/Tests/Readers/Docx.hs.orignew file+405
@@ -0,0 +1,405 @@
1+module Tests.Readers.Docx (tests) where
2+
3+import Codec.Archive.Zip
4+import qualified Data.ByteString as BS
5+import qualified Data.ByteString.Lazy as B
6+import qualified Data.Map as M
7+import qualified Data.Text as T
8+import Data.Maybe
9+import System.IO.Unsafe
10+import Test.Tasty
11+import Test.Tasty.HUnit
12+import Tests.Helpers
13+import Text.Pandoc
14+import qualified Text.Pandoc.Class as P
15+import Text.Pandoc.MediaBag (MediaBag, lookupMedia, mediaDirectory)
16+import Text.Pandoc.UTF8 as UTF8
17+
18+-- We define a wrapper around pandoc that doesn't normalize in the
19+-- tests. Since we do our own normalization, we want to make sure
20+-- we're doing it right.
21+
22+data NoNormPandoc = NoNormPandoc {unNoNorm :: Pandoc}
23+ deriving Show
24+
25+noNorm :: Pandoc -> NoNormPandoc
26+noNorm = NoNormPandoc
27+
28+defopts :: ReaderOptions
29+defopts = def{ readerExtensions = getDefaultExtensions "docx" }
30+
31+instance ToString NoNormPandoc where
32+ toString d = T.unpack $ purely (writeNative def{ writerTemplate = s }) $ toPandoc d
33+ where s = case d of
34+ NoNormPandoc (Pandoc (Meta m) _)
35+ | M.null m -> Nothing
36+ | otherwise -> Just "" -- need this to get meta output
37+
38+instance ToPandoc NoNormPandoc where
39+ toPandoc = unNoNorm
40+
41+compareOutput :: ReaderOptions
42+ -> FilePath
43+ -> FilePath
44+ -> IO (NoNormPandoc, NoNormPandoc)
45+compareOutput opts docxFile nativeFile = do
46+ df <- B.readFile docxFile
47+ nf <- UTF8.toText <$> BS.readFile nativeFile
48+ p <- runIOorExplode $ readDocx opts df
49+ df' <- runIOorExplode $ readNative def nf
50+ return (noNorm p, noNorm df')
51+
52+testCompareWithOptsIO :: ReaderOptions -> String -> FilePath -> FilePath -> IO TestTree
53+testCompareWithOptsIO opts name docxFile nativeFile = do
54+ (dp, np) <- compareOutput opts docxFile nativeFile
55+ return $ test id name (dp, np)
56+
57+testCompareWithOpts :: ReaderOptions -> String -> FilePath -> FilePath -> TestTree
58+testCompareWithOpts opts name docxFile nativeFile =
59+ unsafePerformIO $ testCompareWithOptsIO opts name docxFile nativeFile
60+
61+testCompare :: String -> FilePath -> FilePath -> TestTree
62+testCompare = testCompareWithOpts defopts
63+
64+testForWarningsWithOptsIO :: ReaderOptions -> String -> FilePath -> [String] -> IO TestTree
65+testForWarningsWithOptsIO opts name docxFile expected = do
66+ df <- B.readFile docxFile
67+ logs <- runIOorExplode $ setVerbosity ERROR >> readDocx opts df >> P.getLog
68+ let warns = [m | DocxParserWarning m <- logs]
69+ return $ test id name (unlines warns, unlines expected)
70+
71+testForWarningsWithOpts :: ReaderOptions -> String -> FilePath -> [String] -> TestTree
72+testForWarningsWithOpts opts name docxFile expected =
73+ unsafePerformIO $ testForWarningsWithOptsIO opts name docxFile expected
74+
75+-- testForWarnings :: String -> FilePath -> [String] -> TestTree
76+-- testForWarnings = testForWarningsWithOpts defopts
77+
78+getMedia :: FilePath -> FilePath -> IO (Maybe B.ByteString)
79+getMedia archivePath mediaPath = do
80+ zf <- B.readFile archivePath >>= return . toArchive
81+ return $ findEntryByPath ("word/" ++ mediaPath) zf >>= (Just . fromEntry)
82+
83+compareMediaPathIO :: FilePath -> MediaBag -> FilePath -> IO Bool
84+compareMediaPathIO mediaPath mediaBag docxPath = do
85+ docxMedia <- getMedia docxPath mediaPath
86+ let mbBS = case lookupMedia mediaPath mediaBag of
87+ Just (_, bs) -> bs
88+ Nothing -> error ("couldn't find " ++
89+ mediaPath ++
90+ " in media bag")
91+ docxBS = fromMaybe (error ("couldn't find " ++
92+ mediaPath ++
93+ " in media bag")) docxMedia
94+ return $ mbBS == docxBS
95+
96+compareMediaBagIO :: FilePath -> IO Bool
97+compareMediaBagIO docxFile = do
98+ df <- B.readFile docxFile
99+ mb <- runIOorExplode $ readDocx defopts df >> P.getMediaBag
100+ bools <- mapM
101+ (\(fp, _, _) -> compareMediaPathIO fp mb docxFile)
102+ (mediaDirectory mb)
103+ return $ and bools
104+
105+testMediaBagIO :: String -> FilePath -> IO TestTree
106+testMediaBagIO name docxFile = do
107+ outcome <- compareMediaBagIO docxFile
108+ return $ testCase name (assertBool
109+ ("Media didn't match media bag in file " ++ docxFile)
110+ outcome)
111+
112+testMediaBag :: String -> FilePath -> TestTree
113+testMediaBag name docxFile = unsafePerformIO $ testMediaBagIO name docxFile
114+
115+tests :: [TestTree]
116+tests = [ testGroup "inlines"
117+ [ testCompare
118+ "font formatting"
119+ "docx/inline_formatting.docx"
120+ "docx/inline_formatting.native"
121+ , testCompare
122+ "font formatting with character styles"
123+ "docx/char_styles.docx"
124+ "docx/char_styles.native"
125+ , testCompare
126+ "hyperlinks"
127+ "docx/links.docx"
128+ "docx/links.native"
129+ , testCompare
130+ "hyperlinks in <w:instrText> tag"
131+ "docx/instrText_hyperlink.docx"
132+ "docx/instrText_hyperlink.native"
133+ , testCompare
134+ "inline image"
135+ "docx/image.docx"
136+ "docx/image_no_embed.native"
137+ , testCompare
138+ "VML image"
139+ "docx/image_vml.docx"
140+ "docx/image_vml.native"
141+ , testCompare
142+ "inline image in links"
143+ "docx/inline_images.docx"
144+ "docx/inline_images.native"
145+ , testCompare
146+ "handling unicode input"
147+ "docx/unicode.docx"
148+ "docx/unicode.native"
149+ , testCompare
150+ "literal tabs"
151+ "docx/tabs.docx"
152+ "docx/tabs.native"
153+ , testCompare
154+ "special punctuation"
155+ "docx/special_punctuation.docx"
156+ "docx/special_punctuation.native"
157+ , testCompare
158+ "normalizing inlines"
159+ "docx/normalize.docx"
160+ "docx/normalize.native"
161+ , testCompare
162+ "normalizing inlines deep inside blocks"
163+ "docx/deep_normalize.docx"
164+ "docx/deep_normalize.native"
165+ , testCompare
166+ "move trailing spaces outside of formatting"
167+ "docx/trailing_spaces_in_formatting.docx"
168+ "docx/trailing_spaces_in_formatting.native"
169+ , testCompare
170+ "inline code (with VerbatimChar style)"
171+ "docx/inline_code.docx"
172+ "docx/inline_code.native"
173+ , testCompare
174+ "inline code in subscript and superscript"
175+ "docx/verbatim_subsuper.docx"
176+ "docx/verbatim_subsuper.native"
177+ , testCompare
178+ "inlines inside of Structured Document Tags"
179+ "docx/sdt_elements.docx"
180+ "docx/sdt_elements.native"
181+ , testCompare
182+ "nested Structured Document Tags"
183+ "docx/nested_sdt.docx"
184+ "docx/nested_sdt.native"
185+ , testCompare
186+ "nested Smart Tags"
187+ "docx/nested_smart_tags.docx"
188+ "docx/nested_smart_tags.native"
189+ , testCompare
190+ "remove anchor spans with nothing pointing to them"
191+ "docx/unused_anchors.docx"
192+ "docx/unused_anchors.native"
193+ , testCompare
194+ "collapse overlapping targets (anchor spans)"
195+ "docx/overlapping_targets.docx"
196+ "docx/overlapping_targets.native"
197+ ]
198+ , testGroup "blocks"
199+ [ testCompare
200+ "headers"
201+ "docx/headers.docx"
202+ "docx/headers.native"
203+ , testCompare
204+ "headers already having auto identifiers"
205+ "docx/already_auto_ident.docx"
206+ "docx/already_auto_ident.native"
207+ , testCompare
208+ "avoid zero-level headers"
209+ "docx/0_level_headers.docx"
210+ "docx/0_level_headers.native"
211+ , testCompare
212+ "nested anchor spans in header"
213+ "docx/nested_anchors_in_header.docx"
214+ "docx/nested_anchors_in_header.native"
215+ , testCompare
216+ "single numbered item not made into list"
217+ "docx/numbered_header.docx"
218+ "docx/numbered_header.native"
219+ , testCompare
220+ "enumerated headers not made into numbered list"
221+ "docx/enumerated_headings.docx"
222+ "docx/enumerated_headings.native"
223+ , testCompare
224+ "i18n blocks (headers and blockquotes)"
225+ "docx/i18n_blocks.docx"
226+ "docx/i18n_blocks.native"
227+ , testCompare
228+ "lists"
229+ "docx/lists.docx"
230+ "docx/lists.native"
231+ , testCompare
232+ "lists continuing after interruption"
233+ "docx/lists_continuing.docx"
234+ "docx/lists_continuing.native"
235+ , testCompare
236+ "lists restarting after interruption"
237+ "docx/lists_restarting.docx"
238+ "docx/lists_restarting.native"
239+ , testCompare
240+ "definition lists"
241+ "docx/definition_list.docx"
242+ "docx/definition_list.native"
243+ , testCompare
244+ "custom defined lists in styles"
245+ "docx/german_styled_lists.docx"
246+ "docx/german_styled_lists.native"
247+ , testCompare
248+ "user deletes bullet after list item (=> part of item par)"
249+ "docx/dummy_item_after_list_item.docx"
250+ "docx/dummy_item_after_list_item.native"
251+ , testCompare
252+ "user deletes bullet after par (=> new par)"
253+ "docx/dummy_item_after_paragraph.docx"
254+ "docx/dummy_item_after_paragraph.native"
255+ , testCompare
256+ "footnotes and endnotes"
257+ "docx/notes.docx"
258+ "docx/notes.native"
259+ , testCompare
260+ "links in footnotes and endnotes"
261+ "docx/link_in_notes.docx"
262+ "docx/link_in_notes.native"
263+ , testCompare
264+ "blockquotes (parsing indent as blockquote)"
265+ "docx/block_quotes.docx"
266+ "docx/block_quotes_parse_indent.native"
267+ , testCompare
268+ "hanging indents"
269+ "docx/hanging_indent.docx"
270+ "docx/hanging_indent.native"
271+ , testCompare
272+ "tables"
273+ "docx/tables.docx"
274+ "docx/tables.native"
275+ , testCompare
276+ "tables with lists in cells"
277+ "docx/table_with_list_cell.docx"
278+ "docx/table_with_list_cell.native"
279+ , testCompare
280+ "tables with one row"
281+ "docx/table_one_row.docx"
282+ "docx/table_one_row.native"
283+ , testCompare
284+ "tables with variable width"
285+ "docx/table_variable_width.docx"
286+ "docx/table_variable_width.native"
287+ , testCompare
288+ "code block"
289+ "docx/codeblock.docx"
290+ "docx/codeblock.native"
291+ , testCompare
292+ "dropcap paragraphs"
293+ "docx/drop_cap.docx"
294+ "docx/drop_cap.native"
295+ ]
296+ , testGroup "track changes"
297+ [ testCompare
298+ "insertion (default)"
299+ "docx/track_changes_insertion.docx"
300+ "docx/track_changes_insertion_accept.native"
301+ , testCompareWithOpts def{readerTrackChanges=AcceptChanges}
302+ "insert insertion (accept)"
303+ "docx/track_changes_insertion.docx"
304+ "docx/track_changes_insertion_accept.native"
305+ , testCompareWithOpts def{readerTrackChanges=RejectChanges}
306+ "remove insertion (reject)"
307+ "docx/track_changes_insertion.docx"
308+ "docx/track_changes_insertion_reject.native"
309+ , testCompare
310+ "deletion (default)"
311+ "docx/track_changes_deletion.docx"
312+ "docx/track_changes_deletion_accept.native"
313+ , testCompareWithOpts def{readerTrackChanges=AcceptChanges}
314+ "remove deletion (accept)"
315+ "docx/track_changes_deletion.docx"
316+ "docx/track_changes_deletion_accept.native"
317+ , testCompareWithOpts def{readerTrackChanges=RejectChanges}
318+ "insert deletion (reject)"
319+ "docx/track_changes_deletion.docx"
320+ "docx/track_changes_deletion_reject.native"
321+ , testCompareWithOpts def{readerTrackChanges=AllChanges}
322+ "keep insertion (all)"
323+ "docx/track_changes_deletion.docx"
324+ "docx/track_changes_deletion_all.native"
325+ , testCompareWithOpts def{readerTrackChanges=AllChanges}
326+ "keep deletion (all)"
327+ "docx/track_changes_deletion.docx"
328+ "docx/track_changes_deletion_all.native"
329+ , testCompareWithOpts def{readerTrackChanges=AcceptChanges}
330+ "move text (accept)"
331+ "docx/track_changes_move.docx"
332+ "docx/track_changes_move_accept.native"
333+ , testCompareWithOpts def{readerTrackChanges=RejectChanges}
334+ "move text (reject)"
335+ "docx/track_changes_move.docx"
336+ "docx/track_changes_move_reject.native"
337+ , testCompareWithOpts def{readerTrackChanges=AllChanges}
338+ "move text (all)"
339+ "docx/track_changes_move.docx"
340+ "docx/track_changes_move_all.native"
341+ , testCompareWithOpts def{readerTrackChanges=AcceptChanges}
342+ "comments (accept -- no comments)"
343+ "docx/comments.docx"
344+ "docx/comments_no_comments.native"
345+ , testCompareWithOpts def{readerTrackChanges=RejectChanges}
346+ "comments (reject -- comments)"
347+ "docx/comments.docx"
348+ "docx/comments_no_comments.native"
349+ , testCompareWithOpts def{readerTrackChanges=AllChanges}
350+ "comments (all comments)"
351+ "docx/comments.docx"
352+ "docx/comments.native"
353+ , testCompareWithOpts def{readerTrackChanges=AcceptChanges}
354+ "paragraph insertion/deletion (accept)"
355+ "docx/paragraph_insertion_deletion.docx"
356+ "docx/paragraph_insertion_deletion_accept.native"
357+ , testCompareWithOpts def{readerTrackChanges=RejectChanges}
358+ "paragraph insertion/deletion (reject)"
359+ "docx/paragraph_insertion_deletion.docx"
360+ "docx/paragraph_insertion_deletion_reject.native"
361+ , testCompareWithOpts def{readerTrackChanges=AllChanges}
362+ "paragraph insertion/deletion (all)"
363+ "docx/paragraph_insertion_deletion.docx"
364+ "docx/paragraph_insertion_deletion_all.native"
365+ , testForWarningsWithOpts def{readerTrackChanges=AcceptChanges}
366+ "comment warnings (accept -- no warnings)"
367+ "docx/comments_warning.docx"
368+ []
369+ , testForWarningsWithOpts def{readerTrackChanges=RejectChanges}
370+ "comment warnings (reject -- no warnings)"
371+ "docx/comments_warning.docx"
372+ []
373+ , testForWarningsWithOpts def{readerTrackChanges=AllChanges}
374+ "comment warnings (all)"
375+ "docx/comments_warning.docx"
376+ ["Docx comment 1 will not retain formatting"]
377+ ]
378+ , testGroup "media"
379+ [ testMediaBag
380+ "image extraction"
381+ "docx/image.docx"
382+ ]
383+ , testGroup "custom styles"
384+ [ testCompare
385+ "custom styles (`+styles`) not enabled (default)"
386+ "docx/custom-style-reference.docx"
387+ "docx/custom-style-no-styles.native"
388+ , testCompareWithOpts
389+ def{readerExtensions=extensionsFromList [Ext_styles]}
390+ "custom styles (`+styles`) enabled"
391+ "docx/custom-style-reference.docx"
392+ "docx/custom-style-with-styles.native"
393+ ]
394+ , testGroup "metadata"
395+ [ testCompareWithOpts def{readerStandalone=True}
396+ "metadata fields"
397+ "docx/metadata.docx"
398+ "docx/metadata.native"
399+ , testCompareWithOpts def{readerStandalone=True}
400+ "stop recording metadata with normal text"
401+ "docx/metadata_after_normal.docx"
402+ "docx/metadata_after_normal.native"
403+ ]
404+
405+ ]
test/Tests/Readers/EPUB.hs.orignew file+40
@@ -0,0 +1,40 @@
1+module Tests.Readers.EPUB (tests) where
2+
3+import qualified Data.ByteString.Lazy as BL
4+import Test.Tasty
5+import Test.Tasty.HUnit
6+import qualified Text.Pandoc.Class as P
7+import Text.Pandoc.MediaBag (MediaBag, mediaDirectory)
8+import Text.Pandoc.Options
9+import Text.Pandoc.Readers.EPUB
10+
11+getMediaBag :: FilePath -> IO MediaBag
12+getMediaBag fp = do
13+ bs <- BL.readFile fp
14+ P.runIOorExplode $ do
15+ readEPUB def bs
16+ P.getMediaBag
17+
18+testMediaBag :: FilePath -> [(String, String, Int)] -> IO ()
19+testMediaBag fp bag = do
20+ actBag <- mediaDirectory <$> getMediaBag fp
21+ assertBool (show "MediaBag did not match:\nExpected: "
22+ ++ show bag
23+ ++ "\nActual: "
24+ ++ show actBag)
25+ (actBag == bag)
26+
27+featuresBag :: [(String, String, Int)]
28+featuresBag = [("img/check.gif","image/gif",1340)
29+ ,("img/check.jpg","image/jpeg",2661)
30+ ,("img/check.png","image/png",2815)
31+ ,("img/multiscripts_and_greek_alphabet.png","image/png",10060)
32+ ]
33+
34+tests :: [TestTree]
35+tests =
36+ [ testGroup "EPUB Mediabag"
37+ [ testCase "features bag"
38+ (testMediaBag "epub/img.epub" featuresBag)
39+ ]
40+ ]
test/Tests/Readers/HTML.hs.orignew file+54
@@ -0,0 +1,54 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.HTML (tests) where
3+
4+import Data.Text (Text)
5+import Test.Tasty
6+import Tests.Helpers
7+import Text.Pandoc
8+import Text.Pandoc.Arbitrary ()
9+import Text.Pandoc.Builder
10+
11+html :: Text -> Pandoc
12+html = purely $ readHtml def
13+
14+htmlNativeDivs :: Text -> Pandoc
15+htmlNativeDivs = purely $ readHtml def { readerExtensions = enableExtension Ext_native_divs $ readerExtensions def }
16+
17+tests :: [TestTree]
18+tests = [ testGroup "base tag"
19+ [ test html "simple" $
20+ "<head><base href=\"http://www.w3schools.com/images/foo\" ></head><body><img src=\"stickman.gif\" alt=\"Stickman\"></head>" =?>
21+ plain (image "http://www.w3schools.com/images/stickman.gif" "" (text "Stickman"))
22+ , test html "slash at end of base" $
23+ "<head><base href=\"http://www.w3schools.com/images/\" ></head><body><img src=\"stickman.gif\" alt=\"Stickman\"></head>" =?>
24+ plain (image "http://www.w3schools.com/images/stickman.gif" "" (text "Stickman"))
25+ , test html "slash at beginning of href" $
26+ "<head><base href=\"http://www.w3schools.com/images/\" ></head><body><img src=\"/stickman.gif\" alt=\"Stickman\"></head>" =?>
27+ plain (image "http://www.w3schools.com/stickman.gif" "" (text "Stickman"))
28+ , test html "absolute URL" $
29+ "<head><base href=\"http://www.w3schools.com/images/\" ></head><body><img src=\"http://example.com/stickman.gif\" alt=\"Stickman\"></head>" =?>
30+ plain (image "http://example.com/stickman.gif" "" (text "Stickman"))
31+ ]
32+ , testGroup "anchors"
33+ [ test html "anchor without href" $ "<a name=\"anchor\"/>" =?>
34+ plain (spanWith ("anchor",[],[]) mempty)
35+ ]
36+ , testGroup "lang"
37+ [ test html "lang on <html>" $ "<html lang=\"es\">hola" =?>
38+ setMeta "lang" (text "es") (doc (plain (text "hola")))
39+ , test html "xml:lang on <html>" $ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"es\"><head></head><body>hola</body></html>" =?>
40+ setMeta "lang" (text "es") (doc (plain (text "hola")))
41+ ]
42+ , testGroup "main"
43+ [ test htmlNativeDivs "<main> becomes <div role=main>" $ "<main>hello</main>" =?>
44+ doc (divWith ("", [], [("role", "main")]) (plain (text "hello")))
45+ , test htmlNativeDivs "<main role=X> becomes <div role=X>" $ "<main role=foobar>hello</main>" =?>
46+ doc (divWith ("", [], [("role", "foobar")]) (plain (text "hello")))
47+ , test htmlNativeDivs "<main> has attributes preserved" $ "<main id=foo class=bar data-baz=qux>hello</main>" =?>
48+ doc (divWith ("foo", ["bar"], [("role", "main"), ("data-baz", "qux")]) (plain (text "hello")))
49+ , test htmlNativeDivs "<main> closes <p>" $ "<p>hello<main>main content</main>" =?>
50+ doc (para (text "hello") <> divWith ("", [], [("role", "main")]) (plain (text "main content")))
51+ , test htmlNativeDivs "<main> followed by text" $ "<main>main content</main>non-main content" =?>
52+ doc (divWith ("", [], [("role", "main")]) (plain (text "main content")) <> plain (text "non-main content"))
53+ ]
54+ ]
test/Tests/Readers/JATS.hs.orignew file+116
@@ -0,0 +1,116 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.JATS (tests) where
3+
4+import Data.Text (Text)
5+import Test.Tasty
6+import Tests.Helpers
7+import Text.Pandoc
8+import Text.Pandoc.Arbitrary ()
9+import Text.Pandoc.Builder
10+
11+jats :: Text -> Pandoc
12+jats = purely $ readJATS def
13+
14+tests :: [TestTree]
15+tests = [ testGroup "inline code"
16+ [ test jats "basic" $ "<p>\n <monospace>@&amp;</monospace>\n</p>" =?> para (code "@&")
17+ , test jats "lang" $ "<p>\n <code language=\"c\">@&amp;</code>\n</p>" =?> para (codeWith ("", ["c"], []) "@&")
18+ ]
19+ , testGroup "block code"
20+ [ test jats "basic" $ "<preformat>@&amp;</preformat>" =?> codeBlock "@&"
21+ , test jats "lang" $ "<code language=\"c\">@&amp;</code>" =?> codeBlockWith ("", ["c"], []) "@&"
22+ ]
23+ , testGroup "images"
24+ [ test jats "basic" $ "<graphic mimetype=\"image\" mime-subtype=\"\" xlink:href=\"/url\" xlink:title=\"title\" />"
25+ =?> para (image "/url" "title" mempty)
26+ ]
27+ , test jats "bullet list" $
28+ "<list list-type=\"bullet\">\n\
29+ \ <list-item>\n\
30+ \ <p>\n\
31+ \ first\n\
32+ \ </p>\n\
33+ \ </list-item>\n\
34+ \ <list-item>\n\
35+ \ <p>\n\
36+ \ second\n\
37+ \ </p>\n\
38+ \ </list-item>\n\
39+ \ <list-item>\n\
40+ \ <p>\n\
41+ \ third\n\
42+ \ </p>\n\
43+ \ </list-item>\n\
44+ \</list>"
45+ =?> bulletList [ para $ text "first"
46+ , para $ text "second"
47+ , para $ text "third"
48+ ]
49+ , testGroup "definition lists"
50+ [ test jats "with internal link" $
51+ "<def-list>\n\
52+ \ <def-item>\n\
53+ \ <term>\n\
54+ \ <xref alt=\"testing\" rid=\"go\">testing</xref>\n\
55+ \ </term>\n\
56+ \ <def>\n\
57+ \ <p>\n\
58+ \ hi there\n\
59+ \ </p>\n\
60+ \ </def>\n\
61+ \ </def-item>\n\
62+ \</def-list>"
63+ =?> definitionList [(link "#go" "" (str "testing"),
64+ [para (text "hi there")])]
65+ ]
66+ , testGroup "math"
67+ [ test jats "escape |" $
68+ "<p>\n\
69+ \ <inline-formula><alternatives>\n\
70+ \ <tex-math><![CDATA[\\sigma|_{\\{x\\}}]]></tex-math>\n\
71+ \ <mml:math display=\"inline\" xmlns:mml=\"http://www.w3.org/1998/Math/MathML\"><mml:mrow><mml:mi>σ</mml:mi><mml:msub><mml:mo stretchy=\"false\" form=\"prefix\">|</mml:mo><mml:mrow><mml:mo stretchy=\"false\" form=\"prefix\">{</mml:mo><mml:mi>x</mml:mi><mml:mo stretchy=\"false\" form=\"postfix\">}</mml:mo></mml:mrow></mml:msub></mml:mrow></mml:math></alternatives></inline-formula>\n\
72+ \</p>"
73+ =?> para (math "\\sigma|_{\\{x\\}}")
74+ , test jats "tex-math only" $
75+ "<p>\n\
76+ \ <inline-formula><alternatives>\n\
77+ \ <tex-math><![CDATA[\\sigma|_{\\{x\\}}]]></tex-math>\n\
78+ \</p>"
79+ =?> para (math "\\sigma|_{\\{x\\}}")
80+ , test jats "math ml only" $
81+ "<p>\n\
82+ \ <inline-formula><alternatives>\n\
83+ \ <mml:math display=\"inline\" xmlns:mml=\"http://www.w3.org/1998/Math/MathML\"><mml:mrow><mml:mi>σ</mml:mi><mml:msub><mml:mo stretchy=\"false\" form=\"prefix\">|</mml:mo><mml:mrow><mml:mo stretchy=\"false\" form=\"prefix\">{</mml:mo><mml:mi>x</mml:mi><mml:mo stretchy=\"false\" form=\"postfix\">}</mml:mo></mml:mrow></mml:msub></mml:mrow></mml:math></alternatives></inline-formula>\n\
84+ \</p>"
85+ =?> para (math "\\sigma|_{\\{ x\\}}")
86+ ]
87+ , testGroup "headers"
88+-- TODO fix footnotes in headers
89+-- [ test jats "unnumbered header" $
90+-- "<sec>\n\
91+-- \ <title>Header 1<fn>\n\
92+-- \ <p>\n\
93+-- \ note\n\
94+-- \ </p>\n\
95+-- \ </fn></title>\n\
96+-- \</sec>"
97+-- =?> header 1
98+-- (text "Header 1" <> note (plain $ text "note"))
99+ [ test jats "unnumbered sub header" $
100+ "<sec id=\"foo\">\n\
101+ \ <title>Header</title>\n\
102+ \ <sec id=\"foo2\">\n\
103+ \ <title>Sub-Header</title>\n\
104+ \ </sec>\n\
105+ \</sec>"
106+ =?> headerWith ("foo", [], []) 1
107+ (text "Header")
108+ <> headerWith ("foo2", [], []) 2
109+ (text "Sub-Header")
110+ , test jats "containing image" $
111+ "<sec>\n\
112+ \ <title><inline-graphic mimetype=\"image\" mime-subtype=\"jpeg\" xlink:href=\"imgs/foo.jpg\" /></title>\n\
113+ \</sec>"
114+ =?> header 1 (image "imgs/foo.jpg" "" mempty)
115+ ]
116+ ]
test/Tests/Readers/LaTeX.hs.orignew file+341
@@ -0,0 +1,341 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.LaTeX (tests) where
3+
4+import Data.Text (Text)
5+import qualified Data.Text as T
6+import qualified Text.Pandoc.UTF8 as UTF8
7+import Text.Pandoc.Readers.LaTeX (tokenize, untokenize)
8+import Test.Tasty
9+import Test.Tasty.HUnit
10+import Test.Tasty.QuickCheck
11+import Tests.Helpers
12+import Text.Pandoc
13+import Text.Pandoc.Arbitrary ()
14+import Text.Pandoc.Builder
15+
16+latex :: Text -> Pandoc
17+latex = purely $ readLaTeX def{
18+ readerExtensions = getDefaultExtensions "latex" }
19+
20+infix 4 =:
21+(=:) :: ToString c
22+ => String -> (Text, c) -> TestTree
23+(=:) = test latex
24+
25+simpleTable' :: [Alignment] -> [[Blocks]] -> Blocks
26+simpleTable' aligns = table "" (zip aligns (repeat 0.0))
27+ (map (const mempty) aligns)
28+
29+tokUntokRt :: String -> Bool
30+tokUntokRt s = untokenize (tokenize "random" t) == t
31+ where t = T.pack s
32+
33+tests :: [TestTree]
34+tests = [ testGroup "tokenization"
35+ [ testCase "tokenizer round trip on test case" $ do
36+ orig <- T.pack <$> UTF8.readFile "../test/latex-reader.latex"
37+ let new = untokenize $ tokenize "../test/latex-reader.latex"
38+ orig
39+ assertEqual "untokenize . tokenize is identity" orig new
40+ , testProperty "untokenize . tokenize is identity" tokUntokRt
41+ ]
42+
43+ , testGroup "basic"
44+ [ "simple" =:
45+ "word" =?> para "word"
46+ , "space" =:
47+ "some text" =?> para "some text"
48+ , "emphasized" =:
49+ "\\emph{emphasized}" =?> para (emph "emphasized")
50+ ]
51+
52+ , testGroup "headers"
53+ [ "level 1" =:
54+ "\\section{header}" =?> headerWith ("header",[],[]) 1 "header"
55+ , "level 2" =:
56+ "\\subsection{header}" =?> headerWith ("header",[],[]) 2 "header"
57+ , "level 3" =:
58+ "\\subsubsection{header}" =?> headerWith ("header",[],[]) 3 "header"
59+ , "emph" =:
60+ "\\section{text \\emph{emph}}" =?>
61+ headerWith ("text-emph",[],[]) 1 ("text" <> space <> emph "emph")
62+ , "link" =:
63+ "\\section{text \\href{/url}{link}}" =?>
64+ headerWith ("text-link",[],[]) 1 ("text" <> space <> link "/url" "" "link")
65+ ]
66+
67+ , testGroup "math"
68+ [ "escaped $" =:
69+ "$x=\\$4$" =?> para (math "x=\\$4")
70+ ]
71+
72+ , testGroup "space and comments"
73+ [ "blank lines + space at beginning" =:
74+ "\n \n hi" =?> para "hi"
75+ , "blank lines + space + comments" =:
76+ "% my comment\n\n \n % another\n\nhi" =?> para "hi"
77+ , "comment in paragraph" =:
78+ "hi % this is a comment\nthere\n" =?>
79+ para ("hi" <> softbreak <> "there")
80+ ]
81+
82+ , testGroup "code blocks"
83+ [ "identifier" =:
84+ "\\begin{lstlisting}[label=test]\\end{lstlisting}" =?> codeBlockWith ("test", [], [("label","test")]) ""
85+ , "no identifier" =:
86+ "\\begin{lstlisting}\\end{lstlisting}" =?> codeBlock ""
87+ ]
88+
89+ , testGroup "tables"
90+ [ "Single cell table" =:
91+ "\\begin{tabular}{|l|}Test\\\\\\end{tabular}" =?>
92+ simpleTable' [AlignLeft] [[plain "Test"]]
93+ , "Multi cell table" =:
94+ "\\begin{tabular}{|rl|}One & Two\\\\ \\end{tabular}" =?>
95+ simpleTable' [AlignRight,AlignLeft] [[plain "One", plain "Two"]]
96+ , "Multi line table" =:
97+ T.unlines [ "\\begin{tabular}{|c|}"
98+ , "One\\\\"
99+ , "Two\\\\"
100+ , "Three\\\\"
101+ , "\\end{tabular}" ] =?>
102+ simpleTable' [AlignCenter]
103+ [[plain "One"], [plain "Two"], [plain "Three"]]
104+ , "Empty table" =:
105+ "\\begin{tabular}{}\\end{tabular}" =?>
106+ simpleTable' [] []
107+ , "Table with fixed column width" =:
108+ "\\begin{tabular}{|p{5cm}r|}One & Two\\\\ \\end{tabular}" =?>
109+ simpleTable' [AlignLeft,AlignRight] [[plain "One", plain "Two"]]
110+ , "Table with empty column separators" =:
111+ "\\begin{tabular}{@{}r@{}l}One & Two\\\\ \\end{tabular}" =?>
112+ simpleTable' [AlignRight,AlignLeft] [[plain "One", plain "Two"]]
113+ , "Table with custom column separators" =:
114+ T.unlines [ "\\begin{tabular}{@{($\\to$)}r@{\\hspace{2cm}}l}"
115+ , "One&Two\\\\"
116+ , "\\end{tabular}" ] =?>
117+ simpleTable' [AlignRight,AlignLeft] [[plain "One", plain "Two"]]
118+ , "Table with vertical alignment argument" =:
119+ "\\begin{tabular}[t]{r|r}One & Two\\\\ \\end{tabular}" =?>
120+ simpleTable' [AlignRight,AlignRight] [[plain "One", plain "Two"]]
121+ ]
122+
123+ , testGroup "citations"
124+ [ natbibCitations
125+ , biblatexCitations
126+ ]
127+
128+ , testGroup "images"
129+ [ "Basic image" =:
130+ "\\includegraphics{foo.png}" =?>
131+ para (image "foo.png" "" (text "image"))
132+ , "Basic image with blank options" =:
133+ "\\includegraphics[]{foo.png}" =?>
134+ para (image "foo.png" "" (text "image"))
135+ , "Image with both width and height" =:
136+ "\\includegraphics[width=17cm,height=5cm]{foo.png}" =?>
137+ para (imageWith ("", [], [("width", "17cm"), ("height", "5cm")]) "foo.png" "" "image")
138+ , "Image with width and height and a bunch of other options" =:
139+ "\\includegraphics[width=17cm,height=5cm,clip,keepaspectratio]{foo.png}" =?>
140+ para (imageWith ("", [], [("width", "17cm"), ("height", "5cm")]) "foo.png" "" "image")
141+ , "Image with just width" =:
142+ "\\includegraphics[width=17cm]{foo.png}" =?>
143+ para (imageWith ("", [], [("width", "17cm")]) "foo.png" "" "image")
144+ , "Image with just height" =:
145+ "\\includegraphics[height=17cm]{foo.png}" =?>
146+ para (imageWith ("", [], [("height", "17cm")]) "foo.png" "" "image")
147+ , "Image width relative to textsize" =:
148+ "\\includegraphics[width=0.6\\textwidth]{foo.png}" =?>
149+ para (imageWith ("", [], [("width", "60%")]) "foo.png" "" "image")
150+ , "Image with options with spaces" =:
151+ "\\includegraphics[width=12cm, height = 5cm]{foo.png}" =?>
152+ para (imageWith ("", [], [("width", "12cm"), ("height", "5cm")]) "foo.png" "" "image")
153+ ]
154+
155+ , let hex = ['0'..'9']++['a'..'f'] in
156+ testGroup "Character Escapes"
157+ [ "Two-character escapes" =:
158+ mconcat ["^^" <> T.pack [i,j] | i <- hex, j <- hex] =?>
159+ para (str ['\0'..'\255'])
160+ , "One-character escapes" =:
161+ mconcat ["^^" <> T.pack [i] | i <- hex] =?>
162+ para (str $ ['p'..'y']++['!'..'&'])
163+ ]
164+ , testGroup "memoir scene breaks"
165+ [ "plainbreak" =:
166+ "hello\\plainbreak{2}goodbye" =?>
167+ para (str "hello") <> horizontalRule <> para (str "goodbye")
168+ , "plainbreak*" =:
169+ "hello\\plainbreak*{2}goodbye" =?>
170+ para (str "hello") <> horizontalRule <> para (str "goodbye")
171+ , "fancybreak" =:
172+ "hello\\fancybreak{b r e a k}goodbye" =?>
173+ para (str "hello") <> horizontalRule <> para (str "goodbye")
174+ , "fancybreak*" =:
175+ "hello\\fancybreak*{b r e a k}goodbye" =?>
176+ para (str "hello") <> horizontalRule <> para (str "goodbye")
177+ , "plainfancybreak" =:
178+ "hello\\plainfancybreak{4}{2}{b r e a k}goodbye" =?>
179+ para (str "hello") <> horizontalRule <> para (str "goodbye")
180+ , "plainfancybreak*" =:
181+ "hello\\plainfancybreak*{4}{2}{b r e a k}goodbye" =?>
182+ para (str "hello") <> horizontalRule <> para (str "goodbye")
183+ , "pfbreak" =:
184+ "hello\\pfbreak{}goodbye" =?>
185+ para (str "hello") <> horizontalRule <> para (str "goodbye")
186+ , "pfbreak*" =:
187+ "hello\\pfbreak*{}goodbye" =?>
188+ para (str "hello") <> horizontalRule <> para (str "goodbye")
189+ ]
190+ , testGroup "biblatex roman numerals"
191+ [ "upper" =:
192+ "number \\RN{12}" =?>
193+ para (str "number" <> space <> str "XII")
194+ , "lower" =:
195+ "number \\Rn{29}" =?>
196+ para (str "number" <> space <> str "xxix")
197+ , "leading zero" =:
198+ "\\Rn{014}" =?>
199+ para (str "xiv")
200+ , "surrounding spaces" =:
201+ "number \\Rn{ 41 }" =?>
202+ para (str "number" <> space <> str "xli")
203+ , "zero" =:
204+ "\\RN{0}" =?>
205+ para (str "")
206+ , "space then unbraced argument" =:
207+ "\\RN 7 ok" =?>
208+ para (str "VII" <> space <> str "ok")
209+ , "space before braced argument" =:
210+ "\\Rn {13}ok" =?>
211+ para (str "xiiiok")
212+ ]
213+ , testGroup "polyglossia language spans"
214+ [ "french" =:
215+ "hello \\textfrench{bonjour}" =?>
216+ para (str "hello" <> space <> spanWith ("", [], [("lang", "fr")]) (str "bonjour"))
217+ , "nested" =:
218+ "\\textfrench{quelle c'est \\textlatin{primus}?}" =?>
219+ para (spanWith ("", [], [("lang", "fr")]) $
220+ str "quelle" <> space <> str "c\8217est" <> space <>
221+ spanWith ("", [], [("lang", "la")]) (str "primus") <> str "?")
222+ , "with formatting" =:
223+ "\\textgerman{wie \\emph{spaet} ist es?}" =?>
224+ para (spanWith ("", [], [("lang", "de")]) $
225+ str "wie" <> space <> emph (str "spaet") <> space <> str "ist" <> space <> str "es?")
226+ , "language options" =:
227+ "\\textgerman[variant=swiss]{hoechdeutsche}" =?>
228+ para (spanWith ("", [], [("lang", "de-CH")]) $ str "hoechdeutsche")
229+ , "unknown option fallback" =:
230+ "\\textgerman[variant=moon]{ueberhoechdeutsche}" =?>
231+ para (spanWith ("", [], [("lang", "de")]) $ str "ueberhoechdeutsche")
232+ ]
233+ ]
234+
235+baseCitation :: Citation
236+baseCitation = Citation{ citationId = "item1"
237+ , citationPrefix = []
238+ , citationSuffix = []
239+ , citationMode = AuthorInText
240+ , citationNoteNum = 0
241+ , citationHash = 0
242+ }
243+
244+rt :: String -> Inlines
245+rt = rawInline "latex"
246+
247+natbibCitations :: TestTree
248+natbibCitations = testGroup "natbib"
249+ [ "citet" =: "\\citet{item1}"
250+ =?> para (cite [baseCitation] (rt "\\citet{item1}"))
251+ , "suffix" =: "\\citet[p.~30]{item1}"
252+ =?> para
253+ (cite [baseCitation{ citationSuffix = toList $ text "p.\160\&30" }] (rt "\\citet[p.~30]{item1}"))
254+ , "suffix long" =: "\\citet[p.~30, with suffix]{item1}"
255+ =?> para (cite [baseCitation{ citationSuffix =
256+ toList $ text "p.\160\&30, with suffix" }] (rt "\\citet[p.~30, with suffix]{item1}"))
257+ , "multiple" =: "\\citeauthor{item1} \\citetext{\\citeyear{item1}; \\citeyear[p.~30]{item2}; \\citealp[see also][]{item3}}"
258+ =?> para (cite [baseCitation{ citationMode = AuthorInText }
259+ ,baseCitation{ citationMode = SuppressAuthor
260+ , citationSuffix = [Str "p.\160\&30"]
261+ , citationId = "item2" }
262+ ,baseCitation{ citationId = "item3"
263+ , citationPrefix = [Str "see",Space,Str "also"]
264+ , citationMode = NormalCitation }
265+ ] (rt "\\citetext{\\citeyear{item1}; \\citeyear[p.~30]{item2}; \\citealp[see also][]{item3}}"))
266+ , "group" =: "\\citetext{\\citealp[see][p.~34--35]{item1}; \\citealp[also][chap. 3]{item3}}"
267+ =?> para (cite [baseCitation{ citationMode = NormalCitation
268+ , citationPrefix = [Str "see"]
269+ , citationSuffix = [Str "p.\160\&34\8211\&35"] }
270+ ,baseCitation{ citationMode = NormalCitation
271+ , citationId = "item3"
272+ , citationPrefix = [Str "also"]
273+ , citationSuffix = [Str "chap.",Space,Str "3"] }
274+ ] (rt "\\citetext{\\citealp[see][p.~34--35]{item1}; \\citealp[also][chap. 3]{item3}}"))
275+ , "suffix and locator" =: "\\citep[pp.~33, 35--37, and nowhere else]{item1}"
276+ =?> para (cite [baseCitation{ citationMode = NormalCitation
277+ , citationSuffix = [Str "pp.\160\&33,",Space,Str "35\8211\&37,",Space,Str "and",Space,Str "nowhere",Space, Str "else"] }] (rt "\\citep[pp.~33, 35--37, and nowhere else]{item1}"))
278+ , "suffix only" =: "\\citep[and nowhere else]{item1}"
279+ =?> para (cite [baseCitation{ citationMode = NormalCitation
280+ , citationSuffix = toList $ text "and nowhere else" }] (rt "\\citep[and nowhere else]{item1}"))
281+ , "no author" =: "\\citeyearpar{item1}, and now Doe with a locator \\citeyearpar[p.~44]{item2}"
282+ =?> para (cite [baseCitation{ citationMode = SuppressAuthor }] (rt "\\citeyearpar{item1}") <>
283+ text ", and now Doe with a locator " <>
284+ cite [baseCitation{ citationMode = SuppressAuthor
285+ , citationSuffix = [Str "p.\160\&44"]
286+ , citationId = "item2" }] (rt "\\citeyearpar[p.~44]{item2}"))
287+ , "markup" =: "\\citep[\\emph{see}][p. \\textbf{32}]{item1}"
288+ =?> para (cite [baseCitation{ citationMode = NormalCitation
289+ , citationPrefix = [Emph [Str "see"]]
290+ , citationSuffix = [Str "p.",Space,
291+ Strong [Str "32"]] }] (rt "\\citep[\\emph{see}][p. \\textbf{32}]{item1}"))
292+ ]
293+
294+biblatexCitations :: TestTree
295+biblatexCitations = testGroup "biblatex"
296+ [ "textcite" =: "\\textcite{item1}"
297+ =?> para (cite [baseCitation] (rt "\\textcite{item1}"))
298+ , "suffix" =: "\\textcite[p.~30]{item1}"
299+ =?> para
300+ (cite [baseCitation{ citationSuffix = toList $ text "p.\160\&30" }] (rt "\\textcite[p.~30]{item1}"))
301+ , "suffix long" =: "\\textcite[p.~30, with suffix]{item1}"
302+ =?> para (cite [baseCitation{ citationSuffix =
303+ toList $ text "p.\160\&30, with suffix" }] (rt "\\textcite[p.~30, with suffix]{item1}"))
304+ , "multiple" =: "\\textcites{item1}[p.~30]{item2}[see also][]{item3}"
305+ =?> para (cite [baseCitation{ citationMode = AuthorInText }
306+ ,baseCitation{ citationMode = NormalCitation
307+ , citationSuffix = [Str "p.\160\&30"]
308+ , citationId = "item2" }
309+ ,baseCitation{ citationId = "item3"
310+ , citationPrefix = [Str "see",Space,Str "also"]
311+ , citationMode = NormalCitation }
312+ ] (rt "\\textcites{item1}[p.~30]{item2}[see also][]{item3}"))
313+ , "group" =: "\\autocites[see][p.~34--35]{item1}[also][chap. 3]{item3}"
314+ =?> para (cite [baseCitation{ citationMode = NormalCitation
315+ , citationPrefix = [Str "see"]
316+ , citationSuffix = [Str "p.\160\&34\8211\&35"] }
317+ ,baseCitation{ citationMode = NormalCitation
318+ , citationId = "item3"
319+ , citationPrefix = [Str "also"]
320+ , citationSuffix = [Str "chap.",Space,Str "3"] }
321+ ] (rt "\\autocites[see][p.~34--35]{item1}[also][chap. 3]{item3}"))
322+ , "suffix and locator" =: "\\autocite[pp.~33, 35--37, and nowhere else]{item1}"
323+ =?> para (cite [baseCitation{ citationMode = NormalCitation
324+ , citationSuffix = [Str "pp.\160\&33,",Space,Str "35\8211\&37,",Space,Str "and",Space,Str "nowhere",Space, Str "else"] }] (rt "\\autocite[pp.~33, 35--37, and nowhere else]{item1}"))
325+ , "suffix only" =: "\\autocite[and nowhere else]{item1}"
326+ =?> para (cite [baseCitation{ citationMode = NormalCitation
327+ , citationSuffix = toList $ text "and nowhere else" }] (rt "\\autocite[and nowhere else]{item1}"))
328+ , "no author" =: "\\autocite*{item1}, and now Doe with a locator \\autocite*[p.~44]{item2}"
329+ =?> para (cite [baseCitation{ citationMode = SuppressAuthor }] (rt "\\autocite*{item1}") <>
330+ text ", and now Doe with a locator " <>
331+ cite [baseCitation{ citationMode = SuppressAuthor
332+ , citationSuffix = [Str "p.\160\&44"]
333+ , citationId = "item2" }] (rt "\\autocite*[p.~44]{item2}"))
334+ , "markup" =: "\\autocite[\\emph{see}][p. \\textbf{32}]{item1}"
335+ =?> para (cite [baseCitation{ citationMode = NormalCitation
336+ , citationPrefix = [Emph [Str "see"]]
337+ , citationSuffix = [Str "p.",Space,
338+ Strong [Str "32"]] }] (rt "\\autocite[\\emph{see}][p. \\textbf{32}]{item1}"))
339+ , "parencite" =: "\\parencite{item1}"
340+ =?> para (cite [baseCitation{ citationMode = NormalCitation }] (rt "\\parencite{item1}"))
341+ ]
test/Tests/Readers/Markdown.hs.orignew file+462
@@ -0,0 +1,462 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Markdown (tests) where
3+
4+import Data.Text (Text, unpack)
5+import qualified Data.Text as T
6+import Test.Tasty
7+import Tests.Helpers
8+import Text.Pandoc
9+import Text.Pandoc.Arbitrary ()
10+import Text.Pandoc.Builder
11+
12+markdown :: Text -> Pandoc
13+markdown = purely $ readMarkdown def { readerExtensions =
14+ disableExtension Ext_smart pandocExtensions }
15+
16+markdownSmart :: Text -> Pandoc
17+markdownSmart = purely $ readMarkdown def { readerExtensions =
18+ enableExtension Ext_smart pandocExtensions }
19+
20+markdownCDL :: Text -> Pandoc
21+markdownCDL = purely $ readMarkdown def { readerExtensions = enableExtension
22+ Ext_compact_definition_lists pandocExtensions }
23+
24+markdownGH :: Text -> Pandoc
25+markdownGH = purely $ readMarkdown def {
26+ readerExtensions = githubMarkdownExtensions }
27+
28+infix 4 =:
29+(=:) :: ToString c
30+ => String -> (Text, c) -> TestTree
31+(=:) = test markdown
32+
33+testBareLink :: (Text, Inlines) -> TestTree
34+testBareLink (inp, ils) =
35+ test (purely $ readMarkdown def{ readerExtensions =
36+ extensionsFromList [Ext_autolink_bare_uris, Ext_raw_html] })
37+ (unpack inp) (inp, doc $ para ils)
38+
39+autolink :: String -> Inlines
40+autolink = autolinkWith nullAttr
41+
42+autolinkWith :: Attr -> String -> Inlines
43+autolinkWith attr s = linkWith attr s "" (str s)
44+
45+bareLinkTests :: [(Text, Inlines)]
46+bareLinkTests =
47+ [ ("http://google.com is a search engine.",
48+ autolink "http://google.com" <> " is a search engine.")
49+ , ("<a href=\"http://foo.bar.baz\">http://foo.bar.baz</a>",
50+ rawInline "html" "<a href=\"http://foo.bar.baz\">" <>
51+ "http://foo.bar.baz" <> rawInline "html" "</a>")
52+ , ("Try this query: http://google.com?search=fish&time=hour.",
53+ "Try this query: " <> autolink "http://google.com?search=fish&time=hour" <> ".")
54+ , ("HTTPS://GOOGLE.COM,",
55+ autolink "HTTPS://GOOGLE.COM" <> ",")
56+ , ("http://el.wikipedia.org/wiki/Τεχνολογία,",
57+ autolink "http://el.wikipedia.org/wiki/Τεχνολογία" <> ",")
58+ , ("doi:10.1000/182,",
59+ autolink "doi:10.1000/182" <> ",")
60+ , ("git://github.com/foo/bar.git,",
61+ autolink "git://github.com/foo/bar.git" <> ",")
62+ , ("file:///Users/joe/joe.txt, and",
63+ autolink "file:///Users/joe/joe.txt" <> ", and")
64+ , ("mailto:someone@somedomain.com.",
65+ autolink "mailto:someone@somedomain.com" <> ".")
66+ , ("Use http: this is not a link!",
67+ "Use http: this is not a link!")
68+ , ("(http://google.com).",
69+ "(" <> autolink "http://google.com" <> ").")
70+ , ("http://en.wikipedia.org/wiki/Sprite_(computer_graphics)",
71+ autolink "http://en.wikipedia.org/wiki/Sprite_(computer_graphics)")
72+ , ("http://en.wikipedia.org/wiki/Sprite_[computer_graphics]",
73+ link "http://en.wikipedia.org/wiki/Sprite_%5Bcomputer_graphics%5D" ""
74+ (str "http://en.wikipedia.org/wiki/Sprite_[computer_graphics]"))
75+ , ("http://en.wikipedia.org/wiki/Sprite_{computer_graphics}",
76+ link "http://en.wikipedia.org/wiki/Sprite_%7Bcomputer_graphics%7D" ""
77+ (str "http://en.wikipedia.org/wiki/Sprite_{computer_graphics}"))
78+ , ("http://example.com/Notification_Center-GitHub-20101108-140050.jpg",
79+ autolink "http://example.com/Notification_Center-GitHub-20101108-140050.jpg")
80+ , ("https://github.com/github/hubot/blob/master/scripts/cream.js#L20-20",
81+ autolink "https://github.com/github/hubot/blob/master/scripts/cream.js#L20-20")
82+ , ("http://www.rubyonrails.com",
83+ autolink "http://www.rubyonrails.com")
84+ , ("http://www.rubyonrails.com:80",
85+ autolink "http://www.rubyonrails.com:80")
86+ , ("http://www.rubyonrails.com/~minam",
87+ autolink "http://www.rubyonrails.com/~minam")
88+ , ("https://www.rubyonrails.com/~minam",
89+ autolink "https://www.rubyonrails.com/~minam")
90+ , ("http://www.rubyonrails.com/~minam/url%20with%20spaces",
91+ autolink "http://www.rubyonrails.com/~minam/url%20with%20spaces")
92+ , ("http://www.rubyonrails.com/foo.cgi?something=here",
93+ autolink "http://www.rubyonrails.com/foo.cgi?something=here")
94+ , ("http://www.rubyonrails.com/foo.cgi?something=here&and=here",
95+ autolink "http://www.rubyonrails.com/foo.cgi?something=here&and=here")
96+ , ("http://www.rubyonrails.com/contact;new",
97+ autolink "http://www.rubyonrails.com/contact;new")
98+ , ("http://www.rubyonrails.com/contact;new%20with%20spaces",
99+ autolink "http://www.rubyonrails.com/contact;new%20with%20spaces")
100+ , ("http://www.rubyonrails.com/contact;new?with=query&string=params",
101+ autolink "http://www.rubyonrails.com/contact;new?with=query&string=params")
102+ , ("http://www.rubyonrails.com/~minam/contact;new?with=query&string=params",
103+ autolink "http://www.rubyonrails.com/~minam/contact;new?with=query&string=params")
104+ , ("http://en.wikipedia.org/wiki/Wikipedia:Today%27s_featured_picture_%28animation%29/January_20%2C_2007",
105+ autolink "http://en.wikipedia.org/wiki/Wikipedia:Today%27s_featured_picture_%28animation%29/January_20%2C_2007")
106+ , ("http://www.mail-archive.com/rails@lists.rubyonrails.org/",
107+ autolink "http://www.mail-archive.com/rails@lists.rubyonrails.org/")
108+ , ("http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1",
109+ autolink "http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1")
110+ , ("http://en.wikipedia.org/wiki/Texas_hold%27em",
111+ autolink "http://en.wikipedia.org/wiki/Texas_hold%27em")
112+ , ("https://www.google.com/doku.php?id=gps:resource:scs:start",
113+ autolink "https://www.google.com/doku.php?id=gps:resource:scs:start")
114+ , ("http://www.rubyonrails.com",
115+ autolink "http://www.rubyonrails.com")
116+ , ("http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281",
117+ autolink "http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281")
118+ , ("http://foo.example.com/controller/action?parm=value&p2=v2#anchor123",
119+ autolink "http://foo.example.com/controller/action?parm=value&p2=v2#anchor123")
120+ , ("http://foo.example.com:3000/controller/action",
121+ autolink "http://foo.example.com:3000/controller/action")
122+ , ("http://foo.example.com:3000/controller/action+pack",
123+ autolink "http://foo.example.com:3000/controller/action+pack")
124+ , ("http://business.timesonline.co.uk/article/0,,9065-2473189,00.html",
125+ autolink "http://business.timesonline.co.uk/article/0,,9065-2473189,00.html")
126+ , ("http://www.mail-archive.com/ruby-talk@ruby-lang.org/",
127+ autolink "http://www.mail-archive.com/ruby-talk@ruby-lang.org/")
128+ , ("https://example.org/?anchor=lala-",
129+ autolink "https://example.org/?anchor=lala-")
130+ , ("https://example.org/?anchor=-lala",
131+ autolink "https://example.org/?anchor=-lala")
132+ ]
133+
134+{-
135+p_markdown_round_trip :: Block -> Bool
136+p_markdown_round_trip b = matches d' d''
137+ where d' = normalize $ Pandoc (Meta [] [] []) [b]
138+ d'' = normalize
139+ $ readMarkdown def { readerSmart = True }
140+ $ writeMarkdown def d'
141+ matches (Pandoc _ [Plain []]) (Pandoc _ []) = True
142+ matches (Pandoc _ [Para []]) (Pandoc _ []) = True
143+ matches (Pandoc _ [Plain xs]) (Pandoc _ [Para xs']) = xs == xs'
144+ matches x y = x == y
145+-}
146+
147+tests :: [TestTree]
148+tests = [ testGroup "inline code"
149+ [ "with attribute" =:
150+ "`document.write(\"Hello\");`{.javascript}"
151+ =?> para
152+ (codeWith ("",["javascript"],[]) "document.write(\"Hello\");")
153+ , "with attribute space" =:
154+ "`*` {.haskell .special x=\"7\"}"
155+ =?> para (code "*" <> space <> str "{.haskell" <> space <>
156+ str ".special" <> space <> str "x=\"7\"}")
157+ ]
158+ , testGroup "emph and strong"
159+ [ "two strongs in emph" =:
160+ "***a**b **c**d*" =?> para (emph (strong (str "a") <> str "b" <> space
161+ <> strong (str "c") <> str "d"))
162+ , "emph and strong emph alternating" =:
163+ "*xxx* ***xxx*** xxx\n*xxx* ***xxx*** xxx"
164+ =?> para (emph "xxx" <> space <> strong (emph "xxx") <>
165+ space <> "xxx" <> softbreak <>
166+ emph "xxx" <> space <> strong (emph "xxx") <>
167+ space <> "xxx")
168+ , "emph with spaced strong" =:
169+ "*x **xx** x*"
170+ =?> para (emph ("x" <> space <> strong "xx" <> space <> "x"))
171+ , "intraword underscore with opening underscore (#1121)" =:
172+ "_foot_ball_" =?> para (emph (text "foot_ball"))
173+ ]
174+ , testGroup "raw LaTeX"
175+ [ "in URL" =:
176+ "\\begin\n" =?> para (text "\\begin")
177+ ]
178+ , testGroup "raw HTML"
179+ [ "nesting (issue #1330)" =:
180+ "<del>test</del>" =?>
181+ rawBlock "html" "<del>" <> plain (str "test") <>
182+ rawBlock "html" "</del>"
183+ , "invalid tag (issue #1820" =:
184+ "</ div></.div>" =?>
185+ para (text "</ div></.div>")
186+ , "technically invalid comment" =:
187+ "<!-- pandoc --help -->" =?>
188+ rawBlock "html" "<!-- pandoc --help -->"
189+ , test markdownGH "issue 2469" $
190+ "<\n\na>" =?>
191+ para (text "<") <> para (text "a>")
192+ ]
193+ , testGroup "raw email addresses"
194+ [ test markdownGH "issue 2940" $
195+ "**@user**" =?>
196+ para (strong (text "@user"))
197+ ]
198+ , testGroup "emoji"
199+ [ test markdownGH "emoji symbols" $
200+ ":smile: and :+1:" =?> para (text "😄 and 👍")
201+ ]
202+ , "unbalanced brackets" =:
203+ "[[[[[[[[[[[[hi" =?> para (text "[[[[[[[[[[[[hi")
204+ , testGroup "backslash escapes"
205+ [ "in URL" =:
206+ "[hi](/there\\))"
207+ =?> para (link "/there)" "" "hi")
208+ , "in title" =:
209+ "[hi](/there \"a\\\"a\")"
210+ =?> para (link "/there" "a\"a" "hi")
211+ , "in reference link title" =:
212+ "[hi]\n\n[hi]: /there (a\\)a)"
213+ =?> para (link "/there" "a)a" "hi")
214+ , "in reference link URL" =:
215+ "[hi]\n\n[hi]: /there\\.0"
216+ =?> para (link "/there.0" "" "hi")
217+ ]
218+ , testGroup "bare URIs"
219+ (map testBareLink bareLinkTests)
220+ , testGroup "autolinks"
221+ [ "with unicode dash following" =:
222+ "<http://foo.bar>\8212" =?> para (autolink "http://foo.bar" <>
223+ str "\8212")
224+ , "a partial URL (#2277)" =:
225+ "<www.boe.es/buscar/act.php?id=BOE-A-1996-8930#a66>" =?>
226+ para (text "<www.boe.es/buscar/act.php?id=BOE-A-1996-8930#a66>")
227+ , "with some attributes" =:
228+ "<http://foo.bar>{#i .j .z k=v}" =?>
229+ para (autolinkWith ("i", ["j", "z"], [("k", "v")]) "http://foo.bar")
230+ , "with some attributes and spaces" =:
231+ "<http://foo.bar> {#i .j .z k=v}" =?>
232+ para (autolink "http://foo.bar" <> space <> text "{#i .j .z k=v}")
233+ ]
234+ , testGroup "links"
235+ [ "no autolink inside link" =:
236+ "[<https://example.org>](url)" =?>
237+ para (link "url" "" (text "<https://example.org>"))
238+ , "no inline link inside link" =:
239+ "[[a](url2)](url)" =?>
240+ para (link "url" "" (text "[a](url2)"))
241+ , "no bare URI inside link" =:
242+ "[https://example.org(](url)" =?>
243+ para (link "url" "" (text "https://example.org("))
244+ ]
245+ , testGroup "Headers"
246+ [ "blank line before header" =:
247+ "\n# Header\n"
248+ =?> headerWith ("header",[],[]) 1 "Header"
249+ , "bracketed text (#2062)" =:
250+ "# [hi]\n"
251+ =?> headerWith ("hi",[],[]) 1 "[hi]"
252+ , "ATX header without trailing #s" =:
253+ "# Foo bar\n\n" =?>
254+ headerWith ("foo-bar",[],[]) 1 "Foo bar"
255+ , "ATX header without trailing #s" =:
256+ "# Foo bar with # #" =?>
257+ headerWith ("foo-bar-with",[],[]) 1 "Foo bar with #"
258+ , "setext header" =:
259+ "Foo bar\n=\n\n Foo bar 2 \n=" =?>
260+ headerWith ("foo-bar",[],[]) 1 "Foo bar"
261+ <> headerWith ("foo-bar-2",[],[]) 1 "Foo bar 2"
262+ ]
263+ , testGroup "Implicit header references"
264+ [ "ATX header without trailing #s" =:
265+ "# Header\n[header]\n\n[header ]\n\n[ header]" =?>
266+ headerWith ("header",[],[]) 1 "Header"
267+ <> para (link "#header" "" (text "header"))
268+ <> para (link "#header" "" (text "header"))
269+ <> para (link "#header" "" (text "header"))
270+ , "ATX header with trailing #s" =:
271+ "# Foo bar #\n[foo bar]\n\n[foo bar ]\n\n[ foo bar]" =?>
272+ headerWith ("foo-bar",[],[]) 1 "Foo bar"
273+ <> para (link "#foo-bar" "" (text "foo bar"))
274+ <> para (link "#foo-bar" "" (text "foo bar"))
275+ <> para (link "#foo-bar" "" (text "foo bar"))
276+ , "setext header" =:
277+ " Header \n=\n\n[header]\n\n[header ]\n\n[ header]" =?>
278+ headerWith ("header",[],[]) 1 "Header"
279+ <> para (link "#header" "" (text "header"))
280+ <> para (link "#header" "" (text "header"))
281+ <> para (link "#header" "" (text "header"))
282+ ]
283+ , testGroup "smart punctuation"
284+ [ test markdownSmart "quote before ellipses"
285+ ("'...hi'"
286+ =?> para (singleQuoted "…hi"))
287+ , test markdownSmart "apostrophe before emph"
288+ ("D'oh! A l'*aide*!"
289+ =?> para ("D’oh! A l’" <> emph "aide" <> "!"))
290+ , test markdownSmart "apostrophe in French"
291+ ("À l'arrivée de la guerre, le thème de l'«impossibilité du socialisme»"
292+ =?> para "À l’arrivée de la guerre, le thème de l’«impossibilité du socialisme»")
293+ , test markdownSmart "apostrophe after math" $ -- issue #1909
294+ "The value of the $x$'s and the systems' condition." =?>
295+ para (text "The value of the " <> math "x" <> text "\8217s and the systems\8217 condition.")
296+ ]
297+ , testGroup "footnotes"
298+ [ "indent followed by newline and flush-left text" =:
299+ "[^1]\n\n[^1]: my note\n\n \nnot in note\n"
300+ =?> para (note (para "my note")) <> para "not in note"
301+ , "indent followed by newline and indented text" =:
302+ "[^1]\n\n[^1]: my note\n \n in note\n"
303+ =?> para (note (para "my note" <> para "in note"))
304+ , "recursive note" =:
305+ "[^1]\n\n[^1]: See [^1]\n"
306+ =?> para (note (para "See [^1]"))
307+ ]
308+ , testGroup "lhs"
309+ [ test (purely $ readMarkdown def{ readerExtensions = enableExtension
310+ Ext_literate_haskell pandocExtensions })
311+ "inverse bird tracks and html" $
312+ "> a\n\n< b\n\n<div>\n"
313+ =?> codeBlockWith ("",["sourceCode","literate","haskell"],[]) "a"
314+ <>
315+ codeBlockWith ("",["sourceCode","haskell"],[]) "b"
316+ <>
317+ rawBlock "html" "<div>\n\n"
318+ ]
319+-- the round-trip properties frequently fail
320+-- , testGroup "round trip"
321+-- [ property "p_markdown_round_trip" p_markdown_round_trip
322+-- ]
323+ , testGroup "definition lists"
324+ [ "no blank space" =:
325+ "foo1\n : bar\n\nfoo2\n : bar2\n : bar3\n" =?>
326+ definitionList [ (text "foo1", [plain (text "bar")])
327+ , (text "foo2", [plain (text "bar2"),
328+ plain (text "bar3")])
329+ ]
330+ , "blank space before first def" =:
331+ "foo1\n\n : bar\n\nfoo2\n\n : bar2\n : bar3\n" =?>
332+ definitionList [ (text "foo1", [para (text "bar")])
333+ , (text "foo2", [para (text "bar2"),
334+ plain (text "bar3")])
335+ ]
336+ , "blank space before second def" =:
337+ "foo1\n : bar\n\nfoo2\n : bar2\n\n : bar3\n" =?>
338+ definitionList [ (text "foo1", [plain (text "bar")])
339+ , (text "foo2", [plain (text "bar2"),
340+ para (text "bar3")])
341+ ]
342+ , "laziness" =:
343+ "foo1\n : bar\nbaz\n : bar2\n" =?>
344+ definitionList [ (text "foo1", [plain (text "bar" <>
345+ softbreak <> text "baz"),
346+ plain (text "bar2")])
347+ ]
348+ , "no blank space before first of two paragraphs" =:
349+ "foo1\n : bar\n\n baz\n" =?>
350+ definitionList [ (text "foo1", [para (text "bar") <>
351+ para (text "baz")])
352+ ]
353+ , "first line not indented" =:
354+ "foo\n: bar\n" =?>
355+ definitionList [ (text "foo", [plain (text "bar")]) ]
356+ , "list in definition" =:
357+ "foo\n: - bar\n" =?>
358+ definitionList [ (text "foo", [bulletList [plain (text "bar")]]) ]
359+ , "in div" =:
360+ "<div>foo\n: - bar\n</div>" =?>
361+ divWith nullAttr (definitionList
362+ [ (text "foo", [bulletList [plain (text "bar")]]) ])
363+ ]
364+ , testGroup "+compact_definition_lists"
365+ [ test markdownCDL "basic compact list" $
366+ "foo1\n: bar\n baz\nfoo2\n: bar2\n" =?>
367+ definitionList [ (text "foo1", [plain (text "bar" <> softbreak <>
368+ text "baz")])
369+ , (text "foo2", [plain (text "bar2")])
370+ ]
371+ ]
372+ , testGroup "lists"
373+ [ "issue #1154" =:
374+ " - <div>\n first div breaks\n </div>\n\n <button>if this button exists</button>\n\n <div>\n with this div too.\n </div>\n"
375+ =?> bulletList [divWith nullAttr (para $ text "first div breaks") <>
376+ rawBlock "html" "<button>" <>
377+ plain (text "if this button exists") <>
378+ rawBlock "html" "</button>" <>
379+ divWith nullAttr (para $ text "with this div too.")]
380+ , test markdownGH "issue #1636" $
381+ T.unlines [ "* a"
382+ , "* b"
383+ , "* c"
384+ , " * d" ]
385+ =?>
386+ bulletList [ plain "a"
387+ , plain "b"
388+ , plain "c" <> bulletList [plain "d"] ]
389+ ]
390+ , testGroup "entities"
391+ [ "character references" =:
392+ "&lang; &ouml;" =?> para (text "\10216 ö")
393+ , "numeric" =:
394+ "&#44;&#x44;&#X44;" =?> para (text ",DD")
395+ , "in link title" =:
396+ "[link](/url \"title &lang; &ouml; &#44;\")" =?>
397+ para (link "/url" "title \10216 ö ," (text "link"))
398+ ]
399+ , testGroup "citations"
400+ [ "simple" =:
401+ "@item1" =?> para (cite [
402+ Citation{ citationId = "item1"
403+ , citationPrefix = []
404+ , citationSuffix = []
405+ , citationMode = AuthorInText
406+ , citationNoteNum = 0
407+ , citationHash = 0
408+ }
409+ ] "@item1")
410+ , "key starts with digit" =:
411+ "@1657:huyghens" =?> para (cite [
412+ Citation{ citationId = "1657:huyghens"
413+ , citationPrefix = []
414+ , citationSuffix = []
415+ , citationMode = AuthorInText
416+ , citationNoteNum = 0
417+ , citationHash = 0
418+ }
419+ ] "@1657:huyghens")
420+ ]
421+ , let citation = cite [Citation "cita" [] [] AuthorInText 0 0] (str "@cita")
422+ in testGroup "footnote/link following citation" -- issue #2083
423+ [ "footnote" =:
424+ T.unlines [ "@cita[^note]"
425+ , ""
426+ , "[^note]: note" ] =?>
427+ para (
428+ citation <> note (para $ str "note")
429+ )
430+ , "normal link" =:
431+ "@cita [link](http://www.com)" =?>
432+ para (
433+ citation <> space <> link "http://www.com" "" (str "link")
434+ )
435+ , "reference link" =:
436+ T.unlines [ "@cita [link][link]"
437+ , ""
438+ , "[link]: http://www.com" ] =?>
439+ para (
440+ citation <> space <> link "http://www.com" "" (str "link")
441+ )
442+ , "short reference link" =:
443+ T.unlines [ "@cita [link]"
444+ , ""
445+ , "[link]: http://www.com" ] =?>
446+ para (
447+ citation <> space <> link "http://www.com" "" (str "link")
448+ )
449+ , "implicit header link" =:
450+ T.unlines [ "# Header"
451+ , "@cita [Header]" ] =?>
452+ headerWith ("header",[],[]) 1 (str "Header") <> para (
453+ citation <> space <> link "#header" "" (str "Header")
454+ )
455+ , "regular citation" =:
456+ "@cita [foo]" =?>
457+ para (
458+ cite [Citation "cita" [] [Str "foo"] AuthorInText 0 0]
459+ (str "@cita" <> space <> str "[foo]")
460+ )
461+ ]
462+ ]
test/Tests/Readers/Muse.hs.orignew file+1262
@@ -0,0 +1,1262 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Muse (tests) where
3+
4+import Data.List (intersperse)
5+import Data.Text (Text)
6+import qualified Data.Text as T
7+import Test.Tasty
8+import Test.Tasty.QuickCheck
9+import Tests.Helpers
10+import Text.Pandoc
11+import Text.Pandoc.Arbitrary ()
12+import Text.Pandoc.Builder
13+import Text.Pandoc.Shared (underlineSpan)
14+import Text.Pandoc.Walk (walk)
15+
16+amuse :: Text -> Pandoc
17+amuse = purely $ readMuse def { readerExtensions = extensionsFromList [Ext_amuse]}
18+
19+emacsMuse :: Text -> Pandoc
20+emacsMuse = purely $ readMuse def { readerExtensions = emptyExtensions }
21+
22+infix 4 =:
23+(=:) :: ToString c
24+ => String -> (Text, c) -> TestTree
25+(=:) = test amuse
26+
27+spcSep :: [Inlines] -> Inlines
28+spcSep = mconcat . intersperse space
29+
30+-- Tables don't round-trip yet
31+--
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
36+makeRoundTrip x = x
37+
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]
43+ d' = rewrite d
44+ d'' = rewrite d'
45+ rewrite = amuse . T.pack . (++ "\n") . T.unpack .
46+ purely (writeMuse def { writerExtensions = extensionsFromList [Ext_amuse]
47+ , writerWrapText = WrapPreserve
48+ })
49+
50+tests :: [TestTree]
51+tests =
52+ [ testGroup "Inlines"
53+ [ "Plain String" =:
54+ "Hello, World" =?>
55+ para "Hello, World"
56+
57+ , "Muse is not XML" =: "&lt;" =?> para "&lt;"
58+
59+ , "Emphasis" =:
60+ "*Foo bar*" =?>
61+ para (emph . spcSep $ ["Foo", "bar"])
62+
63+ , "Comma after closing *" =:
64+ "Foo *bar*, baz" =?>
65+ para ("Foo " <> emph "bar" <> ", baz")
66+
67+ , "Letter after closing *" =:
68+ "Foo *bar*x baz" =?>
69+ para "Foo *bar*x baz"
70+
71+ , "Letter before opening *" =:
72+ "Foo x*bar* baz" =?>
73+ para "Foo x*bar* baz"
74+
75+ , "Emphasis tag" =:
76+ "<em>Foo bar</em>" =?>
77+ para (emph . spcSep $ ["Foo", "bar"])
78+
79+ , "Strong" =:
80+ "**Cider**" =?>
81+ para (strong "Cider")
82+
83+ , "Strong tag" =: "<strong>Strong</strong>" =?> para (strong "Strong")
84+
85+ , "Strong Emphasis" =:
86+ "***strength***" =?>
87+ para (strong . emph $ "strength")
88+
89+ , test emacsMuse "Underline"
90+ ("_Underline_" =?> para (underlineSpan "Underline"))
91+
92+ , "Superscript tag" =: "<sup>Superscript</sup>" =?> para (superscript "Superscript")
93+
94+ , "Subscript tag" =: "<sub>Subscript</sub>" =?> para (subscript "Subscript")
95+
96+ , "Strikeout tag" =: "<del>Strikeout</del>" =?> para (strikeout "Strikeout")
97+
98+ , "Opening inline tags" =: "foo <em> bar <strong>baz" =?> para "foo <em> bar <strong>baz"
99+
100+ , "Closing inline tags" =: "foo </em> bar </strong>baz" =?> para "foo </em> bar </strong>baz"
101+
102+ , "Tag soup" =: "foo <em> bar </strong>baz" =?> para "foo <em> bar </strong>baz"
103+
104+ -- Both inline tags must be within the same paragraph
105+ , "No multiparagraph inline tags" =:
106+ T.unlines [ "First line"
107+ , "<em>Second line"
108+ , ""
109+ , "Fourth line</em>"
110+ ] =?>
111+ para "First line\n<em>Second line" <>
112+ para "Fourth line</em>"
113+
114+ , "Linebreak" =: "Line <br> break" =?> para ("Line" <> linebreak <> "break")
115+
116+ , "Trailing whitespace inside paragraph" =:
117+ T.unlines [ "First line " -- trailing whitespace here
118+ , "second line"
119+ ]
120+ =?> para "First line\nsecond line"
121+
122+ , "Non-breaking space" =: "Foo~~bar" =?> para "Foo\160bar"
123+ , "Single ~" =: "Foo~bar" =?> para "Foo~bar"
124+
125+ , testGroup "Code markup"
126+ [ "Code" =: "=foo(bar)=" =?> para (code "foo(bar)")
127+
128+ , "Not code" =: "a=b= =c=d" =?> para (text "a=b= =c=d")
129+
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 ="
132+
133+ , "Not code if opening = is detached" =: "= this is not a code=" =?> para "= this is not a code="
134+
135+ , "Code if followed by comma" =:
136+ "Foo =bar=, baz" =?>
137+ para (text "Foo " <> code "bar" <> text ", baz")
138+
139+ , "One character code" =: "=c=" =?> para (code "c")
140+
141+ , "Three = characters is not a code" =: "===" =?> para "==="
142+
143+ , "Multiline code markup" =:
144+ "foo =bar\nbaz= end of code" =?>
145+ para (text "foo " <> code "bar\nbaz" <> text " end of code")
146+
147+{- Emacs Muse 3.20 has a bug: it publishes
148+ - <p>foo <code>bar
149+ -
150+ - baz</code> foo</p>
151+ - which is displayed as one paragraph by browsers.
152+ - We follow Amusewiki here and avoid joining paragraphs.
153+ -}
154+ , "No multiparagraph code" =:
155+ T.unlines [ "foo =bar"
156+ , ""
157+ , "baz= foo"
158+ ] =?>
159+ para "foo =bar" <>
160+ para "baz= foo"
161+
162+ , "Code at the beginning of paragraph but not first column" =:
163+ " - =foo=" =?> bulletList [ para $ code "foo" ]
164+ ]
165+
166+ , "Code tag" =: "<code>foo(bar)</code>" =?> para (code "foo(bar)")
167+
168+ , "Verbatim tag" =: "*<verbatim>*</verbatim>*" =?> para (emph "*")
169+
170+ , "Verbatim inside code" =: "<code><verbatim>foo</verbatim></code>" =?> para (code "<verbatim>foo</verbatim>")
171+
172+ , "Verbatim tag after text" =: "Foo <verbatim>bar</verbatim>" =?> para "Foo bar"
173+
174+ , "Class tag" =: "<class name=\"foo\">bar</class>" =?> para (spanWith ("", ["foo"], []) "bar")
175+ , "Class tag without name" =: "<class>foobar</class>" =?> para (spanWith ("", [], []) "foobar")
176+
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")
179+
180+ , testGroup "Links"
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"))
187+ , "Image" =:
188+ "[[image.jpg]]" =?>
189+ para (image "image.jpg" "" mempty)
190+ , "Image with description" =:
191+ "[[image.jpg][Image]]" =?>
192+ para (image "image.jpg" "" (text "Image"))
193+ , "Image link" =:
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
201+ --
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"
205+ ]
206+
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"))
211+ ]
212+ ]
213+
214+ , testGroup "Blocks"
215+ [ testProperty "Round trip" roundTrip,
216+ "Block elements end paragraphs" =:
217+ T.unlines [ "First paragraph"
218+ , "----"
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
226+ ]
227+ , testGroup "Paragraphs"
228+ [ "Simple paragraph" =:
229+ T.unlines [ "First line"
230+ , "second line."
231+ ] =?>
232+ para "First line\nsecond line."
233+ , "Indented paragraph" =:
234+ T.unlines [ " First line"
235+ , "second line."
236+ ] =?>
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"
242+ , " second line"
243+ , "third line"
244+ ] =?>
245+ para "First line\nsecond line\nthird line"
246+ , "Quote" =:
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"
252+ , "</quote>"
253+ ] =?>
254+ blockQuote (para "Not a nested quote")
255+ , "Multiline quote" =:
256+ T.unlines [ " This is a quotation"
257+ , " with a continuation"
258+ ] =?>
259+ blockQuote (para "This is a quotation\nwith a continuation")
260+ , testGroup "Div"
261+ [ "Div without id" =:
262+ T.unlines [ "<div>"
263+ , "Foo bar"
264+ , "</div>"
265+ ] =?>
266+ divWith nullAttr (para "Foo bar")
267+ , "Div with id" =:
268+ T.unlines [ "<div id=\"foo\">"
269+ , "Foo bar"
270+ , "</div>"
271+ ] =?>
272+ divWith ("foo", [], []) (para "Foo bar")
273+ ]
274+ , "Verse" =:
275+ T.unlines [ "> This is"
276+ , "> First stanza"
277+ , ">" -- Emacs produces verbatim ">" here, we follow Amusewiki
278+ , "> And this is"
279+ , "> Second stanza"
280+ , ">"
281+ , ""
282+ , ">"
283+ , ""
284+ , "> Another verse"
285+ , "> is here"
286+ ] =?>
287+ lineBlock [ "This is"
288+ , "First stanza"
289+ , ""
290+ , "And this is"
291+ , "\160\160Second stanza"
292+ , ""
293+ ] <>
294+ lineBlock [ "" ] <>
295+ lineBlock [ "Another verse"
296+ , "\160\160\160is here"
297+ ]
298+ ]
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"
303+ , " > bar"
304+ ] =?>
305+ bulletList [ lineBlock [ "foo", "bar" ] ]
306+ , "Paragraph after verse in list" =:
307+ T.unlines [ " - > foo"
308+ , " bar"
309+ ] =?>
310+ bulletList [ lineBlock [ "foo" ] <> para "bar" ]
311+ , "Empty quote tag" =:
312+ T.unlines [ "<quote>"
313+ , "</quote>"
314+ ]
315+ =?> blockQuote mempty
316+ , "Quote tag" =:
317+ T.unlines [ "<quote>"
318+ , "Hello, world"
319+ , "</quote>"
320+ ]
321+ =?> blockQuote (para $ text "Hello, world")
322+ , "Nested quote tag" =:
323+ T.unlines [ "<quote>"
324+ , "foo"
325+ , "<quote>"
326+ , "bar"
327+ , "</quote>"
328+ , "baz"
329+ , "</quote>"
330+ ] =?>
331+ blockQuote (para "foo" <> blockQuote (para "bar") <> para "baz")
332+ , "Indented quote inside list" =:
333+ T.unlines [ " - <quote>"
334+ , " foo"
335+ , " </quote>"
336+ ] =?>
337+ bulletList [ blockQuote (para "foo") ]
338+ , "Verse tag" =:
339+ T.unlines [ "<verse>"
340+ , ""
341+ , "Foo bar baz"
342+ , " One two three"
343+ , ""
344+ , "</verse>"
345+ ] =?>
346+ lineBlock [ ""
347+ , text "Foo bar baz"
348+ , text "\160\160One two three"
349+ , ""
350+ ]
351+ , "Verse tag with empty line inside" =:
352+ T.unlines [ "<verse>"
353+ , ""
354+ , "</verse>"
355+ ] =?>
356+ lineBlock [ "" ]
357+ , testGroup "Example"
358+ [ "Braces on separate lines" =:
359+ T.unlines [ "{{{"
360+ , "Example line"
361+ , "}}}"
362+ ] =?>
363+ codeBlock "Example line"
364+ , "Spaces after opening braces" =:
365+ T.unlines [ "{{{ "
366+ , "Example line"
367+ , "}}}"
368+ ] =?>
369+ codeBlock "Example line"
370+ , "One blank line in the beginning" =:
371+ T.unlines [ "{{{"
372+ , ""
373+ , "Example line"
374+ , "}}}"
375+ ] =?>
376+ codeBlock "\nExample line"
377+ , "One blank line in the end" =:
378+ T.unlines [ "{{{"
379+ , "Example line"
380+ , ""
381+ , "}}}"
382+ ] =?>
383+ codeBlock "Example line\n"
384+ -- Amusewiki requires braces to be on separate line,
385+ -- this is an extension.
386+ , "One line" =:
387+ "{{{Example line}}}" =?>
388+ codeBlock "Example line"
389+ ]
390+ , testGroup "Example tag"
391+ [ "Tags on separate lines" =:
392+ T.unlines [ "<example>"
393+ , "Example line"
394+ , "</example>"
395+ ] =?>
396+ codeBlock "Example line"
397+ , "One line" =:
398+ "<example>Example line</example>" =?>
399+ codeBlock "Example line"
400+ , "One blank line in the beginning" =:
401+ T.unlines [ "<example>"
402+ , ""
403+ , "Example line"
404+ , "</example>"
405+ ] =?>
406+ codeBlock "\nExample line"
407+ , "One blank line in the end" =:
408+ T.unlines [ "<example>"
409+ , "Example line"
410+ , ""
411+ , "</example>"
412+ ] =?>
413+ codeBlock "Example line\n"
414+ , "Example inside list" =:
415+ T.unlines [ " - <example>"
416+ , " foo"
417+ , " </example>"
418+ ] =?>
419+ bulletList [ codeBlock "foo" ]
420+ , "Empty example inside list" =:
421+ T.unlines [ " - <example>"
422+ , " </example>"
423+ ] =?>
424+ bulletList [ codeBlock "" ]
425+ , "Example inside list with empty lines" =:
426+ T.unlines [ " - <example>"
427+ , " foo"
428+ , " </example>"
429+ , ""
430+ , " bar"
431+ , ""
432+ , " <example>"
433+ , " baz"
434+ , " </example>"
435+ ] =?>
436+ bulletList [ codeBlock "foo" <> para "bar" <> codeBlock "baz" ]
437+ , "Indented example inside list" =:
438+ T.unlines [ " - <example>"
439+ , " foo"
440+ , " </example>"
441+ ] =?>
442+ bulletList [ codeBlock "foo" ]
443+ , "Example inside definition list" =:
444+ T.unlines [ " foo :: <example>"
445+ , " bar"
446+ , " </example>"
447+ ] =?>
448+ definitionList [ ("foo", [codeBlock "bar"]) ]
449+ , "Example inside list definition with empty lines" =:
450+ T.unlines [ " term :: <example>"
451+ , " foo"
452+ , " </example>"
453+ , ""
454+ , " bar"
455+ , ""
456+ , " <example>"
457+ , " baz"
458+ , " </example>"
459+ ] =?>
460+ definitionList [ ("term", [codeBlock "foo" <> para "bar" <> codeBlock "baz"]) ]
461+ , "Example inside note" =:
462+ T.unlines [ "Foo[1]"
463+ , ""
464+ , "[1] <example>"
465+ , " bar"
466+ , " </example>"
467+ ] =?>
468+ para ("Foo" <> note (codeBlock "bar"))
469+ ]
470+ , testGroup "Literal blocks"
471+ [ test emacsMuse "Literal block"
472+ (T.unlines [ "<literal style=\"latex\">"
473+ , "\\newpage"
474+ , "</literal>"
475+ ] =?>
476+ rawBlock "latex" "\\newpage")
477+ ]
478+ , "Center" =:
479+ T.unlines [ "<center>"
480+ , "Hello, world"
481+ , "</center>"
482+ ] =?>
483+ para (text "Hello, world")
484+ , "Right" =:
485+ T.unlines [ "<right>"
486+ , "Hello, world"
487+ , "</right>"
488+ ] =?>
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")
497+ ]
498+ , testGroup "Headers"
499+ [ "Part" =:
500+ "* First level" =?>
501+ header 1 "First level"
502+ , "Chapter" =:
503+ "** Second level" =?>
504+ header 2 "Second level"
505+ , "Section" =:
506+ "*** Third level" =?>
507+ header 3 "Third level"
508+ , "Subsection" =:
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]"
517+ , "[1] * Bar"
518+ ] =?>
519+ para (text "Foo" <>
520+ note (para "* Bar"))
521+ , "No headers in quotes" =:
522+ T.unlines [ "<quote>"
523+ , "* Hi"
524+ , "</quote>"
525+ ] =?>
526+ blockQuote (para "* Hi")
527+ , "Headers consume anchors" =:
528+ T.unlines [ "** Foo"
529+ , "#bar"
530+ ] =?>
531+ headerWith ("bar",[],[]) 2 "Foo"
532+ , "Headers don't consume anchors separated with a blankline" =:
533+ T.unlines [ "** Foo"
534+ , ""
535+ , "#bar"
536+ ] =?>
537+ header 2 "Foo" <>
538+ para (spanWith ("bar", [], []) mempty)
539+ , "Headers terminate lists" =:
540+ T.unlines [ " - foo"
541+ , "* bar"
542+ ] =?>
543+ bulletList [ para "foo" ] <>
544+ header 1 "bar"
545+ ]
546+ , testGroup "Directives"
547+ [ "Title" =:
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"
561+ ] =?>
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"
567+ , "and second line"
568+ , "#author Name"
569+ ] =?>
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
573+ ]
574+ , testGroup "Anchors"
575+ [ "Anchor" =:
576+ T.unlines [ "; A comment to make sure anchor is not parsed as a directive"
577+ , "#anchor Target"
578+ ] =?>
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"
583+ ] =?>
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."
591+ ] =?>
592+ para ("Paragraph starts here\n" <> spanWith ("anchor", [], []) mempty <> "and ends here.")
593+ ]
594+ , testGroup "Footnotes"
595+ [ "Simple footnote" =:
596+ T.unlines [ "Here is a footnote[1]."
597+ , ""
598+ , "[1] Footnote contents"
599+ ] =?>
600+ para (text "Here is a footnote" <>
601+ note (para "Footnote contents") <>
602+ str ".")
603+ , "Recursive footnote" =:
604+ T.unlines [ "Start recursion here[1]"
605+ , ""
606+ , "[1] Recursion continues here[1]"
607+ ] =?>
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]."
612+ , ""
613+ , "[0] Footnote contents"
614+ ] =?>
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]."
619+ , ""
620+ , "[01] Footnote contents"
621+ ] =?>
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]"
627+ , ""
628+ , "[1] First footnote paragraph"
629+ , ""
630+ , " Second footnote paragraph"
631+ , "with continuation"
632+ , ""
633+ , "Not a note"
634+ , "[2] Second footnote"
635+ ] =?>
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")
642+
643+ -- Verse requires precise indentation, so it is good to test indentation requirements
644+ , "Note continuation with verse" =:
645+ T.unlines [ "Foo[1]"
646+ , ""
647+ , "[1] Bar"
648+ , ""
649+ , " > Baz"
650+ ] =?>
651+ para ("Foo" <> note (para "Bar" <> lineBlock ["Baz"]))
652+ , test emacsMuse "Emacs multiparagraph footnotes"
653+ (T.unlines
654+ [ "First footnote reference[1] and second footnote reference[2]."
655+ , ""
656+ , "[1] First footnote paragraph"
657+ , ""
658+ , "Second footnote"
659+ , "paragraph"
660+ , ""
661+ , "[2] Third footnote paragraph"
662+ , ""
663+ , "Fourth footnote paragraph"
664+ ] =?>
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") <>
671+ text "."))
672+ ]
673+ ]
674+ ]
675+ , testGroup "Tables"
676+ [ "Two cell table" =:
677+ "One | Two" =?>
678+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
679+ []
680+ [[plain "One", plain "Two"]]
681+ , "Table with multiple words" =:
682+ "One two | three four" =?>
683+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
684+ []
685+ [[plain "One two", plain "three four"]]
686+ , "Not a table" =:
687+ "One| Two" =?>
688+ para (text "One| Two")
689+ , "Not a table again" =:
690+ "One |Two" =?>
691+ para (text "One |Two")
692+ , "Two line table" =:
693+ T.unlines
694+ [ "One | Two"
695+ , "Three | Four"
696+ ] =?>
697+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
698+ []
699+ [[plain "One", plain "Two"],
700+ [plain "Three", plain "Four"]]
701+ , "Table with one header" =:
702+ T.unlines
703+ [ "First || Second"
704+ , "Third | Fourth"
705+ ] =?>
706+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
707+ [plain "First", plain "Second"]
708+ [[plain "Third", plain "Fourth"]]
709+ , "Table with two headers" =:
710+ T.unlines
711+ [ "First || header"
712+ , "Second || header"
713+ , "Foo | bar"
714+ ] =?>
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" =:
720+ T.unlines
721+ [ "Foo ||| bar"
722+ , "Baz || foo"
723+ , "Bar | baz"
724+ ] =?>
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" =:
730+ T.unlines
731+ [ "Foo || bar || baz"
732+ , "First | row | here"
733+ , "Second | row | there"
734+ , "|+ Table caption +|"
735+ ] =?>
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" =:
744+ T.unlines
745+ [ " Foo | bar"
746+ , " Baz | foo"
747+ , " Bar | baz"
748+ ] =?>
749+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
750+ []
751+ [[plain "Foo", plain "bar"],
752+ [plain "Baz", plain "foo"],
753+ [plain "Bar", plain "baz"]]
754+ , "Empty cells" =:
755+ T.unlines
756+ [ " | Foo"
757+ , " |"
758+ , " bar |"
759+ , " || baz"
760+ ] =?>
761+ table mempty [(AlignDefault, 0.0), (AlignDefault, 0.0)]
762+ [plain "", plain "baz"]
763+ [[plain "", plain "Foo"],
764+ [plain "", plain ""],
765+ [plain "bar", plain ""]]
766+ ]
767+ , testGroup "Lists"
768+ [ "Bullet list" =:
769+ T.unlines
770+ [ " - Item1"
771+ , ""
772+ , " - Item2"
773+ ] =?>
774+ bulletList [ para "Item1"
775+ , para "Item2"
776+ ]
777+ , "Ordered list" =:
778+ T.unlines
779+ [ " 1. Item1"
780+ , ""
781+ , " 2. Item2"
782+ ] =?>
783+ orderedListWith (1, Decimal, Period) [ para "Item1"
784+ , para "Item2"
785+ ]
786+ , "Ordered list with implicit numbers" =:
787+ T.unlines
788+ [ " 1. Item1"
789+ , ""
790+ , " 1. Item2"
791+ , ""
792+ , " 1. Item3"
793+ ] =?>
794+ orderedListWith (1, Decimal, Period) [ para "Item1"
795+ , para "Item2"
796+ , para "Item3"
797+ ]
798+ , "Ordered list with roman numerals" =:
799+ T.unlines
800+ [ " i. First"
801+ , " ii. Second"
802+ , " iii. Third"
803+ , " iv. Fourth"
804+ ] =?>
805+ orderedListWith (1, LowerRoman, Period) [ para "First"
806+ , para "Second"
807+ , para "Third"
808+ , para "Fourth"
809+ ]
810+ , "Bullet list with empty items" =:
811+ T.unlines
812+ [ " -"
813+ , ""
814+ , " - Item2"
815+ ] =?>
816+ bulletList [ mempty
817+ , para "Item2"
818+ ]
819+ , "Ordered list with empty items" =:
820+ T.unlines
821+ [ " 1."
822+ , ""
823+ , " 2."
824+ , ""
825+ , " 3. Item3"
826+ ] =?>
827+ orderedListWith (1, Decimal, Period) [ mempty
828+ , mempty
829+ , para "Item3"
830+ ]
831+ , "Bullet list with last item empty" =:
832+ T.unlines
833+ [ " -"
834+ , ""
835+ , "foo"
836+ ] =?>
837+ bulletList [ mempty ] <>
838+ para "foo"
839+ , testGroup "Nested lists"
840+ [ "Nested bullet list" =:
841+ T.unlines [ " - Item1"
842+ , " - Item2"
843+ , " - Item3"
844+ , " - Item4"
845+ , " - Item5"
846+ , " - Item6"
847+ ] =?>
848+ bulletList [ para "Item1" <>
849+ bulletList [ para "Item2" <>
850+ bulletList [ para "Item3" ]
851+ , para "Item4" <>
852+ bulletList [ para "Item5" ]
853+ ]
854+ , para "Item6"
855+ ]
856+ , "Nested ordered list" =:
857+ T.unlines [ " 1. Item1"
858+ , " 1. Item2"
859+ , " 1. Item3"
860+ , " 2. Item4"
861+ , " 1. Item5"
862+ , " 2. Item6"
863+ ] =?>
864+ orderedListWith (1, Decimal, Period) [ para "Item1" <>
865+ orderedListWith (1, Decimal, Period) [ para "Item2" <>
866+ orderedListWith (1, Decimal, Period) [ para "Item3" ]
867+ , para "Item4" <>
868+ orderedListWith (1, Decimal, Period) [ para "Item5" ]
869+ ]
870+ , para "Item6"
871+ ]
872+ , "Mixed nested list" =:
873+ T.unlines
874+ [ " - Item1"
875+ , " - Item2"
876+ , " - Item3"
877+ , " - Item4"
878+ , " 1. Nested"
879+ , " 2. Ordered"
880+ , " 3. List"
881+ ] =?>
882+ bulletList [ mconcat [ para "Item1"
883+ , bulletList [ para "Item2"
884+ , para "Item3"
885+ ]
886+ ]
887+ , mconcat [ para "Item4"
888+ , orderedListWith (1, Decimal, Period) [ para "Nested"
889+ , para "Ordered"
890+ , para "List"
891+ ]
892+ ]
893+ ]
894+ , "Text::Amuse includes only one space in list marker" =:
895+ T.unlines
896+ [ " - First item"
897+ , " - Nested item"
898+ ] =?>
899+ bulletList [ para "First item" <> bulletList [ para "Nested item"]]
900+ ]
901+ , "List continuation" =:
902+ T.unlines
903+ [ " - a"
904+ , ""
905+ , " b"
906+ , ""
907+ , " c"
908+ ] =?>
909+ bulletList [ mconcat [ para "a"
910+ , para "b"
911+ , para "c"
912+ ]
913+ ]
914+ , "List continuation afeter nested list" =:
915+ T.unlines
916+ [ " - - foo"
917+ , ""
918+ , " bar"
919+ ] =?>
920+ bulletList [ bulletList [ para "foo" ] <>
921+ para "bar"
922+ ]
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" =:
928+ T.unlines
929+ [ " - Foo"
930+ , " - Bar"
931+ ] =?>
932+ bulletList [ para "Foo"
933+ , para "Bar"
934+ ]
935+ , "One blank line between list items is allowed" =:
936+ T.unlines
937+ [ " - Foo"
938+ , ""
939+ , " - Bar"
940+ ] =?>
941+ bulletList [ para "Foo"
942+ , para "Bar"
943+ ]
944+ , "Two blank lines separate lists" =:
945+ T.unlines
946+ [ " - Foo"
947+ , ""
948+ , ""
949+ , " - Bar"
950+ ] =?>
951+ bulletList [ para "Foo" ] <> bulletList [ para "Bar" ]
952+ , "No blank line after multiline first item" =:
953+ T.unlines
954+ [ " - Foo"
955+ , " bar"
956+ , " - Baz"
957+ ] =?>
958+ bulletList [ para "Foo\nbar"
959+ , para "Baz"
960+ ]
961+ , "One blank line after multiline first item" =:
962+ T.unlines
963+ [ " - Foo"
964+ , " bar"
965+ , ""
966+ , " - Baz"
967+ ] =?>
968+ bulletList [ para "Foo\nbar"
969+ , para "Baz"
970+ ]
971+ , "Two blank lines after multiline first item" =:
972+ T.unlines
973+ [ " - Foo"
974+ , " bar"
975+ , ""
976+ , ""
977+ , " - Baz"
978+ ] =?>
979+ bulletList [ para "Foo\nbar" ] <> bulletList [ para "Baz" ]
980+ , "No blank line after list continuation" =:
981+ T.unlines
982+ [ " - Foo"
983+ , ""
984+ , " bar"
985+ , " - Baz"
986+ ] =?>
987+ bulletList [ para "Foo" <> para "bar"
988+ , para "Baz"
989+ ]
990+ , "One blank line after list continuation" =:
991+ T.unlines
992+ [ " - Foo"
993+ , ""
994+ , " bar"
995+ , ""
996+ , " - Baz"
997+ ] =?>
998+ bulletList [ para "Foo" <> para "bar"
999+ , para "Baz"
1000+ ]
1001+ , "Two blank lines after list continuation" =:
1002+ T.unlines
1003+ [ " - Foo"
1004+ , ""
1005+ , " bar"
1006+ , ""
1007+ , ""
1008+ , " - Baz"
1009+ ] =?>
1010+ bulletList [ para "Foo" <> para "bar" ] <> bulletList [ para "Baz" ]
1011+ , "No blank line after blockquote" =:
1012+ T.unlines
1013+ [ " - <quote>"
1014+ , " foo"
1015+ , " </quote>"
1016+ , " - bar"
1017+ ] =?>
1018+ bulletList [ blockQuote $ para "foo", para "bar" ]
1019+ , "One blank line after blockquote" =:
1020+ T.unlines
1021+ [ " - <quote>"
1022+ , " foo"
1023+ , " </quote>"
1024+ , ""
1025+ , " - bar"
1026+ ] =?>
1027+ bulletList [ blockQuote $ para "foo", para "bar" ]
1028+ , "Two blank lines after blockquote" =:
1029+ T.unlines
1030+ [ " - <quote>"
1031+ , " foo"
1032+ , " </quote>"
1033+ , ""
1034+ , ""
1035+ , " - bar"
1036+ ] =?>
1037+ bulletList [ blockQuote $ para "foo" ] <> bulletList [ para "bar" ]
1038+ , "No blank line after verse" =:
1039+ T.unlines
1040+ [ " - > foo"
1041+ , " - bar"
1042+ ] =?>
1043+ bulletList [ lineBlock [ "foo" ], para "bar" ]
1044+ , "One blank line after verse" =:
1045+ T.unlines
1046+ [ " - > foo"
1047+ , ""
1048+ , " - bar"
1049+ ] =?>
1050+ bulletList [ lineBlock [ "foo" ], para "bar" ]
1051+ , "Two blank lines after verse" =:
1052+ T.unlines
1053+ [ " - > foo"
1054+ , ""
1055+ , ""
1056+ , " - bar"
1057+ ] =?>
1058+ bulletList [ lineBlock [ "foo" ] ] <> bulletList [ para "bar" ]
1059+ ]
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" =:
1063+ T.unlines
1064+ [ "First :: second"
1065+ , "Foo :: bar"
1066+ ] =?>
1067+ para "First :: second\nFoo :: bar"
1068+ , test emacsMuse "Emacs Muse definition list"
1069+ (T.unlines
1070+ [ "First :: second"
1071+ , "Foo :: bar"
1072+ ] =?>
1073+ definitionList [ ("First", [ para "second" ])
1074+ , ("Foo", [ para "bar" ])
1075+ ])
1076+ , "Definition list" =:
1077+ T.unlines
1078+ [ " First :: second"
1079+ , " Foo :: bar"
1080+ ] =?>
1081+ definitionList [ ("First", [ para "second" ])
1082+ , ("Foo", [ para "bar" ])
1083+ ]
1084+ , "Definition list term cannot include newline" =:
1085+ T.unlines
1086+ [ " Foo" -- "Foo" is not a part of the definition list term
1087+ , " Bar :: baz"
1088+ ] =?>
1089+ para "Foo" <>
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" =:
1101+ T.unlines
1102+ [ " First term :: Definition of first term"
1103+ , "and its continuation."
1104+ , " Second term :: Definition of second term."
1105+ ] =?>
1106+ definitionList [ ("First term", [ para "Definition of first term\nand its continuation." ])
1107+ , ("Second term", [ para "Definition of second term." ])
1108+ ]
1109+ , test emacsMuse "Multi-line definition lists from Emacs Muse manual"
1110+ (T.unlines
1111+ [ "Term1 ::"
1112+ , " This is a first definition"
1113+ , " And it has two lines;"
1114+ , "no, make that three."
1115+ , ""
1116+ , "Term2 :: This is a second definition"
1117+ ] =?>
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"])
1120+ ])
1121+ -- Text::Amuse requires indentation with one space
1122+ , "Multi-line definition lists from Emacs Muse manual with initial space" =:
1123+ (T.unlines
1124+ [ " Term1 ::"
1125+ , " This is a first definition"
1126+ , " And it has two lines;"
1127+ , "no, make that three."
1128+ , ""
1129+ , " Term2 :: This is a second definition"
1130+ ] =?>
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"])
1133+ ])
1134+ , "One-line nested definition list" =:
1135+ " Foo :: bar :: baz" =?>
1136+ definitionList [ ("Foo", [ definitionList [ ("bar", [ para "baz" ])]])]
1137+ , "Nested definition list" =:
1138+ T.unlines
1139+ [ " First :: Second :: Third"
1140+ , " Fourth :: Fifth :: Sixth"
1141+ , " Seventh :: Eighth"
1142+ ] =?>
1143+ definitionList [ ("First", [ definitionList [ ("Second", [ para "Third" ]),
1144+ ("Fourth", [ definitionList [ ("Fifth", [ para "Sixth"] ) ] ] ) ] ] )
1145+ , ("Seventh", [ para "Eighth" ])
1146+ ]
1147+ , testGroup "Definition lists with multiple descriptions"
1148+ [ "Correctly indented second description" =:
1149+ T.unlines
1150+ [ " First term :: first description"
1151+ , " :: second description"
1152+ ] =?>
1153+ definitionList [ ("First term", [ para "first description"
1154+ , para "second description"
1155+ ])
1156+ ]
1157+ , "Incorrectly indented second description" =:
1158+ T.unlines
1159+ [ " First term :: first description"
1160+ , " :: second description"
1161+ ] =?>
1162+ definitionList [ ("First term", [ para "first description" ])
1163+ , ("", [ para "second description" ])
1164+ ]
1165+ ]
1166+ , "Two blank lines separate definition lists" =:
1167+ T.unlines
1168+ [ " First :: list"
1169+ , ""
1170+ , ""
1171+ , " Second :: list"
1172+ ] =?>
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" =:
1177+ T.unlines
1178+ [ " - Foo"
1179+ , ""
1180+ , " * Bar"
1181+ ] =?>
1182+ bulletList [ mconcat [ para "Foo"
1183+ , para "* Bar"
1184+ ]
1185+ ]
1186+ , "Bullet list inside a tag" =:
1187+ T.unlines
1188+ [ "<quote>"
1189+ , " - First"
1190+ , ""
1191+ , " - Second"
1192+ , ""
1193+ , " - Third"
1194+ , "</quote>"
1195+ ] =?>
1196+ blockQuote (bulletList [ para "First"
1197+ , para "Second"
1198+ , para "Third"
1199+ ])
1200+ , "Ordered list inside a tag" =:
1201+ T.unlines
1202+ [ "<quote>"
1203+ , " 1. First"
1204+ , ""
1205+ , " 2. Second"
1206+ , ""
1207+ , " 3. Third"
1208+ , "</quote>"
1209+ ] =?>
1210+ blockQuote (orderedListWith (1, Decimal, Period) [ para "First"
1211+ , para "Second"
1212+ , para "Third"
1213+ ])
1214+ -- Regression test for a bug caught by round-trip test
1215+ , "Do not consume whitespace while looking for end tag" =:
1216+ T.unlines
1217+ [ "<quote>"
1218+ , " - <quote>"
1219+ , " foo"
1220+ , " </quote>"
1221+ , " bar" -- Do not consume whitespace while looking for arbitraritly indented </quote>
1222+ , "</quote>"
1223+ ] =?>
1224+ blockQuote (bulletList [ blockQuote $ para "foo" ] <> para "bar")
1225+
1226+ , "Unclosed quote tag" =:
1227+ T.unlines
1228+ [ "<quote>"
1229+ , "<verse>"
1230+ , "</quote>"
1231+ , "</verse>"
1232+ ] =?>
1233+ para "<quote>" <> lineBlock [ "</quote>" ]
1234+
1235+ , "Unclosed quote tag inside list" =:
1236+ T.unlines
1237+ [ " - <quote>"
1238+ , " <verse>"
1239+ , " </quote>"
1240+ , " </verse>"
1241+ ] =?>
1242+ bulletList [ para "<quote>" <> lineBlock [ "</quote>" ] ]
1243+
1244+ -- Allowing indented closing tags is dangerous,
1245+ -- as they may terminate lists
1246+ , "No indented closing tags" =:
1247+ T.unlines
1248+ [ "<quote>"
1249+ , ""
1250+ , " - Foo"
1251+ , ""
1252+ , " </quote>"
1253+ , ""
1254+ , " bar"
1255+ , ""
1256+ , " <verse>"
1257+ , " </quote>"
1258+ , " </verse>"
1259+ ] =?>
1260+ para "<quote>" <> bulletList [ para "Foo" <> para "</quote>" <> para "bar" <> lineBlock [ "</quote>" ] ]
1261+ ]
1262+ ]
test/Tests/Readers/Odt.hs.orignew file+170
@@ -0,0 +1,170 @@
1+module Tests.Readers.Odt (tests) where
2+
3+import Control.Monad (liftM)
4+import qualified Data.ByteString as BS
5+import qualified Data.ByteString.Lazy as B
6+import qualified Data.Map as M
7+import Data.Text (unpack)
8+import System.IO.Unsafe (unsafePerformIO)
9+import Test.Tasty
10+import Tests.Helpers
11+import Text.Pandoc
12+import qualified Text.Pandoc.UTF8 as UTF8
13+
14+defopts :: ReaderOptions
15+defopts = def{ readerExtensions = getDefaultExtensions "odt" }
16+
17+tests :: [TestTree]
18+tests = testsComparingToMarkdown ++ testsComparingToNative
19+
20+testsComparingToMarkdown :: [TestTree]
21+testsComparingToMarkdown = map nameToTest namesOfTestsComparingToMarkdown
22+ where nameToTest name = createTest
23+ compareOdtToMarkdown
24+ name
25+ (toOdtPath name)
26+ (toMarkdownPath name)
27+ toOdtPath name = "odt/odt/" ++ name ++ ".odt"
28+ toMarkdownPath name = "odt/markdown/" ++ name ++ ".md"
29+
30+testsComparingToNative :: [TestTree]
31+testsComparingToNative = map nameToTest namesOfTestsComparingToNative
32+ where nameToTest name = createTest
33+ compareOdtToNative
34+ name
35+ (toOdtPath name)
36+ (toNativePath name)
37+ toOdtPath name = "odt/odt/" ++ name ++ ".odt"
38+ toNativePath name = "odt/native/" ++ name ++ ".native"
39+
40+
41+newtype NoNormPandoc = NoNormPandoc {unNoNorm :: Pandoc}
42+ deriving ( Show )
43+
44+instance ToString NoNormPandoc where
45+ toString d = unpack $
46+ purely (writeNative def{ writerTemplate = s }) $ toPandoc d
47+ where s = case d of
48+ NoNormPandoc (Pandoc (Meta m) _)
49+ | M.null m -> Nothing
50+ | otherwise -> Just "" -- need this for Meta output
51+
52+instance ToPandoc NoNormPandoc where
53+ toPandoc = unNoNorm
54+
55+getNoNormVia :: (a -> Pandoc) -> String -> Either PandocError a -> NoNormPandoc
56+getNoNormVia _ readerName (Left _) = error (readerName ++ " reader failed")
57+getNoNormVia f _ (Right a) = NoNormPandoc (f a)
58+
59+type TestCreator = ReaderOptions
60+ -> FilePath -> FilePath
61+ -> IO (NoNormPandoc, NoNormPandoc)
62+
63+compareOdtToNative :: TestCreator
64+compareOdtToNative opts odtPath nativePath = do
65+ nativeFile <- UTF8.toText <$> BS.readFile nativePath
66+ odtFile <- B.readFile odtPath
67+ native <- getNoNormVia id "native" <$> runIO (readNative def nativeFile)
68+ odt <- getNoNormVia id "odt" <$> runIO (readOdt opts odtFile)
69+ return (odt,native)
70+
71+compareOdtToMarkdown :: TestCreator
72+compareOdtToMarkdown opts odtPath markdownPath = do
73+ markdownFile <- UTF8.toText <$> BS.readFile markdownPath
74+ odtFile <- B.readFile odtPath
75+ markdown <- getNoNormVia id "markdown" <$>
76+ runIO (readMarkdown def{ readerExtensions = pandocExtensions }
77+ markdownFile)
78+ odt <- getNoNormVia id "odt" <$> runIO (readOdt opts odtFile)
79+ return (odt,markdown)
80+
81+
82+createTest :: TestCreator
83+ -> TestName
84+ -> FilePath -> FilePath
85+ -> TestTree
86+createTest creator name path1 path2 =
87+ unsafePerformIO $ liftM (test id name) (creator defopts path1 path2)
88+
89+{-
90+--
91+
92+getMedia :: FilePath -> FilePath -> IO (Maybe B.ByteString)
93+getMedia archivePath mediaPath = do
94+ zf <- B.readFile archivePath >>= return . toArchive
95+ return $ findEntryByPath ("Pictures/" ++ mediaPath) zf >>= (Just . fromEntry)
96+
97+compareMediaPathIO :: FilePath -> MediaBag -> FilePath -> IO Bool
98+compareMediaPathIO mediaPath mediaBag odtPath = do
99+ odtMedia <- getMedia odtPath mediaPath
100+ let mbBS = case lookupMedia mediaPath mediaBag of
101+ Just (_, bs) -> bs
102+ Nothing -> error ("couldn't find " ++
103+ mediaPath ++
104+ " in media bag")
105+ odtBS = case odtMedia of
106+ Just bs -> bs
107+ Nothing -> error ("couldn't find " ++
108+ mediaPath ++
109+ " in media bag")
110+ return $ mbBS == odtBS
111+
112+compareMediaBagIO :: FilePath -> IO Bool
113+compareMediaBagIO odtFile = do
114+ df <- B.readFile odtFile
115+ let (_, mb) = readOdt def df
116+ bools <- mapM
117+ (\(fp, _, _) -> compareMediaPathIO fp mb odtFile)
118+ (mediaDirectory mb)
119+ return $ and bools
120+
121+testMediaBagIO :: String -> FilePath -> IO TestTree
122+testMediaBagIO name odtFile = do
123+ outcome <- compareMediaBagIO odtFile
124+ return $ testCase name (assertBool
125+ ("Media didn't match media bag in file " ++ odtFile)
126+ outcome)
127+
128+testMediaBag :: String -> FilePath -> TestTree
129+testMediaBag name odtFile = buildTest $ testMediaBagIO name odtFile
130+-}
131+--
132+
133+
134+
135+namesOfTestsComparingToMarkdown :: [ String ]
136+namesOfTestsComparingToMarkdown = [ "bold"
137+-- , "citation"
138+ , "endnote"
139+ , "externalLink"
140+ , "footnote"
141+ , "headers"
142+-- , "horizontalRule"
143+ , "italic"
144+-- , "listBlocks"
145+ , "paragraph"
146+ , "strikeout"
147+-- , "trackedChanges"
148+ , "underlined"
149+ ]
150+
151+namesOfTestsComparingToNative :: [ String ]
152+namesOfTestsComparingToNative = [ "blockquote"
153+ , "image"
154+ , "imageIndex"
155+ , "imageWithCaption"
156+ , "inlinedCode"
157+ , "orderedListMixed"
158+ , "orderedListRoman"
159+ , "orderedListSimple"
160+ , "referenceToChapter"
161+ , "referenceToListItem"
162+ , "referenceToText"
163+ , "simpleTable"
164+ , "simpleTableWithCaption"
165+-- , "table"
166+ , "textMixedStyles"
167+ , "tableWithContents"
168+ , "unicode"
169+ , "unorderedList"
170+ ]
test/Tests/Readers/Org.hs.orignew file+16
@@ -0,0 +1,16 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org (tests) where
3+
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
9+
10+tests :: [TestTree]
11+tests =
12+ [ testGroup "Inlines" Inline.tests
13+ , testGroup "Basic Blocks" Block.tests
14+ , testGroup "Meta Information" Meta.tests
15+ , testGroup "Directives" Directive.tests
16+ ]
test/Tests/Readers/Org/Block.hs.orignew file+192
@@ -0,0 +1,192 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block (tests) where
3+
4+import Test.Tasty (TestTree, testGroup)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep)
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+import qualified Tests.Readers.Org.Block.CodeBlock as CodeBlock
10+import qualified Tests.Readers.Org.Block.Figure as Figure
11+import qualified Tests.Readers.Org.Block.Header as Header
12+import qualified Tests.Readers.Org.Block.List as List
13+import qualified Tests.Readers.Org.Block.Table as Table
14+
15+tests :: [TestTree]
16+tests =
17+ [ "Paragraph" =:
18+ "Paragraph\n" =?>
19+ para "Paragraph"
20+
21+ , "Paragraph starting with an asterisk" =:
22+ "*five" =?>
23+ para "*five"
24+
25+ , "Paragraph containing asterisk at beginning of line" =:
26+ T.unlines [ "lucky"
27+ , "*star"
28+ ] =?>
29+ para ("lucky" <> softbreak <> "*star")
30+
31+ , "Example block" =:
32+ T.unlines [ ": echo hello"
33+ , ": echo dear tester"
34+ ] =?>
35+ codeBlockWith ("", ["example"], []) "echo hello\necho dear tester\n"
36+
37+ , "Example block surrounded by text" =:
38+ T.unlines [ "Greetings"
39+ , ": echo hello"
40+ , ": echo dear tester"
41+ , "Bye"
42+ ] =?>
43+ mconcat [ para "Greetings"
44+ , codeBlockWith ("", ["example"], [])
45+ "echo hello\necho dear tester\n"
46+ , para "Bye"
47+ ]
48+
49+ , "Horizontal Rule" =:
50+ T.unlines [ "before"
51+ , "-----"
52+ , "after"
53+ ] =?>
54+ mconcat [ para "before"
55+ , horizontalRule
56+ , para "after"
57+ ]
58+
59+ , "Not a Horizontal Rule" =:
60+ "----- em and en dash" =?>
61+ para "\8212\8211 em and en dash"
62+
63+ , "Comment Block" =:
64+ T.unlines [ "#+BEGIN_COMMENT"
65+ , "stuff"
66+ , "bla"
67+ , "#+END_COMMENT"] =?>
68+ (mempty::Blocks)
69+
70+ , testGroup "Blocks and fragments"
71+ [ "HTML block" =:
72+ T.unlines [ "#+BEGIN_HTML"
73+ , "<aside>HTML5 is pretty nice.</aside>"
74+ , "#+END_HTML"
75+ ] =?>
76+ rawBlock "html" "<aside>HTML5 is pretty nice.</aside>\n"
77+
78+ , "Quote block" =:
79+ T.unlines [ "#+BEGIN_QUOTE"
80+ , "/Niemand/ hat die Absicht, eine Mauer zu errichten!"
81+ , "#+END_QUOTE"
82+ ] =?>
83+ blockQuote (para (spcSep [ emph "Niemand", "hat", "die", "Absicht,"
84+ , "eine", "Mauer", "zu", "errichten!"
85+ ]))
86+
87+ , "Verse block" =:
88+ T.unlines [ "The first lines of Goethe's /Faust/:"
89+ , "#+begin_verse"
90+ , "Habe nun, ach! Philosophie,"
91+ , "Juristerei und Medizin,"
92+ , "Und leider auch Theologie!"
93+ , "Durchaus studiert, mit heißem Bemühn."
94+ , "#+end_verse"
95+ ] =?>
96+ mconcat
97+ [ para $ spcSep [ "The", "first", "lines", "of"
98+ , "Goethe's", emph "Faust" <> ":"]
99+ , lineBlock
100+ [ "Habe nun, ach! Philosophie,"
101+ , "Juristerei und Medizin,"
102+ , "Und leider auch Theologie!"
103+ , "Durchaus studiert, mit heißem Bemühn."
104+ ]
105+ ]
106+
107+ , "Verse block with blank lines" =:
108+ T.unlines [ "#+BEGIN_VERSE"
109+ , "foo"
110+ , ""
111+ , "bar"
112+ , "#+END_VERSE"
113+ ] =?>
114+ lineBlock [ "foo", mempty, "bar" ]
115+
116+ , "Verse block with varying indentation" =:
117+ T.unlines [ "#+BEGIN_VERSE"
118+ , " hello darkness"
119+ , "my old friend"
120+ , "#+END_VERSE"
121+ ] =?>
122+ lineBlock [ "\160\160hello darkness", "my old friend" ]
123+
124+ , "Raw block LaTeX" =:
125+ T.unlines [ "#+BEGIN_LaTeX"
126+ , "The category $\\cat{Set}$ is adhesive."
127+ , "#+END_LaTeX"
128+ ] =?>
129+ rawBlock "latex" "The category $\\cat{Set}$ is adhesive.\n"
130+
131+ , "Raw LaTeX line" =:
132+ "#+LATEX: \\let\\foo\\bar" =?>
133+ rawBlock "latex" "\\let\\foo\\bar"
134+
135+ , "Raw Beamer line" =:
136+ "#+beamer: \\pause" =?>
137+ rawBlock "beamer" "\\pause"
138+
139+ , "Raw HTML line" =:
140+ "#+HTML: <aside>not important</aside>" =?>
141+ rawBlock "html" "<aside>not important</aside>"
142+
143+ , "Export block HTML" =:
144+ T.unlines [ "#+BEGIN_export html"
145+ , "<samp>Hello, World!</samp>"
146+ , "#+END_export"
147+ ] =?>
148+ rawBlock "html" "<samp>Hello, World!</samp>\n"
149+
150+ , "LaTeX fragment" =:
151+ T.unlines [ "\\begin{equation}"
152+ , "X_i = \\begin{cases}"
153+ , " G_{\\alpha(i)} & \\text{if }\\alpha(i-1) = \\alpha(i)\\\\"
154+ , " C_{\\alpha(i)} & \\text{otherwise}"
155+ , " \\end{cases}"
156+ , "\\end{equation}"
157+ ] =?>
158+ rawBlock "latex"
159+ (unlines [ "\\begin{equation}"
160+ , "X_i = \\begin{cases}"
161+ , " G_{\\alpha(i)} & \\text{if }\\alpha(i-1) =" <>
162+ " \\alpha(i)\\\\"
163+ , " C_{\\alpha(i)} & \\text{otherwise}"
164+ , " \\end{cases}"
165+ , "\\end{equation}"
166+ ])
167+
168+ , "Convert blank lines in blocks to single newlines" =:
169+ T.unlines [ "#+begin_html"
170+ , ""
171+ , "<span>boring</span>"
172+ , ""
173+ , "#+end_html"
174+ ] =?>
175+ rawBlock "html" "\n<span>boring</span>\n\n"
176+
177+ , "Accept `ATTR_HTML` attributes for generic block" =:
178+ T.unlines [ "#+ATTR_HTML: :title hello, world :id test :class fun code"
179+ , "#+BEGIN_TEST"
180+ , "nonsense"
181+ , "#+END_TEST"
182+ ] =?>
183+ let attr = ("test", ["fun", "code", "TEST"], [("title", "hello, world")])
184+ in divWith attr (para "nonsense")
185+ ]
186+
187+ , testGroup "Headers" Header.tests
188+ , testGroup "Figures" Figure.tests
189+ , testGroup "Lists" List.tests
190+ , testGroup "CodeBlocks" CodeBlock.tests
191+ , testGroup "Tables" Table.tests
192+ ]
test/Tests/Readers/Org/Block/CodeBlock.hs.orignew file+194
@@ -0,0 +1,194 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block.CodeBlock (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep)
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+
10+tests :: [TestTree]
11+tests =
12+ [ "Source block" =:
13+ T.unlines [ " #+BEGIN_SRC haskell"
14+ , " main = putStrLn greeting"
15+ , " where greeting = \"moin\""
16+ , " #+END_SRC" ] =?>
17+ let attr' = ("", ["haskell"], [])
18+ code' = "main = putStrLn greeting\n" <>
19+ " where greeting = \"moin\"\n"
20+ in codeBlockWith attr' code'
21+
22+ , "Source block with indented code" =:
23+ T.unlines [ " #+BEGIN_SRC haskell"
24+ , " main = putStrLn greeting"
25+ , " where greeting = \"moin\""
26+ , " #+END_SRC" ] =?>
27+ let attr' = ("", ["haskell"], [])
28+ code' = "main = putStrLn greeting\n" <>
29+ " where greeting = \"moin\"\n"
30+ in codeBlockWith attr' code'
31+
32+ , "Source block with tab-indented code" =:
33+ T.unlines [ "\t#+BEGIN_SRC haskell"
34+ , "\tmain = putStrLn greeting"
35+ , "\t where greeting = \"moin\""
36+ , "\t#+END_SRC" ] =?>
37+ let attr' = ("", ["haskell"], [])
38+ code' = "main = putStrLn greeting\n" <>
39+ " where greeting = \"moin\"\n"
40+ in codeBlockWith attr' code'
41+
42+ , "Empty source block" =:
43+ T.unlines [ " #+BEGIN_SRC haskell"
44+ , " #+END_SRC" ] =?>
45+ let attr' = ("", ["haskell"], [])
46+ code' = ""
47+ in codeBlockWith attr' code'
48+
49+ , "Source block between paragraphs" =:
50+ T.unlines [ "Low German greeting"
51+ , " #+BEGIN_SRC haskell"
52+ , " main = putStrLn greeting"
53+ , " where greeting = \"Moin!\""
54+ , " #+END_SRC" ] =?>
55+ let attr' = ("", ["haskell"], [])
56+ code' = "main = putStrLn greeting\n" <>
57+ " where greeting = \"Moin!\"\n"
58+ in mconcat [ para $ spcSep [ "Low", "German", "greeting" ]
59+ , codeBlockWith attr' code'
60+ ]
61+ , "Source block with babel arguments" =:
62+ T.unlines [ "#+BEGIN_SRC emacs-lisp :exports both"
63+ , "(progn (message \"Hello, World!\")"
64+ , " (+ 23 42))"
65+ , "#+END_SRC" ] =?>
66+ let classes = [ "commonlisp" ] -- as kate doesn't know emacs-lisp syntax
67+ params = [ ("org-language", "emacs-lisp")
68+ , ("exports", "both")
69+ ]
70+ code' = unlines [ "(progn (message \"Hello, World!\")"
71+ , " (+ 23 42))" ]
72+ in codeBlockWith ("", classes, params) code'
73+
74+ , "Source block with results and :exports both" =:
75+ T.unlines [ "#+BEGIN_SRC emacs-lisp :exports both"
76+ , "(progn (message \"Hello, World!\")"
77+ , " (+ 23 42))"
78+ , "#+END_SRC"
79+ , ""
80+ , "#+RESULTS:"
81+ , ": 65"] =?>
82+ let classes = [ "commonlisp" ]
83+ params = [ ("org-language", "emacs-lisp")
84+ , ("exports", "both")
85+ ]
86+ code' = unlines [ "(progn (message \"Hello, World!\")"
87+ , " (+ 23 42))" ]
88+ results' = "65\n"
89+ in codeBlockWith ("", classes, params) code'
90+ <>
91+ codeBlockWith ("", ["example"], []) results'
92+
93+ , "Source block with results and :exports code" =:
94+ T.unlines [ "#+BEGIN_SRC emacs-lisp :exports code"
95+ , "(progn (message \"Hello, World!\")"
96+ , " (+ 23 42))"
97+ , "#+END_SRC"
98+ , ""
99+ , "#+RESULTS:"
100+ , ": 65" ] =?>
101+ let classes = [ "commonlisp" ]
102+ params = [ ("org-language", "emacs-lisp")
103+ , ("exports", "code")
104+ ]
105+ code' = unlines [ "(progn (message \"Hello, World!\")"
106+ , " (+ 23 42))" ]
107+ in codeBlockWith ("", classes, params) code'
108+
109+ , "Source block with results and :exports results" =:
110+ T.unlines [ "#+BEGIN_SRC emacs-lisp :exports results"
111+ , "(progn (message \"Hello, World!\")"
112+ , " (+ 23 42))"
113+ , "#+END_SRC"
114+ , ""
115+ , "#+RESULTS:"
116+ , ": 65" ] =?>
117+ let results' = "65\n"
118+ in codeBlockWith ("", ["example"], []) results'
119+
120+ , "Source block with results and :exports none" =:
121+ T.unlines [ "#+BEGIN_SRC emacs-lisp :exports none"
122+ , "(progn (message \"Hello, World!\")"
123+ , " (+ 23 42))"
124+ , "#+END_SRC"
125+ , ""
126+ , "#+RESULTS:"
127+ , ": 65" ] =?>
128+ (mempty :: Blocks)
129+
130+ , "Source block with toggling header arguments" =:
131+ T.unlines [ "#+BEGIN_SRC sh :noeval"
132+ , "echo $HOME"
133+ , "#+END_SRC"
134+ ] =?>
135+ let classes = [ "bash" ]
136+ params = [ ("org-language", "sh"), ("noeval", "yes") ]
137+ in codeBlockWith ("", classes, params) "echo $HOME\n"
138+
139+ , "Source block with line number switch" =:
140+ T.unlines [ "#+BEGIN_SRC sh -n 10"
141+ , ":() { :|:& };:"
142+ , "#+END_SRC"
143+ ] =?>
144+ let classes = [ "bash", "numberLines" ]
145+ params = [ ("org-language", "sh"), ("startFrom", "10") ]
146+ in codeBlockWith ("", classes, params) ":() { :|:& };:\n"
147+
148+ , "Source block with multi-word parameter values" =:
149+ T.unlines [ "#+BEGIN_SRC dot :cmdline -Kdot -Tpng "
150+ , "digraph { id [label=\"ID\"] }"
151+ , "#+END_SRC"
152+ ] =?>
153+ let classes = [ "dot" ]
154+ params = [ ("cmdline", "-Kdot -Tpng") ]
155+ in codeBlockWith ("", classes, params) "digraph { id [label=\"ID\"] }\n"
156+
157+ , "Example block" =:
158+ T.unlines [ "#+begin_example"
159+ , "A chosen representation of"
160+ , "a rule."
161+ , "#+eND_exAMPle"
162+ ] =?>
163+ codeBlockWith ("", ["example"], [])
164+ "A chosen representation of\na rule.\n"
165+
166+ , "Code block with caption" =:
167+ T.unlines [ "#+CAPTION: Functor laws in Haskell"
168+ , "#+NAME: functor-laws"
169+ , "#+BEGIN_SRC haskell"
170+ , "fmap id = id"
171+ , "fmap (p . q) = (fmap p) . (fmap q)"
172+ , "#+END_SRC"
173+ ] =?>
174+ divWith
175+ nullAttr
176+ (mappend
177+ (plain $ spanWith ("", ["label"], [])
178+ (spcSep [ "Functor", "laws", "in", "Haskell" ]))
179+ (codeBlockWith ("functor-laws", ["haskell"], [])
180+ (unlines [ "fmap id = id"
181+ , "fmap (p . q) = (fmap p) . (fmap q)"
182+ ])))
183+
184+ , "Non-letter chars in source block parameters" =:
185+ T.unlines [ "#+BEGIN_SRC C :tangle xxxx.c :city Zürich"
186+ , "code body"
187+ , "#+END_SRC"
188+ ] =?>
189+ let params = [ ("org-language", "C")
190+ , ("tangle", "xxxx.c")
191+ , ("city", "Zürich")
192+ ]
193+ in codeBlockWith ( "", ["c"], params) "code body\n"
194+ ]
test/Tests/Readers/Org/Block/Figure.hs.orignew file+57
@@ -0,0 +1,57 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block.Figure (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:))
7+import Text.Pandoc.Builder (image, imageWith, para)
8+import qualified Data.Text as T
9+
10+tests :: [TestTree]
11+tests =
12+ [ "Figure" =:
13+ T.unlines [ "#+caption: A very courageous man."
14+ , "#+name: goodguy"
15+ , "[[file:edward.jpg]]"
16+ ] =?>
17+ para (image "edward.jpg" "fig:goodguy" "A very courageous man.")
18+
19+ , "Figure with no name" =:
20+ T.unlines [ "#+caption: I've been through the desert on this"
21+ , "[[file:horse.png]]"
22+ ] =?>
23+ para (image "horse.png" "fig:" "I've been through the desert on this")
24+
25+ , "Figure with `fig:` prefix in name" =:
26+ T.unlines [ "#+caption: Used as a metapher in evolutionary biology."
27+ , "#+name: fig:redqueen"
28+ , "[[./the-red-queen.jpg]]"
29+ ] =?>
30+ para (image "./the-red-queen.jpg" "fig:redqueen"
31+ "Used as a metapher in evolutionary biology.")
32+
33+ , "Figure with HTML attributes" =:
34+ T.unlines [ "#+CAPTION: mah brain just explodid"
35+ , "#+NAME: lambdacat"
36+ , "#+ATTR_HTML: :style color: blue :role button"
37+ , "[[file:lambdacat.jpg]]"
38+ ] =?>
39+ let kv = [("style", "color: blue"), ("role", "button")]
40+ name = "fig:lambdacat"
41+ caption = "mah brain just explodid"
42+ in para (imageWith (mempty, mempty, kv) "lambdacat.jpg" name caption)
43+
44+ , "Labelled figure" =:
45+ T.unlines [ "#+CAPTION: My figure"
46+ , "#+LABEL: fig:myfig"
47+ , "[[file:blub.png]]"
48+ ] =?>
49+ let attr = ("fig:myfig", mempty, mempty)
50+ in para (imageWith attr "blub.png" "fig:" "My figure")
51+
52+ , "Figure with empty caption" =:
53+ T.unlines [ "#+CAPTION:"
54+ , "[[file:guess.jpg]]"
55+ ] =?>
56+ para (image "guess.jpg" "fig:" "")
57+ ]
test/Tests/Readers/Org/Block/Header.hs.orignew file+182
@@ -0,0 +1,182 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block.Header (tests) where
3+
4+import Test.Tasty (TestTree, testGroup)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep, tagSpan)
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+
10+tests :: [TestTree]
11+tests =
12+ [ "First Level Header" =:
13+ "* Headline\n" =?>
14+ headerWith ("headline", [], []) 1 "Headline"
15+
16+ , "Third Level Header" =:
17+ "*** Third Level Headline\n" =?>
18+ headerWith ("third-level-headline", [], [])
19+ 3
20+ ("Third" <> space <> "Level" <> space <> "Headline")
21+
22+ , "Compact Headers with Paragraph" =:
23+ T.unlines [ "* First Level"
24+ , "** Second Level"
25+ , " Text"
26+ ] =?>
27+ mconcat [ headerWith ("first-level", [], [])
28+ 1
29+ ("First" <> space <> "Level")
30+ , headerWith ("second-level", [], [])
31+ 2
32+ ("Second" <> space <> "Level")
33+ , para "Text"
34+ ]
35+
36+ , "Separated Headers with Paragraph" =:
37+ T.unlines [ "* First Level"
38+ , ""
39+ , "** Second Level"
40+ , ""
41+ , " Text"
42+ ] =?>
43+ mconcat [ headerWith ("first-level", [], [])
44+ 1
45+ ("First" <> space <> "Level")
46+ , headerWith ("second-level", [], [])
47+ 2
48+ ("Second" <> space <> "Level")
49+ , para "Text"
50+ ]
51+
52+ , "Headers not preceded by a blank line" =:
53+ T.unlines [ "** eat dinner"
54+ , "Spaghetti and meatballs tonight."
55+ , "** walk dog"
56+ ] =?>
57+ mconcat [ headerWith ("eat-dinner", [], [])
58+ 2
59+ ("eat" <> space <> "dinner")
60+ , para $ spcSep [ "Spaghetti", "and", "meatballs", "tonight." ]
61+ , headerWith ("walk-dog", [], [])
62+ 2
63+ ("walk" <> space <> "dog")
64+ ]
65+
66+ , testGroup "Todo keywords"
67+ [ "Header with known todo keyword" =:
68+ "* TODO header" =?>
69+ let todoSpan = spanWith ("", ["todo", "TODO"], []) "TODO"
70+ in headerWith ("header", [], []) 1 (todoSpan <> space <> "header")
71+
72+ , "Header marked as done" =:
73+ "* DONE header" =?>
74+ let todoSpan = spanWith ("", ["done", "DONE"], []) "DONE"
75+ in headerWith ("header", [], []) 1 (todoSpan <> space <> "header")
76+
77+ , "Header with unknown todo keyword" =:
78+ "* WAITING header" =?>
79+ headerWith ("waiting-header", [], []) 1 "WAITING header"
80+
81+ , "Custom todo keywords" =:
82+ T.unlines [ "#+TODO: WAITING CANCELLED"
83+ , "* WAITING compile"
84+ , "* CANCELLED lunch"
85+ ] =?>
86+ let todoSpan = spanWith ("", ["todo", "WAITING"], []) "WAITING"
87+ doneSpan = spanWith ("", ["done", "CANCELLED"], []) "CANCELLED"
88+ in headerWith ("compile", [], []) 1 (todoSpan <> space <> "compile")
89+ <> headerWith ("lunch", [], []) 1 (doneSpan <> space <> "lunch")
90+
91+ , "Custom todo keywords with multiple done-states" =:
92+ T.unlines [ "#+TODO: WAITING | DONE CANCELLED "
93+ , "* WAITING compile"
94+ , "* CANCELLED lunch"
95+ , "* DONE todo-feature"
96+ ] =?>
97+ let waiting = spanWith ("", ["todo", "WAITING"], []) "WAITING"
98+ cancelled = spanWith ("", ["done", "CANCELLED"], []) "CANCELLED"
99+ done = spanWith ("", ["done", "DONE"], []) "DONE"
100+ in headerWith ("compile", [], []) 1 (waiting <> space <> "compile")
101+ <> headerWith ("lunch", [], []) 1 (cancelled <> space <> "lunch")
102+ <> headerWith ("todo-feature", [], []) 1 (done <> space <> "todo-feature")
103+ ]
104+
105+ , "Tagged headers" =:
106+ T.unlines [ "* Personal :PERSONAL:"
107+ , "** Call Mom :@PHONE:"
108+ , "** Call John :@PHONE:JOHN: "
109+ ] =?>
110+ mconcat [ headerWith ("personal", [], [])
111+ 1
112+ ("Personal " <> tagSpan "PERSONAL")
113+ , headerWith ("call-mom", [], [])
114+ 2
115+ ("Call Mom " <> tagSpan "@PHONE")
116+ , headerWith ("call-john", [], [])
117+ 2
118+ ("Call John " <> tagSpan "@PHONE" <> "\160" <> tagSpan "JOHN")
119+ ]
120+
121+ , "Untagged header containing colons" =:
122+ "* This: is not: tagged" =?>
123+ headerWith ("this-is-not-tagged", [], []) 1 "This: is not: tagged"
124+
125+ , "Header starting with strokeout text" =:
126+ T.unlines [ "foo"
127+ , ""
128+ , "* +thing+ other thing"
129+ ] =?>
130+ mconcat [ para "foo"
131+ , headerWith ("thing-other-thing", [], [])
132+ 1
133+ (strikeout "thing" <> " other thing")
134+ ]
135+
136+ , "Comment Trees" =:
137+ T.unlines [ "* COMMENT A comment tree"
138+ , " Not much going on here"
139+ , "** This will be dropped"
140+ , "* Comment tree above"
141+ ] =?>
142+ headerWith ("comment-tree-above", [], []) 1 "Comment tree above"
143+
144+ , "Nothing but a COMMENT header" =:
145+ "* COMMENT Test" =?>
146+ (mempty::Blocks)
147+
148+ , "Tree with :noexport:" =:
149+ T.unlines [ "* Should be ignored :archive:noexport:old:"
150+ , "** Old stuff"
151+ , " This is not going to be exported"
152+ ] =?>
153+ (mempty::Blocks)
154+
155+ , "Subtree with :noexport:" =:
156+ T.unlines [ "* Exported"
157+ , "** This isn't exported :noexport:"
158+ , "*** This neither"
159+ , "** But this is"
160+ ] =?>
161+ mconcat [ headerWith ("exported", [], []) 1 "Exported"
162+ , headerWith ("but-this-is", [], []) 2 "But this is"
163+ ]
164+
165+ , "Preferences are treated as header attributes" =:
166+ T.unlines [ "* foo"
167+ , " :PROPERTIES:"
168+ , " :custom_id: fubar"
169+ , " :bar: baz"
170+ , " :END:"
171+ ] =?>
172+ headerWith ("fubar", [], [("bar", "baz")]) 1 "foo"
173+
174+
175+ , "Headers marked with a unnumbered property get a class of the same name" =:
176+ T.unlines [ "* Not numbered"
177+ , " :PROPERTIES:"
178+ , " :UNNUMBERED: t"
179+ , " :END:"
180+ ] =?>
181+ headerWith ("not-numbered", ["unnumbered"], []) 1 "Not numbered"
182+ ]
test/Tests/Readers/Org/Block/List.hs.orignew file+244
@@ -0,0 +1,244 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block.List (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep)
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+
10+tests :: [TestTree]
11+tests =
12+ [ "Simple Bullet Lists" =:
13+ ("- Item1\n" <>
14+ "- Item2\n") =?>
15+ bulletList [ plain "Item1"
16+ , plain "Item2"
17+ ]
18+
19+ , "Indented Bullet Lists" =:
20+ (" - Item1\n" <>
21+ " - Item2\n") =?>
22+ bulletList [ plain "Item1"
23+ , plain "Item2"
24+ ]
25+
26+ , "Unindented *" =:
27+ ("- Item1\n" <>
28+ "* Item2\n") =?>
29+ bulletList [ plain "Item1"
30+ ] <>
31+ headerWith ("item2", [], []) 1 "Item2"
32+
33+ , "Multi-line Bullet Lists" =:
34+ ("- *Fat\n" <>
35+ " Tony*\n" <>
36+ "- /Sideshow\n" <>
37+ " Bob/") =?>
38+ bulletList [ plain $ strong ("Fat" <> softbreak <> "Tony")
39+ , plain $ emph ("Sideshow" <> softbreak <> "Bob")
40+ ]
41+
42+ , "Nested Bullet Lists" =:
43+ ("- Discovery\n" <>
44+ " + One More Time\n" <>
45+ " + Harder, Better, Faster, Stronger\n" <>
46+ "- Homework\n" <>
47+ " + Around the World\n"<>
48+ "- Human After All\n" <>
49+ " + Technologic\n" <>
50+ " + Robot Rock\n") =?>
51+ bulletList [ mconcat
52+ [ plain "Discovery"
53+ , bulletList [ plain ("One" <> space <>
54+ "More" <> space <>
55+ "Time")
56+ , plain ("Harder," <> space <>
57+ "Better," <> space <>
58+ "Faster," <> space <>
59+ "Stronger")
60+ ]
61+ ]
62+ , mconcat
63+ [ plain "Homework"
64+ , bulletList [ plain ("Around" <> space <>
65+ "the" <> space <>
66+ "World")
67+ ]
68+ ]
69+ , mconcat
70+ [ plain ("Human" <> space <> "After" <> space <> "All")
71+ , bulletList [ plain "Technologic"
72+ , plain ("Robot" <> space <> "Rock")
73+ ]
74+ ]
75+ ]
76+
77+ , "Bullet List with Decreasing Indent" =:
78+ " - Discovery\n\
79+ \ - Human After All\n" =?>
80+ mconcat [ bulletList [ plain "Discovery" ]
81+ , bulletList [ plain ("Human" <> space <> "After" <> space <> "All")]
82+ ]
83+
84+ , "Header follows Bullet List" =:
85+ " - Discovery\n\
86+ \ - Human After All\n\
87+ \* Homework" =?>
88+ mconcat [ bulletList [ plain "Discovery"
89+ , plain ("Human" <> space <> "After" <> space <> "All")
90+ ]
91+ , headerWith ("homework", [], []) 1 "Homework"
92+ ]
93+
94+ , "Bullet List Unindented with trailing Header" =:
95+ "- Discovery\n\
96+ \- Homework\n\
97+ \* NotValidListItem" =?>
98+ mconcat [ bulletList [ plain "Discovery"
99+ , plain "Homework"
100+ ]
101+ , headerWith ("notvalidlistitem", [], []) 1 "NotValidListItem"
102+ ]
103+
104+ , "Empty bullet points" =:
105+ T.unlines [ "-"
106+ , "- "
107+ ] =?>
108+ bulletList [ plain "", plain "" ]
109+
110+ , "Simple Ordered List" =:
111+ ("1. Item1\n" <>
112+ "2. Item2\n") =?>
113+ let listStyle = (1, DefaultStyle, DefaultDelim)
114+ listStructure = [ plain "Item1"
115+ , plain "Item2"
116+ ]
117+ in orderedListWith listStyle listStructure
118+
119+ , "Simple Ordered List with Parens" =:
120+ ("1) Item1\n" <>
121+ "2) Item2\n") =?>
122+ let listStyle = (1, DefaultStyle, DefaultDelim)
123+ listStructure = [ plain "Item1"
124+ , plain "Item2"
125+ ]
126+ in orderedListWith listStyle listStructure
127+
128+ , "Indented Ordered List" =:
129+ (" 1. Item1\n" <>
130+ " 2. Item2\n") =?>
131+ let listStyle = (1, DefaultStyle, DefaultDelim)
132+ listStructure = [ plain "Item1"
133+ , plain "Item2"
134+ ]
135+ in orderedListWith listStyle listStructure
136+
137+ , "Empty ordered list item" =:
138+ T.unlines [ "1."
139+ , "3. "
140+ ] =?>
141+ orderedList [ plain "", plain "" ]
142+
143+ , "Nested Ordered Lists" =:
144+ ("1. One\n" <>
145+ " 1. One-One\n" <>
146+ " 2. One-Two\n" <>
147+ "2. Two\n" <>
148+ " 1. Two-One\n"<>
149+ " 2. Two-Two\n") =?>
150+ let listStyle = (1, DefaultStyle, DefaultDelim)
151+ listStructure = [ mconcat
152+ [ plain "One"
153+ , orderedList [ plain "One-One"
154+ , plain "One-Two"
155+ ]
156+ ]
157+ , mconcat
158+ [ plain "Two"
159+ , orderedList [ plain "Two-One"
160+ , plain "Two-Two"
161+ ]
162+ ]
163+ ]
164+ in orderedListWith listStyle listStructure
165+
166+ , "Ordered List in Bullet List" =:
167+ ("- Emacs\n" <>
168+ " 1. Org\n") =?>
169+ bulletList [ plain "Emacs" <>
170+ orderedList [ plain "Org"]
171+ ]
172+
173+ , "Bullet List in Ordered List" =:
174+ ("1. GNU\n" <>
175+ " - Freedom\n") =?>
176+ orderedList [ plain "GNU" <> bulletList [ plain "Freedom" ] ]
177+
178+ , "Definition List" =:
179+ T.unlines [ "- PLL :: phase-locked loop"
180+ , "- TTL ::"
181+ , " transistor-transistor logic"
182+ , "- PSK :: phase-shift keying"
183+ , ""
184+ , " a digital modulation scheme"
185+ ] =?>
186+ definitionList [ ("PLL", [ plain $ "phase-locked" <> space <> "loop" ])
187+ , ("TTL", [ plain $ "transistor-transistor" <> space <>
188+ "logic" ])
189+ , ("PSK", [ mconcat
190+ [ para $ "phase-shift" <> space <> "keying"
191+ , para $ spcSep [ "a", "digital"
192+ , "modulation", "scheme" ]
193+ ]
194+ ])
195+ ]
196+ , "Definition list with multi-word term" =:
197+ " - Elijah Wood :: He plays Frodo" =?>
198+ definitionList [ ("Elijah" <> space <> "Wood", [plain $ "He" <> space <> "plays" <> space <> "Frodo"])]
199+ , "Compact definition list" =:
200+ T.unlines [ "- ATP :: adenosine 5' triphosphate"
201+ , "- DNA :: deoxyribonucleic acid"
202+ , "- PCR :: polymerase chain reaction"
203+ , ""
204+ ] =?>
205+ definitionList
206+ [ ("ATP", [ plain $ spcSep [ "adenosine", "5'", "triphosphate" ] ])
207+ , ("DNA", [ plain $ spcSep [ "deoxyribonucleic", "acid" ] ])
208+ , ("PCR", [ plain $ spcSep [ "polymerase", "chain", "reaction" ] ])
209+ ]
210+
211+ , "Definition List With Trailing Header" =:
212+ "- definition :: list\n\
213+ \- cool :: defs\n\
214+ \* header" =?>
215+ mconcat [ definitionList [ ("definition", [plain "list"])
216+ , ("cool", [plain "defs"])
217+ ]
218+ , headerWith ("header", [], []) 1 "header"
219+ ]
220+
221+ , "Definition lists double-colon markers must be surrounded by whitespace" =:
222+ "- std::cout" =?>
223+ bulletList [ plain "std::cout" ]
224+
225+ , "Loose bullet list" =:
226+ T.unlines [ "- apple"
227+ , ""
228+ , "- orange"
229+ , ""
230+ , "- peach"
231+ ] =?>
232+ bulletList [ para "apple"
233+ , para "orange"
234+ , para "peach"
235+ ]
236+
237+ , "Recognize preceding paragraphs in non-list contexts" =:
238+ T.unlines [ "CLOSED: [2015-10-19 Mon 15:03]"
239+ , "- Note taken on [2015-10-19 Mon 13:24]"
240+ ] =?>
241+ mconcat [ para "CLOSED: [2015-10-19 Mon 15:03]"
242+ , bulletList [ plain "Note taken on [2015-10-19 Mon 13:24]" ]
243+ ]
244+ ]
test/Tests/Readers/Org/Block/Table.hs.orignew file+150
@@ -0,0 +1,150 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block.Table (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep)
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+
10+simpleTable' :: Int
11+ -> [Blocks]
12+ -> [[Blocks]]
13+ -> Blocks
14+simpleTable' n = table "" (replicate n (AlignDefault, 0.0))
15+
16+tests :: [TestTree]
17+tests =
18+ [ "Single cell table" =:
19+ "|Test|" =?>
20+ simpleTable' 1 mempty [[plain "Test"]]
21+
22+ , "Multi cell table" =:
23+ "| One | Two |" =?>
24+ simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
25+
26+ , "Multi line table" =:
27+ T.unlines [ "| One |"
28+ , "| Two |"
29+ , "| Three |"
30+ ] =?>
31+ simpleTable' 1 mempty
32+ [ [ plain "One" ]
33+ , [ plain "Two" ]
34+ , [ plain "Three" ]
35+ ]
36+
37+ , "Empty table" =:
38+ "||" =?>
39+ simpleTable' 1 mempty [[mempty]]
40+
41+ , "Glider Table" =:
42+ T.unlines [ "| 1 | 0 | 0 |"
43+ , "| 0 | 1 | 1 |"
44+ , "| 1 | 1 | 0 |"
45+ ] =?>
46+ simpleTable' 3 mempty
47+ [ [ plain "1", plain "0", plain "0" ]
48+ , [ plain "0", plain "1", plain "1" ]
49+ , [ plain "1", plain "1", plain "0" ]
50+ ]
51+
52+ , "Table between Paragraphs" =:
53+ T.unlines [ "Before"
54+ , "| One | Two |"
55+ , "After"
56+ ] =?>
57+ mconcat [ para "Before"
58+ , simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
59+ , para "After"
60+ ]
61+
62+ , "Table with Header" =:
63+ T.unlines [ "| Species | Status |"
64+ , "|--------------+--------------|"
65+ , "| cervisiae | domesticated |"
66+ , "| paradoxus | wild |"
67+ ] =?>
68+ simpleTable [ plain "Species", plain "Status" ]
69+ [ [ plain "cervisiae", plain "domesticated" ]
70+ , [ plain "paradoxus", plain "wild" ]
71+ ]
72+
73+ , "Table with final hline" =:
74+ T.unlines [ "| cervisiae | domesticated |"
75+ , "| paradoxus | wild |"
76+ , "|--------------+--------------|"
77+ ] =?>
78+ simpleTable' 2 mempty
79+ [ [ plain "cervisiae", plain "domesticated" ]
80+ , [ plain "paradoxus", plain "wild" ]
81+ ]
82+
83+ , "Table in a box" =:
84+ T.unlines [ "|---------|---------|"
85+ , "| static | Haskell |"
86+ , "| dynamic | Lisp |"
87+ , "|---------+---------|"
88+ ] =?>
89+ simpleTable' 2 mempty
90+ [ [ plain "static", plain "Haskell" ]
91+ , [ plain "dynamic", plain "Lisp" ]
92+ ]
93+
94+ , "Table with empty cells" =:
95+ "|||c|" =?>
96+ simpleTable' 3 mempty [[mempty, mempty, plain "c"]]
97+
98+ , "Table with empty rows" =:
99+ T.unlines [ "| first |"
100+ , "| |"
101+ , "| third |"
102+ ] =?>
103+ simpleTable' 1 mempty [[plain "first"], [mempty], [plain "third"]]
104+
105+ , "Table with alignment row" =:
106+ T.unlines [ "| Numbers | Text | More |"
107+ , "| <c> | <r> | |"
108+ , "| 1 | One | foo |"
109+ , "| 2 | Two | bar |"
110+ ] =?>
111+ table "" (zip [AlignCenter, AlignRight, AlignDefault] [0, 0, 0])
112+ []
113+ [ [ plain "Numbers", plain "Text", plain "More" ]
114+ , [ plain "1" , plain "One" , plain "foo" ]
115+ , [ plain "2" , plain "Two" , plain "bar" ]
116+ ]
117+
118+ , "Pipe within text doesn't start a table" =:
119+ "Ceci n'est pas une | pipe " =?>
120+ para (spcSep [ "Ceci", "n'est", "pas", "une", "|", "pipe" ])
121+
122+ , "Missing pipe at end of row" =:
123+ "|incomplete-but-valid" =?>
124+ simpleTable' 1 mempty [ [ plain "incomplete-but-valid" ] ]
125+
126+ , "Table with differing row lengths" =:
127+ T.unlines [ "| Numbers | Text "
128+ , "|-"
129+ , "| <c> | <r> |"
130+ , "| 1 | One | foo |"
131+ , "| 2"
132+ ] =?>
133+ table "" (zip [AlignCenter, AlignRight] [0, 0])
134+ [ plain "Numbers", plain "Text" ]
135+ [ [ plain "1" , plain "One" , plain "foo" ]
136+ , [ plain "2" ]
137+ ]
138+
139+ , "Table with caption" =:
140+ T.unlines [ "#+CAPTION: Hitchhiker's Multiplication Table"
141+ , "| x | 6 |"
142+ , "| 9 | 42 |"
143+ ] =?>
144+ table "Hitchhiker's Multiplication Table"
145+ [(AlignDefault, 0), (AlignDefault, 0)]
146+ []
147+ [ [ plain "x", plain "6" ]
148+ , [ plain "9", plain "42" ]
149+ ]
150+ ]
test/Tests/Readers/Org/Directive.hs.orignew file+199
@@ -0,0 +1,199 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Directive (tests) where
3+
4+import Data.Time (UTCTime (UTCTime), secondsToDiffTime)
5+import Data.Time.Calendar (Day (ModifiedJulianDay))
6+import Test.Tasty (TestTree, testGroup)
7+import Tests.Helpers ((=?>), ToString, purely, test)
8+import Tests.Readers.Org.Shared ((=:), tagSpan)
9+import Text.Pandoc
10+import Text.Pandoc.Builder
11+import qualified Data.ByteString as BS
12+import qualified Data.Text as T
13+
14+testWithFiles :: (ToString c)
15+ => [(FilePath, BS.ByteString)]
16+ -> String -- ^ name of test case
17+ -> (T.Text, c) -- ^ (input, expected value)
18+ -> TestTree
19+testWithFiles fileDefs = test (orgWithFiles fileDefs)
20+ where
21+orgWithFiles :: [(FilePath, BS.ByteString)] -> T.Text -> Pandoc
22+orgWithFiles fileDefs input =
23+ let readOrg' = readOrg def{ readerExtensions = getDefaultExtensions "org" }
24+ in flip purely input $ \inp -> do
25+ modifyPureState (\st -> st { stFiles = files fileDefs })
26+ readOrg' inp
27+
28+
29+files :: [(FilePath, BS.ByteString)] -> FileTree
30+files fileDefs =
31+ let dummyTime = UTCTime (ModifiedJulianDay 125) (secondsToDiffTime 0)
32+ in foldr (\(fp, bs) -> insertInFileTree fp (FileInfo dummyTime bs))
33+ mempty fileDefs
34+
35+tests :: [TestTree]
36+tests =
37+ [ testGroup "export options"
38+ [ "disable simple sub/superscript syntax" =:
39+ T.unlines [ "#+OPTIONS: ^:nil"
40+ , "a^b"
41+ ] =?>
42+ para "a^b"
43+
44+ , "directly select drawers to be exported" =:
45+ T.unlines [ "#+OPTIONS: d:(\"IMPORTANT\")"
46+ , ":IMPORTANT:"
47+ , "23"
48+ , ":END:"
49+ , ":BORING:"
50+ , "very boring"
51+ , ":END:"
52+ ] =?>
53+ divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "23")
54+
55+ , "exclude drawers from being exported" =:
56+ T.unlines [ "#+OPTIONS: d:(not \"BORING\")"
57+ , ":IMPORTANT:"
58+ , "5"
59+ , ":END:"
60+ , ":BORING:"
61+ , "very boring"
62+ , ":END:"
63+ ] =?>
64+ divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "5")
65+
66+ , "don't include archive trees" =:
67+ T.unlines [ "#+OPTIONS: arch:nil"
68+ , "* old :ARCHIVE:"
69+ ] =?>
70+ (mempty ::Blocks)
71+
72+ , "include complete archive trees" =:
73+ T.unlines [ "#+OPTIONS: arch:t"
74+ , "* old :ARCHIVE:"
75+ , " boring"
76+ ] =?>
77+ mconcat [ headerWith ("old", [], mempty) 1
78+ ("old" <> space <> tagSpan "ARCHIVE")
79+ , para "boring"
80+ ]
81+
82+ , "include archive tree header only" =:
83+ T.unlines [ "#+OPTIONS: arch:headline"
84+ , "* old :ARCHIVE:"
85+ , " boring"
86+ ] =?>
87+ headerWith ("old", [], mempty) 1 ("old" <> space <> tagSpan "ARCHIVE")
88+
89+ , "limit headline depth" =:
90+ T.unlines [ "#+OPTIONS: H:2"
91+ , "* top-level section"
92+ , "** subsection"
93+ , "*** list item 1"
94+ , "*** list item 2"
95+ ] =?>
96+ mconcat [ headerWith ("top-level-section", [], []) 1 "top-level section"
97+ , headerWith ("subsection", [], []) 2 "subsection"
98+ , orderedList [ para "list item 1", para "list item 2" ]
99+ ]
100+
101+ , "turn all headlines into lists" =:
102+ T.unlines [ "#+OPTIONS: H:0"
103+ , "first block"
104+ , "* top-level section 1"
105+ , "** subsection"
106+ , "* top-level section 2"
107+ ] =?>
108+ mconcat [ para "first block"
109+ , orderedList
110+ [ para "top-level section 1" <>
111+ orderedList [ para "subsection" ]
112+ , para "top-level section 2" ]
113+ ]
114+
115+ , "preserve linebreaks as hard breaks" =:
116+ T.unlines [ "#+OPTIONS: \\n:t"
117+ , "first"
118+ , "second"
119+ ] =?>
120+ para ("first" <> linebreak <> "second")
121+
122+ , "disable author export" =:
123+ T.unlines [ "#+OPTIONS: author:nil"
124+ , "#+AUTHOR: ShyGuy"
125+ ] =?>
126+ Pandoc nullMeta mempty
127+
128+ , "disable creator export" =:
129+ T.unlines [ "#+OPTIONS: creator:nil"
130+ , "#+creator: The Architect"
131+ ] =?>
132+ Pandoc nullMeta mempty
133+
134+ , "disable email export" =:
135+ T.unlines [ "#+OPTIONS: email:nil"
136+ , "#+email: no-mail-please@example.com"
137+ ] =?>
138+ Pandoc nullMeta mempty
139+
140+ , "disable inclusion of todo keywords" =:
141+ T.unlines [ "#+OPTIONS: todo:nil"
142+ , "** DONE todo export"
143+ ] =?>
144+ headerWith ("todo-export", [], []) 2 "todo export"
145+
146+ , "remove tags from headlines" =:
147+ T.unlines [ "#+OPTIONS: tags:nil"
148+ , "* Headline :hello:world:"
149+ ] =?>
150+ headerWith ("headline", [], mempty) 1 "Headline"
151+ ]
152+
153+ , testGroup "Include"
154+ [ testWithFiles [("./other.org", "content of other file\n")]
155+ "file inclusion"
156+ (T.unlines [ "#+include: \"other.org\"" ] =?>
157+ plain "content of other file")
158+
159+ , testWithFiles [("./world.org", "World\n\n")]
160+ "Included file belongs to item"
161+ (T.unlines [ "- Hello,\n #+include: \"world.org\"" ] =?>
162+ bulletList [para "Hello," <> para "World"])
163+
164+ , testWithFiles [("./level3.org", "*** Level3\n\n")]
165+ "Default include preserves level"
166+ (T.unlines [ "#+include: \"level3.org\"" ] =?>
167+ headerWith ("level3", [], []) 3 "Level3")
168+
169+ , testWithFiles [("./level3.org", "*** Level3\n\n")]
170+ "Minlevel shifts level"
171+ (T.unlines [ "#+include: \"level3.org\" :minlevel 1" ] =?>
172+ headerWith ("level3", [], []) 1 "Level3")
173+
174+ , testWithFiles [("./src.hs", "putStrLn outString\n")]
175+ "Include file as source code snippet"
176+ (T.unlines [ "#+include: \"src.hs\" src haskell" ] =?>
177+ codeBlockWith ("", ["haskell"], []) "putStrLn outString\n")
178+
179+ , testWithFiles [("./export-latex.org", "\\emph{Hello}\n")]
180+ "Include file as export snippet"
181+ (T.unlines [ "#+include: \"export-latex.org\" export latex" ] =?>
182+ rawBlock "latex" "\\emph{Hello}\n")
183+
184+ , testWithFiles [("./subdir/foo-bar.latex", "foo\n"),
185+ ("./hello.lisp", "(print \"Hello!\")\n")
186+ ]
187+ "include directive is limited to one line"
188+ (T.unlines [ "#+INCLUDE: \"hello.lisp\" src lisp"
189+ , "#+include: \"subdir/foo-bar.latex\" export latex"
190+ , "bar"
191+ ] =?>
192+ mconcat
193+ [ codeBlockWith ("", ["lisp"], []) "(print \"Hello!\")\n"
194+ , rawBlock "latex" "foo\n"
195+ , para "bar"
196+ ]
197+ )
198+ ]
199+ ]
test/Tests/Readers/Org/Inline.hs.orignew file+352
@@ -0,0 +1,352 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Inline (tests) where
3+
4+import Data.List (intersperse)
5+import Test.Tasty (TestTree, testGroup)
6+import Tests.Helpers ((=?>))
7+import Tests.Readers.Org.Shared ((=:), spcSep)
8+import Text.Pandoc.Builder
9+import Text.Pandoc.Shared (underlineSpan)
10+import qualified Data.Text as T
11+import qualified Tests.Readers.Org.Inline.Citation as Citation
12+import qualified Tests.Readers.Org.Inline.Note as Note
13+import qualified Tests.Readers.Org.Inline.Smart as Smart
14+
15+tests :: [TestTree]
16+tests =
17+ [ "Plain String" =:
18+ "Hello, World" =?>
19+ para (spcSep [ "Hello,", "World" ])
20+
21+ , "Emphasis" =:
22+ "/Planet Punk/" =?>
23+ para (emph . spcSep $ ["Planet", "Punk"])
24+
25+ , "Strong" =:
26+ "*Cider*" =?>
27+ para (strong "Cider")
28+
29+ , "Strong Emphasis" =:
30+ "/*strength*/" =?>
31+ para (emph . strong $ "strength")
32+
33+ , "Emphasized Strong preceded by space" =:
34+ " */super/*" =?>
35+ para (strong . emph $ "super")
36+
37+ , "Underline" =:
38+ "_underline_" =?>
39+ para (underlineSpan "underline")
40+
41+ , "Strikeout" =:
42+ "+Kill Bill+" =?>
43+ para (strikeout . spcSep $ [ "Kill", "Bill" ])
44+
45+ , "Verbatim" =:
46+ "=Robot.rock()=" =?>
47+ para (code "Robot.rock()")
48+
49+ , "Code" =:
50+ "~word for word~" =?>
51+ para (code "word for word")
52+
53+ , "Math $..$" =:
54+ "$E=mc^2$" =?>
55+ para (math "E=mc^2")
56+
57+ , "Math $$..$$" =:
58+ "$$E=mc^2$$" =?>
59+ para (displayMath "E=mc^2")
60+
61+ , "Math \\[..\\]" =:
62+ "\\[E=ℎν\\]" =?>
63+ para (displayMath "E=ℎν")
64+
65+ , "Math \\(..\\)" =:
66+ "\\(σ_x σ_p ≥ \\frac{ℏ}{2}\\)" =?>
67+ para (math "σ_x σ_p ≥ \\frac{ℏ}{2}")
68+
69+ , "Symbol" =:
70+ "A * symbol" =?>
71+ para (str "A" <> space <> str "*" <> space <> "symbol")
72+
73+ , "Superscript simple expression" =:
74+ "2^-λ" =?>
75+ para (str "2" <> superscript "-λ")
76+
77+ , "Superscript multi char" =:
78+ "2^{n-1}" =?>
79+ para (str "2" <> superscript "n-1")
80+
81+ , "Subscript simple expression" =:
82+ "a_n" =?>
83+ para (str "a" <> subscript "n")
84+
85+ , "Subscript multi char" =:
86+ "a_{n+1}" =?>
87+ para (str "a" <> subscript "n+1")
88+
89+ , "Linebreak" =:
90+ "line \\\\ \nbreak" =?>
91+ para ("line" <> linebreak <> "break")
92+
93+ , "Inline note" =:
94+ "[fn::Schreib mir eine E-Mail]" =?>
95+ para (note $ para "Schreib mir eine E-Mail")
96+
97+ , "Markup-chars not occuring on word break are symbols" =:
98+ T.unlines [ "this+that+ +so+on"
99+ , "seven*eight* nine*"
100+ , "+not+funny+"
101+ ] =?>
102+ para ("this+that+ +so+on" <> softbreak <>
103+ "seven*eight* nine*" <> softbreak <>
104+ strikeout "not+funny")
105+
106+ , "No empty markup" =:
107+ "// ** __ <> == ~~ $$" =?>
108+ para (spcSep [ "//", "**", "__", "<>", "==", "~~", "$$" ])
109+
110+ , "Adherence to Org's rules for markup borders" =:
111+ "/t/& a/ / ./r/ (*l*) /e/! /b/." =?>
112+ para (spcSep [ emph $ "t/&" <> space <> "a"
113+ , "/"
114+ , "./r/"
115+ , "(" <> strong "l" <> ")"
116+ , emph "e" <> "!"
117+ , emph "b" <> "."
118+ ])
119+
120+ , "Quotes are allowed border chars" =:
121+ "/'yep/ *sure\"*" =?>
122+ para (emph "'yep" <> space <> strong "sure\"")
123+
124+ , "Spaces are forbidden border chars" =:
125+ "/nada /" =?>
126+ para "/nada /"
127+
128+ , "Markup should work properly after a blank line" =:
129+ T.unlines ["foo", "", "/bar/"] =?>
130+ para (text "foo") <>
131+ para (emph $ text "bar")
132+
133+ , "Inline math must stay within three lines" =:
134+ T.unlines [ "$a", "b", "c$", "$d", "e", "f", "g$" ] =?>
135+ para (math "a\nb\nc" <> softbreak <>
136+ "$d" <> softbreak <> "e" <> softbreak <>
137+ "f" <> softbreak <> "g$")
138+
139+ , "Single-character math" =:
140+ "$a$ $b$! $c$?" =?>
141+ para (spcSep [ math "a"
142+ , "$b$!"
143+ , math "c" <> "?"
144+ ])
145+
146+ , "Markup may not span more than two lines" =:
147+ "/this *is +totally\nnice+ not*\nemph/" =?>
148+ para ("/this" <> space <>
149+ strong ("is" <> space <>
150+ strikeout ("totally" <>
151+ softbreak <> "nice") <>
152+ space <> "not") <>
153+ softbreak <> "emph/")
154+
155+ , "Sub- and superscript expressions" =:
156+ T.unlines [ "a_(a(b)(c)d)"
157+ , "e^(f(g)h)"
158+ , "i_(jk)l)"
159+ , "m^()n"
160+ , "o_{p{q{}r}}"
161+ , "s^{t{u}v}"
162+ , "w_{xy}z}"
163+ , "1^{}2"
164+ , "3_{{}}"
165+ , "4^(a(*b(c*)d))"
166+ ] =?>
167+ para (mconcat $ intersperse softbreak
168+ [ "a" <> subscript "(a(b)(c)d)"
169+ , "e" <> superscript "(f(g)h)"
170+ , "i" <> subscript "(jk)" <> "l)"
171+ , "m" <> superscript "()" <> "n"
172+ , "o" <> subscript "p{q{}r}"
173+ , "s" <> superscript "t{u}v"
174+ , "w" <> subscript "xy" <> "z}"
175+ , "1" <> superscript "" <> "2"
176+ , "3" <> subscript "{}"
177+ , "4" <> superscript ("(a(" <> strong "b(c" <> ")d))")
178+ ])
179+ , "Verbatim text can contain equal signes (=)" =:
180+ "=is_subst = True=" =?>
181+ para (code "is_subst = True")
182+
183+ , testGroup "Images"
184+ [ "Image" =:
185+ "[[./sunset.jpg]]" =?>
186+ para (image "./sunset.jpg" "" "")
187+
188+ , "Image with explicit file: prefix" =:
189+ "[[file:sunrise.jpg]]" =?>
190+ para (image "sunrise.jpg" "" "")
191+
192+ , "Multiple images within a paragraph" =:
193+ T.unlines [ "[[file:sunrise.jpg]]"
194+ , "[[file:sunset.jpg]]"
195+ ] =?>
196+ para ((image "sunrise.jpg" "" "")
197+ <> softbreak
198+ <> (image "sunset.jpg" "" ""))
199+
200+ , "Image with html attributes" =:
201+ T.unlines [ "#+ATTR_HTML: :width 50%"
202+ , "[[file:guinea-pig.gif]]"
203+ ] =?>
204+ para (imageWith ("", [], [("width", "50%")]) "guinea-pig.gif" "" "")
205+ ]
206+
207+ , "Explicit link" =:
208+ "[[http://zeitlens.com/][pseudo-random /nonsense/]]" =?>
209+ para (link "http://zeitlens.com/" ""
210+ ("pseudo-random" <> space <> emph "nonsense"))
211+
212+ , "Self-link" =:
213+ "[[http://zeitlens.com/]]" =?>
214+ para (link "http://zeitlens.com/" "" "http://zeitlens.com/")
215+
216+ , "Absolute file link" =:
217+ "[[/url][hi]]" =?>
218+ para (link "file:///url" "" "hi")
219+
220+ , "Link to file in parent directory" =:
221+ "[[../file.txt][moin]]" =?>
222+ para (link "../file.txt" "" "moin")
223+
224+ , "Empty link (for gitit interop)" =:
225+ "[[][New Link]]" =?>
226+ para (link "" "" "New Link")
227+
228+ , "Image link" =:
229+ "[[sunset.png][file:dusk.svg]]" =?>
230+ para (link "sunset.png" "" (image "dusk.svg" "" ""))
231+
232+ , "Image link with non-image target" =:
233+ "[[http://example.com][./logo.png]]" =?>
234+ para (link "http://example.com" "" (image "./logo.png" "" ""))
235+
236+ , "Plain link" =:
237+ "Posts on http://zeitlens.com/ can be funny at times." =?>
238+ para (spcSep [ "Posts", "on"
239+ , link "http://zeitlens.com/" "" "http://zeitlens.com/"
240+ , "can", "be", "funny", "at", "times."
241+ ])
242+
243+ , "Angle link" =:
244+ "Look at <http://moltkeplatz.de> for fnords." =?>
245+ para (spcSep [ "Look", "at"
246+ , link "http://moltkeplatz.de" "" "http://moltkeplatz.de"
247+ , "for", "fnords."
248+ ])
249+
250+ , "Absolute file link" =:
251+ "[[file:///etc/passwd][passwd]]" =?>
252+ para (link "file:///etc/passwd" "" "passwd")
253+
254+ , "File link" =:
255+ "[[file:target][title]]" =?>
256+ para (link "target" "" "title")
257+
258+ , "Anchor" =:
259+ "<<anchor>> Link here later." =?>
260+ para (spanWith ("anchor", [], []) mempty <>
261+ "Link" <> space <> "here" <> space <> "later.")
262+
263+ , "Inline code block" =:
264+ "src_emacs-lisp{(message \"Hello\")}" =?>
265+ para (codeWith ( ""
266+ , [ "commonlisp" ]
267+ , [ ("org-language", "emacs-lisp") ])
268+ "(message \"Hello\")")
269+
270+ , "Inline code block with arguments" =:
271+ "src_sh[:export both :results output]{echo 'Hello, World'}" =?>
272+ para (codeWith ( ""
273+ , [ "bash" ]
274+ , [ ("org-language", "sh")
275+ , ("export", "both")
276+ , ("results", "output")
277+ ]
278+ )
279+ "echo 'Hello, World'")
280+
281+ , "Inline code block with toggle" =:
282+ "src_sh[:toggle]{echo $HOME}" =?>
283+ para (codeWith ( ""
284+ , [ "bash" ]
285+ , [ ("org-language", "sh")
286+ , ("toggle", "yes")
287+ ]
288+ )
289+ "echo $HOME")
290+
291+ , "Inline LaTeX symbol" =:
292+ "\\dots" =?>
293+ para "…"
294+
295+ , "Inline LaTeX command" =:
296+ "\\textit{Emphasised}" =?>
297+ para (emph "Emphasised")
298+
299+ , "Inline LaTeX command with spaces" =:
300+ "\\emph{Emphasis mine}" =?>
301+ para (emph "Emphasis mine")
302+
303+ , "Inline LaTeX math symbol" =:
304+ "\\tau" =?>
305+ para (emph "τ")
306+
307+ , "Unknown inline LaTeX command" =:
308+ "\\notacommand{foo}" =?>
309+ para (rawInline "latex" "\\notacommand{foo}")
310+
311+ , "Export snippet" =:
312+ "@@html:<kbd>M-x org-agenda</kbd>@@" =?>
313+ para (rawInline "html" "<kbd>M-x org-agenda</kbd>")
314+
315+ , "MathML symbol in LaTeX-style" =:
316+ "There is a hackerspace in Lübeck, Germany, called nbsp (unicode symbol: '\\nbsp')." =?>
317+ para "There is a hackerspace in Lübeck, Germany, called nbsp (unicode symbol: ' ')."
318+
319+ , "MathML symbol in LaTeX-style, including braces" =:
320+ "\\Aacute{}stor" =?>
321+ para "Ástor"
322+
323+ , "MathML copy sign" =:
324+ "\\copy" =?>
325+ para "©"
326+
327+ , "MathML symbols, space separated" =:
328+ "\\ForAll \\Auml" =?>
329+ para "∀ Ä"
330+
331+ , "Macro" =:
332+ T.unlines [ "#+MACRO: HELLO /Hello, $1/"
333+ , "{{{HELLO(World)}}}"
334+ ] =?>
335+ para (emph "Hello, World")
336+
337+ , "Macro repeting its argument" =:
338+ T.unlines [ "#+MACRO: HELLO $1$1"
339+ , "{{{HELLO(moin)}}}"
340+ ] =?>
341+ para "moinmoin"
342+
343+ , "Macro called with too few arguments" =:
344+ T.unlines [ "#+MACRO: HELLO Foo $1 $2 Bar"
345+ , "{{{HELLO()}}}"
346+ ] =?>
347+ para "Foo Bar"
348+
349+ , testGroup "Citations" Citation.tests
350+ , testGroup "Footnotes" Note.tests
351+ , testGroup "Smart punctuation" Smart.tests
352+ ]
test/Tests/Readers/Org/Inline/Citation.hs.orignew file+179
@@ -0,0 +1,179 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Inline.Citation (tests) where
3+
4+import Test.Tasty (TestTree, testGroup)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:))
7+import Text.Pandoc.Builder
8+
9+tests :: [TestTree]
10+tests =
11+ [ testGroup "Markdown-style citations"
12+ [ "Citation" =:
13+ "[@nonexistent]" =?>
14+ let citation = Citation
15+ { citationId = "nonexistent"
16+ , citationPrefix = []
17+ , citationSuffix = []
18+ , citationMode = NormalCitation
19+ , citationNoteNum = 0
20+ , citationHash = 0}
21+ in (para $ cite [citation] "[@nonexistent]")
22+
23+ , "Citation containing text" =:
24+ "[see @item1 p. 34-35]" =?>
25+ let citation = Citation
26+ { citationId = "item1"
27+ , citationPrefix = [Str "see"]
28+ , citationSuffix = [Space ,Str "p.",Space,Str "34-35"]
29+ , citationMode = NormalCitation
30+ , citationNoteNum = 0
31+ , citationHash = 0}
32+ in (para $ cite [citation] "[see @item1 p. 34-35]")
33+ ]
34+
35+ , testGroup "org-ref citations"
36+ [ "simple citation" =:
37+ "cite:pandoc" =?>
38+ let citation = Citation
39+ { citationId = "pandoc"
40+ , citationPrefix = mempty
41+ , citationSuffix = mempty
42+ , citationMode = AuthorInText
43+ , citationNoteNum = 0
44+ , citationHash = 0
45+ }
46+ in (para $ cite [citation] "cite:pandoc")
47+
48+ , "simple citation with underscores" =:
49+ "cite:pandoc_org_ref" =?>
50+ let citation = Citation
51+ { citationId = "pandoc_org_ref"
52+ , citationPrefix = mempty
53+ , citationSuffix = mempty
54+ , citationMode = AuthorInText
55+ , citationNoteNum = 0
56+ , citationHash = 0
57+ }
58+ in (para $ cite [citation] "cite:pandoc_org_ref")
59+
60+ , "simple citation succeeded by comma" =:
61+ "cite:pandoc," =?>
62+ let citation = Citation
63+ { citationId = "pandoc"
64+ , citationPrefix = mempty
65+ , citationSuffix = mempty
66+ , citationMode = AuthorInText
67+ , citationNoteNum = 0
68+ , citationHash = 0
69+ }
70+ in (para $ cite [citation] "cite:pandoc" <> str ",")
71+
72+ , "simple citation succeeded by dot" =:
73+ "cite:pandoc." =?>
74+ let citation = Citation
75+ { citationId = "pandoc"
76+ , citationPrefix = mempty
77+ , citationSuffix = mempty
78+ , citationMode = AuthorInText
79+ , citationNoteNum = 0
80+ , citationHash = 0
81+ }
82+ in (para $ cite [citation] "cite:pandoc" <> str ".")
83+
84+ , "simple citation succeeded by colon" =:
85+ "cite:pandoc:" =?>
86+ let citation = Citation
87+ { citationId = "pandoc"
88+ , citationPrefix = mempty
89+ , citationSuffix = mempty
90+ , citationMode = AuthorInText
91+ , citationNoteNum = 0
92+ , citationHash = 0
93+ }
94+ in (para $ cite [citation] "cite:pandoc" <> str ":")
95+
96+ , "simple citep citation" =:
97+ "citep:pandoc" =?>
98+ let citation = Citation
99+ { citationId = "pandoc"
100+ , citationPrefix = mempty
101+ , citationSuffix = mempty
102+ , citationMode = NormalCitation
103+ , citationNoteNum = 0
104+ , citationHash = 0
105+ }
106+ in (para $ cite [citation] "citep:pandoc")
107+
108+ , "extended citation" =:
109+ "[[citep:Dominik201408][See page 20::, for example]]" =?>
110+ let citation = Citation
111+ { citationId = "Dominik201408"
112+ , citationPrefix = toList "See page 20"
113+ , citationSuffix = toList ", for example"
114+ , citationMode = NormalCitation
115+ , citationNoteNum = 0
116+ , citationHash = 0
117+ }
118+ in (para $ cite [citation] "[[citep:Dominik201408][See page 20::, for example]]")
119+ ]
120+
121+ , testGroup "Berkeley-style citations" $
122+ let pandocCite = Citation
123+ { citationId = "Pandoc"
124+ , citationPrefix = mempty
125+ , citationSuffix = mempty
126+ , citationMode = NormalCitation
127+ , citationNoteNum = 0
128+ , citationHash = 0
129+ }
130+ pandocInText = pandocCite { citationMode = AuthorInText }
131+ dominikCite = Citation
132+ { citationId = "Dominik201408"
133+ , citationPrefix = mempty
134+ , citationSuffix = mempty
135+ , citationMode = NormalCitation
136+ , citationNoteNum = 0
137+ , citationHash = 0
138+ }
139+ dominikInText = dominikCite { citationMode = AuthorInText }
140+ in
141+ [ "Berkeley-style in-text citation" =:
142+ "See @Dominik201408." =?>
143+ para ("See "
144+ <> cite [dominikInText] "@Dominik201408"
145+ <> ".")
146+
147+ , "Berkeley-style parenthetical citation list" =:
148+ "[(cite): see; @Dominik201408;also @Pandoc; and others]" =?>
149+ let pandocCite' = pandocCite {
150+ citationPrefix = toList "also"
151+ , citationSuffix = toList "and others"
152+ }
153+ dominikCite' = dominikCite {
154+ citationPrefix = toList "see"
155+ }
156+ in (para $ cite [dominikCite', pandocCite'] "")
157+
158+ , "Berkeley-style plain citation list" =:
159+ "[cite: See; @Dominik201408; and @Pandoc; and others]" =?>
160+ let pandocCite' = pandocInText { citationPrefix = toList "and" }
161+ in (para $ "See "
162+ <> cite [dominikInText] ""
163+ <> "," <> space
164+ <> cite [pandocCite'] ""
165+ <> "," <> space <> "and others")
166+ ]
167+
168+ , "LaTeX citation" =:
169+ "\\cite{Coffee}" =?>
170+ let citation = Citation
171+ { citationId = "Coffee"
172+ , citationPrefix = []
173+ , citationSuffix = []
174+ , citationMode = NormalCitation
175+ , citationNoteNum = 0
176+ , citationHash = 0}
177+ in (para . cite [citation] $ rawInline "latex" "\\cite{Coffee}")
178+
179+ ]
test/Tests/Readers/Org/Inline/Note.hs.orignew file+86
@@ -0,0 +1,86 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Inline.Note (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:))
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+
10+tests :: [TestTree]
11+tests =
12+ [ "Footnote" =:
13+ T.unlines [ "A footnote[1]"
14+ , ""
15+ , "[1] First paragraph"
16+ , ""
17+ , "second paragraph"
18+ ] =?>
19+ para (mconcat
20+ [ "A", space, "footnote"
21+ , note $ mconcat [ para ("First" <> space <> "paragraph")
22+ , para ("second" <> space <> "paragraph")
23+ ]
24+ ])
25+
26+ , "Two footnotes" =:
27+ T.unlines [ "Footnotes[fn:1][fn:2]"
28+ , ""
29+ , "[fn:1] First note."
30+ , ""
31+ , "[fn:2] Second note."
32+ ] =?>
33+ para (mconcat
34+ [ "Footnotes"
35+ , note $ para ("First" <> space <> "note.")
36+ , note $ para ("Second" <> space <> "note.")
37+ ])
38+
39+ , "Emphasized text before footnote" =:
40+ T.unlines [ "/text/[fn:1]"
41+ , ""
42+ , "[fn:1] unicorn"
43+ ] =?>
44+ para (mconcat
45+ [ emph "text"
46+ , note . para $ "unicorn"
47+ ])
48+
49+ , "Footnote that starts with emphasized text" =:
50+ T.unlines [ "text[fn:1]"
51+ , ""
52+ , "[fn:1] /emphasized/"
53+ ] =?>
54+ para (mconcat
55+ [ "text"
56+ , note . para $ emph "emphasized"
57+ ])
58+
59+ , "Footnote followed by header" =:
60+ T.unlines [ "Another note[fn:yay]"
61+ , ""
62+ , "[fn:yay] This is great!"
63+ , ""
64+ , "** Headline"
65+ ] =?>
66+ mconcat
67+ [ para (mconcat
68+ [ "Another", space, "note"
69+ , note $ para ("This" <> space <> "is" <> space <> "great!")
70+ ])
71+ , headerWith ("headline", [], []) 2 "Headline"
72+ ]
73+
74+ , "Footnote followed by two blank lines" =:
75+ T.unlines [ "footnote[fn:blanklines]"
76+ , ""
77+ , "[fn:blanklines] followed by blank lines"
78+ , ""
79+ , ""
80+ , "next"
81+ ] =?>
82+ mconcat
83+ [ para ("footnote" <> note (para "followed by blank lines"))
84+ , para "next"
85+ ]
86+ ]
test/Tests/Readers/Org/Inline/Smart.hs.orignew file+46
@@ -0,0 +1,46 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Inline.Smart (tests) where
3+
4+import Data.Text (Text)
5+import Test.Tasty (TestTree)
6+import Tests.Helpers ((=?>), purely, test)
7+import Text.Pandoc (ReaderOptions (readerExtensions),
8+ Extension (Ext_smart), def, enableExtension,
9+ getDefaultExtensions, readOrg)
10+import Text.Pandoc.Builder
11+
12+orgSmart :: Text -> Pandoc
13+orgSmart = purely $
14+ let extensionsSmart = enableExtension Ext_smart (getDefaultExtensions "org")
15+ in readOrg def{ readerExtensions = extensionsSmart }
16+
17+tests :: [TestTree]
18+tests =
19+ [ test orgSmart "quote before ellipses"
20+ ("'...hi'"
21+ =?> para (singleQuoted "…hi"))
22+
23+ , test orgSmart "apostrophe before emph"
24+ ("D'oh! A l'/aide/!"
25+ =?> para ("D’oh! A l’" <> emph "aide" <> "!"))
26+
27+ , test orgSmart "apostrophe in French"
28+ ("À l'arrivée de la guerre, le thème de l'«impossibilité du socialisme»"
29+ =?> para "À l’arrivée de la guerre, le thème de l’«impossibilité du socialisme»")
30+
31+ , test orgSmart "Quotes cannot occur at the end of emphasized text"
32+ ("/say \"yes\"/" =?>
33+ para ("/say" <> space <> doubleQuoted "yes" <> "/"))
34+
35+ , test orgSmart "Dashes are allowed at the borders of emphasis'"
36+ ("/foo---/" =?>
37+ para (emph "foo—"))
38+
39+ , test orgSmart "Single quotes can be followed by emphasized text"
40+ ("Singles on the '/meat market/'" =?>
41+ para ("Singles on the " <> singleQuoted (emph "meat market")))
42+
43+ , test orgSmart "Double quotes can be followed by emphasized text"
44+ ("Double income, no kids: \"/DINK/\"" =?>
45+ para ("Double income, no kids: " <> doubleQuoted (emph "DINK")))
46+ ]
test/Tests/Readers/Org/Meta.hs.orignew file+191
@@ -0,0 +1,191 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Meta (tests) where
3+
4+import Test.Tasty (TestTree, testGroup)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep)
7+import Text.Pandoc
8+import Text.Pandoc.Builder
9+import qualified Data.Text as T
10+
11+tests :: [TestTree]
12+tests =
13+ [ "Comment" =:
14+ "# Nothing to see here" =?>
15+ (mempty::Blocks)
16+
17+ , "Not a comment" =:
18+ "#-tag" =?>
19+ para "#-tag"
20+
21+ , "Comment surrounded by Text" =:
22+ T.unlines [ "Before"
23+ , "# Comment"
24+ , "After"
25+ ] =?>
26+ mconcat [ para "Before"
27+ , para "After"
28+ ]
29+
30+ , "Title" =:
31+ "#+TITLE: Hello, World" =?>
32+ let titleInline = toList $ "Hello," <> space <> "World"
33+ meta = setMeta "title" (MetaInlines titleInline) nullMeta
34+ in Pandoc meta mempty
35+
36+ , "Author" =:
37+ "#+author: John /Emacs-Fanboy/ Doe" =?>
38+ let author = toList . spcSep $ [ "John", emph "Emacs-Fanboy", "Doe" ]
39+ meta = setMeta "author" (MetaList [MetaInlines author]) nullMeta
40+ in Pandoc meta mempty
41+
42+ , "Multiple authors" =:
43+ "#+author: James Dewey Watson, Francis Harry Compton Crick " =?>
44+ let watson = MetaInlines $ toList "James Dewey Watson"
45+ crick = MetaInlines $ toList "Francis Harry Compton Crick"
46+ meta = setMeta "author" (MetaList [watson, crick]) nullMeta
47+ in Pandoc meta mempty
48+
49+ , "Date" =:
50+ "#+Date: Feb. *28*, 2014" =?>
51+ let date = toList . spcSep $ [ "Feb.", strong "28" <> ",", "2014" ]
52+ meta = setMeta "date" (MetaInlines date) nullMeta
53+ in Pandoc meta mempty
54+
55+ , "Description" =:
56+ "#+DESCRIPTION: Explanatory text" =?>
57+ let description = "Explanatory text"
58+ meta = setMeta "description" (MetaString description) nullMeta
59+ in Pandoc meta mempty
60+
61+ , "Properties drawer" =:
62+ T.unlines [ " :PROPERTIES:"
63+ , " :setting: foo"
64+ , " :END:"
65+ ] =?>
66+ (mempty::Blocks)
67+
68+ , "LaTeX_headers options are translated to header-includes" =:
69+ "#+LaTeX_header: \\usepackage{tikz}" =?>
70+ let latexInlines = rawInline "latex" "\\usepackage{tikz}"
71+ inclList = MetaList [MetaInlines (toList latexInlines)]
72+ meta = setMeta "header-includes" inclList nullMeta
73+ in Pandoc meta mempty
74+
75+ , "LaTeX_class option is translated to documentclass" =:
76+ "#+LATEX_CLASS: article" =?>
77+ let meta = setMeta "documentclass" (MetaString "article") nullMeta
78+ in Pandoc meta mempty
79+
80+ , "LaTeX_class_options is translated to classoption" =:
81+ "#+LATEX_CLASS_OPTIONS: [a4paper]" =?>
82+ let meta = setMeta "classoption" (MetaString "a4paper") nullMeta
83+ in Pandoc meta mempty
84+
85+ , "LaTeX_class_options is translated to classoption" =:
86+ "#+html_head: <meta/>" =?>
87+ let html = rawInline "html" "<meta/>"
88+ inclList = MetaList [MetaInlines (toList html)]
89+ meta = setMeta "header-includes" inclList nullMeta
90+ in Pandoc meta mempty
91+
92+ , "later meta definitions take precedence" =:
93+ T.unlines [ "#+AUTHOR: this will not be used"
94+ , "#+author: Max"
95+ ] =?>
96+ let author = MetaInlines [Str "Max"]
97+ meta = setMeta "author" (MetaList [author]) nullMeta
98+ in Pandoc meta mempty
99+
100+ , "Logbook drawer" =:
101+ T.unlines [ " :LogBook:"
102+ , " - State \"DONE\" from \"TODO\" [2014-03-03 Mon 11:00]"
103+ , " :END:"
104+ ] =?>
105+ (mempty::Blocks)
106+
107+ , "Drawer surrounded by text" =:
108+ T.unlines [ "Before"
109+ , ":PROPERTIES:"
110+ , ":END:"
111+ , "After"
112+ ] =?>
113+ para "Before" <> para "After"
114+
115+ , "Drawer markers must be the only text in the line" =:
116+ T.unlines [ " :LOGBOOK: foo"
117+ , " :END: bar"
118+ ] =?>
119+ para (":LOGBOOK: foo" <> softbreak <> ":END: bar")
120+
121+ , "Drawers can be arbitrary" =:
122+ T.unlines [ ":FOO:"
123+ , "/bar/"
124+ , ":END:"
125+ ] =?>
126+ divWith (mempty, ["FOO", "drawer"], mempty) (para $ emph "bar")
127+
128+ , "Anchor reference" =:
129+ T.unlines [ "<<link-here>> Target."
130+ , ""
131+ , "[[link-here][See here!]]"
132+ ] =?>
133+ (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
134+ para (link "#link-here" "" ("See" <> space <> "here!")))
135+
136+ , "Search links are read as emph" =:
137+ "[[Wally][Where's Wally?]]" =?>
138+ para (emph $ "Where's" <> space <> "Wally?")
139+
140+ , "Link to nonexistent anchor" =:
141+ T.unlines [ "<<link-here>> Target."
142+ , ""
143+ , "[[link$here][See here!]]"
144+ ] =?>
145+ (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
146+ para (emph ("See" <> space <> "here!")))
147+
148+ , "Link abbreviation" =:
149+ T.unlines [ "#+LINK: wp https://en.wikipedia.org/wiki/%s"
150+ , "[[wp:Org_mode][Wikipedia on Org-mode]]"
151+ ] =?>
152+ para (link "https://en.wikipedia.org/wiki/Org_mode" ""
153+ ("Wikipedia" <> space <> "on" <> space <> "Org-mode"))
154+
155+ , "Link abbreviation, defined after first use" =:
156+ T.unlines [ "[[zl:non-sense][Non-sense articles]]"
157+ , "#+LINK: zl http://zeitlens.com/tags/%s.html"
158+ ] =?>
159+ para (link "http://zeitlens.com/tags/non-sense.html" ""
160+ ("Non-sense" <> space <> "articles"))
161+
162+ , "Link abbreviation, URL encoded arguments" =:
163+ T.unlines [ "#+link: expl http://example.com/%h/foo"
164+ , "[[expl:Hello, World!][Moin!]]"
165+ ] =?>
166+ para (link "http://example.com/Hello%2C%20World%21/foo" "" "Moin!")
167+
168+ , "Link abbreviation, append arguments" =:
169+ T.unlines [ "#+link: expl http://example.com/"
170+ , "[[expl:foo][bar]]"
171+ ] =?>
172+ para (link "http://example.com/foo" "" "bar")
173+
174+ , testGroup "emphasis config"
175+ [ "Changing pre and post chars for emphasis" =:
176+ T.unlines [ "#+pandoc-emphasis-pre: \"[)\""
177+ , "#+pandoc-emphasis-post: \"]\\n\""
178+ , "([/emph/])*foo*"
179+ ] =?>
180+ para ("([" <> emph "emph" <> "])" <> strong "foo")
181+
182+ , "setting an invalid value restores the default" =:
183+ T.unlines [ "#+pandoc-emphasis-pre: \"[\""
184+ , "#+pandoc-emphasis-post: \"]\""
185+ , "#+pandoc-emphasis-pre:"
186+ , "#+pandoc-emphasis-post:"
187+ , "[/noemph/]"
188+ ] =?>
189+ para ("[/noemph/]")
190+ ]
191+ ]
test/Tests/Readers/Org/Shared.hs.orignew file+29
@@ -0,0 +1,29 @@
1+module Tests.Readers.Org.Shared
2+ ( (=:)
3+ , org
4+ , spcSep
5+ , tagSpan
6+ ) where
7+
8+import Data.List (intersperse)
9+import Data.Text (Text)
10+import Tests.Helpers (ToString, purely, test)
11+import Test.Tasty (TestTree)
12+import Text.Pandoc (Pandoc, ReaderOptions (readerExtensions),
13+ def, getDefaultExtensions, readOrg)
14+import Text.Pandoc.Builder (Inlines, smallcaps, space, spanWith, str)
15+
16+org :: Text -> Pandoc
17+org = purely $ readOrg def{ readerExtensions = getDefaultExtensions "org" }
18+
19+infix 4 =:
20+(=:) :: ToString c
21+ => String -> (Text, c) -> TestTree
22+(=:) = test org
23+
24+spcSep :: [Inlines] -> Inlines
25+spcSep = mconcat . intersperse space
26+
27+-- | Create a span for the given tag.
28+tagSpan :: String -> Inlines
29+tagSpan t = spanWith ("", ["tag"], [("tag-name", t)]) . smallcaps $ str t
test/Tests/Readers/RST.hs.orignew file+189
@@ -0,0 +1,189 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+module Tests.Readers.RST (tests) where
4+
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.Arbitrary ()
11+import Text.Pandoc.Builder
12+
13+rst :: Text -> Pandoc
14+rst = purely $ readRST def{ readerStandalone = True }
15+
16+infix 4 =:
17+(=:) :: ToString c
18+ => String -> (Text, c) -> TestTree
19+(=:) = test rst
20+
21+tests :: [TestTree]
22+tests = [ "line block with blank line" =:
23+ "| a\n|\n| b" =?> lineBlock [ "a", mempty, "\160b" ]
24+ , testGroup "field list"
25+ [ "general" =: T.unlines
26+ [ "para"
27+ , ""
28+ , ":Hostname: media08"
29+ , ":IP address: 10.0.0.19"
30+ , ":Size: 3ru"
31+ , ":Version: 1"
32+ , ":Indentation: Since the field marker may be quite long, the second"
33+ , " and subsequent lines of the field body do not have to line up"
34+ , " with the first line, but they must be indented relative to the"
35+ , " field name marker, and they must line up with each other."
36+ , ":Parameter i: integer"
37+ , ":Final: item"
38+ , " on two lines" ]
39+ =?>
40+ doc (para "para" <>
41+ definitionList [ (str "Hostname", [para "media08"])
42+ , (text "IP address", [para "10.0.0.19"])
43+ , (str "Size", [para "3ru"])
44+ , (str "Version", [para "1"])
45+ , (str "Indentation", [para "Since the field marker may be quite long, the second\nand subsequent lines of the field body do not have to line up\nwith the first line, but they must be indented relative to the\nfield name marker, and they must line up with each other."])
46+ , (text "Parameter i", [para "integer"])
47+ , (str "Final", [para "item\non two lines"])
48+ ])
49+ , "metadata" =: T.unlines
50+ [ "====="
51+ , "Title"
52+ , "====="
53+ , "--------"
54+ , "Subtitle"
55+ , "--------"
56+ , ""
57+ , ":Version: 1"
58+ ]
59+ =?>
60+ setMeta "version" (para "1") (setMeta "title" ("Title" :: Inlines)
61+ $ setMeta "subtitle" ("Subtitle" :: Inlines)
62+ $ doc mempty)
63+ , "with inline markup" =: T.unlines
64+ [ ":*Date*: today"
65+ , ""
66+ , ".."
67+ , ""
68+ , ":*one*: emphasis"
69+ , ":two_: reference"
70+ , ":`three`_: another one"
71+ , ":``four``: literal"
72+ , ""
73+ , ".. _two: http://example.com"
74+ , ".. _three: http://example.org"
75+ ]
76+ =?>
77+ setMeta "date" (str "today") (doc
78+ $ definitionList [ (emph "one", [para "emphasis"])
79+ , (link "http://example.com" "" "two", [para "reference"])
80+ , (link "http://example.org" "" "three", [para "another one"])
81+ , (code "four", [para "literal"])
82+ ])
83+ ]
84+ , "URLs with following punctuation" =:
85+ ("http://google.com, http://yahoo.com; http://foo.bar.baz.\n" <>
86+ "http://foo.bar/baz_(bam) (http://foo.bar)") =?>
87+ para (link "http://google.com" "" "http://google.com" <> ", " <>
88+ link "http://yahoo.com" "" "http://yahoo.com" <> "; " <>
89+ link "http://foo.bar.baz" "" "http://foo.bar.baz" <> ". " <>
90+ softbreak <>
91+ link "http://foo.bar/baz_(bam)" "" "http://foo.bar/baz_(bam)"
92+ <> " (" <> link "http://foo.bar" "" "http://foo.bar" <> ")")
93+ , "Reference names with special characters" =:
94+ ("A-1-B_2_C:3:D+4+E.5.F_\n\n" <>
95+ ".. _A-1-B_2_C:3:D+4+E.5.F: https://example.com\n") =?>
96+ para (link "https://example.com" "" "A-1-B_2_C:3:D+4+E.5.F")
97+ , "Code directive with class and number-lines" =: T.unlines
98+ [ ".. code::python"
99+ , " :number-lines: 34"
100+ , " :class: class1 class2 class3"
101+ , ""
102+ , " def func(x):"
103+ , " return y"
104+ ] =?>
105+ doc (codeBlockWith
106+ ( ""
107+ , ["sourceCode", "python", "numberLines", "class1", "class2", "class3"]
108+ , [ ("startFrom", "34") ]
109+ )
110+ "def func(x):\n return y")
111+ , "Code directive with number-lines, no line specified" =: T.unlines
112+ [ ".. code::python"
113+ , " :number-lines: "
114+ , ""
115+ , " def func(x):"
116+ , " return y"
117+ ] =?>
118+ doc (codeBlockWith
119+ ( ""
120+ , ["sourceCode", "python", "numberLines"]
121+ , [ ("startFrom", "") ]
122+ )
123+ "def func(x):\n return y")
124+ , testGroup "literal / line / code blocks"
125+ [ "indented literal block" =: T.unlines
126+ [ "::"
127+ , ""
128+ , " block quotes"
129+ , ""
130+ , " can go on for many lines"
131+ , "but must stop here"]
132+ =?>
133+ doc (
134+ codeBlock "block quotes\n\ncan go on for many lines" <>
135+ para "but must stop here")
136+ , "line block with 3 lines" =: "| a\n| b\n| c"
137+ =?> lineBlock ["a", "b", "c"]
138+ , "line blocks with blank lines" =: T.unlines
139+ [ "|"
140+ , ""
141+ , "|"
142+ , "| a"
143+ , "| b"
144+ , "|"
145+ , ""
146+ , "|"
147+ ] =?>
148+ lineBlock [""] <>
149+ lineBlock ["", "a", "b", ""] <>
150+ lineBlock [""]
151+ , "quoted literal block using >" =: "::\n\n> quoted\n> block\n\nOrdinary paragraph"
152+ =?> codeBlock "> quoted\n> block" <> para "Ordinary paragraph"
153+ , "quoted literal block using | (not a line block)" =: "::\n\n| quoted\n| block\n\nOrdinary paragraph"
154+ =?> codeBlock "| quoted\n| block" <> para "Ordinary paragraph"
155+ , "class directive with single paragraph" =: ".. class:: special\n\nThis is a \"special\" paragraph."
156+ =?> divWith ("", ["special"], []) (para "This is a \"special\" paragraph.")
157+ , "class directive with two paragraphs" =: ".. class:: exceptional remarkable\n\n First paragraph.\n\n Second paragraph."
158+ =?> divWith ("", ["exceptional", "remarkable"], []) (para "First paragraph." <> para "Second paragraph.")
159+ , "class directive around literal block" =: ".. class:: classy\n\n::\n\n a\n b"
160+ =?> divWith ("", ["classy"], []) (codeBlock "a\nb")]
161+ , testGroup "interpreted text roles"
162+ [ "literal role prefix" =: ":literal:`a`" =?> para (code "a")
163+ , "literal role postfix" =: "`a`:literal:" =?> para (code "a")
164+ , "literal text" =: "``text``" =?> para (code "text")
165+ , "code role" =: ":code:`a`" =?> para (codeWith ("", ["sourceCode"], []) "a")
166+ , "inherited code role" =: ".. role:: codeLike(code)\n\n:codeLike:`a`"
167+ =?> para (codeWith ("", ["codeLike", "sourceCode"], []) "a")
168+ , "custom code role with language field"
169+ =: ".. role:: lhs(code)\n :language: haskell\n\n:lhs:`a`"
170+ =?> para (codeWith ("", ["lhs", "haskell","sourceCode"], []) "a")
171+ , "custom role with unspecified parent role"
172+ =: ".. role:: classy\n\n:classy:`text`"
173+ =?> para (spanWith ("", ["classy"], []) "text")
174+ , "role with recursive inheritance"
175+ =: ".. role:: haskell(code)\n.. role:: lhs(haskell)\n\n:lhs:`text`"
176+ =?> para (codeWith ("", ["lhs", "haskell", "sourceCode"], []) "text")
177+ , "unknown role" =: ":unknown:`text`" =?>
178+ para (spanWith ("",[],[("role","unknown")]) (str "text"))
179+ ]
180+ , testGroup "footnotes"
181+ [ "remove space before note" =: T.unlines
182+ [ "foo [1]_"
183+ , ""
184+ , ".. [1]"
185+ , " bar"
186+ ] =?>
187+ para ("foo" <> note (para "bar"))
188+ ]
189+ ]
test/Tests/Readers/Txt2Tags.hs.orignew file+437
@@ -0,0 +1,437 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Txt2Tags (tests) where
3+
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.Arbitrary ()
11+import Text.Pandoc.Builder
12+import Text.Pandoc.Shared (underlineSpan)
13+
14+t2t :: Text -> Pandoc
15+-- t2t = handleError . readTxt2Tags (T2TMeta "date" "mtime" "in" "out") def
16+t2t = purely $ \s -> do
17+ setInputFiles ["in"]
18+ setOutputFile (Just "out")
19+ readTxt2Tags def s
20+
21+infix 4 =:
22+(=:) :: ToString c
23+ => String -> (Text, c) -> TestTree
24+(=:) = test t2t
25+
26+spcSep :: [Inlines] -> Inlines
27+spcSep = mconcat . intersperse space
28+
29+simpleTable' :: Int
30+ -> [Blocks]
31+ -> [[Blocks]]
32+ -> Blocks
33+simpleTable' n = table "" (replicate n (AlignCenter, 0.0))
34+
35+tests :: [TestTree]
36+tests =
37+ [ testGroup "Inlines"
38+ [ "Plain String" =:
39+ "Hello, World" =?>
40+ para (spcSep [ "Hello,", "World" ])
41+
42+ , "Emphasis" =:
43+ "//Planet Punk//" =?>
44+ para (emph . spcSep $ ["Planet", "Punk"])
45+
46+ , "Strong" =:
47+ "**Cider**" =?>
48+ para (strong "Cider")
49+
50+ , "Strong Emphasis" =:
51+ "//**strength**//" =?>
52+ para (emph . strong $ "strength")
53+
54+ , "Strikeout" =:
55+ "--Kill Bill--" =?>
56+ para (strikeout . spcSep $ [ "Kill", "Bill" ])
57+
58+ , "Verbatim" =:
59+ "``Robot.rock()``" =?>
60+ para (code "Robot.rock()")
61+
62+ , "Symbol" =:
63+ "A * symbol" =?>
64+ para (str "A" <> space <> str "*" <> space <> "symbol")
65+
66+ , "No empty markup" =:
67+ "//// **** ____ ---- ```` \"\"\"\" ''''" =?>
68+ para (spcSep [ "////", "****", "____", "----", "````", "\"\"\"\"", "''''" ])
69+
70+ , "Inline markup is greedy" =:
71+ "***** ///// _____ ----- ````` \"\"\"\"\" '''''" =?>
72+ para (spcSep [strong "*", emph "/", underlineSpan "_"
73+ , strikeout "-", code "`", text "\""
74+ , rawInline "html" "'"])
75+ , "Markup must be greedy" =:
76+ "********** ////////// __________ ---------- `````````` \"\"\"\"\"\"\"\"\"\" ''''''''''" =?>
77+ para (spcSep [strong "******", emph "//////", underlineSpan "______"
78+ , strikeout "------", code "``````", text "\"\"\"\"\"\""
79+ , rawInline "html" "''''''"])
80+ , "Inlines must be glued" =:
81+ "** a** **a ** ** a **" =?>
82+ para (text "** a** **a ** ** a **")
83+
84+ , "Macros: Date" =:
85+ "%%date" =?>
86+ para "1970-01-01"
87+ , "Macros: Mod Time" =:
88+ "%%mtime" =?>
89+ para (str "")
90+ , "Macros: Infile" =:
91+ "%%infile" =?>
92+ para "in"
93+ , "Macros: Outfile" =:
94+ "%%outfile" =?>
95+ para "out"
96+ , "Autolink" =:
97+ "http://www.google.com" =?>
98+ para (link "http://www.google.com" "" (str "http://www.google.com"))
99+ , "JPEG Image" =:
100+ "[image.jpg]" =?>
101+ para (image "image.jpg" "" mempty)
102+ , "PNG Image" =:
103+ "[image.png]" =?>
104+ para (image "image.png" "" mempty)
105+
106+ , "Link" =:
107+ "[title http://google.com]" =?>
108+ para (link "http://google.com" "" (str "title"))
109+
110+ , "Image link" =:
111+ "[[image.jpg] abc]" =?>
112+ para (link "abc" "" (image "image.jpg" "" mempty))
113+ , "Invalid link: No trailing space" =:
114+ "[title invalid ]" =?>
115+ para (text "[title invalid ]")
116+
117+
118+ ]
119+
120+ , testGroup "Basic Blocks"
121+ ["Paragraph, lines grouped together" =:
122+ "A paragraph\n A blank line ends the \n current paragraph\n"
123+ =?> para "A paragraph\n A blank line ends the\n current paragraph"
124+ , "Paragraph, ignore leading and trailing spaces" =:
125+ " Leading and trailing spaces are ignored. \n" =?>
126+ para "Leading and trailing spaces are ignored."
127+ , "Comment line in paragraph" =:
128+ "A comment line can be placed inside a paragraph.\n% this comment will be ignored \nIt will not affect it.\n"
129+ =?> para "A comment line can be placed inside a paragraph.\nIt will not affect it."
130+ , "Paragraph" =:
131+ "Paragraph\n" =?>
132+ para "Paragraph"
133+
134+ , "First Level Header" =:
135+ "+ Headline +\n" =?>
136+ header 1 "Headline"
137+
138+ , "Third Level Header" =:
139+ "=== Third Level Headline ===\n" =?>
140+ header 3 ("Third" <> space <>
141+ "Level" <> space <>
142+ "Headline")
143+
144+ , "Header with label" =:
145+ "= header =[label]" =?>
146+ headerWith ("label", [], []) 1 "header"
147+
148+ , "Invalid header, mismatched delimiters" =:
149+ "== header =" =?>
150+ para (text "== header =")
151+
152+ , "Invalid header, spaces in label" =:
153+ "== header ==[ haha ]" =?>
154+ para (text "== header ==[ haha ]")
155+
156+ , "Invalid header, invalid label character" =:
157+ "== header ==[lab/el]" =?>
158+ para (text "== header ==[lab/el]")
159+ , "Headers not preceded by a blank line" =:
160+ T.unlines [ "++ eat dinner ++"
161+ , "Spaghetti and meatballs tonight."
162+ , "== walk dog =="
163+ ] =?>
164+ mconcat [ header 2 ("eat" <> space <> "dinner")
165+ , para $ spcSep [ "Spaghetti", "and", "meatballs", "tonight." ]
166+ , header 2 ("walk" <> space <> "dog")
167+ ]
168+
169+ , "Paragraph starting with an equals" =:
170+ "=five" =?>
171+ para "=five"
172+
173+ , "Paragraph containing asterisk at beginning of line" =:
174+ T.unlines [ "lucky"
175+ , "*star"
176+ ] =?>
177+ para ("lucky" <> softbreak <> "*star")
178+
179+ , "Horizontal Rule" =:
180+ T.unlines [ "before"
181+ , T.replicate 20 "-"
182+ , T.replicate 20 "="
183+ , T.replicate 20 "_"
184+ , "after"
185+ ] =?>
186+ mconcat [ para "before"
187+ , horizontalRule
188+ , horizontalRule
189+ , horizontalRule
190+ , para "after"
191+ ]
192+
193+ , "Comment Block" =:
194+ T.unlines [ "%%%"
195+ , "stuff"
196+ , "bla"
197+ , "%%%"] =?>
198+ (mempty::Blocks)
199+
200+
201+ ]
202+
203+ , testGroup "Lists"
204+ [ "Simple Bullet Lists" =:
205+ ("- Item1\n" <>
206+ "- Item2\n") =?>
207+ bulletList [ plain "Item1"
208+ , plain "Item2"
209+ ]
210+
211+ , "Indented Bullet Lists" =:
212+ (" - Item1\n" <>
213+ " - Item2\n") =?>
214+ bulletList [ plain "Item1"
215+ , plain "Item2"
216+ ]
217+
218+
219+
220+ , "Nested Bullet Lists" =:
221+ ("- Discovery\n" <>
222+ " + One More Time\n" <>
223+ " + Harder, Better, Faster, Stronger\n" <>
224+ "- Homework\n" <>
225+ " + Around the World\n"<>
226+ "- Human After All\n" <>
227+ " + Technologic\n" <>
228+ " + Robot Rock\n") =?>
229+ bulletList [ mconcat
230+ [ plain "Discovery"
231+ , orderedList [ plain ("One" <> space <>
232+ "More" <> space <>
233+ "Time")
234+ , plain ("Harder," <> space <>
235+ "Better," <> space <>
236+ "Faster," <> space <>
237+ "Stronger")
238+ ]
239+ ]
240+ , mconcat
241+ [ plain "Homework"
242+ , orderedList [ plain ("Around" <> space <>
243+ "the" <> space <>
244+ "World")
245+ ]
246+ ]
247+ , mconcat
248+ [ plain ("Human" <> space <> "After" <> space <> "All")
249+ , orderedList [ plain "Technologic"
250+ , plain ("Robot" <> space <> "Rock")
251+ ]
252+ ]
253+ ]
254+
255+ , "Simple Ordered List" =:
256+ ("+ Item1\n" <>
257+ "+ Item2\n") =?>
258+ let listStyle = (1, DefaultStyle, DefaultDelim)
259+ listStructure = [ plain "Item1"
260+ , plain "Item2"
261+ ]
262+ in orderedListWith listStyle listStructure
263+
264+
265+ , "Indented Ordered List" =:
266+ (" + Item1\n" <>
267+ " + Item2\n") =?>
268+ let listStyle = (1, DefaultStyle, DefaultDelim)
269+ listStructure = [ plain "Item1"
270+ , plain "Item2"
271+ ]
272+ in orderedListWith listStyle listStructure
273+
274+ , "Nested Ordered Lists" =:
275+ ("+ One\n" <>
276+ " + One-One\n" <>
277+ " + One-Two\n" <>
278+ "+ Two\n" <>
279+ " + Two-One\n"<>
280+ " + Two-Two\n") =?>
281+ let listStyle = (1, DefaultStyle, DefaultDelim)
282+ listStructure = [ mconcat
283+ [ plain "One"
284+ , orderedList [ plain "One-One"
285+ , plain "One-Two"
286+ ]
287+ ]
288+ , mconcat
289+ [ plain "Two"
290+ , orderedList [ plain "Two-One"
291+ , plain "Two-Two"
292+ ]
293+ ]
294+ ]
295+ in orderedListWith listStyle listStructure
296+
297+ , "Ordered List in Bullet List" =:
298+ ("- Emacs\n" <>
299+ " + Org\n") =?>
300+ bulletList [ (plain "Emacs") <>
301+ (orderedList [ plain "Org"])
302+ ]
303+
304+ , "Bullet List in Ordered List" =:
305+ ("+ GNU\n" <>
306+ " - Freedom\n") =?>
307+ orderedList [ (plain "GNU") <> bulletList [ (plain "Freedom") ] ]
308+
309+ , "Definition List" =:
310+ T.unlines [ ": PLL"
311+ , " phase-locked loop"
312+ , ": TTL"
313+ , " transistor-transistor logic"
314+ , ": PSK"
315+ , " a digital"
316+ ] =?>
317+ definitionList [ ("PLL", [ plain $ "phase-locked" <> space <> "loop" ])
318+ , ("TTL", [ plain $ "transistor-transistor" <> space <> "logic" ])
319+ , ("PSK", [ plain $ "a" <> space <> "digital" ])
320+ ]
321+
322+
323+ , "Loose bullet list" =:
324+ T.unlines [ "- apple"
325+ , ""
326+ , "- orange"
327+ , ""
328+ , "- peach"
329+ ] =?>
330+ bulletList [ para "apple"
331+ , para "orange"
332+ , para "peach"
333+ ]
334+ ]
335+
336+ , testGroup "Tables"
337+ [ "Single cell table" =:
338+ "| Test " =?>
339+ simpleTable' 1 mempty [[plain "Test"]]
340+
341+ , "Multi cell table" =:
342+ "| One | Two |" =?>
343+ simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
344+
345+ , "Multi line table" =:
346+ T.unlines [ "| One |"
347+ , "| Two |"
348+ , "| Three |"
349+ ] =?>
350+ simpleTable' 1 mempty
351+ [ [ plain "One" ]
352+ , [ plain "Two" ]
353+ , [ plain "Three" ]
354+ ]
355+
356+ , "Empty table" =:
357+ "| |" =?>
358+ simpleTable' 1 mempty [[mempty]]
359+
360+ , "Glider Table" =:
361+ T.unlines [ "| 1 | 0 | 0 |"
362+ , "| 0 | 1 | 1 |"
363+ , "| 1 | 1 | 0 |"
364+ ] =?>
365+ simpleTable' 3 mempty
366+ [ [ plain "1", plain "0", plain "0" ]
367+ , [ plain "0", plain "1", plain "1" ]
368+ , [ plain "1", plain "1", plain "0" ]
369+ ]
370+
371+
372+ , "Table with Header" =:
373+ T.unlines [ "|| Species | Status |"
374+ , "| cervisiae | domesticated |"
375+ , "| paradoxus | wild |"
376+ ] =?>
377+ simpleTable [ plain "Species", plain "Status" ]
378+ [ [ plain "cervisiae", plain "domesticated" ]
379+ , [ plain "paradoxus", plain "wild" ]
380+ ]
381+
382+ , "Table alignment determined by spacing" =:
383+ T.unlines [ "| Numbers | Text | More |"
384+ , "| 1 | One | foo |"
385+ , "| 2 | Two | bar |"
386+ ] =?>
387+ table "" (zip [AlignCenter, AlignRight, AlignDefault] [0, 0, 0])
388+ []
389+ [ [ plain "Numbers", plain "Text", plain "More" ]
390+ , [ plain "1" , plain "One" , plain "foo" ]
391+ , [ plain "2" , plain "Two" , plain "bar" ]
392+ ]
393+
394+ , "Pipe within text doesn't start a table" =:
395+ "Ceci n'est pas une | pipe " =?>
396+ para (spcSep [ "Ceci", "n'est", "pas", "une", "|", "pipe" ])
397+
398+
399+ , "Table with differing row lengths" =:
400+ T.unlines [ "|| Numbers | Text "
401+ , "| 1 | One | foo |"
402+ , "| 2 "
403+ ] =?>
404+ table "" (zip [AlignCenter, AlignLeft, AlignLeft] [0, 0, 0])
405+ [ plain "Numbers", plain "Text" , plain mempty ]
406+ [ [ plain "1" , plain "One" , plain "foo" ]
407+ , [ plain "2" , plain mempty , plain mempty ]
408+ ]
409+
410+ ]
411+
412+ , testGroup "Blocks and fragments"
413+ [ "Source block" =:
414+ T.unlines [ "```"
415+ , "main = putStrLn greeting"
416+ , " where greeting = \"moin\""
417+ , "```" ] =?>
418+ let code' = "main = putStrLn greeting\n" <>
419+ " where greeting = \"moin\"\n"
420+ in codeBlock code'
421+
422+ , "tagged block" =:
423+ T.unlines [ "'''"
424+ , "<aside>HTML5 is pretty nice.</aside>"
425+ , "'''"
426+ ] =?>
427+ rawBlock "html" "<aside>HTML5 is pretty nice.</aside>\n"
428+
429+ , "Quote block" =:
430+ T.unlines ["\t//Niemand// hat die Absicht, eine Mauer zu errichten!"
431+ ] =?>
432+ blockQuote (para (spcSep [ emph "Niemand", "hat", "die", "Absicht,"
433+ , "eine", "Mauer", "zu", "errichten!"
434+ ]))
435+
436+ ]
437+ ]
test/Tests/Shared.hs.orignew file+39
@@ -0,0 +1,39 @@
1+module Tests.Shared (tests) where
2+
3+import System.FilePath.Posix (joinPath)
4+import Test.Tasty
5+import Test.Tasty.HUnit (assertBool, testCase, (@?=))
6+import Text.Pandoc.Arbitrary ()
7+import Text.Pandoc.Builder
8+import Text.Pandoc.Shared
9+
10+tests :: [TestTree]
11+tests = [ testGroup "compactifyDL"
12+ [ testCase "compactifyDL with empty def" $
13+ assertBool "compactifyDL"
14+ (let x = [(str "word", [para (str "def"), mempty])]
15+ in compactifyDL x == x)
16+ ]
17+ , testGroup "collapseFilePath" testCollapse
18+ ]
19+
20+testCollapse :: [TestTree]
21+testCollapse = map (testCase "collapse")
22+ [ collapseFilePath (joinPath [ ""]) @?= (joinPath [ ""])
23+ , collapseFilePath (joinPath [ ".","foo"]) @?= (joinPath [ "foo"])
24+ , collapseFilePath (joinPath [ ".",".","..","foo"]) @?= (joinPath [ joinPath ["..", "foo"]])
25+ , collapseFilePath (joinPath [ "..","foo"]) @?= (joinPath [ "..","foo"])
26+ , collapseFilePath (joinPath [ "","bar","..","baz"]) @?= (joinPath [ "","baz"])
27+ , collapseFilePath (joinPath [ "","..","baz"]) @?= (joinPath [ "","..","baz"])
28+ , collapseFilePath (joinPath [ ".","foo","..",".","bar","..",".",".","baz"]) @?= (joinPath [ "baz"])
29+ , collapseFilePath (joinPath [ ".",""]) @?= (joinPath [ ""])
30+ , collapseFilePath (joinPath [ ".",".",""]) @?= (joinPath [ ""])
31+ , collapseFilePath (joinPath [ "..",""]) @?= (joinPath [ ".."])
32+ , collapseFilePath (joinPath [ "..",".",""]) @?= (joinPath [ ".."])
33+ , collapseFilePath (joinPath [ ".","..",""]) @?= (joinPath [ ".."])
34+ , collapseFilePath (joinPath [ "..","..",""]) @?= (joinPath [ "..",".."])
35+ , collapseFilePath (joinPath [ "parent","foo","baz","..","bar"]) @?= (joinPath [ "parent","foo","bar"])
36+ , collapseFilePath (joinPath [ "parent","foo","baz","..","..","bar"]) @?= (joinPath [ "parent","bar"])
37+ , collapseFilePath (joinPath [ "parent","foo",".."]) @?= (joinPath [ "parent"])
38+ , collapseFilePath (joinPath [ "","parent","foo","..","..","bar"]) @?= (joinPath [ "","bar"])
39+ , collapseFilePath (joinPath [ "",".","parent","foo"]) @?= (joinPath [ "","parent","foo"])]
test/Tests/Writers/AsciiDoc.hs.orignew file+56
@@ -0,0 +1,56 @@
1+module Tests.Writers.AsciiDoc (tests) where
2+
3+import Data.Text (unpack)
4+import Test.Tasty
5+import Tests.Helpers
6+import Text.Pandoc
7+import Text.Pandoc.Arbitrary ()
8+import Text.Pandoc.Builder
9+
10+asciidoc :: (ToPandoc a) => a -> String
11+asciidoc = unpack . purely (writeAsciiDoc def{ writerWrapText = WrapNone }) . toPandoc
12+
13+tests :: [TestTree]
14+tests = [ testGroup "emphasis"
15+ [ test asciidoc "emph word before" $
16+ para (text "foo" <> emph (text "bar")) =?>
17+ "foo__bar__"
18+ , test asciidoc "emph word after" $
19+ para (emph (text "foo") <> text "bar") =?>
20+ "__foo__bar"
21+ , test asciidoc "emph quoted" $
22+ para (doubleQuoted (emph (text "foo"))) =?>
23+ "``__foo__''"
24+ , test asciidoc "strong word before" $
25+ para (text "foo" <> strong (text "bar")) =?>
26+ "foo**bar**"
27+ , test asciidoc "strong word after" $
28+ para (strong (text "foo") <> text "bar") =?>
29+ "**foo**bar"
30+ , test asciidoc "strong quoted" $
31+ para (singleQuoted (strong (text "foo"))) =?>
32+ "`**foo**'"
33+ ]
34+ , testGroup "tables"
35+ [ test asciidoc "empty cells" $
36+ simpleTable [] [[mempty],[mempty]] =?> unlines
37+ [ "[cols=\"\",]"
38+ , "|===="
39+ , "|"
40+ , "|"
41+ , "|===="
42+ ]
43+ , test asciidoc "multiblock cells" $
44+ simpleTable [] [[para (text "Para 1") <> para (text "Para 2")]]
45+ =?> unlines
46+ [ "[cols=\"\",]"
47+ , "|====="
48+ , "a|"
49+ , "Para 1"
50+ , ""
51+ , "Para 2"
52+ , ""
53+ , "|====="
54+ ]
55+ ]
56+ ]
test/Tests/Writers/ConTeXt.hs.orignew file+149
@@ -0,0 +1,149 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Writers.ConTeXt (tests) where
3+
4+import Data.Text (unpack)
5+import Test.Tasty
6+import Test.Tasty.QuickCheck
7+import Tests.Helpers
8+import Text.Pandoc
9+import Text.Pandoc.Arbitrary ()
10+import Text.Pandoc.Builder
11+
12+context :: (ToPandoc a) => a -> String
13+context = unpack . purely (writeConTeXt def) . toPandoc
14+
15+context' :: (ToPandoc a) => a -> String
16+context' = unpack . purely (writeConTeXt def{ writerWrapText = WrapNone }) . toPandoc
17+
18+contextNtb :: (ToPandoc a) => a -> String
19+contextNtb = unpack . purely (writeConTeXt def{ writerExtensions = enableExtension Ext_ntb pandocExtensions }) . toPandoc
20+
21+contextDiv :: (ToPandoc a) => a -> String
22+contextDiv = unpack . purely (writeConTeXt def{ writerSectionDivs = True }) . toPandoc
23+
24+{-
25+ "my test" =: X =?> Y
26+
27+is shorthand for
28+
29+ test context "my test" $ X =?> Y
30+
31+which is in turn shorthand for
32+
33+ test context "my test" (X,Y)
34+-}
35+
36+infix 4 =:
37+(=:) :: (ToString a, ToPandoc a)
38+ => String -> (a, String) -> TestTree
39+(=:) = test context
40+
41+tests :: [TestTree]
42+tests = [ testGroup "inline code"
43+ [ "with '}'" =: code "}" =?> "\\mono{\\}}"
44+ , "without '}'" =: code "]" =?> "\\type{]}"
45+ , testProperty "code property" $ \s -> null s ||
46+ if '{' `elem` s || '}' `elem` s
47+ then context' (code s) == "\\mono{" ++
48+ context' (str s) ++ "}"
49+ else context' (code s) == "\\type{" ++ s ++ "}"
50+ ]
51+ , testGroup "headers"
52+ [ "level 1" =:
53+ headerWith ("my-header",[],[]) 1 "My header" =?> "\\section[title={My header},reference={my-header}]"
54+ , test contextDiv "section-divs" $
55+ ( headerWith ("header1", [], []) 1 (text "Header1")
56+ <> headerWith ("header2", [], []) 2 (text "Header2")
57+ <> headerWith ("header3", [], []) 3 (text "Header3")
58+ <> headerWith ("header4", [], []) 4 (text "Header4")
59+ <> headerWith ("header5", [], []) 5 (text "Header5")
60+ <> headerWith ("header6", [], []) 6 (text "Header6"))
61+ =?>
62+ unlines [ "\\startsection[title={Header1},reference={header1}]\n"
63+ , "\\startsubsection[title={Header2},reference={header2}]\n"
64+ , "\\startsubsubsection[title={Header3},reference={header3}]\n"
65+ , "\\startsubsubsubsection[title={Header4},reference={header4}]\n"
66+ , "\\startsubsubsubsubsection[title={Header5},reference={header5}]\n"
67+ , "\\startsubsubsubsubsubsection[title={Header6},reference={header6}]\n"
68+ , "\\stopsubsubsubsubsubsection\n"
69+ , "\\stopsubsubsubsubsection\n"
70+ , "\\stopsubsubsubsection\n"
71+ , "\\stopsubsubsection\n"
72+ , "\\stopsubsection\n"
73+ , "\\stopsection" ]
74+ ]
75+ , testGroup "bullet lists"
76+ [ "nested" =:
77+ bulletList [
78+ plain (text "top")
79+ <> bulletList [
80+ plain (text "next")
81+ <> bulletList [plain (text "bot")]
82+ ]
83+ ] =?> unlines
84+ [ "\\startitemize[packed]"
85+ , "\\item"
86+ , " top"
87+ , " \\startitemize[packed]"
88+ , " \\item"
89+ , " next"
90+ , " \\startitemize[packed]"
91+ , " \\item"
92+ , " bot"
93+ , " \\stopitemize"
94+ , " \\stopitemize"
95+ , "\\stopitemize" ]
96+ ]
97+ , testGroup "natural tables"
98+ [ test contextNtb "table with header and caption" $
99+ let caption = text "Table 1"
100+ aligns = [(AlignRight, 0.0), (AlignLeft, 0.0), (AlignCenter, 0.0), (AlignDefault, 0.0)]
101+ headers = [plain $ text "Right",
102+ plain $ text "Left",
103+ plain $ text "Center",
104+ plain $ text "Default"]
105+ rows = [[plain $ text "1.1",
106+ plain $ text "1.2",
107+ plain $ text "1.3",
108+ plain $ text "1.4"]
109+ ,[plain $ text "2.1",
110+ plain $ text "2.2",
111+ plain $ text "2.3",
112+ plain $ text "2.4"]
113+ ,[plain $ text "3.1",
114+ plain $ text "3.2",
115+ plain $ text "3.3",
116+ plain $ text "3.4"]]
117+ in table caption aligns headers rows
118+ =?> unlines [ "\\startplacetable[title={Table 1}]"
119+ , "\\startTABLE"
120+ , "\\startTABLEhead"
121+ , "\\NC[align=left] Right"
122+ , "\\NC[align=right] Left"
123+ , "\\NC[align=middle] Center"
124+ , "\\NC Default"
125+ , "\\NC\\NR"
126+ , "\\stopTABLEhead"
127+ , "\\startTABLEbody"
128+ , "\\NC[align=left] 1.1"
129+ , "\\NC[align=right] 1.2"
130+ , "\\NC[align=middle] 1.3"
131+ , "\\NC 1.4"
132+ , "\\NC\\NR"
133+ , "\\NC[align=left] 2.1"
134+ , "\\NC[align=right] 2.2"
135+ , "\\NC[align=middle] 2.3"
136+ , "\\NC 2.4"
137+ , "\\NC\\NR"
138+ , "\\stopTABLEbody"
139+ , "\\startTABLEfoot"
140+ , "\\NC[align=left] 3.1"
141+ , "\\NC[align=right] 3.2"
142+ , "\\NC[align=middle] 3.3"
143+ , "\\NC 3.4"
144+ , "\\NC\\NR"
145+ , "\\stopTABLEfoot"
146+ , "\\stopTABLE"
147+ , "\\stopplacetable" ]
148+ ]
149+ ]
test/Tests/Writers/Docbook.hs.orignew file+303
@@ -0,0 +1,303 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Writers.Docbook (tests) where
3+
4+import Data.Text (unpack)
5+import Test.Tasty
6+import Tests.Helpers
7+import Text.Pandoc
8+import Text.Pandoc.Arbitrary ()
9+import Text.Pandoc.Builder
10+
11+docbook :: (ToPandoc a) => a -> String
12+docbook = docbookWithOpts def{ writerWrapText = WrapNone }
13+
14+docbookWithOpts :: ToPandoc a => WriterOptions -> a -> String
15+docbookWithOpts opts = unpack . purely (writeDocbook4 opts) . toPandoc
16+
17+{-
18+ "my test" =: X =?> Y
19+
20+is shorthand for
21+
22+ test docbook "my test" $ X =?> Y
23+
24+which is in turn shorthand for
25+
26+ test docbook "my test" (X,Y)
27+-}
28+
29+infix 4 =:
30+(=:) :: (ToString a, ToPandoc a)
31+ => String -> (a, String) -> TestTree
32+(=:) = test docbook
33+
34+lineblock :: Blocks
35+lineblock = para ("some text" <> linebreak <>
36+ "and more lines" <> linebreak <>
37+ "and again")
38+lineblock_out :: [String]
39+lineblock_out = [ "<literallayout>some text"
40+ , "and more lines"
41+ , "and again</literallayout>"
42+ ]
43+
44+tests :: [TestTree]
45+tests = [ testGroup "line blocks"
46+ [ "none" =: para "This is a test"
47+ =?> unlines
48+ [ "<para>"
49+ , " This is a test"
50+ , "</para>"
51+ ]
52+ , "basic" =: lineblock
53+ =?> unlines lineblock_out
54+ , "blockquote" =: blockQuote lineblock
55+ =?> unlines
56+ ( [ "<blockquote>" ] ++
57+ lineblock_out ++
58+ [ "</blockquote>" ]
59+ )
60+ , "footnote" =: para ("This is a test" <>
61+ note lineblock <>
62+ " of footnotes")
63+ =?> unlines
64+ ( [ "<para>"
65+ , " This is a test<footnote>" ] ++
66+ lineblock_out ++
67+ [ " </footnote> of footnotes"
68+ , "</para>" ]
69+ )
70+ ]
71+ , testGroup "compact lists"
72+ [ testGroup "bullet"
73+ [ "compact" =: bulletList [plain "a", plain "b", plain "c"]
74+ =?> unlines
75+ [ "<itemizedlist spacing=\"compact\">"
76+ , " <listitem>"
77+ , " <para>"
78+ , " a"
79+ , " </para>"
80+ , " </listitem>"
81+ , " <listitem>"
82+ , " <para>"
83+ , " b"
84+ , " </para>"
85+ , " </listitem>"
86+ , " <listitem>"
87+ , " <para>"
88+ , " c"
89+ , " </para>"
90+ , " </listitem>"
91+ , "</itemizedlist>"
92+ ]
93+ , "loose" =: bulletList [para "a", para "b", para "c"]
94+ =?> unlines
95+ [ "<itemizedlist>"
96+ , " <listitem>"
97+ , " <para>"
98+ , " a"
99+ , " </para>"
100+ , " </listitem>"
101+ , " <listitem>"
102+ , " <para>"
103+ , " b"
104+ , " </para>"
105+ , " </listitem>"
106+ , " <listitem>"
107+ , " <para>"
108+ , " c"
109+ , " </para>"
110+ , " </listitem>"
111+ , "</itemizedlist>"
112+ ]
113+ ]
114+ , testGroup "ordered"
115+ [ "compact" =: orderedList [plain "a", plain "b", plain "c"]
116+ =?> unlines
117+ [ "<orderedlist spacing=\"compact\">"
118+ , " <listitem>"
119+ , " <para>"
120+ , " a"
121+ , " </para>"
122+ , " </listitem>"
123+ , " <listitem>"
124+ , " <para>"
125+ , " b"
126+ , " </para>"
127+ , " </listitem>"
128+ , " <listitem>"
129+ , " <para>"
130+ , " c"
131+ , " </para>"
132+ , " </listitem>"
133+ , "</orderedlist>"
134+ ]
135+ , "loose" =: orderedList [para "a", para "b", para "c"]
136+ =?> unlines
137+ [ "<orderedlist>"
138+ , " <listitem>"
139+ , " <para>"
140+ , " a"
141+ , " </para>"
142+ , " </listitem>"
143+ , " <listitem>"
144+ , " <para>"
145+ , " b"
146+ , " </para>"
147+ , " </listitem>"
148+ , " <listitem>"
149+ , " <para>"
150+ , " c"
151+ , " </para>"
152+ , " </listitem>"
153+ , "</orderedlist>"
154+ ]
155+ ]
156+ , testGroup "definition"
157+ [ "compact" =: definitionList [ ("an", [plain "apple" ])
158+ , ("a", [plain "banana"])
159+ , ("an", [plain "orange"])]
160+ =?> unlines
161+ [ "<variablelist spacing=\"compact\">"
162+ , " <varlistentry>"
163+ , " <term>"
164+ , " an"
165+ , " </term>"
166+ , " <listitem>"
167+ , " <para>"
168+ , " apple"
169+ , " </para>"
170+ , " </listitem>"
171+ , " </varlistentry>"
172+ , " <varlistentry>"
173+ , " <term>"
174+ , " a"
175+ , " </term>"
176+ , " <listitem>"
177+ , " <para>"
178+ , " banana"
179+ , " </para>"
180+ , " </listitem>"
181+ , " </varlistentry>"
182+ , " <varlistentry>"
183+ , " <term>"
184+ , " an"
185+ , " </term>"
186+ , " <listitem>"
187+ , " <para>"
188+ , " orange"
189+ , " </para>"
190+ , " </listitem>"
191+ , " </varlistentry>"
192+ , "</variablelist>"
193+ ]
194+ , "loose" =: definitionList [ ("an", [para "apple" ])
195+ , ("a", [para "banana"])
196+ , ("an", [para "orange"])]
197+ =?> unlines
198+ [ "<variablelist>"
199+ , " <varlistentry>"
200+ , " <term>"
201+ , " an"
202+ , " </term>"
203+ , " <listitem>"
204+ , " <para>"
205+ , " apple"
206+ , " </para>"
207+ , " </listitem>"
208+ , " </varlistentry>"
209+ , " <varlistentry>"
210+ , " <term>"
211+ , " a"
212+ , " </term>"
213+ , " <listitem>"
214+ , " <para>"
215+ , " banana"
216+ , " </para>"
217+ , " </listitem>"
218+ , " </varlistentry>"
219+ , " <varlistentry>"
220+ , " <term>"
221+ , " an"
222+ , " </term>"
223+ , " <listitem>"
224+ , " <para>"
225+ , " orange"
226+ , " </para>"
227+ , " </listitem>"
228+ , " </varlistentry>"
229+ , "</variablelist>"
230+ ]
231+ ]
232+ ]
233+ , testGroup "writer options"
234+ [ testGroup "top-level division" $
235+ let
236+ headers = header 1 (text "header1")
237+ <> header 2 (text "header2")
238+ <> header 3 (text "header3")
239+
240+ docbookTopLevelDiv :: (ToPandoc a)
241+ => TopLevelDivision -> a -> String
242+ docbookTopLevelDiv division =
243+ docbookWithOpts def{ writerTopLevelDivision = division }
244+ in
245+ [ test (docbookTopLevelDiv TopLevelSection) "sections as top-level" $
246+ headers =?>
247+ unlines [ "<sect1>"
248+ , " <title>header1</title>"
249+ , " <sect2>"
250+ , " <title>header2</title>"
251+ , " <sect3>"
252+ , " <title>header3</title>"
253+ , " <para>"
254+ , " </para>"
255+ , " </sect3>"
256+ , " </sect2>"
257+ , "</sect1>"
258+ ]
259+ , test (docbookTopLevelDiv TopLevelChapter) "chapters as top-level" $
260+ headers =?>
261+ unlines [ "<chapter>"
262+ , " <title>header1</title>"
263+ , " <sect1>"
264+ , " <title>header2</title>"
265+ , " <sect2>"
266+ , " <title>header3</title>"
267+ , " <para>"
268+ , " </para>"
269+ , " </sect2>"
270+ , " </sect1>"
271+ , "</chapter>"
272+ ]
273+ , test (docbookTopLevelDiv TopLevelPart) "parts as top-level" $
274+ headers =?>
275+ unlines [ "<part>"
276+ , " <title>header1</title>"
277+ , " <chapter>"
278+ , " <title>header2</title>"
279+ , " <sect1>"
280+ , " <title>header3</title>"
281+ , " <para>"
282+ , " </para>"
283+ , " </sect1>"
284+ , " </chapter>"
285+ , "</part>"
286+ ]
287+ , test (docbookTopLevelDiv TopLevelDefault) "default top-level" $
288+ headers =?>
289+ unlines [ "<sect1>"
290+ , " <title>header1</title>"
291+ , " <sect2>"
292+ , " <title>header2</title>"
293+ , " <sect3>"
294+ , " <title>header3</title>"
295+ , " <para>"
296+ , " </para>"
297+ , " </sect3>"
298+ , " </sect2>"
299+ , "</sect1>"
300+ ]
301+ ]
302+ ]
303+ ]
test/Tests/Writers/Docx.hs.orignew file+157
@@ -0,0 +1,157 @@
1+module Tests.Writers.Docx (tests) where
2+
3+import Text.Pandoc
4+import Test.Tasty
5+import Tests.Writers.OOXML
6+import Test.Tasty.HUnit
7+import Data.List (isPrefixOf)
8+
9+-- we add an extra check to make sure that we're not writing in the
10+-- toplevel docx directory. We don't want to accidentally overwrite an
11+-- Word-generated docx file used to test the reader.
12+docxTest :: String -> WriterOptions -> FilePath -> FilePath -> TestTree
13+docxTest testName opts nativeFP goldenFP =
14+ if "docx/golden/" `isPrefixOf` goldenFP
15+ then ooxmlTest writeDocx testName opts nativeFP goldenFP
16+ else testCase testName $
17+ assertFailure $
18+ goldenFP ++ " is not in `test/docx/golden`"
19+
20+tests :: [TestTree]
21+tests = [ testGroup "inlines"
22+ [ docxTest
23+ "font formatting"
24+ def
25+ "docx/inline_formatting.native"
26+ "docx/golden/inline_formatting.docx"
27+ , docxTest
28+ "hyperlinks"
29+ def
30+ "docx/links.native"
31+ "docx/golden/links.docx"
32+ , docxTest
33+ "inline image"
34+ def
35+ "docx/image_writer_test.native"
36+ "docx/golden/image.docx"
37+ , docxTest
38+ "inline images"
39+ def
40+ "docx/inline_images_writer_test.native"
41+ "docx/golden/inline_images.docx"
42+ , docxTest
43+ "handling unicode input"
44+ def
45+ "docx/unicode.native"
46+ "docx/golden/unicode.docx"
47+ , docxTest
48+ "inline code"
49+ def
50+ "docx/inline_code.native"
51+ "docx/golden/inline_code.docx"
52+ , docxTest
53+ "inline code in subscript and superscript"
54+ def
55+ "docx/verbatim_subsuper.native"
56+ "docx/golden/verbatim_subsuper.docx"
57+ ]
58+ , testGroup "blocks"
59+ [ docxTest
60+ "headers"
61+ def
62+ "docx/headers.native"
63+ "docx/golden/headers.docx"
64+ , docxTest
65+ "nested anchor spans in header"
66+ def
67+ "docx/nested_anchors_in_header.native"
68+ "docx/golden/nested_anchors_in_header.docx"
69+ , docxTest
70+ "lists"
71+ def
72+ "docx/lists.native"
73+ "docx/golden/lists.docx"
74+ , docxTest
75+ "lists continuing after interruption"
76+ def
77+ "docx/lists_continuing.native"
78+ "docx/golden/lists_continuing.docx"
79+ , docxTest
80+ "lists restarting after interruption"
81+ def
82+ "docx/lists_restarting.native"
83+ "docx/golden/lists_restarting.docx"
84+ , docxTest
85+ "definition lists"
86+ def
87+ "docx/definition_list.native"
88+ "docx/golden/definition_list.docx"
89+ , docxTest
90+ "footnotes and endnotes"
91+ def
92+ "docx/notes.native"
93+ "docx/golden/notes.docx"
94+ , docxTest
95+ "links in footnotes and endnotes"
96+ def
97+ "docx/link_in_notes.native"
98+ "docx/golden/link_in_notes.docx"
99+ , docxTest
100+ "blockquotes"
101+ def
102+ "docx/block_quotes_parse_indent.native"
103+ "docx/golden/block_quotes.docx"
104+ , docxTest
105+ "tables"
106+ def
107+ "docx/tables.native"
108+ "docx/golden/tables.docx"
109+ , docxTest
110+ "tables with lists in cells"
111+ def
112+ "docx/table_with_list_cell.native"
113+ "docx/golden/table_with_list_cell.docx"
114+ , docxTest
115+ "tables with one row"
116+ def
117+ "docx/table_one_row.native"
118+ "docx/golden/table_one_row.docx"
119+ , docxTest
120+ "code block"
121+ def
122+ "docx/codeblock.native"
123+ "docx/golden/codeblock.docx"
124+ ]
125+ , testGroup "track changes"
126+ [ docxTest
127+ "insertion"
128+ def
129+ "docx/track_changes_insertion_all.native"
130+ "docx/golden/track_changes_insertion.docx"
131+ , docxTest
132+ "deletion"
133+ def
134+ "docx/track_changes_deletion_all.native"
135+ "docx/golden/track_changes_deletion.docx"
136+ , docxTest
137+ "move text"
138+ def
139+ "docx/track_changes_move_all.native"
140+ "docx/golden/track_changes_move.docx"
141+ , docxTest
142+ "comments"
143+ def
144+ "docx/comments.native"
145+ "docx/golden/comments.docx"
146+ ]
147+ , testGroup "custom styles"
148+ [ docxTest "custom styles without reference.docx"
149+ def
150+ "docx/custom_style.native"
151+ "docx/golden/custom_style_no_reference.docx"
152+ , docxTest "custom styles with reference.docx"
153+ def{writerReferenceDoc = Just "docx/custom-style-reference.docx"}
154+ "docx/custom_style.native"
155+ "docx/golden/custom_style_reference.docx"
156+ ]
157+ ]
test/Tests/Writers/FB2.hs.orignew file+34
@@ -0,0 +1,34 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Writers.FB2 (tests) where
3+
4+import Test.Tasty
5+import Tests.Helpers
6+import Text.Pandoc
7+import Text.Pandoc.Arbitrary ()
8+import Text.Pandoc.Builder
9+
10+fb2 :: String -> String
11+fb2 x = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ++
12+ "<FictionBook xmlns=\"http://www.gribuser.ru/xml/fictionbook/2.0\" xmlns:l=\"http://www.w3.org/1999/xlink\"><description><title-info><genre>unrecognised</genre></title-info><document-info><program-used>pandoc</program-used></document-info></description><body><title><p /></title><section>" ++ x ++ "</section></body></FictionBook>"
13+
14+infix 4 =:
15+(=:) :: (ToString a, ToPandoc a)
16+ => String -> (a, String) -> TestTree
17+(=:) = test (purely (writeFB2 def) . toPandoc)
18+
19+tests :: [TestTree]
20+tests = [ testGroup "block elements"
21+ ["para" =: para "Lorem ipsum cetera."
22+ =?> fb2 "<p>Lorem ipsum cetera.</p>"
23+ ]
24+ , testGroup "inlines"
25+ [
26+ "Emphasis" =: emph "emphasized"
27+ =?> fb2 "<emphasis>emphasized</emphasis>"
28+ ]
29+ , "bullet list" =: bulletList [ plain $ text "first"
30+ , plain $ text "second"
31+ , plain $ text "third"
32+ ]
33+ =?> fb2 "<p>\x2022 first</p><p>\x2022 second</p><p>\x2022 third</p>"
34+ ]
test/Tests/Writers/HTML.hs.orignew file+44
@@ -0,0 +1,44 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Writers.HTML (tests) where
3+
4+import Data.Text (unpack)
5+import Test.Tasty
6+import Tests.Helpers
7+import Text.Pandoc
8+import Text.Pandoc.Arbitrary ()
9+import Text.Pandoc.Builder
10+
11+html :: (ToPandoc a) => a -> String
12+html = unpack . purely (writeHtml4String def{ writerWrapText = WrapNone }) . toPandoc
13+
14+{-
15+ "my test" =: X =?> Y
16+
17+is shorthand for
18+
19+ test html "my test" $ X =?> Y
20+
21+which is in turn shorthand for
22+
23+ test html "my test" (X,Y)
24+-}
25+
26+infix 4 =:
27+(=:) :: (ToString a, ToPandoc a)
28+ => String -> (a, String) -> TestTree
29+(=:) = test html
30+
31+tests :: [TestTree]
32+tests = [ testGroup "inline code"
33+ [ "basic" =: code "@&" =?> "<code>@&amp;</code>"
34+ , "haskell" =: codeWith ("",["haskell"],[]) ">>="
35+ =?> "<code class=\"sourceCode haskell\"><span class=\"fu\">&gt;&gt;=</span></code>"
36+ , "nolanguage" =: codeWith ("",["nolanguage"],[]) ">>="
37+ =?> "<code class=\"nolanguage\">&gt;&gt;=</code>"
38+ ]
39+ , testGroup "images"
40+ [ "alt with formatting" =:
41+ image "/url" "title" ("my " <> emph "image")
42+ =?> "<img src=\"/url\" title=\"title\" alt=\"my image\" />"
43+ ]
44+ ]
test/Tests/Writers/JATS.hs.orignew file+108
@@ -0,0 +1,108 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Writers.JATS (tests) where
3+
4+import Data.Text (unpack)
5+import Test.Tasty
6+import Tests.Helpers
7+import Text.Pandoc
8+import Text.Pandoc.Arbitrary ()
9+import Text.Pandoc.Builder
10+
11+jats :: (ToPandoc a) => a -> String
12+jats = unpack . purely (writeJATS def{ writerWrapText = WrapNone }) . toPandoc
13+
14+{-
15+ "my test" =: X =?> Y
16+
17+is shorthand for
18+
19+ test jats "my test" $ X =?> Y
20+
21+which is in turn shorthand for
22+
23+ test jats "my test" (X,Y)
24+-}
25+
26+infix 4 =:
27+(=:) :: (ToString a, ToPandoc a)
28+ => String -> (a, String) -> TestTree
29+(=:) = test jats
30+
31+tests :: [TestTree]
32+tests = [ testGroup "inline code"
33+ [ "basic" =: code "@&" =?> "<p><monospace>@&amp;</monospace></p>"
34+ , "lang" =: codeWith ("", ["c"], []) "@&" =?> "<p><code language=\"c\">@&amp;</code></p>"
35+ ]
36+ , testGroup "block code"
37+ [ "basic" =: codeBlock "@&" =?> "<preformat>@&amp;</preformat>"
38+ , "lang" =: codeBlockWith ("", ["c"], []) "@&" =?> "<code language=\"c\">@&amp;</code>"
39+ ]
40+ , testGroup "images"
41+ [ "basic" =:
42+ image "/url" "title" mempty
43+ =?> "<graphic mimetype=\"image\" mime-subtype=\"\" xlink:href=\"/url\" xlink:title=\"title\" />"
44+ ]
45+ , testGroup "inlines"
46+ [ "Emphasis" =: emph "emphasized"
47+ =?> "<p><italic>emphasized</italic></p>"
48+ ]
49+ , "bullet list" =: bulletList [ plain $ text "first"
50+ , plain $ text "second"
51+ , plain $ text "third"
52+ ]
53+ =?> "<list list-type=\"bullet\">\n\
54+ \ <list-item>\n\
55+ \ <p>first</p>\n\
56+ \ </list-item>\n\
57+ \ <list-item>\n\
58+ \ <p>second</p>\n\
59+ \ </list-item>\n\
60+ \ <list-item>\n\
61+ \ <p>third</p>\n\
62+ \ </list-item>\n\
63+ \</list>"
64+ , testGroup "definition lists"
65+ [ "with internal link" =: definitionList [(link "#go" "" (str "testing"),
66+ [plain (text "hi there")])] =?>
67+ "<def-list>\n\
68+ \ <def-item>\n\
69+ \ <term><xref alt=\"testing\" rid=\"go\">testing</xref></term>\n\
70+ \ <def>\n\
71+ \ <p>hi there</p>\n\
72+ \ </def>\n\
73+ \ </def-item>\n\
74+ \</def-list>"
75+ ]
76+ , testGroup "math"
77+ [ "escape |" =: para (math "\\sigma|_{\\{x\\}}") =?>
78+ "<p><inline-formula><alternatives>\n\
79+ \<tex-math><![CDATA[\\sigma|_{\\{x\\}}]]></tex-math>\n\
80+ \<mml:math display=\"inline\" xmlns:mml=\"http://www.w3.org/1998/Math/MathML\"><mml:mrow><mml:mi>σ</mml:mi><mml:msub><mml:mo stretchy=\"false\" form=\"prefix\">|</mml:mo><mml:mrow><mml:mo stretchy=\"false\" form=\"prefix\">{</mml:mo><mml:mi>x</mml:mi><mml:mo stretchy=\"false\" form=\"postfix\">}</mml:mo></mml:mrow></mml:msub></mml:mrow></mml:math></alternatives></inline-formula></p>"
81+ ]
82+ , testGroup "headers"
83+ [ "unnumbered header" =:
84+ headerWith ("foo",["unnumbered"],[]) 1
85+ (text "Header 1" <> note (plain $ text "note")) =?>
86+ "<sec id=\"foo\">\n\
87+ \ <title>Header 1<fn>\n\
88+ \ <p>note</p>\n\
89+ \ </fn></title>\n\
90+ \</sec>"
91+ , "unnumbered sub header" =:
92+ headerWith ("foo",["unnumbered"],[]) 1
93+ (text "Header")
94+ <> headerWith ("foo",["unnumbered"],[]) 2
95+ (text "Sub-Header") =?>
96+ "<sec id=\"foo\">\n\
97+ \ <title>Header</title>\n\
98+ \ <sec id=\"foo\">\n\
99+ \ <title>Sub-Header</title>\n\
100+ \ </sec>\n\
101+ \</sec>"
102+ , "containing image" =:
103+ header 1 (image "imgs/foo.jpg" "" (text "Alt text")) =?>
104+ "<sec>\n\
105+ \ <title><inline-graphic mimetype=\"image\" mime-subtype=\"jpeg\" xlink:href=\"imgs/foo.jpg\" /></title>\n\
106+ \</sec>"
107+ ]
108+ ]
test/Tests/Writers/LaTeX.hs.orignew file+176
@@ -0,0 +1,176 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Writers.LaTeX (tests) where
3+
4+import Data.Text (unpack)
5+import Test.Tasty
6+import Tests.Helpers
7+import Text.Pandoc
8+import Text.Pandoc.Arbitrary ()
9+import Text.Pandoc.Builder
10+
11+latex :: (ToPandoc a) => a -> String
12+latex = latexWithOpts def
13+
14+latexListing :: (ToPandoc a) => a -> String
15+latexListing = latexWithOpts def{ writerListings = True }
16+
17+latexWithOpts :: (ToPandoc a) => WriterOptions -> a -> String
18+latexWithOpts opts = unpack . purely (writeLaTeX opts) . toPandoc
19+
20+beamerWithOpts :: (ToPandoc a) => WriterOptions -> a -> String
21+beamerWithOpts opts = unpack . purely (writeBeamer opts) . toPandoc
22+
23+{-
24+ "my test" =: X =?> Y
25+
26+is shorthand for
27+
28+ test latex "my test" $ X =?> Y
29+
30+which is in turn shorthand for
31+
32+ test latex "my test" (X,Y)
33+-}
34+
35+infix 4 =:
36+(=:) :: (ToString a, ToPandoc a)
37+ => String -> (a, String) -> TestTree
38+(=:) = test latex
39+
40+tests :: [TestTree]
41+tests = [ testGroup "code blocks"
42+ [ "in footnotes" =: note (para "hi" <> codeBlock "hi") =?>
43+ "\\footnote{hi\n\n\\begin{Verbatim}\nhi\n\\end{Verbatim}\n}"
44+ , test latexListing "identifier" $ codeBlockWith ("id",[],[]) "hi" =?>
45+ ("\\begin{lstlisting}[label=id]\nhi\n\\end{lstlisting}" :: String)
46+ , test latexListing "no identifier" $ codeBlock "hi" =?>
47+ ("\\begin{lstlisting}\nhi\n\\end{lstlisting}" :: String)
48+ ]
49+ , testGroup "definition lists"
50+ [ "with internal link" =: definitionList [(link "#go" "" (str "testing"),
51+ [plain (text "hi there")])] =?>
52+ "\\begin{description}\n\\tightlist\n\\item[{\\protect\\hyperlink{go}{testing}}]\nhi there\n\\end{description}"
53+ ]
54+ , testGroup "math"
55+ [ "escape |" =: para (math "\\sigma|_{\\{x\\}}") =?>
56+ "\\(\\sigma|_{\\{x\\}}\\)"
57+ ]
58+ , testGroup "headers"
59+ [ "unnumbered header" =:
60+ headerWith ("foo",["unnumbered"],[]) 1
61+ (text "Header 1" <> note (plain $ text "note")) =?>
62+ "\\hypertarget{foo}{%\n\\section*{\\texorpdfstring{Header 1\\footnote{note}}{Header 1}}\\label{foo}}\n\\addcontentsline{toc}{section}{Header 1}\n"
63+ , "in list item" =:
64+ bulletList [header 2 (text "foo")] =?>
65+ "\\begin{itemize}\n\\item ~\n \\subsection{foo}\n\\end{itemize}"
66+ , "in definition list item" =:
67+ definitionList [(text "foo", [header 2 (text "bar"),
68+ para $ text "baz"])] =?>
69+ "\\begin{description}\n\\item[foo] ~ \n\\subsection{bar}\n\nbaz\n\\end{description}"
70+ , "containing image" =:
71+ header 1 (image "imgs/foo.jpg" "" (text "Alt text")) =?>
72+ "\\section{\\texorpdfstring{\\protect\\includegraphics{imgs/foo.jpg}}{Alt text}}"
73+ ]
74+ , testGroup "inline code"
75+ [ "struck out and highlighted" =:
76+ strikeout (codeWith ("",["haskell"],[]) "foo" <> space
77+ <> str "bar") =?>
78+ "\\sout{\\mbox{\\VERB|\\NormalTok{foo}|} bar}"
79+ , "struck out and not highlighted" =:
80+ strikeout (code "foo" <> space
81+ <> str "bar") =?>
82+ "\\sout{\\texttt{foo} bar}"
83+ , "single quotes" =:
84+ code "dog's" =?> "\\texttt{dog\\textquotesingle{}s}"
85+ , "backtick" =:
86+ code "`nu?`" =?> "\\texttt{\\textasciigrave{}nu?\\textasciigrave{}}"
87+ ]
88+ , testGroup "writer options"
89+ [ testGroup "top-level division" $
90+ let
91+ headers = header 1 (text "header1")
92+ <> header 2 (text "header2")
93+ <> header 3 (text "header3")
94+
95+ latexTopLevelDiv :: (ToPandoc a) => TopLevelDivision -> a -> String
96+ latexTopLevelDiv division =
97+ latexWithOpts def{ writerTopLevelDivision = division }
98+
99+ beamerTopLevelDiv :: (ToPandoc a)
100+ => TopLevelDivision -> a -> String
101+ beamerTopLevelDiv division =
102+ beamerWithOpts def { writerTopLevelDivision = division }
103+ in
104+ [ test (latexTopLevelDiv TopLevelSection)
105+ "sections as top-level" $ headers =?>
106+ unlines [ "\\section{header1}\n"
107+ , "\\subsection{header2}\n"
108+ , "\\subsubsection{header3}"
109+ ]
110+ , test (latexTopLevelDiv TopLevelChapter)
111+ "chapters as top-level" $ headers =?>
112+ unlines [ "\\chapter{header1}\n"
113+ , "\\section{header2}\n"
114+ , "\\subsection{header3}"
115+ ]
116+ , test (latexTopLevelDiv TopLevelPart)
117+ "parts as top-level" $ headers =?>
118+ unlines [ "\\part{header1}\n"
119+ , "\\chapter{header2}\n"
120+ , "\\section{header3}"
121+ ]
122+ , test (latexTopLevelDiv TopLevelDefault)
123+ "default top-level" $ headers =?>
124+ unlines [ "\\section{header1}\n"
125+ , "\\subsection{header2}\n"
126+ , "\\subsubsection{header3}"
127+ ]
128+ , test (beamerTopLevelDiv TopLevelSection)
129+ "sections as top-level in beamer" $ headers =?>
130+ unlines [ "\\section{header1}\n"
131+ , "\\subsection{header2}\n"
132+ , "\\subsubsection{header3}"
133+ ]
134+ , test (beamerTopLevelDiv TopLevelChapter)
135+ "chapters are as part in beamer" $ headers =?>
136+ unlines [ "\\part{header1}\n"
137+ , "\\section{header2}\n"
138+ , "\\subsection{header3}"
139+ ]
140+ , test (beamerTopLevelDiv TopLevelPart)
141+ "parts as top-level in beamer" $ headers =?>
142+ unlines [ "\\part{header1}\n"
143+ , "\\section{header2}\n"
144+ , "\\subsection{header3}"
145+ ]
146+ , test (beamerTopLevelDiv TopLevelDefault)
147+ "default top-level in beamer" $ headers =?>
148+ unlines [ "\\section{header1}\n"
149+ , "\\subsection{header2}\n"
150+ , "\\subsubsection{header3}"
151+ ]
152+ , test (latexTopLevelDiv TopLevelPart)
153+ "part top-level, section not in toc" $
154+ ( headerWith ("", ["unnumbered"], []) 1 (text "header1")
155+ <> headerWith ("", ["unnumbered"], []) 2 (text "header2")
156+ <> headerWith ("", ["unnumbered"], []) 3 (text "header3")
157+ <> headerWith ("", ["unnumbered"], []) 4 (text "header4")
158+ <> headerWith ("", ["unnumbered"], []) 5 (text "header5")
159+ <> headerWith ("", ["unnumbered"], []) 6 (text "header6"))
160+ =?>
161+ unlines [ "\\part*{header1}"
162+ , "\\addcontentsline{toc}{part}{header1}\n"
163+ , "\\chapter*{header2}"
164+ , "\\addcontentsline{toc}{chapter}{header2}\n"
165+ , "\\section*{header3}"
166+ , "\\addcontentsline{toc}{section}{header3}\n"
167+ , "\\subsection*{header4}"
168+ , "\\addcontentsline{toc}{subsection}{header4}\n"
169+ , "\\subsubsection*{header5}"
170+ , "\\addcontentsline{toc}{subsubsection}{header5}\n"
171+ , "\\paragraph{header6}"
172+ , "\\addcontentsline{toc}{paragraph}{header6}"
173+ ]
174+ ]
175+ ]
176+ ]
test/Tests/Writers/Markdown.hs.orignew file+267
@@ -0,0 +1,267 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
3+module Tests.Writers.Markdown (tests) where
4+
5+import Data.Text (unpack)
6+import Test.Tasty
7+import Tests.Helpers
8+import Text.Pandoc
9+import Text.Pandoc.Arbitrary ()
10+import Text.Pandoc.Builder
11+
12+defopts :: WriterOptions
13+defopts = def{ writerExtensions = pandocExtensions }
14+
15+markdown :: (ToPandoc a) => a -> String
16+markdown = unpack . purely (writeMarkdown defopts) . toPandoc
17+
18+markdownWithOpts :: (ToPandoc a) => WriterOptions -> a -> String
19+markdownWithOpts opts x = unpack . purely (writeMarkdown opts) $ toPandoc x
20+
21+{-
22+ "my test" =: X =?> Y
23+
24+is shorthand for
25+
26+ test markdown "my test" $ X =?> Y
27+
28+which is in turn shorthand for
29+
30+ test markdown "my test" (X,Y)
31+-}
32+
33+infix 4 =:
34+(=:) :: (ToString a, ToPandoc a)
35+ => String -> (a, String) -> TestTree
36+(=:) = test markdown
37+
38+tests :: [TestTree]
39+tests = [ "indented code after list"
40+ =: (orderedList [ para "one" <> para "two" ] <> codeBlock "test")
41+ =?> "1. one\n\n two\n\n<!-- -->\n\n test"
42+ , "list with tight sublist"
43+ =: bulletList [ plain "foo" <> bulletList [ plain "bar" ],
44+ plain "baz" ]
45+ =?> "- foo\n - bar\n- baz\n"
46+ ] ++ [noteTests] ++ [shortcutLinkRefsTests]
47+
48+{-
49+
50+Testing with the following text:
51+
52+First Header
53+============
54+
55+This is a footnote.[^1] And this is a [link](https://www.google.com).
56+
57+> A note inside a block quote.[^2]
58+>
59+> A second paragraph.
60+
61+Second Header
62+=============
63+
64+Some more text.
65+
66+
67+[^1]: Down here.
68+
69+[^2]: The second note.
70+
71+-}
72+
73+noteTestDoc :: Blocks
74+noteTestDoc =
75+ header 1 "First Header" <>
76+ para ("This is a footnote." <>
77+ note (para "Down here.") <>
78+ " And this is a " <>
79+ link "https://www.google.com" "" "link" <>
80+ ".") <>
81+ blockQuote (para ("A note inside a block quote." <>
82+ note (para "The second note.")) <>
83+ para "A second paragraph.") <>
84+ header 1 "Second Header" <>
85+ para "Some more text."
86+
87+
88+
89+noteTests :: TestTree
90+noteTests = testGroup "note and reference location"
91+ [ test (markdownWithOpts defopts)
92+ "footnotes at the end of a document" $
93+ noteTestDoc =?>
94+ (unlines [ "First Header"
95+ , "============"
96+ , ""
97+ , "This is a footnote.[^1] And this is a [link](https://www.google.com)."
98+ , ""
99+ , "> A note inside a block quote.[^2]"
100+ , ">"
101+ , "> A second paragraph."
102+ , ""
103+ , "Second Header"
104+ , "============="
105+ , ""
106+ , "Some more text."
107+ , ""
108+ , "[^1]: Down here."
109+ , ""
110+ , "[^2]: The second note."
111+ ])
112+ , test (markdownWithOpts defopts{writerReferenceLocation=EndOfBlock})
113+ "footnotes at the end of blocks" $
114+ noteTestDoc =?>
115+ (unlines [ "First Header"
116+ , "============"
117+ , ""
118+ , "This is a footnote.[^1] And this is a [link](https://www.google.com)."
119+ , ""
120+ , "[^1]: Down here."
121+ , ""
122+ , "> A note inside a block quote.[^2]"
123+ , ">"
124+ , "> A second paragraph."
125+ , ""
126+ , "[^2]: The second note."
127+ , ""
128+ , "Second Header"
129+ , "============="
130+ , ""
131+ , "Some more text."
132+ ])
133+ , test (markdownWithOpts defopts{writerReferenceLocation=EndOfBlock, writerReferenceLinks=True})
134+ "footnotes and reference links at the end of blocks" $
135+ noteTestDoc =?>
136+ (unlines [ "First Header"
137+ , "============"
138+ , ""
139+ , "This is a footnote.[^1] And this is a [link]."
140+ , ""
141+ , "[^1]: Down here."
142+ , ""
143+ , " [link]: https://www.google.com"
144+ , ""
145+ , "> A note inside a block quote.[^2]"
146+ , ">"
147+ , "> A second paragraph."
148+ , ""
149+ , "[^2]: The second note."
150+ , ""
151+ , "Second Header"
152+ , "============="
153+ , ""
154+ , "Some more text."
155+ ])
156+ , test (markdownWithOpts defopts{writerReferenceLocation=EndOfSection})
157+ "footnotes at the end of section" $
158+ noteTestDoc =?>
159+ (unlines [ "First Header"
160+ , "============"
161+ , ""
162+ , "This is a footnote.[^1] And this is a [link](https://www.google.com)."
163+ , ""
164+ , "> A note inside a block quote.[^2]"
165+ , ">"
166+ , "> A second paragraph."
167+ , ""
168+ , "[^1]: Down here."
169+ , ""
170+ , "[^2]: The second note."
171+ , ""
172+ , "Second Header"
173+ , "============="
174+ , ""
175+ , "Some more text."
176+ ])
177+
178+ ]
179+
180+shortcutLinkRefsTests :: TestTree
181+shortcutLinkRefsTests =
182+ let infix 4 =:
183+ (=:) :: (ToString a, ToPandoc a)
184+
185+ => String -> (a, String) -> TestTree
186+ (=:) = test (purely (writeMarkdown defopts{writerReferenceLinks = True}) . toPandoc)
187+ in testGroup "Shortcut reference links"
188+ [ "Simple link (shortcutable)"
189+ =: para (link "/url" "title" "foo")
190+ =?> "[foo]\n\n [foo]: /url \"title\""
191+ , "Followed by another link (unshortcutable)"
192+ =: para ((link "/url1" "title1" "first")
193+ <> (link "/url2" "title2" "second"))
194+ =?> unlines [ "[first][][second]"
195+ , ""
196+ , " [first]: /url1 \"title1\""
197+ , " [second]: /url2 \"title2\""
198+ ]
199+ , "Followed by space and another link (unshortcutable)"
200+ =: para ((link "/url1" "title1" "first") <> " "
201+ <> (link "/url2" "title2" "second"))
202+ =?> unlines [ "[first][] [second]"
203+ , ""
204+ , " [first]: /url1 \"title1\""
205+ , " [second]: /url2 \"title2\""
206+ ]
207+ , "Reference link is used multiple times (unshortcutable)"
208+ =: para ((link "/url1" "" "foo") <> (link "/url2" "" "foo")
209+ <> (link "/url3" "" "foo"))
210+ =?> unlines [ "[foo][][foo][1][foo][2]"
211+ , ""
212+ , " [foo]: /url1"
213+ , " [1]: /url2"
214+ , " [2]: /url3"
215+ ]
216+ , "Reference link is used multiple times (unshortcutable)"
217+ =: para ((link "/url1" "" "foo") <> " " <> (link "/url2" "" "foo")
218+ <> " " <> (link "/url3" "" "foo"))
219+ =?> unlines [ "[foo][] [foo][1] [foo][2]"
220+ , ""
221+ , " [foo]: /url1"
222+ , " [1]: /url2"
223+ , " [2]: /url3"
224+ ]
225+ , "Reference link is followed by text in brackets"
226+ =: para ((link "/url" "" "link") <> "[text in brackets]")
227+ =?> unlines [ "[link][]\\[text in brackets\\]"
228+ , ""
229+ , " [link]: /url"
230+ ]
231+ , "Reference link is followed by space and text in brackets"
232+ =: para ((link "/url" "" "link") <> " [text in brackets]")
233+ =?> unlines [ "[link][] \\[text in brackets\\]"
234+ , ""
235+ , " [link]: /url"
236+ ]
237+ , "Reference link is followed by RawInline"
238+ =: para ((link "/url" "" "link") <> rawInline "markdown" "[rawText]")
239+ =?> unlines [ "[link][][rawText]"
240+ , ""
241+ , " [link]: /url"
242+ ]
243+ , "Reference link is followed by space and RawInline"
244+ =: para ((link "/url" "" "link") <> space <> rawInline "markdown" "[rawText]")
245+ =?> unlines [ "[link][] [rawText]"
246+ , ""
247+ , " [link]: /url"
248+ ]
249+ , "Reference link is followed by RawInline with space"
250+ =: para ((link "/url" "" "link") <> rawInline "markdown" " [rawText]")
251+ =?> unlines [ "[link][] [rawText]"
252+ , ""
253+ , " [link]: /url"
254+ ]
255+ , "Reference link is followed by citation"
256+ =: para ((link "/url" "" "link") <> cite [Citation "author" [] [] NormalCitation 0 0] (str "[@author]"))
257+ =?> unlines [ "[link][][@author]"
258+ , ""
259+ , " [link]: /url"
260+ ]
261+ , "Reference link is followed by space and citation"
262+ =: para ((link "/url" "" "link") <> space <> cite [Citation "author" [] [] NormalCitation 0 0] (str "[@author]"))
263+ =?> unlines [ "[link][] [@author]"
264+ , ""
265+ , " [link]: /url"
266+ ]
267+ ]
test/Tests/Writers/Muse.hs.orignew file+410
@@ -0,0 +1,410 @@
1+module Tests.Writers.Muse (tests) where
2+
3+import Data.Text (unpack)
4+import Test.Tasty
5+import Tests.Helpers
6+import Text.Pandoc
7+import Text.Pandoc.Arbitrary ()
8+import Text.Pandoc.Builder
9+
10+muse :: (ToPandoc a) => a -> String
11+muse = museWithOpts def{ writerWrapText = WrapNone,
12+ writerExtensions = extensionsFromList [Ext_amuse,
13+ Ext_auto_identifiers] }
14+
15+museWithOpts :: (ToPandoc a) => WriterOptions -> a -> String
16+museWithOpts opts = unpack . purely (writeMuse opts) . toPandoc
17+
18+infix 4 =:
19+(=:) :: (ToString a, ToPandoc a)
20+ => String -> (a, String) -> TestTree
21+(=:) = test muse
22+
23+tests :: [TestTree]
24+tests = [ testGroup "block elements"
25+ [ "plain" =: plain (text "Foo bar.") =?> "Foo bar."
26+ , testGroup "paragraphs"
27+ [ "single paragraph" =: para (text "Sample paragraph.")
28+ =?> "Sample paragraph."
29+ , "two paragraphs" =: para (text "First paragraph.") <>
30+ para (text "Second paragraph.")
31+ =?> unlines [ "First paragraph."
32+ , ""
33+ , "Second paragraph."
34+ ]
35+ ]
36+ , "line block" =: lineBlock [text "Foo", text "bar", text "baz"]
37+ =?> unlines [ "> Foo"
38+ , "> bar"
39+ , "> baz"
40+ ]
41+ , "code block" =: codeBlock "int main(void) {\n\treturn 0;\n}"
42+ =?> unlines [ "<example>"
43+ , "int main(void) {"
44+ , "\treturn 0;"
45+ , "}"
46+ , "</example>"
47+ ]
48+ , "html raw block" =: rawBlock "html" "<hr>"
49+ =?> unlines [ "<literal style=\"html\">"
50+ , "<hr>"
51+ , "</literal>"
52+ ]
53+ , "block quote" =: blockQuote (para (text "Foo"))
54+ =?> unlines [ "<quote>"
55+ , "Foo"
56+ , "</quote>"
57+ ]
58+ , testGroup "lists"
59+ [ testGroup "simple lists"
60+ [
61+ "ordered list" =: orderedList [ plain $ text "first"
62+ , plain $ text "second"
63+ , plain $ text "third"
64+ ]
65+ =?> unlines [ " 1. first"
66+ , " 2. second"
67+ , " 3. third"
68+ ]
69+ , "ordered list with Roman numerals"
70+ =: orderedListWith (1, UpperRoman, DefaultDelim)
71+ [ plain $ text "first"
72+ , plain $ text "second"
73+ , plain $ text "third"
74+ ]
75+ =?> unlines [ " I. first"
76+ , " II. second"
77+ , " III. third"
78+ ]
79+ , "bullet list" =: bulletList [ plain $ text "first"
80+ , plain $ text "second"
81+ , plain $ text "third"
82+ ]
83+ =?> unlines [ " - first"
84+ , " - second"
85+ , " - third"
86+ ]
87+ , "definition list" =: definitionList [ (text "first definition", [plain $ text "first description"])
88+ , (text "second definition", [plain $ text "second description"])
89+ , (text "third definition", [plain $ text "third description"])
90+ ]
91+ =?> unlines [ " first definition :: first description"
92+ , " second definition :: second description"
93+ , " third definition :: third description"
94+ ]
95+ , "definition list with multiple descriptions" =:
96+ definitionList [ (text "first definition", [plain $ text "first description"
97+ ,plain $ text "second description"])
98+ , (text "second definition", [plain $ text "third description"])
99+ ]
100+ =?> unlines [ " first definition :: first description"
101+ , " :: second description"
102+ , " second definition :: third description"
103+ ]
104+ , "definition list with empty term" =:
105+ definitionList [ (text "first definition", [plain $ text "first description"])
106+ , (mempty, [plain $ text "second description"])
107+ , (str "", [plain $ text "third description"])
108+ ]
109+ =?> unlines [ " first definition :: first description"
110+ , " <verbatim></verbatim> :: second description"
111+ , " <verbatim></verbatim> :: third description"
112+ ]
113+ ]
114+ -- Test that lists of the same type and style are separated with two blanklines
115+ , testGroup "sequential lists"
116+ [ "bullet lists" =:
117+ bulletList [ para $ text "First"
118+ , para $ text "Second"
119+ , para $ text "Third"
120+ ] <>
121+ bulletList [ para $ text "Fourth"
122+ , para $ text "Fifth"
123+ ] =?>
124+ unlines [ " - First"
125+ , " - Second"
126+ , " - Third"
127+ , ""
128+ , ""
129+ , " - Fourth"
130+ , " - Fifth"
131+ ]
132+ , "ordered lists of the same style" =:
133+ orderedListWith (1, UpperRoman, DefaultDelim) [ para $ text "First"
134+ , para $ text "Second"
135+ ] <>
136+ orderedListWith (1, UpperRoman, DefaultDelim) [ para $ text "Third"
137+ , para $ text "Fourth"
138+ ] =?>
139+ unlines [ " I. First"
140+ , " II. Second"
141+ , ""
142+ , ""
143+ , " I. Third"
144+ , " II. Fourth"
145+ ]
146+ , "ordered lists with equal styles" =:
147+ orderedList [ para $ text "First"
148+ , para $ text "Second"
149+ ] <>
150+ orderedListWith (1, Decimal, DefaultDelim) [ para $ text "Third"
151+ , para $ text "Fourth"
152+ ] =?>
153+ unlines [ " 1. First"
154+ , " 2. Second"
155+ , ""
156+ , ""
157+ , " 1. Third"
158+ , " 2. Fourth"
159+ ]
160+ , "bullet and ordered lists" =:
161+ bulletList [ para $ text "First"
162+ , para $ text "Second"
163+ ] <>
164+ orderedListWith (1, UpperRoman, DefaultDelim) [ para $ text "Third"
165+ , para $ text "Fourth"
166+ ] =?>
167+ unlines [ " - First"
168+ , " - Second"
169+ , ""
170+ , " I. Third"
171+ , " II. Fourth"
172+ ]
173+ , "different style ordered lists" =:
174+ orderedListWith (1, UpperRoman, DefaultDelim) [ para $ text "First"
175+ , para $ text "Second"
176+ ] <>
177+ orderedListWith (1, Decimal, DefaultDelim) [ para $ text "Third"
178+ , para $ text "Fourth"
179+ ] =?>
180+ unlines [ " I. First"
181+ , " II. Second"
182+ , ""
183+ , " 1. Third"
184+ , " 2. Fourth"
185+ ]
186+ ]
187+ , testGroup "nested lists"
188+ [ "nested ordered list" =: orderedList [ plain $ text "First outer"
189+ , plain (text "Second outer:") <>
190+ orderedList [ plain $ text "first"
191+ , plain $ text "second"
192+ ]
193+ , plain $ text "Third outer"
194+ ]
195+ =?> unlines [ " 1. First outer"
196+ , " 2. Second outer:"
197+ , " 1. first"
198+ , " 2. second"
199+ , " 3. Third outer"
200+ ]
201+ , "nested bullet lists" =: bulletList [ plain $ text "First outer"
202+ , plain (text "Second outer:") <>
203+ bulletList [ plain $ text "first"
204+ , plain $ text "second"
205+ ]
206+ , plain $ text "Third outer"
207+ ]
208+ =?> unlines [ " - First outer"
209+ , " - Second outer:"
210+ , " - first"
211+ , " - second"
212+ , " - Third outer"
213+ ]
214+ , "nested definition lists" =: definitionList [ (text "first definition", [plain $ text "first description"])
215+ , (text "second definition",
216+ [ plain (text "second description") <>
217+ definitionList [ ( text "first inner definition"
218+ , [plain $ text "first inner description"])
219+ , ( text "second inner definition"
220+ , [plain $ text "second inner description"])
221+ ]
222+ ]
223+ )
224+ ]
225+ =?> unlines [ " first definition :: first description"
226+ , " second definition :: second description"
227+ , " first inner definition :: first inner description"
228+ , " second inner definition :: second inner description"
229+ ]
230+ ]
231+ -- Check that list is intended with one space even inside a quote
232+ , "List inside block quote" =: blockQuote (orderedList [ plain $ text "first"
233+ , plain $ text "second"
234+ , plain $ text "third"
235+ ])
236+ =?> unlines [ "<quote>"
237+ , " 1. first"
238+ , " 2. second"
239+ , " 3. third"
240+ , "</quote>"
241+ ]
242+ ]
243+ , testGroup "headings"
244+ [ "normal heading" =:
245+ header 1 (text "foo") =?> "* foo"
246+ , "heading levels" =:
247+ header 1 (text "First level") <>
248+ header 3 (text "Third level") =?>
249+ unlines [ "* First level"
250+ , ""
251+ , "*** Third level"
252+ ]
253+ , "heading with ID" =:
254+ headerWith ("bar", [], []) 2 (text "Foo") =?>
255+ unlines [ "** Foo"
256+ , "#bar"
257+ ]
258+ ]
259+ , "horizontal rule" =: horizontalRule =?> "----"
260+ , "escape horizontal rule" =: para (text "----") =?> "<verbatim>----</verbatim>"
261+ , "escape nonbreaking space" =: para (text "~~") =?> "<verbatim>~~</verbatim>"
262+ , testGroup "tables"
263+ [ "table without header" =:
264+ let rows = [[para $ text "Para 1.1", para $ text "Para 1.2"]
265+ ,[para $ text "Para 2.1", para $ text "Para 2.2"]]
266+ in simpleTable [] rows
267+ =?>
268+ unlines [ " Para 1.1 | Para 1.2"
269+ , " Para 2.1 | Para 2.2"
270+ ]
271+ , "table with header" =:
272+ let headers = [plain $ text "header 1", plain $ text "header 2"]
273+ rows = [[para $ text "Para 1.1", para $ text "Para 1.2"]
274+ ,[para $ text "Para 2.1", para $ text "Para 2.2"]]
275+ in simpleTable headers rows
276+ =?>
277+ unlines [ " header 1 || header 2"
278+ , " Para 1.1 | Para 1.2"
279+ , " Para 2.1 | Para 2.2"
280+ ]
281+ , "table with header and caption" =:
282+ let caption = text "Table 1"
283+ headers = [plain $ text "header 1", plain $ text "header 2"]
284+ rows = [[para $ text "Para 1.1", para $ text "Para 1.2"]
285+ ,[para $ text "Para 2.1", para $ text "Para 2.2"]]
286+ in table caption mempty headers rows
287+ =?> unlines [ " header 1 || header 2"
288+ , " Para 1.1 | Para 1.2"
289+ , " Para 2.1 | Para 2.2"
290+ , " |+ Table 1 +|"
291+ ]
292+ ]
293+ , "div with bullet list" =:
294+ divWith nullAttr (bulletList [para $ text "foo"]) =?>
295+ unlines [ " - foo" ] -- Making sure bullets are indented
296+ -- Null is trivial
297+ ]
298+ , testGroup "inline elements"
299+ [ testGroup "string"
300+ [ "string" =: str "foo" =?> "foo"
301+ , "escape footnote" =: str "[1]" =?> "<verbatim>[1]</verbatim>"
302+ , "escape verbatim close tag" =: str "foo</verbatim>bar"
303+ =?> "<verbatim>foo<</verbatim><verbatim>/verbatim>bar</verbatim>"
304+ , "escape pipe to avoid accidental tables" =: str "foo | bar"
305+ =?> "<verbatim>foo | bar</verbatim>"
306+ , "escape hash to avoid accidental anchors" =: text "#foo bar"
307+ =?> "<verbatim>#foo</verbatim> bar"
308+ , "escape definition list markers" =: str "::" =?> "<verbatim>::</verbatim>"
309+ , "normalize strings before escaping" =: fromList [Str ":", Str ":"] =?> "<verbatim>::</verbatim>"
310+ -- We don't want colons to be escaped if they can't be confused
311+ -- with definition list item markers.
312+ , "do not escape colon" =: str ":" =?> ":"
313+ , "escape - to avoid accidental unordered lists" =: text " - foo" =?> " <verbatim>-</verbatim> foo"
314+ , "escape - inside a list to avoid accidental nested unordered lists" =:
315+ bulletList [ (para $ text "foo") <>
316+ (para $ text "- bar")
317+ ] =?>
318+ unlines [ " - foo"
319+ , ""
320+ , " <verbatim>-</verbatim> bar"
321+ ]
322+ ]
323+ , testGroup "emphasis"
324+ [ "emph" =: emph (text "foo") =?> "<em>foo</em>"
325+ , "strong" =: strong (text "foo") =?> "<strong>foo</strong>"
326+ , "strikeout" =: strikeout (text "foo") =?> "<del>foo</del>"
327+ ]
328+ , "superscript" =: superscript (text "foo") =?> "<sup>foo</sup>"
329+ , "subscript" =: subscript (text "foo") =?> "<sub>foo</sub>"
330+ , "smallcaps" =: smallcaps (text "foo") =?> "<em>foo</em>"
331+ , "smallcaps near emphasis" =: emph (str "foo") <> smallcaps (str "bar") =?> "<em>foobar</em>"
332+ , "single quoted" =: singleQuoted (text "foo") =?> "‘foo’"
333+ , "double quoted" =: doubleQuoted (text "foo") =?> "“foo”"
334+ -- Cite is trivial
335+ , testGroup "code"
336+ [ "simple" =: code "foo" =?> "<code>foo</code>"
337+ , "escape tag" =: code "<code>foo = bar</code> baz" =?> "<code><code>foo = bar<</code><code>/code> baz</code>"
338+ , "normalization with attributes" =: codeWith ("",["haskell"],[]) "foo" <> code "bar" =?> "<code>foobar</code>"
339+ , "normalization" =: code "</co" <> code "de>" =?> "<code><</code><code>/code></code>"
340+ , "normalization with empty string" =: code "</co" <> str "" <> code "de>" =?> "<code><</code><code>/code></code>"
341+ ]
342+ , testGroup "spaces"
343+ [ "space" =: text "a" <> space <> text "b" =?> "a b"
344+ , "soft break" =: text "a" <> softbreak <> text "b" =?> "a b"
345+ , test (museWithOpts def{ writerWrapText = WrapPreserve })
346+ "preserve soft break" $ text "a" <> softbreak <> text "b"
347+ =?> "a\nb"
348+ , "line break" =: text "a" <> linebreak <> text "b" =?> "a<br>\nb"
349+ ]
350+ , testGroup "math"
351+ [ "inline math" =: math "2^3" =?> "2<sup>3</sup>"
352+ , "display math" =: displayMath "2^3" =?> "2<sup>3</sup>"
353+ , "multiple letters in inline math" =: math "abc" =?> "<em>abc</em>"
354+ , "expand math before normalization" =: math "[" <> str "2]" =?> "<verbatim>[2]</verbatim>"
355+ , "multiple math expressions inside one inline list" =: math "5_4" <> text ", " <> displayMath "3^2" =?> "5<sub>4</sub>, 3<sup>2</sup>"
356+ ]
357+ , "raw inline"
358+ =: rawInline "html" "<mark>marked text</mark>"
359+ =?> "<literal style=\"html\"><mark>marked text</mark></literal>"
360+ , testGroup "links"
361+ [ "link with description" =: link "https://example.com" "" (str "Link 1")
362+ =?> "[[https://example.com][Link 1]]"
363+ , "link without description" =: link "https://example.com" "" (str "https://example.com")
364+ =?> "[[https://example.com]]"
365+ -- Internal links in Muse include '#'
366+ , "link to anchor" =: link "#intro" "" (str "Introduction")
367+ =?> "[[#intro][Introduction]]"
368+ -- According to Emacs Muse manual, links to images should be prefixed with "URL:"
369+ , "link to image with description" =: link "1.png" "" (str "Link to image")
370+ =?> "[[URL:1.png][Link to image]]"
371+ , "link to image without description" =: link "1.png" "" (str "1.png")
372+ =?> "[[URL:1.png]]"
373+ ]
374+ , "image" =: image "image.png" "Image 1" (str "") =?> "[[image.png][Image 1]]"
375+ , "image with width" =:
376+ imageWith ("", [], [("width", "60%")]) "image.png" "Image" (str "") =?>
377+ "[[image.png 60][Image]]"
378+ , "note" =: note (plain (text "Foo"))
379+ =?> unlines [ "[1]"
380+ , ""
381+ , "[1] Foo"
382+ ]
383+ , "span with class" =: spanWith ("",["foobar"],[]) (text "Some text")
384+ =?> "<class name=\"foobar\">Some text</class>"
385+ , "span with anchor" =: spanWith ("anchor", [], []) (text "Foo bar")
386+ =?> "#anchor Foo bar"
387+ , "span with class and anchor" =: spanWith ("anchor", ["foo"], []) (text "bar")
388+ =?> "#anchor <class name=\"foo\">bar</class>"
389+ , testGroup "combined"
390+ [ "emph word before" =:
391+ para (text "foo" <> emph (text "bar")) =?>
392+ "foo<em>bar</em>"
393+ , "emph word after" =:
394+ para (emph (text "foo") <> text "bar") =?>
395+ "<em>foo</em>bar"
396+ , "emph quoted" =:
397+ para (doubleQuoted (emph (text "foo"))) =?>
398+ "“<em>foo</em>”"
399+ , "strong word before" =:
400+ para (text "foo" <> strong (text "bar")) =?>
401+ "foo<strong>bar</strong>"
402+ , "strong word after" =:
403+ para (strong (text "foo") <> text "bar") =?>
404+ "<strong>foo</strong>bar"
405+ , "strong quoted" =:
406+ para (singleQuoted (strong (text "foo"))) =?>
407+ "‘<strong>foo</strong>’"
408+ ]
409+ ]
410+ ]
test/Tests/Writers/Native.hs.orignew file+22
@@ -0,0 +1,22 @@
1+module Tests.Writers.Native (tests) where
2+
3+import Data.Text (unpack)
4+import Test.Tasty
5+import Test.Tasty.QuickCheck
6+import Tests.Helpers
7+import Text.Pandoc
8+import Text.Pandoc.Arbitrary ()
9+
10+p_write_rt :: Pandoc -> Bool
11+p_write_rt d =
12+ read (unpack $ purely (writeNative def{ writerTemplate = Just "" }) d) == d
13+
14+p_write_blocks_rt :: [Block] -> Bool
15+p_write_blocks_rt bs =
16+ read (unpack $ purely (writeNative def) (Pandoc nullMeta bs)) == bs
17+
18+tests :: [TestTree]
19+tests = [ testProperty "p_write_rt" p_write_rt
20+ , testProperty "p_write_blocks_rt" $ mapSize
21+ (\x -> if x > 3 then 3 else x) p_write_blocks_rt
22+ ]
test/Tests/Writers/OOXML.hs.orignew file+184
@@ -0,0 +1,184 @@
1+{-# LANGUAGE PatternGuards #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+
4+module Tests.Writers.OOXML (ooxmlTest) where
5+
6+import Text.Pandoc
7+import Test.Tasty
8+import Test.Tasty.Golden.Advanced
9+import Codec.Archive.Zip
10+import Text.XML.Light
11+import qualified Data.ByteString as BS
12+import qualified Data.ByteString.Lazy as BL
13+import qualified Data.Text.IO as T
14+import Data.List (isSuffixOf, sort, (\\), intercalate, union)
15+import Data.Maybe (catMaybes, mapMaybe)
16+import Tests.Helpers
17+import Data.Algorithm.Diff
18+import System.FilePath.Glob (compile, match)
19+
20+compareXMLBool :: Content -> Content -> Bool
21+-- We make a special exception for times at the moment, and just pass
22+-- them because we can't control the utctime when running IO. Besides,
23+-- so long as we have two times, we're okay.
24+compareXMLBool (Elem myElem) (Elem goodElem)
25+ | (QName "created" _ (Just "dcterms")) <- elName myElem
26+ , (QName "created" _ (Just "dcterms")) <- elName goodElem =
27+ True
28+compareXMLBool (Elem myElem) (Elem goodElem)
29+ | (QName "modified" _ (Just "dcterms")) <- elName myElem
30+ , (QName "modified" _ (Just "dcterms")) <- elName goodElem =
31+ True
32+compareXMLBool (Elem myElem) (Elem goodElem) =
33+ elName myElem == elName goodElem &&
34+ elAttribs myElem == elAttribs goodElem &&
35+ and (zipWith compareXMLBool (elContent myElem) (elContent goodElem))
36+compareXMLBool (Text myCData) (Text goodCData) =
37+ cdVerbatim myCData == cdVerbatim goodCData &&
38+ cdData myCData == cdData goodCData &&
39+ cdLine myCData == cdLine goodCData
40+compareXMLBool (CRef myStr) (CRef goodStr) =
41+ myStr == goodStr
42+compareXMLBool _ _ = False
43+
44+displayDiff :: Content -> Content -> String
45+displayDiff elemA elemB =
46+ showDiff (1,1) $ getDiff (lines $ ppContent elemA) (lines $ ppContent elemB)
47+
48+goldenArchive :: FilePath -> IO Archive
49+goldenArchive fp = (toArchive . BL.fromStrict) <$> BS.readFile fp
50+
51+testArchive :: (WriterOptions -> Pandoc -> PandocIO BL.ByteString)
52+ -> WriterOptions
53+ -> FilePath
54+ -> IO Archive
55+testArchive writerFn opts fp = do
56+ txt <- T.readFile fp
57+ bs <- runIOorExplode $ readNative def txt >>= writerFn opts
58+ return $ toArchive bs
59+
60+compareFileList :: FilePath -> Archive -> Archive -> Maybe String
61+compareFileList goldenFP goldenArch testArch =
62+ let testFiles = filesInArchive testArch
63+ goldenFiles = filesInArchive goldenArch
64+ diffTestGolden = testFiles \\ goldenFiles
65+ diffGoldenTest = goldenFiles \\ testFiles
66+
67+ results =
68+ [ if null diffGoldenTest
69+ then Nothing
70+ else Just $
71+ "Files in " ++ goldenFP ++ " but not in generated archive:\n" ++
72+ intercalate ", " diffGoldenTest
73+ , if null diffTestGolden
74+ then Nothing
75+ else Just $
76+ "Files in generated archive but not in " ++ goldenFP ++ ":\n" ++
77+ intercalate ", " diffTestGolden
78+ ]
79+ in
80+ if null $ catMaybes results
81+ then Nothing
82+ else Just $ intercalate "\n" $ catMaybes results
83+
84+compareXMLFile' :: FilePath -> Archive -> Archive -> Either String ()
85+compareXMLFile' fp goldenArch testArch = do
86+ testEntry <- case findEntryByPath fp testArch of
87+ Just entry -> Right entry
88+ Nothing -> Left $
89+ "Can't extract " ++ fp ++ " from generated archive"
90+ testXMLDoc <- case parseXMLDoc $ fromEntry testEntry of
91+ Just doc -> Right doc
92+ Nothing -> Left $
93+ "Can't parse xml in " ++ fp ++ " from generated archive"
94+
95+ goldenEntry <- case findEntryByPath fp goldenArch of
96+ Just entry -> Right entry
97+ Nothing -> Left $
98+ "Can't extract " ++ fp ++ " from archive in stored file"
99+ goldenXMLDoc <- case parseXMLDoc $ fromEntry goldenEntry of
100+ Just doc -> Right doc
101+ Nothing -> Left $
102+ "Can't parse xml in " ++ fp ++ " from archive in stored file"
103+
104+ let testContent = Elem testXMLDoc
105+ goldenContent = Elem goldenXMLDoc
106+
107+ if compareXMLBool goldenContent testContent
108+ then Right ()
109+ else Left $
110+ "Non-matching xml in " ++ fp ++ ":\n" ++ displayDiff testContent goldenContent
111+
112+compareXMLFile :: FilePath -> Archive -> Archive -> Maybe String
113+compareXMLFile fp goldenArch testArch =
114+ case compareXMLFile' fp goldenArch testArch of
115+ Right _ -> Nothing
116+ Left s -> Just s
117+
118+compareAllXMLFiles :: Archive -> Archive -> Maybe String
119+compareAllXMLFiles goldenArch testArch =
120+ let allFiles = filesInArchive goldenArch `union` filesInArchive testArch
121+ allXMLFiles = sort $
122+ filter
123+ (\fp -> ".xml" `isSuffixOf` fp || ".rels" `isSuffixOf` fp)
124+ allFiles
125+ results =
126+ mapMaybe (\fp -> compareXMLFile fp goldenArch testArch) allXMLFiles
127+ in
128+ if null results
129+ then Nothing
130+ else Just $ unlines results
131+
132+compareMediaFile' :: FilePath -> Archive -> Archive -> Either String ()
133+compareMediaFile' fp goldenArch testArch = do
134+ testEntry <- case findEntryByPath fp testArch of
135+ Just entry -> Right entry
136+ Nothing -> Left $
137+ "Can't extract " ++ fp ++ " from generated archive"
138+ goldenEntry <- case findEntryByPath fp goldenArch of
139+ Just entry -> Right entry
140+ Nothing -> Left $
141+ "Can't extract " ++ fp ++ " from archive in stored file"
142+
143+ if fromEntry testEntry == fromEntry goldenEntry
144+ then Right ()
145+ else Left $
146+ "Non-matching binary file: " ++ fp
147+
148+compareMediaFile :: FilePath -> Archive -> Archive -> Maybe String
149+compareMediaFile fp goldenArch testArch =
150+ case compareMediaFile' fp goldenArch testArch of
151+ Right _ -> Nothing
152+ Left s -> Just s
153+
154+compareAllMediaFiles :: Archive -> Archive -> Maybe String
155+compareAllMediaFiles goldenArch testArch =
156+ let allFiles = filesInArchive goldenArch `union` filesInArchive testArch
157+ mediaPattern = compile "*/media/*"
158+ allMediaFiles = sort $
159+ filter (match mediaPattern) allFiles
160+ results =
161+ mapMaybe (\fp -> compareMediaFile fp goldenArch testArch) allMediaFiles
162+ in
163+ if null results
164+ then Nothing
165+ else Just $ unlines results
166+
167+ooxmlTest :: (WriterOptions -> Pandoc -> PandocIO BL.ByteString)
168+ -> String
169+ -> WriterOptions
170+ -> FilePath
171+ -> FilePath
172+ -> TestTree
173+ooxmlTest writerFn testName opts nativeFP goldenFP =
174+ goldenTest
175+ testName
176+ (goldenArchive goldenFP)
177+ (testArchive writerFn opts nativeFP)
178+ (\goldenArch testArch ->
179+ let res = catMaybes [ compareFileList goldenFP goldenArch testArch
180+ , compareAllXMLFiles goldenArch testArch
181+ , compareAllMediaFiles goldenArch testArch
182+ ]
183+ in return $ if null res then Nothing else Just $ unlines res)
184+ (\a -> BL.writeFile goldenFP $ fromArchive a)
test/Tests/Writers/Org.hs.orignew file+25
@@ -0,0 +1,25 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Writers.Org (tests) where
3+
4+import Test.Tasty
5+import Tests.Helpers
6+import Text.Pandoc
7+import Text.Pandoc.Arbitrary ()
8+import Text.Pandoc.Builder
9+
10+infix 4 =:
11+(=:) :: (ToString a, ToPandoc a)
12+ => String -> (a, String) -> TestTree
13+(=:) = test (purely (writeOrg def . toPandoc))
14+
15+tests :: [TestTree]
16+tests = [ testGroup "links"
17+ -- See http://orgmode.org/manual/Internal-links.html#Internal-links
18+ [ "simple link"
19+ =: link "/url" "" "foo"
20+ =?> "[[/url][foo]]"
21+ , "internal link to anchor"
22+ =: link "#my-custom-id" "" "#my-custom-id"
23+ =?> "[[#my-custom-id]]"
24+ ]
25+ ]
test/Tests/Writers/Plain.hs.orignew file+21
@@ -0,0 +1,21 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Writers.Plain (tests) where
3+
4+import Test.Tasty
5+import Tests.Helpers
6+import Text.Pandoc
7+import Text.Pandoc.Arbitrary ()
8+import Text.Pandoc.Builder
9+
10+
11+infix 4 =:
12+(=:) :: (ToString a, ToPandoc a)
13+ => String -> (a, String) -> TestTree
14+(=:) = test (purely (writePlain def) . toPandoc)
15+
16+
17+tests :: [TestTree]
18+tests = [ "strongly emphasized text to uppercase"
19+ =: strong "Straße"
20+ =?> "STRASSE"
21+ ]
test/Tests/Writers/Powerpoint.hs.orignew file+93
@@ -0,0 +1,93 @@
1+module Tests.Writers.Powerpoint (tests) where
2+
3+import Tests.Writers.OOXML (ooxmlTest)
4+import Text.Pandoc
5+import Test.Tasty
6+import System.FilePath
7+
8+-- templating is important enough, and can break enough things, that
9+-- we want to run all our tests with both default formatting and a
10+-- template.
11+
12+modifyPptxName :: FilePath -> FilePath
13+modifyPptxName fp =
14+ addExtension (dropExtension fp ++ "_templated") "pptx"
15+
16+pptxTests :: String -> WriterOptions -> FilePath -> FilePath -> (TestTree, TestTree)
17+pptxTests name opts native pptx =
18+ let referenceDoc = "pptx/reference_depth.pptx"
19+ in
20+ ( ooxmlTest
21+ writePowerpoint
22+ name
23+ opts{writerReferenceDoc=Nothing}
24+ native
25+ pptx
26+ , ooxmlTest
27+ writePowerpoint
28+ name
29+ opts{writerReferenceDoc=Just referenceDoc}
30+ native
31+ (modifyPptxName pptx)
32+ )
33+
34+groupPptxTests :: [(TestTree, TestTree)] -> [TestTree]
35+groupPptxTests pairs =
36+ let (noRefs, refs) = unzip pairs
37+ in
38+ [ testGroup "Default slide formatting" noRefs
39+ , testGroup "With `--reference-doc` pptx file" refs
40+ ]
41+
42+
43+tests :: [TestTree]
44+tests = groupPptxTests [ pptxTests "Inline formatting"
45+ def
46+ "pptx/inline_formatting.native"
47+ "pptx/inline_formatting.pptx"
48+ , pptxTests "Slide breaks (default slide-level)"
49+ def
50+ "pptx/slide_breaks.native"
51+ "pptx/slide_breaks.pptx"
52+ , pptxTests "slide breaks (slide-level set to 1)"
53+ def{ writerSlideLevel = Just 1 }
54+ "pptx/slide_breaks.native"
55+ "pptx/slide_breaks_slide_level_1.pptx"
56+ , pptxTests "lists"
57+ def
58+ "pptx/lists.native"
59+ "pptx/lists.pptx"
60+ , pptxTests "tables"
61+ def
62+ "pptx/tables.native"
63+ "pptx/tables.pptx"
64+ , pptxTests "table of contents"
65+ def{ writerTableOfContents = True }
66+ "pptx/slide_breaks.native"
67+ "pptx/slide_breaks_toc.pptx"
68+ , pptxTests "end notes"
69+ def
70+ "pptx/endnotes.native"
71+ "pptx/endnotes.pptx"
72+ , pptxTests "end notes, with table of contents"
73+ def { writerTableOfContents = True }
74+ "pptx/endnotes.native"
75+ "pptx/endnotes_toc.pptx"
76+ , pptxTests "images"
77+ def
78+ "pptx/images.native"
79+ "pptx/images.pptx"
80+ , pptxTests "two-column layout"
81+ def
82+ "pptx/two_column.native"
83+ "pptx/two_column.pptx"
84+ , pptxTests "speaker notes"
85+ def
86+ "pptx/speaker_notes.native"
87+ "pptx/speaker_notes.pptx"
88+ , pptxTests "remove empty slides"
89+ def
90+ "pptx/remove_empty_slides.native"
91+ "pptx/remove_empty_slides.pptx"
92+
93+ ]
test/Tests/Writers/RST.hs.orignew file+130
@@ -0,0 +1,130 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Writers.RST (tests) where
3+
4+import Test.Tasty
5+import Tests.Helpers
6+import Text.Pandoc
7+import Text.Pandoc.Arbitrary ()
8+import Text.Pandoc.Builder
9+
10+infix 4 =:
11+(=:) :: (ToString a, ToPandoc a)
12+ => String -> (a, String) -> TestTree
13+(=:) = test (purely (writeRST def . toPandoc))
14+
15+tests :: [TestTree]
16+tests = [ testGroup "rubrics"
17+ [ "in list item" =:
18+ bulletList [header 2 (text "foo")] =?>
19+ "- .. rubric:: foo"
20+ , "in definition list item" =:
21+ definitionList [(text "foo", [header 2 (text "bar"),
22+ para $ text "baz"])] =?>
23+ unlines
24+ [ "foo"
25+ , " .. rubric:: bar"
26+ , ""
27+ , " baz"]
28+ , "in block quote" =:
29+ blockQuote (header 1 (text "bar")) =?>
30+ " .. rubric:: bar"
31+ , "with id" =:
32+ blockQuote (headerWith ("foo",[],[]) 1 (text "bar")) =?>
33+ unlines
34+ [ " .. rubric:: bar"
35+ , " :name: foo"]
36+ , "with id class" =:
37+ blockQuote (headerWith ("foo",["baz"],[]) 1 (text "bar")) =?>
38+ unlines
39+ [ " .. rubric:: bar"
40+ , " :name: foo"
41+ , " :class: baz"]
42+ ]
43+ , testGroup "ligatures" -- handling specific sequences of blocks
44+ [ "a list is closed by a comment before a quote" =: -- issue 4248
45+ bulletList [plain "bulleted"] <> blockQuote (plain "quoted") =?>
46+ unlines
47+ [ "- bulleted"
48+ , ""
49+ , ".."
50+ , ""
51+ , " quoted"]
52+ ]
53+ , testGroup "inlines"
54+ [ "are removed when empty" =: -- #4434
55+ plain (strong (str "")) =?> ""
56+ , "do not cause the introduction of extra spaces when removed" =:
57+ plain (strong (str "") <> emph (str "text")) =?> "*text*"
58+ , "spaces are stripped at beginning and end" =:
59+ -- pandoc issue 4327 "The text within inline markup may not
60+ -- begin or end with whitespace"
61+ -- http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#inline-markup
62+ strong (space <> str "text" <> space <> space) =?> "**text**"
63+ , "single space stripped" =:
64+ strong space =?> ""
65+ ]
66+ , testGroup "headings"
67+ [ "normal heading" =:
68+ header 1 (text "foo") =?>
69+ unlines
70+ [ "foo"
71+ , "==="]
72+ -- note: heading normalization is only done in standalone mode
73+ , test (purely (writeRST def{ writerTemplate = Just "$body$\n" }) . toPandoc)
74+ "heading levels" $
75+ header 1 (text "Header 1") <>
76+ header 3 (text "Header 2") <>
77+ header 2 (text "Header 2") <>
78+ header 1 (text "Header 1") <>
79+ header 4 (text "Header 2") <>
80+ header 5 (text "Header 3") <>
81+ header 3 (text "Header 2") =?>
82+ unlines
83+ [ "Header 1"
84+ , "========"
85+ , ""
86+ , "Header 2"
87+ , "--------"
88+ , ""
89+ , "Header 2"
90+ , "--------"
91+ , ""
92+ , "Header 1"
93+ , "========"
94+ , ""
95+ , "Header 2"
96+ , "--------"
97+ , ""
98+ , "Header 3"
99+ , "~~~~~~~~"
100+ , ""
101+ , "Header 2"
102+ , "--------"]
103+ , test (purely (writeRST def{ writerTemplate = Just "$body$\n" }) . toPandoc)
104+ "minimal heading levels" $
105+ header 2 (text "Header 1") <>
106+ header 3 (text "Header 2") <>
107+ header 2 (text "Header 1") <>
108+ header 4 (text "Header 2") <>
109+ header 5 (text "Header 3") <>
110+ header 3 (text "Header 2") =?>
111+ unlines
112+ [ "Header 1"
113+ , "========"
114+ , ""
115+ , "Header 2"
116+ , "--------"
117+ , ""
118+ , "Header 1"
119+ , "========"
120+ , ""
121+ , "Header 2"
122+ , "--------"
123+ , ""
124+ , "Header 3"
125+ , "~~~~~~~~"
126+ , ""
127+ , "Header 2"
128+ , "--------"]
129+ ]
130+ ]
test/Tests/Writers/TEI.hs.orignew file+43
@@ -0,0 +1,43 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Writers.TEI (tests) where
3+
4+import Test.Tasty
5+import Tests.Helpers
6+import Text.Pandoc
7+import Text.Pandoc.Arbitrary ()
8+import Text.Pandoc.Builder
9+
10+{-
11+ "my test" =: X =?> Y
12+
13+is shorthand for
14+
15+ test html "my test" $ X =?> Y
16+
17+which is in turn shorthand for
18+
19+ test html "my test" (X,Y)
20+-}
21+
22+infix 4 =:
23+(=:) :: (ToString a, ToPandoc a)
24+ => String -> (a, String) -> TestTree
25+(=:) = test (purely (writeTEI def) . toPandoc)
26+
27+tests :: [TestTree]
28+tests = [ testGroup "block elements"
29+ ["para" =: para "Lorem ipsum cetera."
30+ =?> "<p>Lorem ipsum cetera.</p>"
31+ ]
32+ , testGroup "inlines"
33+ [
34+ "Emphasis" =: emph "emphasized"
35+ =?> "<p><hi rendition=\"simple:italic\">emphasized</hi></p>"
36+ ,"SingleQuoted" =: singleQuoted (text "quoted material")
37+ =?> "<p><quote>quoted material</quote></p>"
38+ ,"DoubleQuoted" =: doubleQuoted (text "quoted material")
39+ =?> "<p><quote>quoted material</quote></p>"
40+ ,"NestedQuoted" =: doubleQuoted (singleQuoted (text "quoted material"))
41+ =?> "<p><quote><quote>quoted material</quote></quote></p>"
42+ ]
43+ ]
test/command/3510-src.hs.orignew file+1
@@ -0,0 +1 @@
1+putStrLn outString
test/command/4550.mdnew file+7
@@ -0,0 +1,7 @@
1+```
2+% pandoc -f markdown-smart -t ms
3+A ‘simple’ example
4+^D
5+.LP
6+A ‘simple’ example
7+```
test/pandoc-test14632-86new file+1448
@@ -0,0 +1,1448 @@
1+<?xml version="1.0" encoding="utf-8" ?>
2+<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Publishing DTD v1.0 20120330//EN"
3+ "JATS-journalpublishing1.dtd">
4+<article xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink" dtd-version="1.0" article-type="other">
5+<front>
6+<journal-meta>
7+<journal-title-group>
8+</journal-title-group>
9+<publisher>
10+<publisher-name></publisher-name>
11+</publisher>
12+</journal-meta>
13+<article-meta>
14+<title-group>
15+<article-title>Pandoc Test Suite</article-title>
16+</title-group>
17+<contrib-group>
18+<contrib contrib-type="author">
19+<name>
20+<string-name>John MacFarlane</string-name>
21+</name>
22+</contrib>
23+<contrib contrib-type="author">
24+<name>
25+<string-name>Anonymous</string-name>
26+</name>
27+</contrib>
28+</contrib-group>
29+<pub-date pub-type="epub">
30+<string-date>July 17, 2006</string-date>
31+</pub-date>
32+</article-meta>
33+</front>
34+<body>
35+<p>
36+ This is a set of tests for pandoc. Most of them are adapted from John
37+ Gruber’s markdown test suite.
38+</p>
39+<sec id="headers">
40+ <title>Headers</title>
41+ <sec id="level-2-with-an-embedded-link">
42+ <title>Level 2 with an
43+ <ext-link ext-link-type="uri" xlink:href="/url">embedded
44+ link</ext-link></title>
45+ <sec id="level-3-with-emphasis">
46+ <title>Level 3 with <italic>emphasis</italic></title>
47+ <sec id="level-4">
48+ <title>Level 4</title>
49+ <sec id="level-5">
50+ <title>Level 5</title>
51+ </sec>
52+ </sec>
53+ </sec>
54+ </sec>
55+</sec>
56+<sec id="level-1">
57+ <title>Level 1</title>
58+ <sec id="level-2-with-emphasis">
59+ <title>Level 2 with <italic>emphasis</italic></title>
60+ <sec id="level-3">
61+ <title>Level 3</title>
62+ <p>
63+ with no blank line
64+ </p>
65+ </sec>
66+ </sec>
67+ <sec id="level-2">
68+ <title>Level 2</title>
69+ <p>
70+ with no blank line
71+ </p>
72+ </sec>
73+</sec>
74+<sec id="paragraphs">
75+ <title>Paragraphs</title>
76+ <p>
77+ Here’s a regular paragraph.
78+ </p>
79+ <p>
80+ In Markdown 1.0.0 and earlier. Version 8. This line turns into a list
81+ item. Because a hard-wrapped line in the middle of a paragraph looked like
82+ a list item.
83+ </p>
84+ <p>
85+ Here’s one with a bullet. * criminey.
86+ </p>
87+ <p>
88+ There should be a hard line break<break />here.
89+ </p>
90+</sec>
91+<sec id="block-quotes">
92+ <title>Block Quotes</title>
93+ <p>
94+ E-mail style:
95+ </p>
96+ <disp-quote>
97+ <p>
98+ This is a block quote. It is pretty short.
99+ </p>
100+ </disp-quote>
101+ <disp-quote>
102+ <p>
103+ Code in a block quote:
104+ </p>
105+ <preformat>sub status {
106+ print &quot;working&quot;;
107+}</preformat>
108+ <p>
109+ A list:
110+ </p>
111+ <list list-type="order">
112+ <list-item>
113+ <p>
114+ item one
115+ </p>
116+ </list-item>
117+ <list-item>
118+ <p>
119+ item two
120+ </p>
121+ </list-item>
122+ </list>
123+ <p>
124+ Nested block quotes:
125+ </p>
126+ <disp-quote>
127+ <p>
128+ nested
129+ </p>
130+ </disp-quote>
131+ <disp-quote>
132+ <p>
133+ nested
134+ </p>
135+ </disp-quote>
136+ </disp-quote>
137+ <p>
138+ This should not be a block quote: 2 &gt; 1.
139+ </p>
140+ <p>
141+ And a following paragraph.
142+ </p>
143+</sec>
144+<sec id="code-blocks">
145+ <title>Code Blocks</title>
146+ <p>
147+ Code:
148+ </p>
149+ <preformat>---- (should be four hyphens)
150+
151+sub status {
152+ print &quot;working&quot;;
153+}
154+
155+this code block is indented by one tab</preformat>
156+ <p>
157+ And:
158+ </p>
159+ <preformat> this code block is indented by two tabs
160+
161+These should not be escaped: \$ \\ \&gt; \[ \{</preformat>
162+</sec>
163+<sec id="lists">
164+ <title>Lists</title>
165+ <sec id="unordered">
166+ <title>Unordered</title>
167+ <p>
168+ Asterisks tight:
169+ </p>
170+ <list list-type="bullet">
171+ <list-item>
172+ <p>
173+ asterisk 1
174+ </p>
175+ </list-item>
176+ <list-item>
177+ <p>
178+ asterisk 2
179+ </p>
180+ </list-item>
181+ <list-item>
182+ <p>
183+ asterisk 3
184+ </p>
185+ </list-item>
186+ </list>
187+ <p>
188+ Asterisks loose:
189+ </p>
190+ <list list-type="bullet">
191+ <list-item>
192+ <p>
193+ asterisk 1
194+ </p>
195+ </list-item>
196+ <list-item>
197+ <p>
198+ asterisk 2
199+ </p>
200+ </list-item>
201+ <list-item>
202+ <p>
203+ asterisk 3
204+ </p>
205+ </list-item>
206+ </list>
207+ <p>
208+ Pluses tight:
209+ </p>
210+ <list list-type="bullet">
211+ <list-item>
212+ <p>
213+ Plus 1
214+ </p>
215+ </list-item>
216+ <list-item>
217+ <p>
218+ Plus 2
219+ </p>
220+ </list-item>
221+ <list-item>
222+ <p>
223+ Plus 3
224+ </p>
225+ </list-item>
226+ </list>
227+ <p>
228+ Pluses loose:
229+ </p>
230+ <list list-type="bullet">
231+ <list-item>
232+ <p>
233+ Plus 1
234+ </p>
235+ </list-item>
236+ <list-item>
237+ <p>
238+ Plus 2
239+ </p>
240+ </list-item>
241+ <list-item>
242+ <p>
243+ Plus 3
244+ </p>
245+ </list-item>
246+ </list>
247+ <p>
248+ Minuses tight:
249+ </p>
250+ <list list-type="bullet">
251+ <list-item>
252+ <p>
253+ Minus 1
254+ </p>
255+ </list-item>
256+ <list-item>
257+ <p>
258+ Minus 2
259+ </p>
260+ </list-item>
261+ <list-item>
262+ <p>
263+ Minus 3
264+ </p>
265+ </list-item>
266+ </list>
267+ <p>
268+ Minuses loose:
269+ </p>
270+ <list list-type="bullet">
271+ <list-item>
272+ <p>
273+ Minus 1
274+ </p>
275+ </list-item>
276+ <list-item>
277+ <p>
278+ Minus 2
279+ </p>
280+ </list-item>
281+ <list-item>
282+ <p>
283+ Minus 3
284+ </p>
285+ </list-item>
286+ </list>
287+ </sec>
288+ <sec id="ordered">
289+ <title>Ordered</title>
290+ <p>
291+ Tight:
292+ </p>
293+ <list list-type="order">
294+ <list-item>
295+ <p>
296+ First
297+ </p>
298+ </list-item>
299+ <list-item>
300+ <p>
301+ Second
302+ </p>
303+ </list-item>
304+ <list-item>
305+ <p>
306+ Third
307+ </p>
308+ </list-item>
309+ </list>
310+ <p>
311+ and:
312+ </p>
313+ <list list-type="order">
314+ <list-item>
315+ <p>
316+ One
317+ </p>
318+ </list-item>
319+ <list-item>
320+ <p>
321+ Two
322+ </p>
323+ </list-item>
324+ <list-item>
325+ <p>
326+ Three
327+ </p>
328+ </list-item>
329+ </list>
330+ <p>
331+ Loose using tabs:
332+ </p>
333+ <list list-type="order">
334+ <list-item>
335+ <p>
336+ First
337+ </p>
338+ </list-item>
339+ <list-item>
340+ <p>
341+ Second
342+ </p>
343+ </list-item>
344+ <list-item>
345+ <p>
346+ Third
347+ </p>
348+ </list-item>
349+ </list>
350+ <p>
351+ and using spaces:
352+ </p>
353+ <list list-type="order">
354+ <list-item>
355+ <p>
356+ One
357+ </p>
358+ </list-item>
359+ <list-item>
360+ <p>
361+ Two
362+ </p>
363+ </list-item>
364+ <list-item>
365+ <p>
366+ Three
367+ </p>
368+ </list-item>
369+ </list>
370+ <p>
371+ Multiple paragraphs:
372+ </p>
373+ <list list-type="order">
374+ <list-item>
375+ <p>
376+ Item 1, graf one.
377+ </p>
378+ <p>
379+ Item 1. graf two. The quick brown fox jumped over the lazy dog’s
380+ back.
381+ </p>
382+ </list-item>
383+ <list-item>
384+ <p>
385+ Item 2.
386+ </p>
387+ </list-item>
388+ <list-item>
389+ <p>
390+ Item 3.
391+ </p>
392+ </list-item>
393+ </list>
394+ </sec>
395+ <sec id="nested">
396+ <title>Nested</title>
397+ <list list-type="bullet">
398+ <list-item>
399+ <p>
400+ Tab
401+ </p>
402+ <list list-type="bullet">
403+ <list-item>
404+ <p>
405+ Tab
406+ </p>
407+ <list list-type="bullet">
408+ <list-item>
409+ <p>
410+ Tab
411+ </p>
412+ </list-item>
413+ </list>
414+ </list-item>
415+ </list>
416+ </list-item>
417+ </list>
418+ <p>
419+ Here’s another:
420+ </p>
421+ <list list-type="order">
422+ <list-item>
423+ <p>
424+ First
425+ </p>
426+ </list-item>
427+ <list-item>
428+ <p>
429+ Second:
430+ </p>
431+ <list list-type="bullet">
432+ <list-item>
433+ <p>
434+ Fee
435+ </p>
436+ </list-item>
437+ <list-item>
438+ <p>
439+ Fie
440+ </p>
441+ </list-item>
442+ <list-item>
443+ <p>
444+ Foe
445+ </p>
446+ </list-item>
447+ </list>
448+ </list-item>
449+ <list-item>
450+ <p>
451+ Third
452+ </p>
453+ </list-item>
454+ </list>
455+ <p>
456+ Same thing but with paragraphs:
457+ </p>
458+ <list list-type="order">
459+ <list-item>
460+ <p>
461+ First
462+ </p>
463+ </list-item>
464+ <list-item>
465+ <p>
466+ Second:
467+ </p>
468+ <list list-type="bullet">
469+ <list-item>
470+ <p>
471+ Fee
472+ </p>
473+ </list-item>
474+ <list-item>
475+ <p>
476+ Fie
477+ </p>
478+ </list-item>
479+ <list-item>
480+ <p>
481+ Foe
482+ </p>
483+ </list-item>
484+ </list>
485+ </list-item>
486+ <list-item>
487+ <p>
488+ Third
489+ </p>
490+ </list-item>
491+ </list>
492+ </sec>
493+ <sec id="tabs-and-spaces">
494+ <title>Tabs and spaces</title>
495+ <list list-type="bullet">
496+ <list-item>
497+ <p>
498+ this is a list item indented with tabs
499+ </p>
500+ </list-item>
501+ <list-item>
502+ <p>
503+ this is a list item indented with spaces
504+ </p>
505+ <list list-type="bullet">
506+ <list-item>
507+ <p>
508+ this is an example list item indented with tabs
509+ </p>
510+ </list-item>
511+ <list-item>
512+ <p>
513+ this is an example list item indented with spaces
514+ </p>
515+ </list-item>
516+ </list>
517+ </list-item>
518+ </list>
519+ </sec>
520+ <sec id="fancy-list-markers">
521+ <title>Fancy list markers</title>
522+ <list list-type="order">
523+ <list-item>
524+ <label>
525+ (2)
526+ </label>
527+ <p>
528+ begins with 2
529+ </p>
530+ </list-item>
531+ <list-item>
532+ <label>
533+ (3)
534+ </label>
535+ <p>
536+ and now 3
537+ </p>
538+ <p>
539+ with a continuation
540+ </p>
541+ <list list-type="roman-lower">
542+ <list-item>
543+ <label>
544+ iv.
545+ </label>
546+ <p>
547+ sublist with roman numerals, starting with 4
548+ </p>
549+ </list-item>
550+ <list-item>
551+ <label>
552+ v.
553+ </label>
554+ <p>
555+ more items
556+ </p>
557+ <list list-type="alpha-upper">
558+ <list-item>
559+ <label>
560+ (A)
561+ </label>
562+ <p>
563+ a subsublist
564+ </p>
565+ </list-item>
566+ <list-item>
567+ <label>
568+ (B)
569+ </label>
570+ <p>
571+ a subsublist
572+ </p>
573+ </list-item>
574+ </list>
575+ </list-item>
576+ </list>
577+ </list-item>
578+ </list>
579+ <p>
580+ Nesting:
581+ </p>
582+ <list list-type="alpha-upper">
583+ <list-item>
584+ <p>
585+ Upper Alpha
586+ </p>
587+ <list list-type="roman-upper">
588+ <list-item>
589+ <p>
590+ Upper Roman.
591+ </p>
592+ <list list-type="order">
593+ <list-item>
594+ <label>
595+ (6)
596+ </label>
597+ <p>
598+ Decimal start with 6
599+ </p>
600+ <list list-type="alpha-lower">
601+ <list-item>
602+ <label>
603+ c)
604+ </label>
605+ <p>
606+ Lower alpha with paren
607+ </p>
608+ </list-item>
609+ </list>
610+ </list-item>
611+ </list>
612+ </list-item>
613+ </list>
614+ </list-item>
615+ </list>
616+ <p>
617+ Autonumbering:
618+ </p>
619+ <list list-type="order">
620+ <list-item>
621+ <p>
622+ Autonumber.
623+ </p>
624+ </list-item>
625+ <list-item>
626+ <p>
627+ More.
628+ </p>
629+ <list list-type="order">
630+ <list-item>
631+ <p>
632+ Nested.
633+ </p>
634+ </list-item>
635+ </list>
636+ </list-item>
637+ </list>
638+ <p>
639+ Should not be a list item:
640+ </p>
641+ <p>
642+ M.A. 2007
643+ </p>
644+ <p>
645+ B. Williams
646+ </p>
647+ </sec>
648+</sec>
649+<sec id="definition-lists">
650+ <title>Definition Lists</title>
651+ <p>
652+ Tight using spaces:
653+ </p>
654+ <def-list>
655+ <def-item>
656+ <term>
657+ apple
658+ </term>
659+ <def>
660+ <p>
661+ red fruit
662+ </p>
663+ </def>
664+ </def-item>
665+ <def-item>
666+ <term>
667+ orange
668+ </term>
669+ <def>
670+ <p>
671+ orange fruit
672+ </p>
673+ </def>
674+ </def-item>
675+ <def-item>
676+ <term>
677+ banana
678+ </term>
679+ <def>
680+ <p>
681+ yellow fruit
682+ </p>
683+ </def>
684+ </def-item>
685+ </def-list>
686+ <p>
687+ Tight using tabs:
688+ </p>
689+ <def-list>
690+ <def-item>
691+ <term>
692+ apple
693+ </term>
694+ <def>
695+ <p>
696+ red fruit
697+ </p>
698+ </def>
699+ </def-item>
700+ <def-item>
701+ <term>
702+ orange
703+ </term>
704+ <def>
705+ <p>
706+ orange fruit
707+ </p>
708+ </def>
709+ </def-item>
710+ <def-item>
711+ <term>
712+ banana
713+ </term>
714+ <def>
715+ <p>
716+ yellow fruit
717+ </p>
718+ </def>
719+ </def-item>
720+ </def-list>
721+ <p>
722+ Loose:
723+ </p>
724+ <def-list>
725+ <def-item>
726+ <term>
727+ apple
728+ </term>
729+ <def>
730+ <p>
731+ red fruit
732+ </p>
733+ </def>
734+ </def-item>
735+ <def-item>
736+ <term>
737+ orange
738+ </term>
739+ <def>
740+ <p>
741+ orange fruit
742+ </p>
743+ </def>
744+ </def-item>
745+ <def-item>
746+ <term>
747+ banana
748+ </term>
749+ <def>
750+ <p>
751+ yellow fruit
752+ </p>
753+ </def>
754+ </def-item>
755+ </def-list>
756+ <p>
757+ Multiple blocks with italics:
758+ </p>
759+ <def-list>
760+ <def-item>
761+ <term>
762+ <italic>apple</italic>
763+ </term>
764+ <def>
765+ <p>
766+ red fruit
767+ </p>
768+ <p>
769+ contains seeds, crisp, pleasant to taste
770+ </p>
771+ </def>
772+ </def-item>
773+ <def-item>
774+ <term>
775+ <italic>orange</italic>
776+ </term>
777+ <def>
778+ <p>
779+ orange fruit
780+ </p>
781+ <preformat>{ orange code block }</preformat>
782+ <disp-quote>
783+ <p>
784+ orange block quote
785+ </p>
786+ </disp-quote>
787+ </def>
788+ </def-item>
789+ </def-list>
790+ <p>
791+ Multiple definitions, tight:
792+ </p>
793+ <def-list>
794+ <def-item>
795+ <term>
796+ apple
797+ </term>
798+ <def>
799+ <p>
800+ red fruit
801+ </p>
802+ <p>
803+ computer
804+ </p>
805+ </def>
806+ </def-item>
807+ <def-item>
808+ <term>
809+ orange
810+ </term>
811+ <def>
812+ <p>
813+ orange fruit
814+ </p>
815+ <p>
816+ bank
817+ </p>
818+ </def>
819+ </def-item>
820+ </def-list>
821+ <p>
822+ Multiple definitions, loose:
823+ </p>
824+ <def-list>
825+ <def-item>
826+ <term>
827+ apple
828+ </term>
829+ <def>
830+ <p>
831+ red fruit
832+ </p>
833+ <p>
834+ computer
835+ </p>
836+ </def>
837+ </def-item>
838+ <def-item>
839+ <term>
840+ orange
841+ </term>
842+ <def>
843+ <p>
844+ orange fruit
845+ </p>
846+ <p>
847+ bank
848+ </p>
849+ </def>
850+ </def-item>
851+ </def-list>
852+ <p>
853+ Blank line after term, indented marker, alternate markers:
854+ </p>
855+ <def-list>
856+ <def-item>
857+ <term>
858+ apple
859+ </term>
860+ <def>
861+ <p>
862+ red fruit
863+ </p>
864+ <p>
865+ computer
866+ </p>
867+ </def>
868+ </def-item>
869+ <def-item>
870+ <term>
871+ orange
872+ </term>
873+ <def>
874+ <p>
875+ orange fruit
876+ </p>
877+ <list list-type="order">
878+ <list-item>
879+ <p>
880+ sublist
881+ </p>
882+ </list-item>
883+ <list-item>
884+ <p>
885+ sublist
886+ </p>
887+ </list-item>
888+ </list>
889+ </def>
890+ </def-item>
891+ </def-list>
892+</sec>
893+<sec id="html-blocks">
894+ <title>HTML Blocks</title>
895+ <p>
896+ Simple block on one line:
897+ </p>
898+ <boxed-text>
899+ <p>
900+ foo
901+ </p>
902+ </boxed-text>
903+ <p>
904+ And nested without indentation:
905+ </p>
906+ <boxed-text>
907+ <boxed-text>
908+ <boxed-text>
909+ <p>
910+ foo
911+ </p>
912+ </boxed-text>
913+ </boxed-text>
914+ <boxed-text>
915+ <p>
916+ bar
917+ </p>
918+ </boxed-text>
919+ </boxed-text>
920+ <p>
921+ Interpreted markdown in a table:
922+ </p>
923+ <p>
924+ This is <italic>emphasized</italic>
925+ </p>
926+ <p>
927+ And this is <bold role="strong">strong</bold>
928+ </p>
929+ <p>
930+ Here’s a simple block:
931+ </p>
932+ <boxed-text>
933+ <p>
934+ foo
935+ </p>
936+ </boxed-text>
937+ <p>
938+ This should be a code block, though:
939+ </p>
940+ <preformat>&lt;div&gt;
941+ foo
942+&lt;/div&gt;</preformat>
943+ <p>
944+ As should this:
945+ </p>
946+ <preformat>&lt;div&gt;foo&lt;/div&gt;</preformat>
947+ <p>
948+ Now, nested:
949+ </p>
950+ <boxed-text>
951+ <boxed-text>
952+ <boxed-text>
953+ <p>
954+ foo
955+ </p>
956+ </boxed-text>
957+ </boxed-text>
958+ </boxed-text>
959+ <p>
960+ This should just be an HTML comment:
961+ </p>
962+ <p>
963+ Multiline:
964+ </p>
965+ <p>
966+ Code block:
967+ </p>
968+ <preformat>&lt;!-- Comment --&gt;</preformat>
969+ <p>
970+ Just plain comment, with trailing spaces on the line:
971+ </p>
972+ <p>
973+ Code:
974+ </p>
975+ <preformat>&lt;hr /&gt;</preformat>
976+ <p>
977+ Hr’s:
978+ </p>
979+</sec>
980+<sec id="inline-markup">
981+ <title>Inline Markup</title>
982+ <p>
983+ This is <italic>emphasized</italic>, and so <italic>is this</italic>.
984+ </p>
985+ <p>
986+ This is <bold role="strong">strong</bold>, and so <bold role="strong">is
987+ this</bold>.
988+ </p>
989+ <p>
990+ An <italic><ext-link ext-link-type="uri" xlink:href="/url">emphasized
991+ link</ext-link></italic>.
992+ </p>
993+ <p>
994+ <bold role="strong"><italic>This is strong and em.</italic></bold>
995+ </p>
996+ <p>
997+ So is <bold role="strong"><italic>this</italic></bold> word.
998+ </p>
999+ <p>
1000+ <bold role="strong"><italic>This is strong and em.</italic></bold>
1001+ </p>
1002+ <p>
1003+ So is <bold role="strong"><italic>this</italic></bold> word.
1004+ </p>
1005+ <p>
1006+ This is code: <monospace>&gt;</monospace>, <monospace>$</monospace>,
1007+ <monospace>\</monospace>, <monospace>\$</monospace>,
1008+ <monospace>&lt;html&gt;</monospace>.
1009+ </p>
1010+ <p>
1011+ <strike>This is <italic>strikeout</italic>.</strike>
1012+ </p>
1013+ <p>
1014+ Superscripts: a<sup>bc</sup>d a<sup><italic>hello</italic></sup>
1015+ a<sup>hello there</sup>.
1016+ </p>
1017+ <p>
1018+ Subscripts: H<sub>2</sub>O, H<sub>23</sub>O, H<sub>many of them</sub>O.
1019+ </p>
1020+ <p>
1021+ These should not be superscripts or subscripts, because of the unescaped
1022+ spaces: a^b c^d, a~b c~d.
1023+ </p>
1024+</sec>
1025+<sec id="smart-quotes-ellipses-dashes">
1026+ <title>Smart quotes, ellipses, dashes</title>
1027+ <p>
1028+ “Hello,” said the spider. “‘Shelob’ is my name.”
1029+ </p>
1030+ <p>
1031+ ‘A’, ‘B’, and ‘C’ are letters.
1032+ </p>
1033+ <p>
1034+ ‘Oak,’ ‘elm,’ and ‘beech’ are names of trees. So is ‘pine.’
1035+ </p>
1036+ <p>
1037+ ‘He said, “I want to go.”’ Were you alive in the 70’s?
1038+ </p>
1039+ <p>
1040+ Here is some quoted ‘<monospace>code</monospace>’ and a
1041+ “<ext-link ext-link-type="uri" xlink:href="http://example.com/?foo=1&amp;bar=2">quoted
1042+ link</ext-link>”.
1043+ </p>
1044+ <p>
1045+ Some dashes: one—two — three—four — five.
1046+ </p>
1047+ <p>
1048+ Dashes between numbers: 5–7, 255–66, 1987–1999.
1049+ </p>
1050+ <p>
1051+ Ellipses…and…and….
1052+ </p>
1053+</sec>
1054+<sec id="latex">
1055+ <title>LaTeX</title>
1056+ <list list-type="bullet">
1057+ <list-item>
1058+ <p>
1059+ </p>
1060+ </list-item>
1061+ <list-item>
1062+ <p>
1063+ <inline-formula><alternatives>
1064+ <tex-math><![CDATA[2+2=4]]></tex-math>
1065+ <mml:math display="inline" xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mrow><mml:mn>2</mml:mn><mml:mo>+</mml:mo><mml:mn>2</mml:mn><mml:mo>=</mml:mo><mml:mn>4</mml:mn></mml:mrow></mml:math></alternatives></inline-formula>
1066+ </p>
1067+ </list-item>
1068+ <list-item>
1069+ <p>
1070+ <inline-formula><alternatives>
1071+ <tex-math><![CDATA[x \in y]]></tex-math>
1072+ <mml:math display="inline" xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mrow><mml:mi>x</mml:mi><mml:mo>∈</mml:mo><mml:mi>y</mml:mi></mml:mrow></mml:math></alternatives></inline-formula>
1073+ </p>
1074+ </list-item>
1075+ <list-item>
1076+ <p>
1077+ <inline-formula><alternatives>
1078+ <tex-math><![CDATA[\alpha \wedge \omega]]></tex-math>
1079+ <mml:math display="inline" xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mrow><mml:mi>α</mml:mi><mml:mo>∧</mml:mo><mml:mi>ω</mml:mi></mml:mrow></mml:math></alternatives></inline-formula>
1080+ </p>
1081+ </list-item>
1082+ <list-item>
1083+ <p>
1084+ <inline-formula><alternatives>
1085+ <tex-math><![CDATA[223]]></tex-math>
1086+ <mml:math display="inline" xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mn>223</mml:mn></mml:math></alternatives></inline-formula>
1087+ </p>
1088+ </list-item>
1089+ <list-item>
1090+ <p>
1091+ <inline-formula><alternatives>
1092+ <tex-math><![CDATA[p]]></tex-math>
1093+ <mml:math display="inline" xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>p</mml:mi></mml:math></alternatives></inline-formula>-Tree
1094+ </p>
1095+ </list-item>
1096+ <list-item>
1097+ <p>
1098+ Here’s some display math: <disp-formula><alternatives>
1099+ <tex-math><![CDATA[\frac{d}{dx}f(x)=\lim_{h\to 0}\frac{f(x+h)-f(x)}{h}]]></tex-math>
1100+ <mml:math display="block" xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mrow><mml:mfrac><mml:mi>d</mml:mi><mml:mrow><mml:mi>d</mml:mi><mml:mi>x</mml:mi></mml:mrow></mml:mfrac><mml:mi>f</mml:mi><mml:mo stretchy="false" form="prefix">(</mml:mo><mml:mi>x</mml:mi><mml:mo stretchy="false" form="postfix">)</mml:mo><mml:mo>=</mml:mo><mml:munder><mml:mo>lim</mml:mo><mml:mrow><mml:mi>h</mml:mi><mml:mo>→</mml:mo><mml:mn>0</mml:mn></mml:mrow></mml:munder><mml:mfrac><mml:mrow><mml:mi>f</mml:mi><mml:mo stretchy="false" form="prefix">(</mml:mo><mml:mi>x</mml:mi><mml:mo>+</mml:mo><mml:mi>h</mml:mi><mml:mo stretchy="false" form="postfix">)</mml:mo><mml:mo>−</mml:mo><mml:mi>f</mml:mi><mml:mo stretchy="false" form="prefix">(</mml:mo><mml:mi>x</mml:mi><mml:mo stretchy="false" form="postfix">)</mml:mo></mml:mrow><mml:mi>h</mml:mi></mml:mfrac></mml:mrow></mml:math></alternatives></disp-formula>
1101+ </p>
1102+ </list-item>
1103+ <list-item>
1104+ <p>
1105+ Here’s one that has a line break in it: <inline-formula><alternatives>
1106+ <tex-math><![CDATA[\alpha + \omega \times x^2]]></tex-math>
1107+ <mml:math display="inline" xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mrow><mml:mi>α</mml:mi><mml:mo>+</mml:mo><mml:mi>ω</mml:mi><mml:mo>×</mml:mo><mml:msup><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:mrow></mml:math></alternatives></inline-formula>.
1108+ </p>
1109+ </list-item>
1110+ </list>
1111+ <p>
1112+ These shouldn’t be math:
1113+ </p>
1114+ <list list-type="bullet">
1115+ <list-item>
1116+ <p>
1117+ To get the famous equation, write <monospace>$e = mc^2$</monospace>.
1118+ </p>
1119+ </list-item>
1120+ <list-item>
1121+ <p>
1122+ $22,000 is a <italic>lot</italic> of money. So is $34,000. (It worked
1123+ if “lot” is emphasized.)
1124+ </p>
1125+ </list-item>
1126+ <list-item>
1127+ <p>
1128+ Shoes ($20) and socks ($5).
1129+ </p>
1130+ </list-item>
1131+ <list-item>
1132+ <p>
1133+ Escaped <monospace>$</monospace>: $73 <italic>this should be
1134+ emphasized</italic> 23$.
1135+ </p>
1136+ </list-item>
1137+ </list>
1138+ <p>
1139+ Here’s a LaTeX table:
1140+ </p>
1141+</sec>
1142+<sec id="special-characters">
1143+ <title>Special Characters</title>
1144+ <p>
1145+ Here is some unicode:
1146+ </p>
1147+ <list list-type="bullet">
1148+ <list-item>
1149+ <p>
1150+ I hat: Î
1151+ </p>
1152+ </list-item>
1153+ <list-item>
1154+ <p>
1155+ o umlaut: ö
1156+ </p>
1157+ </list-item>
1158+ <list-item>
1159+ <p>
1160+ section: §
1161+ </p>
1162+ </list-item>
1163+ <list-item>
1164+ <p>
1165+ set membership: ∈
1166+ </p>
1167+ </list-item>
1168+ <list-item>
1169+ <p>
1170+ copyright: ©
1171+ </p>
1172+ </list-item>
1173+ </list>
1174+ <p>
1175+ AT&amp;T has an ampersand in their name.
1176+ </p>
1177+ <p>
1178+ AT&amp;T is another way to write it.
1179+ </p>
1180+ <p>
1181+ This &amp; that.
1182+ </p>
1183+ <p>
1184+ 4 &lt; 5.
1185+ </p>
1186+ <p>
1187+ 6 &gt; 5.
1188+ </p>
1189+ <p>
1190+ Backslash: \
1191+ </p>
1192+ <p>
1193+ Backtick: `
1194+ </p>
1195+ <p>
1196+ Asterisk: *
1197+ </p>
1198+ <p>
1199+ Underscore: _
1200+ </p>
1201+ <p>
1202+ Left brace: {
1203+ </p>
1204+ <p>
1205+ Right brace: }
1206+ </p>
1207+ <p>
1208+ Left bracket: [
1209+ </p>
1210+ <p>
1211+ Right bracket: ]
1212+ </p>
1213+ <p>
1214+ Left paren: (
1215+ </p>
1216+ <p>
1217+ Right paren: )
1218+ </p>
1219+ <p>
1220+ Greater-than: &gt;
1221+ </p>
1222+ <p>
1223+ Hash: #
1224+ </p>
1225+ <p>
1226+ Period: .
1227+ </p>
1228+ <p>
1229+ Bang: !
1230+ </p>
1231+ <p>
1232+ Plus: +
1233+ </p>
1234+ <p>
1235+ Minus: -
1236+ </p>
1237+</sec>
1238+<sec id="links">
1239+ <title>Links</title>
1240+ <sec id="explicit">
1241+ <title>Explicit</title>
1242+ <p>
1243+ Just a <ext-link ext-link-type="uri" xlink:href="/url/">URL</ext-link>.
1244+ </p>
1245+ <p>
1246+ <ext-link ext-link-type="uri" xlink:href="/url/" xlink:title="title">URL
1247+ and title</ext-link>.
1248+ </p>
1249+ <p>
1250+ <ext-link ext-link-type="uri" xlink:href="/url/" xlink:title="title preceded by two spaces">URL
1251+ and title</ext-link>.
1252+ </p>
1253+ <p>
1254+ <ext-link ext-link-type="uri" xlink:href="/url/" xlink:title="title preceded by a tab">URL
1255+ and title</ext-link>.
1256+ </p>
1257+ <p>
1258+ <ext-link ext-link-type="uri" xlink:href="/url/" xlink:title="title with &quot;quotes&quot; in it">URL
1259+ and title</ext-link>
1260+ </p>
1261+ <p>
1262+ <ext-link ext-link-type="uri" xlink:href="/url/" xlink:title="title with single quotes">URL
1263+ and title</ext-link>
1264+ </p>
1265+ <p>
1266+ <ext-link ext-link-type="uri" xlink:href="/url/with_underscore">with_underscore</ext-link>
1267+ </p>
1268+ <p>
1269+ <ext-link ext-link-type="uri" xlink:href="mailto:nobody@nowhere.net">Email
1270+ link</ext-link>
1271+ </p>
1272+ <p>
1273+ <ext-link ext-link-type="uri" xlink:href="">Empty</ext-link>.
1274+ </p>
1275+ </sec>
1276+ <sec id="reference">
1277+ <title>Reference</title>
1278+ <p>
1279+ Foo <ext-link ext-link-type="uri" xlink:href="/url/">bar</ext-link>.
1280+ </p>
1281+ <p>
1282+ With <ext-link ext-link-type="uri" xlink:href="/url/">embedded
1283+ [brackets]</ext-link>.
1284+ </p>
1285+ <p>
1286+ <ext-link ext-link-type="uri" xlink:href="/url/">b</ext-link> by itself
1287+ should be a link.
1288+ </p>
1289+ <p>
1290+ Indented
1291+ <ext-link ext-link-type="uri" xlink:href="/url">once</ext-link>.
1292+ </p>
1293+ <p>
1294+ Indented
1295+ <ext-link ext-link-type="uri" xlink:href="/url">twice</ext-link>.
1296+ </p>
1297+ <p>
1298+ Indented
1299+ <ext-link ext-link-type="uri" xlink:href="/url">thrice</ext-link>.
1300+ </p>
1301+ <p>
1302+ This should [not][] be a link.
1303+ </p>
1304+ <preformat>[not]: /url</preformat>
1305+ <p>
1306+ Foo
1307+ <ext-link ext-link-type="uri" xlink:href="/url/" xlink:title="Title with &quot;quotes&quot; inside">bar</ext-link>.
1308+ </p>
1309+ <p>
1310+ Foo
1311+ <ext-link ext-link-type="uri" xlink:href="/url/" xlink:title="Title with &quot;quote&quot; inside">biz</ext-link>.
1312+ </p>
1313+ </sec>
1314+ <sec id="with-ampersands">
1315+ <title>With ampersands</title>
1316+ <p>
1317+ Here’s a
1318+ <ext-link ext-link-type="uri" xlink:href="http://example.com/?foo=1&amp;bar=2">link
1319+ with an ampersand in the URL</ext-link>.
1320+ </p>
1321+ <p>
1322+ Here’s a link with an amersand in the link text:
1323+ <ext-link ext-link-type="uri" xlink:href="http://att.com/" xlink:title="AT&amp;T">AT&amp;T</ext-link>.
1324+ </p>
1325+ <p>
1326+ Here’s an
1327+ <ext-link ext-link-type="uri" xlink:href="/script?foo=1&amp;bar=2">inline
1328+ link</ext-link>.
1329+ </p>
1330+ <p>
1331+ Here’s an
1332+ <ext-link ext-link-type="uri" xlink:href="/script?foo=1&amp;bar=2">inline
1333+ link in pointy braces</ext-link>.
1334+ </p>
1335+ </sec>
1336+ <sec id="autolinks">
1337+ <title>Autolinks</title>
1338+ <p>
1339+ With an ampersand:
1340+ <ext-link ext-link-type="uri" xlink:href="http://example.com/?foo=1&amp;bar=2">http://example.com/?foo=1&amp;bar=2</ext-link>
1341+ </p>
1342+ <list list-type="bullet">
1343+ <list-item>
1344+ <p>
1345+ In a list?
1346+ </p>
1347+ </list-item>
1348+ <list-item>
1349+ <p>
1350+ <ext-link ext-link-type="uri" xlink:href="http://example.com/">http://example.com/</ext-link>
1351+ </p>
1352+ </list-item>
1353+ <list-item>
1354+ <p>
1355+ It should.
1356+ </p>
1357+ </list-item>
1358+ </list>
1359+ <p>
1360+ An e-mail address: <email>nobody@nowhere.net</email>
1361+ </p>
1362+ <disp-quote>
1363+ <p>
1364+ Blockquoted:
1365+ <ext-link ext-link-type="uri" xlink:href="http://example.com/">http://example.com/</ext-link>
1366+ </p>
1367+ </disp-quote>
1368+ <p>
1369+ Auto-links should not occur here:
1370+ <monospace>&lt;http://example.com/&gt;</monospace>
1371+ </p>
1372+ <preformat>or here: &lt;http://example.com/&gt;</preformat>
1373+ </sec>
1374+</sec>
1375+<sec id="images">
1376+ <title>Images</title>
1377+ <p>
1378+ From “Voyage dans la Lune” by Georges Melies (1902):
1379+ </p>
1380+ <fig>
1381+ <caption>lalune</caption>
1382+ <graphic mimetype="image" mime-subtype="jpeg" xlink:href="lalune.jpg" xlink:title="Voyage dans la Lune" />
1383+ </fig>
1384+ <p>
1385+ Here is a movie
1386+ <inline-graphic mimetype="image" mime-subtype="jpeg" xlink:href="movie.jpg" />
1387+ icon.
1388+ </p>
1389+</sec>
1390+<sec id="footnotes">
1391+ <title>Footnotes</title>
1392+ <p>
1393+ Here is a footnote reference,<fn>
1394+ <p>
1395+ Here is the footnote. It can go anywhere after the footnote reference.
1396+ It need not be placed at the end of the document.
1397+ </p>
1398+ </fn> and another.<fn>
1399+ <p>
1400+ Here’s the long note. This one contains multiple blocks.
1401+ </p>
1402+ <p>
1403+ Subsequent blocks are indented to show that they belong to the
1404+ footnote (as with list items).
1405+ </p>
1406+ <preformat> { &lt;code&gt; }</preformat>
1407+ <p>
1408+ If you want, you can indent every line, but you can also be lazy and
1409+ just indent the first line of each block.
1410+ </p>
1411+ </fn> This should <italic>not</italic> be a footnote reference, because it
1412+ contains a space.[^my note] Here is an inline note.<fn>
1413+ <p>
1414+ This is <italic>easier</italic> to type. Inline notes may contain
1415+ <ext-link ext-link-type="uri" xlink:href="http://google.com">links</ext-link>
1416+ and <monospace>]</monospace> verbatim characters, as well as
1417+ [bracketed text].
1418+ </p>
1419+ </fn>
1420+ </p>
1421+ <disp-quote>
1422+ <p>
1423+ Notes can go in quotes.<fn>
1424+ <p>
1425+ In quote.
1426+ </p>
1427+ </fn>
1428+ </p>
1429+ </disp-quote>
1430+ <list list-type="order">
1431+ <list-item>
1432+ <p>
1433+ And in list items.<fn>
1434+ <p>
1435+ In list.
1436+ </p>
1437+ </fn>
1438+ </p>
1439+ </list-item>
1440+ </list>
1441+ <p>
1442+ This paragraph should not be part of the note, as it is not indented.
1443+ </p>
1444+</sec>
1445+</body>
1446+<back>
1447+</back>
1448+</article>
test/pandoc-test14632-89new file

No changes to the content.

test/pandoc-test14632-90new file

No changes to the content.

test/pandoc-test14632-91new file

No changes to the content.

test/pandoc-test14632-94new file

No changes to the content.

test/pandoc-test14632-95new file

No changes to the content.

test/pandoc-test14632-96new file

No changes to the content.

test/pandoc-test14632-97new file

No changes to the content.

test/pandoc-test77993-78new file

No changes to the content.

test/pandoc-test77993-79new file

No changes to the content.

test/pandoc-test77993-82new file

No changes to the content.

test/pandoc-test77993-83new file

No changes to the content.

test/pandoc-test77993-86new file

No changes to the content.

test/pandoc-test77993-87new file

No changes to the content.

test/pandoc-test77993-90new file

No changes to the content.

test/pandoc-test77993-91new file

No changes to the content.

test/pandoc-test88473-110new file

No changes to the content.

test/pandoc-test88473-111new file

No changes to the content.

test/pandoc-test88473-112new file

No changes to the content.

test/pandoc-test88473-113new file

No changes to the content.

test/pandoc-test88473-116new file

No changes to the content.

test/pandoc-test88473-117new file

No changes to the content.

test/pandoc-test88473-118new file

No changes to the content.

test/pandoc-test88473-119new file

No changes to the content.

test/pandoc-test94017-86new file

No changes to the content.

test/pandoc-test94017-87new file

No changes to the content.

test/pandoc-test94017-90new file

No changes to the content.

test/pandoc-test94017-91new file

No changes to the content.

test/pandoc-test94017-92new file

No changes to the content.

test/pandoc-test94017-93new file

No changes to the content.

test/tables.ms+4−4
@@ -135,7 +135,7 @@ T}
135135.LP
136136Multiline table with caption:
137137.PP
138−Here's the caption. It may span multiple lines.
138+Here’s the caption. It may span multiple lines.
139139.TS
140140delim(@@) tab( );
141141cw(10.5n) lw(9.6n) rw(11.4n) lw(23.6n).
@@ -165,7 +165,7 @@ row
165165T} T{
1661665.0
167167T} T{
168−Here's another one.
168+Here’s another one.
169169Note the blank line between rows.
170170T}
171171.TE
@@ -201,7 +201,7 @@ row
201201T} T{
2022025.0
203203T} T{
204−Here's another one.
204+Here’s another one.
205205Note the blank line between rows.
206206T}
207207.TE
@@ -261,7 +261,7 @@ row
261261T} T{
2622625.0
263263T} T{
264−Here's another one.
264+Here’s another one.
265265Note the blank line between rows.
266266T}
267267.TE
test/test-pandoc.hs.orignew file+83
@@ -0,0 +1,83 @@
1+{-# OPTIONS_GHC -Wall #-}
2+
3+module Main where
4+
5+import GHC.IO.Encoding
6+import Test.Tasty
7+import qualified Tests.Command
8+import qualified Tests.Lua
9+import qualified Tests.Old
10+import qualified Tests.Readers.Creole
11+import qualified Tests.Readers.Docx
12+import qualified Tests.Readers.EPUB
13+import qualified Tests.Readers.HTML
14+import qualified Tests.Readers.JATS
15+import qualified Tests.Readers.LaTeX
16+import qualified Tests.Readers.Markdown
17+import qualified Tests.Readers.Muse
18+import qualified Tests.Readers.Odt
19+import qualified Tests.Readers.Org
20+import qualified Tests.Readers.RST
21+import qualified Tests.Readers.Txt2Tags
22+import qualified Tests.Shared
23+import qualified Tests.Writers.AsciiDoc
24+import qualified Tests.Writers.ConTeXt
25+import qualified Tests.Writers.Docbook
26+import qualified Tests.Writers.Docx
27+import qualified Tests.Writers.FB2
28+import qualified Tests.Writers.HTML
29+import qualified Tests.Writers.JATS
30+import qualified Tests.Writers.LaTeX
31+import qualified Tests.Writers.Markdown
32+import qualified Tests.Writers.Muse
33+import qualified Tests.Writers.Native
34+import qualified Tests.Writers.Org
35+import qualified Tests.Writers.Plain
36+import qualified Tests.Writers.Powerpoint
37+import qualified Tests.Writers.RST
38+import qualified Tests.Writers.TEI
39+import Text.Pandoc.Shared (inDirectory)
40+
41+tests :: TestTree
42+tests = testGroup "pandoc tests" [ Tests.Command.tests
43+ , testGroup "Old" Tests.Old.tests
44+ , testGroup "Shared" Tests.Shared.tests
45+ , testGroup "Writers"
46+ [ testGroup "Native" Tests.Writers.Native.tests
47+ , testGroup "ConTeXt" Tests.Writers.ConTeXt.tests
48+ , testGroup "LaTeX" Tests.Writers.LaTeX.tests
49+ , testGroup "HTML" Tests.Writers.HTML.tests
50+ , testGroup "JATS" Tests.Writers.JATS.tests
51+ , testGroup "Docbook" Tests.Writers.Docbook.tests
52+ , testGroup "Markdown" Tests.Writers.Markdown.tests
53+ , testGroup "Org" Tests.Writers.Org.tests
54+ , testGroup "Plain" Tests.Writers.Plain.tests
55+ , testGroup "AsciiDoc" Tests.Writers.AsciiDoc.tests
56+ , testGroup "Docx" Tests.Writers.Docx.tests
57+ , testGroup "RST" Tests.Writers.RST.tests
58+ , testGroup "TEI" Tests.Writers.TEI.tests
59+ , testGroup "Muse" Tests.Writers.Muse.tests
60+ , testGroup "FB2" Tests.Writers.FB2.tests
61+ , testGroup "PowerPoint" Tests.Writers.Powerpoint.tests
62+ ]
63+ , testGroup "Readers"
64+ [ testGroup "LaTeX" Tests.Readers.LaTeX.tests
65+ , testGroup "Markdown" Tests.Readers.Markdown.tests
66+ , testGroup "HTML" Tests.Readers.HTML.tests
67+ , testGroup "JATS" Tests.Readers.JATS.tests
68+ , testGroup "Org" Tests.Readers.Org.tests
69+ , testGroup "RST" Tests.Readers.RST.tests
70+ , testGroup "Docx" Tests.Readers.Docx.tests
71+ , testGroup "Odt" Tests.Readers.Odt.tests
72+ , testGroup "Txt2Tags" Tests.Readers.Txt2Tags.tests
73+ , testGroup "EPUB" Tests.Readers.EPUB.tests
74+ , testGroup "Muse" Tests.Readers.Muse.tests
75+ , testGroup "Creole" Tests.Readers.Creole.tests
76+ ]
77+ , testGroup "Lua filters" Tests.Lua.tests
78+ ]
79+
80+main :: IO ()
81+main = do
82+ setLocaleEncoding utf8
83+ inDirectory "test" $ defaultMain tests
test/writer.ms+17−17
@@ -72,7 +72,7 @@ Anonymous
7272.1C
7373.LP
7474This is a set of tests for pandoc.
75−Most of them are adapted from John Gruber's markdown test suite.
75+Most of them are adapted from John Gruber’s markdown test suite.
7676.HLINE
7777.SH 1
7878Headers
@@ -123,7 +123,7 @@ Paragraphs
123123.pdfhref O 1 "Paragraphs"
124124.pdfhref M "paragraphs"
125125.LP
126−Here's a regular paragraph.
126+Here’s a regular paragraph.
127127.PP
128128In Markdown 1.0.0 and earlier.
129129Version 8.
@@ -131,7 +131,7 @@ This line turns into a list item.
131131Because a hard-wrapped line in the middle of a paragraph looked like a list
132132item.
133133.PP
134−Here's one with a bullet.
134+Here’s one with a bullet.
135135* criminey.
136136.PP
137137There should be a hard line break
@@ -311,7 +311,7 @@ Item 1, graf one.
311311.PP
312312Item 1.
313313graf two.
314−The quick brown fox jumped over the lazy dog's back.
314+The quick brown fox jumped over the lazy dog’s back.
315315.RE
316316.IP " 2." 4
317317Item 2.
@@ -332,7 +332,7 @@ Tab
332332.RE
333333.RE
334334.LP
335−Here's another:
335+Here’s another:
336336.IP " 1." 4
337337First
338338.IP " 2." 4
@@ -567,7 +567,7 @@ Interpreted markdown in a table:
567567This is \f[I]emphasized\f[]
568568And this is \f[B]strong\f[]
569569.PP
570−Here's a simple block:
570+Here’s a simple block:
571571.LP
572572foo
573573.LP
@@ -614,7 +614,7 @@ Code:
614614\f[]
615615.fi
616616.LP
617−Hr's:
617+Hr’s:
618618.HLINE
619619.SH 1
620620Inline Markup
@@ -663,7 +663,7 @@ Smart quotes, ellipses, dashes
663663`Oak,' `elm,' and `beech' are names of trees.
664664So is `pine.'
665665.PP
666−`He said, \[lq]I want to go.\[rq]' Were you alive in the 70's?
666+`He said, \[lq]I want to go.\[rq]' Were you alive in the 70’s?
667667.PP
668668Here is some quoted `\f[C]code\f[]' and a \[lq]\c
669669.pdfhref W -D "http://example.com/?foo=1&bar=2" -A "\c" \
@@ -692,14 +692,14 @@ LaTeX
692692.IP \[bu] 3
693693@p@-Tree
694694.IP \[bu] 3
695−Here's some display math:
695+Here’s some display math:
696696.EQ
697697d over {d x} f ( x ) = lim sub {h -> 0} {f ( x + h ) \[u2212] f ( x )} over h
698698.EN
699699.IP \[bu] 3
700−Here's one that has a line break in it: @alpha + omega times x sup 2@.
700+Here’s one that has a line break in it: @alpha + omega times x sup 2@.
701701.LP
702−These shouldn't be math:
702+These shouldn’t be math:
703703.IP \[bu] 3
704704To get the famous equation, write \f[C]$e\ =\ mc\[ha]2$\f[].
705705.IP \[bu] 3
@@ -711,7 +711,7 @@ Shoes ($20) and socks ($5).
711711.IP \[bu] 3
712712Escaped \f[C]$\f[]: $73 \f[I]this should be emphasized\f[] 23$.
713713.LP
714−Here's a LaTeX table:
714+Here’s a LaTeX table:
715715.HLINE
716716.SH 1
717717Special Characters
@@ -882,22 +882,22 @@ With ampersands
882882.pdfhref O 2 "With ampersands"
883883.pdfhref M "with-ampersands"
884884.LP
885−Here's a \c
885+Here’s a \c
886886.pdfhref W -D "http://example.com/?foo=1&bar=2" -A "\c" \
887887 -- "link with an ampersand in the URL"
888888\&.
889889.PP
890−Here's a link with an amersand in the link text: \c
890+Here’s a link with an amersand in the link text: \c
891891.pdfhref W -D "http://att.com/" -A "\c" \
892892 -- "AT&T"
893893\&.
894894.PP
895−Here's an \c
895+Here’s an \c
896896.pdfhref W -D "/script?foo=1&bar=2" -A "\c" \
897897 -- "inline link"
898898\&.
899899.PP
900−Here's an \c
900+Here’s an \c
901901.pdfhref W -D "/script?foo=1&bar=2" -A "\c" \
902902 -- "inline link in pointy braces"
903903\&.
@@ -964,7 +964,7 @@ It need not be placed at the end of the document.
964964.FE
965965and another.\**
966966.FS
967−Here's the long note.
967+Here’s the long note.
968968This one contains multiple blocks.
969969.PP
970970Subsequent blocks are indented to show that they belong to the footnote (as