summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Tests/Arbitrary.hs181
-rw-r--r--src/Tests/Helpers.hs116
-rw-r--r--src/Tests/Old.hs201
-rw-r--r--src/Tests/Readers/LaTeX.hs152
-rw-r--r--src/Tests/Readers/Markdown.hs29
-rw-r--r--src/Tests/Readers/RST.hs46
-rw-r--r--src/Tests/Shared.hs21
-rw-r--r--src/Tests/Writers/ConTeXt.hs72
-rw-r--r--src/Tests/Writers/Native.hs20
-rw-r--r--src/test-pandoc.hs30
10 files changed, 868 insertions, 0 deletions
diff --git a/src/Tests/Arbitrary.hs b/src/Tests/Arbitrary.hs
new file mode 100644
index 000000000..978717bef
--- /dev/null
+++ b/src/Tests/Arbitrary.hs
@@ -0,0 +1,181 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+-- provides Arbitrary instance for Pandoc types
+module Tests.Arbitrary ()
+where
+import Test.QuickCheck.Gen
+import Test.QuickCheck.Arbitrary
+import Control.Monad (liftM, liftM2)
+import Text.Pandoc
+import Text.Pandoc.Shared
+import Text.Pandoc.Builder
+
+realString :: Gen String
+realString = resize 8 arbitrary -- elements wordlist
+
+{-
+wordlist :: [String]
+wordlist = ["foo","Bar","baz","\\","/",":","\"","'","féé"]
+-}
+
+instance Arbitrary Inlines where
+ arbitrary = liftM fromList arbitrary
+
+instance Arbitrary Blocks where
+ arbitrary = liftM fromList arbitrary
+
+instance Arbitrary Inline where
+ arbitrary = resize 3 $ arbInline 3
+
+-- restrict to 3 levels of nesting max; otherwise we get
+-- bogged down in indefinitely large structures
+arbInline :: Int -> Gen Inline
+arbInline n = frequency $ [ (60, liftM Str realString)
+ , (60, return Space)
+ , (10, liftM2 Code arbitrary realString)
+ , (5, return EmDash)
+ , (5, return EnDash)
+ , (5, return Apostrophe)
+ , (5, return Ellipses)
+ , (5, elements [ RawInline "html" "<a>*&amp;*</a>"
+ , RawInline "latex" "\\my{command}" ])
+ ] ++ [ x | x <- nesters, n > 1]
+ where nesters = [ (10, liftM Emph $ listOf $ arbInline (n-1))
+ , (10, liftM Strong $ listOf $ arbInline (n-1))
+ , (10, liftM Strikeout $ listOf $ arbInline (n-1))
+ , (10, liftM Superscript $ listOf $ arbInline (n-1))
+ , (10, liftM Subscript $ listOf $ arbInline (n-1))
+ , (10, liftM SmallCaps $ listOf $ arbInline (n-1))
+ , (10, do x1 <- arbitrary
+ x2 <- listOf $ arbInline (n-1)
+ return $ Quoted x1 x2)
+ , (10, do x1 <- arbitrary
+ x2 <- realString
+ return $ Math x1 x2)
+ , (10, do x1 <- listOf $ arbInline (n-1)
+ x3 <- realString
+ x2 <- realString
+ return $ Link x1 (x2,x3))
+ , (10, do x1 <- listOf $ arbInline (n-1)
+ x3 <- realString
+ x2 <- realString
+ return $ Image x1 (x2,x3))
+ , (2, liftM Note $ resize 3 $ listOf1 arbitrary)
+ ]
+
+instance Arbitrary Block where
+ arbitrary = resize 3 $ arbBlock 3
+
+arbBlock :: Int -> Gen Block
+arbBlock n = frequency $ [ (10, liftM Plain arbitrary)
+ , (15, liftM Para arbitrary)
+ , (5, liftM2 CodeBlock arbitrary realString)
+ , (2, elements [ RawBlock "html"
+ "<div>\n*&amp;*\n</div>"
+ , RawBlock "latex"
+ "\\begin[opt]{env}\nhi\n{\\end{env}"
+ ])
+ , (5, do x1 <- choose (1 :: Int, 6)
+ x2 <- arbitrary
+ return (Header x1 x2))
+ , (2, return HorizontalRule)
+ ] ++ [x | x <- nesters, n > 0]
+ where nesters = [ (5, liftM BlockQuote $ listOf $ arbBlock (n-1))
+ , (5, liftM2 OrderedList arbitrary
+ $ (listOf1 $ listOf1 $ arbBlock (n-1)))
+ , (5, liftM BulletList $ (listOf1 $ listOf1 $ arbBlock (n-1)))
+ , (5, do x1 <- listOf $ listOf1 $ listOf1 $ arbBlock (n-1)
+ x2 <- arbitrary
+ return (DefinitionList $ zip x2 x1))
+ , (2, do rs <- choose (1 :: Int, 4)
+ cs <- choose (1 :: Int, 4)
+ x1 <- arbitrary
+ x2 <- vector cs
+ x3 <- vectorOf cs $ elements [0, 0.25]
+ x4 <- vectorOf cs $ listOf $ arbBlock (n-1)
+ x5 <- vectorOf rs $ vectorOf cs
+ $ listOf $ arbBlock (n-1)
+ return (Table x1 x2 x3 x4 x5))
+ ]
+
+instance Arbitrary Pandoc where
+ arbitrary = resize 8 $ liftM normalize
+ $ liftM2 Pandoc arbitrary arbitrary
+
+{-
+instance Arbitrary CitationMode where
+ arbitrary
+ = do x <- choose (0 :: Int, 2)
+ case x of
+ 0 -> return AuthorInText
+ 1 -> return SuppressAuthor
+ 2 -> return NormalCitation
+ _ -> error "FATAL ERROR: Arbitrary instance, logic bug"
+
+instance Arbitrary Citation where
+ arbitrary
+ = do x1 <- liftM (filter (`notElem` ",;]@ \t\n")) arbitrary
+ x2 <- arbitrary
+ x3 <- arbitrary
+ x4 <- arbitrary
+ x5 <- arbitrary
+ x6 <- arbitrary
+ return (Citation x1 x2 x3 x4 x5 x6)
+-}
+
+instance Arbitrary MathType where
+ arbitrary
+ = do x <- choose (0 :: Int, 1)
+ case x of
+ 0 -> return DisplayMath
+ 1 -> return InlineMath
+ _ -> error "FATAL ERROR: Arbitrary instance, logic bug"
+
+instance Arbitrary QuoteType where
+ arbitrary
+ = do x <- choose (0 :: Int, 1)
+ case x of
+ 0 -> return SingleQuote
+ 1 -> return DoubleQuote
+ _ -> error "FATAL ERROR: Arbitrary instance, logic bug"
+
+instance Arbitrary Meta where
+ arbitrary
+ = do x1 <- arbitrary
+ x2 <- liftM (filter (not . null)) arbitrary
+ x3 <- arbitrary
+ return (Meta x1 x2 x3)
+
+instance Arbitrary Alignment where
+ arbitrary
+ = do x <- choose (0 :: Int, 3)
+ case x of
+ 0 -> return AlignLeft
+ 1 -> return AlignRight
+ 2 -> return AlignCenter
+ 3 -> return AlignDefault
+ _ -> error "FATAL ERROR: Arbitrary instance, logic bug"
+
+instance Arbitrary ListNumberStyle where
+ arbitrary
+ = do x <- choose (0 :: Int, 6)
+ case x of
+ 0 -> return DefaultStyle
+ 1 -> return Example
+ 2 -> return Decimal
+ 3 -> return LowerRoman
+ 4 -> return UpperRoman
+ 5 -> return LowerAlpha
+ 6 -> return UpperAlpha
+ _ -> error "FATAL ERROR: Arbitrary instance, logic bug"
+
+instance Arbitrary ListNumberDelim where
+ arbitrary
+ = do x <- choose (0 :: Int, 3)
+ case x of
+ 0 -> return DefaultDelim
+ 1 -> return Period
+ 2 -> return OneParen
+ 3 -> return TwoParens
+ _ -> error "FATAL ERROR: Arbitrary instance, logic bug"
+
diff --git a/src/Tests/Helpers.hs b/src/Tests/Helpers.hs
new file mode 100644
index 000000000..b8d6b83a7
--- /dev/null
+++ b/src/Tests/Helpers.hs
@@ -0,0 +1,116 @@
+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, TemplateHaskell #-}
+-- Utility functions for the test suite.
+
+module Tests.Helpers ( lit
+ , file
+ , test
+ , (=?>)
+ , property
+ , ToString(..)
+ , ToPandoc(..)
+ )
+ where
+
+import Text.Pandoc.Definition
+import Text.Pandoc.Builder (Inlines, Blocks, doc, plain)
+import Test.Framework
+import Test.Framework.Providers.HUnit
+import Test.Framework.Providers.QuickCheck2
+import Test.HUnit (assertBool)
+import Text.Pandoc.Shared (normalize, defaultWriterOptions,
+ WriterOptions(..), removeTrailingSpace)
+import Text.Pandoc.Writers.Native (writeNative)
+import Language.Haskell.TH.Quote (QuasiQuoter(..))
+import Language.Haskell.TH.Syntax (Q, runIO)
+import qualified Test.QuickCheck.Property as QP
+import System.Console.ANSI
+import Data.Algorithm.Diff
+
+lit :: QuasiQuoter
+lit = QuasiQuoter {
+ quoteExp = (\a -> let b = rnl a in [|b|]) . filter (/= '\r')
+ , quotePat = error "Cannot use lit as a pattern"
+ }
+ where rnl ('\n':xs) = xs
+ rnl xs = xs
+
+file :: QuasiQuoter
+file = quoteFile lit
+
+-- adapted from TH 2.5 code
+quoteFile :: QuasiQuoter -> QuasiQuoter
+quoteFile (QuasiQuoter { quoteExp = qe, quotePat = qp }) =
+ QuasiQuoter { quoteExp = get qe, quotePat = get qp }
+ where
+ get :: (String -> Q a) -> String -> Q a
+ get old_quoter file_name = do { file_cts <- runIO (readFile file_name)
+ ; old_quoter file_cts }
+
+test :: (ToString a, ToString b, ToString c)
+ => (a -> b) -- ^ function to test
+ -> String -- ^ name of test case
+ -> (a, c) -- ^ (input, expected value)
+ -> Test
+test fn name (input, expected) =
+ testCase name $ assertBool msg (actual' == expected')
+ where msg = nl ++ dashes "input" ++ nl ++ input' ++ nl ++
+ dashes "expected" ++ nl ++ expected'' ++
+ dashes "got" ++ nl ++ actual'' ++
+ dashes ""
+ nl = "\n"
+ input' = toString input
+ actual' = toString $ fn input
+ expected' = toString expected
+ diff = getDiff (lines expected') (lines actual')
+ expected'' = unlines $ map vividize $ filter (\(d,_) -> d /= S) diff
+ actual'' = unlines $ map vividize $ filter (\(d,_) -> d /= F) diff
+ dashes "" = replicate 72 '-'
+ dashes x = replicate (72 - length x - 5) '-' ++ " " ++ x ++ " ---"
+
+vividize :: (DI,String) -> String
+vividize (B,s) = s
+vividize (_,s) = vivid s
+
+property :: QP.Testable a => TestName -> a -> Test
+property = testProperty
+
+vivid :: String -> String
+vivid s = setSGRCode [SetColor Background Dull Red
+ , SetColor Foreground Vivid White] ++ s
+ ++ setSGRCode [Reset]
+
+infix 6 =?>
+(=?>) :: a -> b -> (a,b)
+x =?> y = (x, y)
+
+class ToString a where
+ toString :: a -> String
+
+instance ToString Pandoc where
+ toString d = writeNative defaultWriterOptions{ writerStandalone = s }
+ $ toPandoc d
+ where s = case d of
+ (Pandoc (Meta [] [] []) _) -> False
+ _ -> True
+
+instance ToString Blocks where
+ toString = writeNative defaultWriterOptions . toPandoc
+
+instance ToString Inlines where
+ toString = removeTrailingSpace . writeNative defaultWriterOptions .
+ toPandoc
+
+instance ToString String where
+ toString = id
+
+class ToPandoc a where
+ toPandoc :: a -> Pandoc
+
+instance ToPandoc Pandoc where
+ toPandoc = normalize
+
+instance ToPandoc Blocks where
+ toPandoc = normalize . doc
+
+instance ToPandoc Inlines where
+ toPandoc = normalize . doc . plain
diff --git a/src/Tests/Old.hs b/src/Tests/Old.hs
new file mode 100644
index 000000000..cb1417ffa
--- /dev/null
+++ b/src/Tests/Old.hs
@@ -0,0 +1,201 @@
+module Tests.Old (tests) where
+
+import Test.Framework (testGroup, Test )
+import Test.Framework.Providers.HUnit
+import Test.HUnit ( assertBool )
+
+import System.IO ( openTempFile, stderr )
+import System.Process ( runProcess, waitForProcess )
+import System.FilePath ( (</>), (<.>) )
+import System.Directory
+import System.Exit
+import Data.Algorithm.Diff
+import Text.Pandoc.Shared ( normalize, defaultWriterOptions )
+import Text.Pandoc.Writers.Native ( writeNative )
+import Text.Pandoc.Readers.Native ( readNative )
+import Text.Pandoc.Highlighting ( languages )
+import Prelude hiding ( readFile )
+import qualified Data.ByteString.Lazy as B
+import Data.ByteString.Lazy.UTF8 (toString)
+import Text.Printf
+
+readFileUTF8 :: FilePath -> IO String
+readFileUTF8 f = B.readFile f >>= return . toString
+
+pandocPath :: FilePath
+pandocPath = ".." </> "dist" </> "build" </> "pandoc" </> "pandoc"
+
+data TestResult = TestPassed
+ | TestError ExitCode
+ | TestFailed String FilePath [(DI, String)]
+ deriving (Eq)
+
+instance Show TestResult where
+ show TestPassed = "PASSED"
+ show (TestError ec) = "ERROR " ++ show ec
+ show (TestFailed cmd file d) = '\n' : dash ++
+ "\n--- " ++ file ++
+ "\n+++ " ++ cmd ++ "\n" ++ showDiff (1,1) d ++
+ dash
+ where dash = replicate 72 '-'
+
+showDiff :: (Int,Int) -> [(DI, String)] -> String
+showDiff _ [] = ""
+showDiff (l,r) ((F, ln) : ds) =
+ printf "+%4d " l ++ ln ++ "\n" ++ showDiff (l+1,r) ds
+showDiff (l,r) ((S, ln) : ds) =
+ printf "-%4d " r ++ ln ++ "\n" ++ showDiff (l,r+1) ds
+showDiff (l,r) ((B, _ ) : ds) =
+ showDiff (l+1,r+1) ds
+
+tests :: [Test]
+tests = [ testGroup "markdown"
+ [ testGroup "writer"
+ $ writerTests "markdown" ++ lhsWriterTests "markdown"
+ , testGroup "reader"
+ [ test "basic" ["-r", "markdown", "-w", "native", "-s", "-S"]
+ "testsuite.txt" "testsuite.native"
+ , test "tables" ["-r", "markdown", "-w", "native", "--columns=80"]
+ "tables.txt" "tables.native"
+ , test "more" ["-r", "markdown", "-w", "native", "-S"]
+ "markdown-reader-more.txt" "markdown-reader-more.native"
+ , lhsReaderTest "markdown+lhs"
+ ]
+ , testGroup "citations" markdownCitationTests
+ ]
+ , testGroup "rst"
+ [ testGroup "writer" (writerTests "rst" ++ lhsWriterTests "rst")
+ , testGroup "reader"
+ [ test "basic" ["-r", "rst", "-w", "native",
+ "-s", "-S", "--columns=80"] "rst-reader.rst" "rst-reader.native"
+ , test "tables" ["-r", "rst", "-w", "native", "--columns=80"]
+ "tables.rst" "tables-rstsubset.native"
+ , lhsReaderTest "rst+lhs"
+ ]
+ ]
+ , testGroup "latex"
+ [ testGroup "writer" (writerTests "latex" ++ lhsWriterTests "latex")
+ , testGroup "reader"
+ [ test "basic" ["-r", "latex", "-w", "native", "-s", "-R"]
+ "latex-reader.latex" "latex-reader.native"
+ , lhsReaderTest "latex+lhs"
+ ]
+ ]
+ , testGroup "html"
+ [ testGroup "writer" (writerTests "html" ++ lhsWriterTests "html")
+ , test "reader" ["-r", "html", "-w", "native", "-s"]
+ "html-reader.html" "html-reader.native"
+ ]
+ , testGroup "s5"
+ [ s5WriterTest "basic" ["-s"] "s5"
+ , s5WriterTest "fancy" ["-s","-m","-i"] "s5"
+ , s5WriterTest "fragment" [] "html"
+ , s5WriterTest "inserts" ["-s", "-H", "insert",
+ "-B", "insert", "-A", "insert", "-c", "main.css"] "html"
+ ]
+ , testGroup "textile"
+ [ testGroup "writer" $ writerTests "textile"
+ , test "reader" ["-r", "textile", "-w", "native", "-s"]
+ "textile-reader.textile" "textile-reader.native"
+ ]
+ , testGroup "native"
+ [ testGroup "writer" $ writerTests "native"
+ , test "reader" ["-r", "native", "-w", "native", "-s"]
+ "testsuite.native" "testsuite.native"
+ ]
+ , testGroup "other writers" $ map (\f -> testGroup f $ writerTests f)
+ [ "docbook", "opendocument" , "context" , "texinfo"
+ , "man" , "plain" , "mediawiki", "rtf", "org"
+ ]
+ ]
+
+-- makes sure file is fully closed after reading
+readFile' :: FilePath -> IO String
+readFile' f = do s <- readFileUTF8 f
+ return $! (length s `seq` s)
+
+lhsWriterTests :: String -> [Test]
+lhsWriterTests format
+ = [ t "lhs to normal" format
+ , t "lhs to lhs" (format ++ "+lhs")
+ ]
+ where
+ t n f = test n ["--columns=78", "-r", "native", "-s", "-w", f]
+ "lhs-test.native" ("lhs-test" <.> ext f)
+ ext f = if null languages && format == "html"
+ then "nohl" <.> f
+ else f
+
+lhsReaderTest :: String -> Test
+lhsReaderTest format =
+ testWithNormalize normalizer "lhs" ["-r", format, "-w", "native"]
+ ("lhs-test" <.> format) "lhs-test.native"
+ where normalizer = writeNative defaultWriterOptions . normalize . readNative
+
+writerTests :: String -> [Test]
+writerTests format
+ = [ test "basic" (opts ++ ["-s"]) "testsuite.native" ("writer" <.> format)
+ , test "tables" opts "tables.native" ("tables" <.> format)
+ ]
+ where
+ opts = ["-r", "native", "-w", format, "--columns=78"]
+
+s5WriterTest :: String -> [String] -> String -> Test
+s5WriterTest modifier opts format
+ = test (format ++ " writer (" ++ modifier ++ ")")
+ (["-r", "native", "-w", format] ++ opts)
+ "s5.native" ("s5." ++ modifier <.> "html")
+
+markdownCitationTests :: [Test]
+markdownCitationTests
+ = map styleToTest ["chicago-author-date","ieee","mhra"]
+ ++ [test "natbib" wopts "markdown-citations.txt"
+ "markdown-citations.txt"]
+ where
+ ropts = ["-r", "markdown", "-w", "markdown", "--bibliography",
+ "biblio.bib", "--no-wrap"]
+ wopts = ropts ++ ["--natbib"]
+ styleToTest style = test style (ropts ++ ["--csl", style ++ ".csl"])
+ "markdown-citations.txt"
+ ("markdown-citations." ++ style ++ ".txt")
+
+-- | Run a test without normalize function, return True if test passed.
+test :: String -- ^ Title of test
+ -> [String] -- ^ Options to pass to pandoc
+ -> String -- ^ Input filepath
+ -> FilePath -- ^ Norm (for test results) filepath
+ -> Test
+test = testWithNormalize id
+
+-- | Run a test with normalize function, return True if test passed.
+testWithNormalize :: (String -> String) -- ^ Normalize function for output
+ -> String -- ^ Title of test
+ -> [String] -- ^ Options to pass to pandoc
+ -> String -- ^ Input filepath
+ -> FilePath -- ^ Norm (for test results) filepath
+ -> Test
+testWithNormalize normalizer testname opts inp norm = testCase testname $ do
+ (outputPath, hOut) <- openTempFile "" "pandoc-test"
+ let inpPath = inp
+ let normPath = norm
+ let options = ["--data-dir", ".."] ++ [inpPath] ++ opts
+ let cmd = pandocPath ++ " " ++ unwords options
+ ph <- runProcess pandocPath options Nothing
+ (Just [("LANG","en_US.UTF-8"),("HOME", "./")]) Nothing (Just hOut)
+ (Just stderr)
+ ec <- waitForProcess ph
+ result <- if ec == ExitSuccess
+ then do
+ -- filter \r so the tests will work on Windows machines
+ outputContents <- readFile' outputPath >>=
+ return . filter (/='\r') . normalizer
+ normContents <- readFile' normPath >>=
+ return . filter (/='\r') . normalizer
+ if outputContents == normContents
+ then return TestPassed
+ else return
+ $ TestFailed cmd normPath
+ $ getDiff (lines outputContents) (lines normContents)
+ else return $ TestError ec
+ removeFile outputPath
+ assertBool (show result) (result == TestPassed)
diff --git a/src/Tests/Readers/LaTeX.hs b/src/Tests/Readers/LaTeX.hs
new file mode 100644
index 000000000..7ea4c73ee
--- /dev/null
+++ b/src/Tests/Readers/LaTeX.hs
@@ -0,0 +1,152 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Tests.Readers.LaTeX (tests) where
+
+import Text.Pandoc.Definition
+import Test.Framework
+import Tests.Helpers
+import Tests.Arbitrary()
+import Text.Pandoc.Builder
+import Text.Pandoc
+
+latex :: String -> Pandoc
+latex = readLaTeX defaultParserState
+
+infix 5 =:
+(=:) :: ToString c
+ => String -> (String, c) -> Test
+(=:) = test latex
+
+tests :: [Test]
+tests = [ testGroup "basic"
+ [ "simple" =:
+ "word" =?> para "word"
+ , "space" =:
+ "some text" =?> para ("some text")
+ , "emphasized" =:
+ "\\emph{emphasized}" =?> para (emph "emphasized")
+ ]
+
+ , testGroup "headers"
+ [ "level 1" =:
+ "\\section{header}" =?> header 1 "header"
+ , "level 2" =:
+ "\\subsection{header}" =?> header 2 "header"
+ , "level 3" =:
+ "\\subsubsection{header}" =?> header 3 "header"
+ , "emph" =:
+ "\\section{text \\emph{emph}}" =?>
+ header 1 ("text" +++ space +++ emph "emph")
+ , "link" =:
+ "\\section{text \\href{/url}{link}}" =?>
+ header 1 ("text" +++ space +++ link "/url" "" "link")
+ ]
+
+ , testGroup "citations"
+ [ natbibCitations
+ , biblatexCitations
+ ]
+ ]
+
+baseCitation :: Citation
+baseCitation = Citation{ citationId = "item1"
+ , citationPrefix = []
+ , citationSuffix = []
+ , citationMode = AuthorInText
+ , citationNoteNum = 0
+ , citationHash = 0 }
+
+natbibCitations :: Test
+natbibCitations = testGroup "natbib"
+ [ "citet" =: "\\citet{item1}"
+ =?> para (cite [baseCitation] empty)
+ , "suffix" =: "\\citet[p.~30]{item1}"
+ =?> para
+ (cite [baseCitation{ citationSuffix = toList $ text "p.\160\&30" }] empty)
+ , "suffix long" =: "\\citet[p.~30, with suffix]{item1}"
+ =?> para (cite [baseCitation{ citationSuffix =
+ toList $ text "p.\160\&30, with suffix" }] empty)
+ , "multiple" =: "\\citeauthor{item1} \\citetext{\\citeyear{item1}; \\citeyear[p.~30]{item2}; \\citealp[see also][]{item3}}"
+ =?> para (cite [baseCitation{ citationMode = AuthorInText }
+ ,baseCitation{ citationMode = SuppressAuthor
+ , citationSuffix = [Str "p.\160\&30"]
+ , citationId = "item2" }
+ ,baseCitation{ citationId = "item3"
+ , citationPrefix = [Str "see",Space,Str "also"]
+ , citationMode = NormalCitation }
+ ] empty)
+ , "group" =: "\\citetext{\\citealp[see][p.~34--35]{item1}; \\citealp[also][chap. 3]{item3}}"
+ =?> para (cite [baseCitation{ citationMode = NormalCitation
+ , citationPrefix = [Str "see"]
+ , citationSuffix = [Str "p.\160\&34",EnDash,Str "35"] }
+ ,baseCitation{ citationMode = NormalCitation
+ , citationId = "item3"
+ , citationPrefix = [Str "also"]
+ , citationSuffix = [Str "chap.",Space,Str "3"] }
+ ] empty)
+ , "suffix and locator" =: "\\citep[pp.~33, 35--37, and nowhere else]{item1}"
+ =?> para (cite [baseCitation{ citationMode = NormalCitation
+ , citationSuffix = [Str "pp.\160\&33,",Space,Str "35",EnDash,Str "37,",Space,Str "and",Space,Str "nowhere",Space, Str "else"] }] empty)
+ , "suffix only" =: "\\citep[and nowhere else]{item1}"
+ =?> para (cite [baseCitation{ citationMode = NormalCitation
+ , citationSuffix = toList $ text "and nowhere else" }] empty)
+ , "no author" =: "\\citeyearpar{item1}, and now Doe with a locator \\citeyearpar[p.~44]{item2}"
+ =?> para (cite [baseCitation{ citationMode = SuppressAuthor }] empty +++
+ text ", and now Doe with a locator " +++
+ cite [baseCitation{ citationMode = SuppressAuthor
+ , citationSuffix = [Str "p.\160\&44"]
+ , citationId = "item2" }] empty)
+ , "markup" =: "\\citep[\\emph{see}][p. \\textbf{32}]{item1}"
+ =?> para (cite [baseCitation{ citationMode = NormalCitation
+ , citationPrefix = [Emph [Str "see"]]
+ , citationSuffix = [Str "p.",Space,
+ Strong [Str "32"]] }] empty)
+ ]
+
+biblatexCitations :: Test
+biblatexCitations = testGroup "biblatex"
+ [ "textcite" =: "\\textcite{item1}"
+ =?> para (cite [baseCitation] empty)
+ , "suffix" =: "\\textcite[p.~30]{item1}"
+ =?> para
+ (cite [baseCitation{ citationSuffix = toList $ text "p.\160\&30" }] empty)
+ , "suffix long" =: "\\textcite[p.~30, with suffix]{item1}"
+ =?> para (cite [baseCitation{ citationSuffix =
+ toList $ text "p.\160\&30, with suffix" }] empty)
+ , "multiple" =: "\\textcites{item1}[p.~30]{item2}[see also][]{item3}"
+ =?> para (cite [baseCitation{ citationMode = AuthorInText }
+ ,baseCitation{ citationMode = NormalCitation
+ , citationSuffix = [Str "p.\160\&30"]
+ , citationId = "item2" }
+ ,baseCitation{ citationId = "item3"
+ , citationPrefix = [Str "see",Space,Str "also"]
+ , citationMode = NormalCitation }
+ ] empty)
+ , "group" =: "\\autocites[see][p.~34--35]{item1}[also][chap. 3]{item3}"
+ =?> para (cite [baseCitation{ citationMode = NormalCitation
+ , citationPrefix = [Str "see"]
+ , citationSuffix = [Str "p.\160\&34",EnDash,Str "35"] }
+ ,baseCitation{ citationMode = NormalCitation
+ , citationId = "item3"
+ , citationPrefix = [Str "also"]
+ , citationSuffix = [Str "chap.",Space,Str "3"] }
+ ] empty)
+ , "suffix and locator" =: "\\autocite[pp.~33, 35--37, and nowhere else]{item1}"
+ =?> para (cite [baseCitation{ citationMode = NormalCitation
+ , citationSuffix = [Str "pp.\160\&33,",Space,Str "35",EnDash,Str "37,",Space,Str "and",Space,Str "nowhere",Space, Str "else"] }] empty)
+ , "suffix only" =: "\\autocite[and nowhere else]{item1}"
+ =?> para (cite [baseCitation{ citationMode = NormalCitation
+ , citationSuffix = toList $ text "and nowhere else" }] empty)
+ , "no author" =: "\\autocite*{item1}, and now Doe with a locator \\autocite*[p.~44]{item2}"
+ =?> para (cite [baseCitation{ citationMode = SuppressAuthor }] empty +++
+ text ", and now Doe with a locator " +++
+ cite [baseCitation{ citationMode = SuppressAuthor
+ , citationSuffix = [Str "p.\160\&44"]
+ , citationId = "item2" }] empty)
+ , "markup" =: "\\autocite[\\emph{see}][p. \\textbf{32}]{item1}"
+ =?> para (cite [baseCitation{ citationMode = NormalCitation
+ , citationPrefix = [Emph [Str "see"]]
+ , citationSuffix = [Str "p.",Space,
+ Strong [Str "32"]] }] empty)
+ , "parencite" =: "\\parencite{item1}"
+ =?> para (cite [baseCitation{ citationMode = NormalCitation }] empty)
+ ]
diff --git a/src/Tests/Readers/Markdown.hs b/src/Tests/Readers/Markdown.hs
new file mode 100644
index 000000000..722a45bdb
--- /dev/null
+++ b/src/Tests/Readers/Markdown.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
+module Tests.Readers.Markdown (tests) where
+
+import Text.Pandoc.Definition
+import Test.Framework
+import Tests.Helpers
+import Tests.Arbitrary()
+import Text.Pandoc.Builder
+import Text.Pandoc
+
+markdown :: String -> Pandoc
+markdown = readMarkdown defaultParserState{ stateStandalone = True }
+
+infix 5 =:
+(=:) :: ToString c
+ => String -> (String, c) -> Test
+(=:) = test markdown
+
+tests :: [Test]
+tests = [ testGroup "inline code"
+ [ "with attribute" =:
+ "`document.write(\"Hello\");`{.javascript}"
+ =?> para
+ (codeWith ("",["javascript"],[]) "document.write(\"Hello\");")
+ , "with attribute space" =:
+ "`*` {.haskell .special x=\"7\"}"
+ =?> para (codeWith ("",["haskell","special"],[("x","7")]) "*")
+ ]
+ ]
diff --git a/src/Tests/Readers/RST.hs b/src/Tests/Readers/RST.hs
new file mode 100644
index 000000000..c0f60ff51
--- /dev/null
+++ b/src/Tests/Readers/RST.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
+module Tests.Readers.RST (tests) where
+
+import Text.Pandoc.Definition
+import Test.Framework
+import Tests.Helpers
+import Tests.Arbitrary()
+import Text.Pandoc.Builder
+import Text.Pandoc
+
+rst :: String -> Pandoc
+rst = readRST defaultParserState{ stateStandalone = True }
+
+infix 5 =:
+(=:) :: ToString c
+ => String -> (String, c) -> Test
+(=:) = test rst
+
+tests :: [Test]
+tests = [ "field list" =:
+ [_LIT|
+:Hostname: media08
+:IP address: 10.0.0.19
+:Size: 3ru
+:Date: 2001-08-16
+:Version: 1
+:Authors: - Me
+ - Myself
+ - I
+:Indentation: Since the field marker may be quite long, the second
+ and subsequent lines of the field body do not have to line up
+ with the first line, but they must be indented relative to the
+ field name marker, and they must line up with each other.
+:Parameter i: integer
+|] =?> ( setAuthors ["Me","Myself","I"]
+ $ setDate "2001-08-16"
+ $ doc
+ $ definitionList [ (str "Hostname", [para "media08"])
+ , (str "IP address", [para "10.0.0.19"])
+ , (str "Size", [para "3ru"])
+ , (str "Version", [para "1"])
+ , (str "Indentation", [para "Since the field marker may be quite long, the second and subsequent lines of the field body do not have to line up with the first line, but they must be indented relative to the field name marker, and they must line up with each other."])
+ , (str "Parameter i", [para "integer"])
+ ])
+ ]
+
diff --git a/src/Tests/Shared.hs b/src/Tests/Shared.hs
new file mode 100644
index 000000000..c35a158c1
--- /dev/null
+++ b/src/Tests/Shared.hs
@@ -0,0 +1,21 @@
+module Tests.Shared (tests) where
+
+import Text.Pandoc.Definition
+import Text.Pandoc.Shared
+import Test.Framework
+import Tests.Helpers
+import Tests.Arbitrary()
+
+tests :: [Test]
+tests = [ testGroup "normalize"
+ [ property "p_normalize_blocks_rt" p_normalize_blocks_rt
+ , property "p_normalize_inlines_rt" p_normalize_inlines_rt
+ ]
+ ]
+
+p_normalize_blocks_rt :: [Block] -> Bool
+p_normalize_blocks_rt bs = normalize bs == normalize (normalize bs)
+
+p_normalize_inlines_rt :: [Inline] -> Bool
+p_normalize_inlines_rt ils = normalize ils == normalize (normalize ils)
+
diff --git a/src/Tests/Writers/ConTeXt.hs b/src/Tests/Writers/ConTeXt.hs
new file mode 100644
index 000000000..704571e95
--- /dev/null
+++ b/src/Tests/Writers/ConTeXt.hs
@@ -0,0 +1,72 @@
+{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
+module Tests.Writers.ConTeXt (tests) where
+
+import Test.Framework
+import Text.Pandoc.Builder
+import Text.Pandoc
+import Tests.Helpers
+import Tests.Arbitrary()
+
+context :: (ToString a, ToPandoc a) => a -> String
+context = writeConTeXt defaultWriterOptions . toPandoc
+
+context' :: (ToString a, ToPandoc a) => a -> String
+context' = writeConTeXt defaultWriterOptions{ writerWrapText = False }
+ . toPandoc
+
+{-
+ "my test" =: X =?> Y
+
+is shorthand for
+
+ test context "my test" $ X =?> Y
+
+which is in turn shorthand for
+
+ test context "my test" (X,Y)
+-}
+
+infix 5 =:
+(=:) :: (ToString a, ToPandoc a)
+ => String -> (a, String) -> Test
+(=:) = test context
+
+tests :: [Test]
+tests = [ testGroup "inline code"
+ [ "with '}'" =: code "}" =?> "\\mono{\\letterclosebrace{}}"
+ , "without '}'" =: code "]" =?> "\\type{]}"
+ , property "code property" $ \s -> null s ||
+ if '{' `elem` s || '}' `elem` s
+ then (context' $ code s) == "\\mono{" ++
+ (context' $ str s) ++ "}"
+ else (context' $ code s) == "\\type{" ++ s ++ "}"
+ ]
+ , testGroup "headers"
+ [ "level 1" =:
+ header 1 "My header" =?> "\\subject{My header}"
+ , property "header 1 property" $ \ils ->
+ context' (header 1 ils) == "\\subject{" ++ context' ils ++ "}"
+ ]
+ , testGroup "bullet lists"
+ [ "nested" =:
+ bulletList [plain (text "top")
+ ,bulletList [plain (text "next")
+ ,bulletList [plain (text "bot")]]]
+ =?> [_LIT|
+\startitemize
+\item
+ top
+\item
+ \startitemize
+ \item
+ next
+ \item
+ \startitemize
+ \item
+ bot
+ \stopitemize
+ \stopitemize
+\stopitemize|]
+ ]
+ ]
+
diff --git a/src/Tests/Writers/Native.hs b/src/Tests/Writers/Native.hs
new file mode 100644
index 000000000..234fe938a
--- /dev/null
+++ b/src/Tests/Writers/Native.hs
@@ -0,0 +1,20 @@
+module Tests.Writers.Native (tests) where
+
+import Test.Framework
+import Text.Pandoc.Builder
+import Text.Pandoc
+import Tests.Helpers
+import Tests.Arbitrary()
+
+p_write_rt :: Pandoc -> Bool
+p_write_rt d =
+ read (writeNative defaultWriterOptions{ writerStandalone = True } d) == d
+
+p_write_blocks_rt :: [Block] -> Bool
+p_write_blocks_rt bs =
+ read (writeNative defaultWriterOptions (Pandoc (Meta [] [] []) bs)) == bs
+
+tests :: [Test]
+tests = [ property "p_write_rt" p_write_rt
+ , property "p_write_blocks_rt" p_write_blocks_rt
+ ]
diff --git a/src/test-pandoc.hs b/src/test-pandoc.hs
new file mode 100644
index 000000000..b7e4f7bd5
--- /dev/null
+++ b/src/test-pandoc.hs
@@ -0,0 +1,30 @@
+{-# OPTIONS_GHC -Wall #-}
+
+module Main where
+
+import Test.Framework
+
+import qualified Tests.Old
+import qualified Tests.Readers.LaTeX
+import qualified Tests.Readers.Markdown
+import qualified Tests.Readers.RST
+import qualified Tests.Writers.ConTeXt
+import qualified Tests.Writers.Native
+import qualified Tests.Shared
+
+tests :: [Test]
+tests = [ testGroup "Old" Tests.Old.tests
+ , testGroup "Shared" Tests.Shared.tests
+ , testGroup "Writers"
+ [ testGroup "Native" Tests.Writers.Native.tests
+ , testGroup "ConTeXt" Tests.Writers.ConTeXt.tests
+ ]
+ , testGroup "Readers"
+ [ testGroup "LaTeX" Tests.Readers.LaTeX.tests
+ , testGroup "Markdown" Tests.Readers.Markdown.tests
+ , testGroup "RST" Tests.Readers.RST.tests
+ ]
+ ]
+
+main :: IO ()
+main = defaultMain tests