{-# LANGUAGE OverloadedStrings #-}
module Tests.Writers.Muse (tests) where

import Prelude hiding (unlines)
import Data.Text (Text, unlines)
import Test.Tasty
import Test.Tasty.HUnit (HasCallStack)
import Tests.Helpers
import Text.Pandoc
import Text.Pandoc.Arbitrary ()
import Text.Pandoc.Builder

defopts :: WriterOptions
defopts = def{ writerWrapText = WrapPreserve,
               writerExtensions = extensionsFromList [Ext_amuse,
                                                      Ext_auto_identifiers] }

muse :: (ToPandoc a) => a -> Text
muse = museWithOpts defopts

museWithOpts :: (ToPandoc a) => WriterOptions -> a -> Text
museWithOpts opts = purely (writeMuse opts) . toPandoc

infix 4 =:
(=:) :: (ToString a, ToPandoc a, HasCallStack)
     => String -> (a, Text) -> TestTree
(=:) = test muse

noteLocationTestDoc :: Blocks
noteLocationTestDoc =
  header 1 "First Header" <>
  para ("This is a footnote." <>
        note (para "First note.")) <>
  blockQuote (para ("A note inside a block quote." <>
                    note (para "The second note.")) <>
              para "A second paragraph.") <>
  header 1 "Second Header" <>
  para "Some more text."

noteLocationTests :: TestTree
noteLocationTests = testGroup "note location"
  [ test (museWithOpts defopts {writerReferenceLocation=EndOfDocument})
    "footnotes at the end of document" $
    noteLocationTestDoc =?>
    unlines [ "* First Header"
            , ""
            , "This is a footnote.[1]"
            , ""
            , "<quote>"
            , "A note inside a block quote.[2]"
            , ""
            , "A second paragraph."
            , "</quote>"
            , ""
            , "* Second Header"
            , ""
            , "Some more text."
            , ""
            , "[1] First note."
            , ""
            , "[2] The second note."
            ]
  , test (museWithOpts defopts {writerReferenceLocation=EndOfBlock})
    "footnotes at the end of block" $
    noteLocationTestDoc =?>
    unlines [ "* First Header"
            , ""
            , "This is a footnote.[1]"
            , ""
            , "[1] First note."
            , ""
            , "<quote>"
            , "A note inside a block quote.[2]"
            , ""
            , "[2] The second note."
            , ""
            , "A second paragraph."
            , "</quote>"
            , ""
            , "* Second Header"
            , ""
            , "Some more text."
            ]
  , test (museWithOpts defopts {writerReferenceLocation=EndOfSection})
    "footnotes at the end of section" $
    noteLocationTestDoc =?>
    unlines [ "* First Header"
            , ""
            , "This is a footnote.[1]"
            , ""
            , "<quote>"
            , "A note inside a block quote.[2]"
            , ""
            , "A second paragraph."
            , "</quote>"
            , ""
            , "[1] First note."
            , ""
            , "[2] The second note."
            , ""
            , "* Second Header"
            , ""
            , "Some more text."
            ]
  ]

tests :: [TestTree]
tests = [ testGroup "block elements"
          [ "plain" =: plain "Foo bar." =?> "Foo bar."
          , testGroup "paragraphs"
            [ "single paragraph" =: para "Sample paragraph."
                                 =?> "Sample paragraph."
            , "two paragraphs" =: para "First paragraph." <>
                                  para "Second paragraph."
                               =?> unlines [ "First paragraph."
                                           , ""
                                           , "Second paragraph."
                                           ]
            ]
          , "line block" =: lineBlock ["Foo", "bar", "baz"]
                         =?> unlines [ "> Foo"
                                     , "> bar"
                                     , "> baz"
                                     ]
          , "code block" =: codeBlock "int main(void) {\n\treturn 0;\n}"
                         =?> unlines [ "<example>"
                                     , "int main(void) {"
                                     , "\treturn 0;"
                                     , "}"
                                     , "</example>"
                                     ]
          , "html raw block" =: rawBlock "html" "<hr>"
                             =?> unlines [ "<literal style=\"html\">"
                                         , "<hr>"
                                         , "</literal>"
                                         ]
          , "block quote" =: blockQuote (para "Foo")
                          =?> unlines [ "<quote>"
                                      , "Foo"
                                      , "</quote>"
                                      ]
          , testGroup "lists"
            [ testGroup "simple lists"
              [
                "ordered list" =: orderedList [ plain "first"
                                              , plain "second"
                                              , plain "third"
                                              ]
                               =?> unlines [ " 1. first"
                                           , " 2. second"
                                           , " 3. third"
                                           ]
              , "ordered list with Roman numerals"
                =: orderedListWith (1, UpperRoman, DefaultDelim)
                   [ plain "first"
                   , plain "second"
                   , plain "third"
                   ]
                =?> unlines [ " I. first"
                            , " II. second"
                            , " III. third"
                            ]
              , "bullet list" =: bulletList [ plain "first"
                                            , plain "second"
                                            , plain "third"
                                            ]
                              =?> unlines [ " - first"
                                          , " - second"
                                          , " - third"
                                          ]
              , "definition list" =: definitionList [ ("first definition", [plain "first description"])
                                                    , ("second definition", [plain "second description"])
                                                    , ("third definition", [plain "third description"])
                                                    ]
                                  =?> unlines [ " first definition :: first description"
                                              , " second definition :: second description"
                                              , " third definition :: third description"
                                              ]
              , "definition list with multiple descriptions" =:
                definitionList [ ("first definition", [ plain "first description"
                                                      , plain "second description"
                                                      ])
                               , ("second definition", [plain "third description"])
                               ]
                =?> unlines [ " first definition :: first description"
                            , "                  :: second description"
                            , " second definition :: third description"
                            ]
              , "definition list with empty term" =:
                definitionList [ ("first definition", [plain "first description"])
                               , (mempty, [plain "second description"])
                               , (str "", [plain "third description"])
                               ]
                =?> unlines [ " first definition :: first description"
                            , " <verbatim></verbatim> :: second description"
                            , " <verbatim></verbatim> :: third description"
                            ]
              , "definition list terms starting with space" =:
                definitionList [ ("first definition", [plain "first description"])
                               , (space <> str "foo", [plain "second description"])
                               , (str " > bar", [plain "third description"])
                               ]
                =?> unlines [ " first definition :: first description"
                            , " <verbatim></verbatim> foo :: second description"
                            , " <verbatim></verbatim> > bar :: third description"
                            ]
              , "definition list terms starting with list markers" =:
                definitionList [ ("first definition", [plain "first description"])
                               , (str "-", [plain "second description"])
                               , (str "1.", [plain "third description"])
                               ]
                =?> unlines [ " first definition :: first description"
                            , " <verbatim></verbatim>- :: second description"
                            , " <verbatim></verbatim>1. :: third description"
                            ]
              ]
            -- Test that lists of the same type and style are separated with two blanklines
            , testGroup "sequential lists"
              [ "bullet lists" =:
                bulletList [ para "First"
                           , para "Second"
                           , para "Third"
                           ] <>
                bulletList [ para "Fourth"
                           , para "Fifth"
                           ] =?>
                unlines [ " - First"
                        , " - Second"
                        , " - Third"
                        , ""
                        , ""
                        , " - Fourth"
                        , " - Fifth"
                        ]
              , "ordered lists of the same style" =:
                orderedListWith (1, UpperRoman, DefaultDelim) [ para "First"
                                                              , para "Second"
                                                              ] <>
                orderedListWith (1, UpperRoman, DefaultDelim) [ para "Third"
                                                              , para "Fourth"
                                                              ] =?>
                unlines [ " I. First"
                        , " II. Second"
                        , ""
                        , ""
                        , " I. Third"
                        , " II. Fourth"
                        ]
              , "ordered lists with equal styles" =:
                orderedList [ para "First"
                            , para "Second"
                            ] <>
                orderedListWith (1, Decimal, DefaultDelim) [ para "Third"
                                                           , para "Fourth"
                                                           ] =?>
                unlines [ " 1. First"
                        , " 2. Second"
                        , ""
                        , ""
                        , " 1. Third"
                        , " 2. Fourth"
                        ]
              , "bullet and ordered lists" =:
                bulletList [ para "First"
                           , para "Second"
                           ] <>
                orderedListWith (1, UpperRoman, DefaultDelim) [ para "Third"
                                                              , para "Fourth"
                                                              ] =?>
                unlines [ " - First"
                        , " - Second"
                        , ""
                        , " I. Third"
                        , " II. Fourth"
                        ]
              , "different style ordered lists" =:
                orderedListWith (1, UpperRoman, DefaultDelim) [ para "First"
                                                              , para "Second"
                                                              ] <>
                orderedListWith (1, Decimal, DefaultDelim) [ para "Third"
                                                           , para "Fourth"
                                                           ] =?>
                unlines [ " I. First"
                        , " II. Second"
                        , ""
                        , " 1. Third"
                        , " 2. Fourth"
                        ]
              ]
            , testGroup "nested lists"
              [ "nested ordered list" =: orderedList [ plain "First outer"
                                                     , plain "Second outer:" <>
                                                       orderedList [ plain "first"
                                                                   , plain "second"
                                                                   ]
                                                     , plain "Third outer"
                                                     ]
                                      =?> unlines [ " 1. First outer"
                                                  , " 2. Second outer:"
                                                  , "    1. first"
                                                  , "    2. second"
                                                  , " 3. Third outer"
                                                  ]
              , "nested bullet lists" =: bulletList [ plain "First outer"
                                                    , plain "Second outer:" <>
                                                      bulletList [ plain "first"
                                                                 , plain "second"
                                                                 ]
                                                    , plain "Third outer"
                                                    ]
                                      =?> unlines [ " - First outer"
                                                  , " - Second outer:"
                                                  , "   - first"
                                                  , "   - second"
                                                  , " - Third outer"
                                                  ]
              , "nested definition lists" =: definitionList [ ("first definition", [plain "first description"])
                                                            , ("second definition",
                                                               [ plain "second description" <>
                                                                 definitionList [ ("first inner definition"
                                                                                  , [plain "first inner description"])
                                                                                , ( "second inner definition"
                                                                                  , [plain "second inner description"])
                                                                                ]
                                                               ]
                                                              )
                                                            ]
                                          =?> unlines [ " first definition :: first description"
                                                      , " second definition :: second description"
                                                      , "                      first inner definition :: first inner description"
                                                      , "                      second inner definition :: second inner description"
                                                      ]
              , "list item starting with list" =: bulletList [ bulletList [ plain "foo"] ] =?> " - - foo"
              ]
            -- Check that list is intended with one space even inside a quote
            , "List inside block quote" =: blockQuote (orderedList [ plain "first"
                                                                   , plain "second"
                                                                   , plain "third"
                                                                   ])
                                        =?> unlines [ "<quote>"
                                                    , " 1. first"
                                                    , " 2. second"
                                                    , " 3. third"
                                                    , "</quote>"
                                                    ]
            ]
          , testGroup "headings"
            [ "normal heading" =:
              header 1 "foo" =?> "* foo"
            , "heading levels" =:
              header 1 "First level" <>
              header 3 "Third level" =?>
              unlines [ "* First level"
                      , ""
                      , "*** Third level"
                      ]
            , "heading with ID" =:
               headerWith ("bar", [], []) 2 "Foo" =?>
               unlines [ "#bar"
                       , "** Foo"
                      ]
            , "empty heading" =: header 4 mempty =?> "**** <verbatim></verbatim>"
            ]
          , "horizontal rule" =: horizontalRule =?> "----"
          , "escape horizontal rule" =: para "----" =?> "<verbatim></verbatim>----"
          , "escape long horizontal rule" =: para "----------" =?> "<verbatim></verbatim>----------"
          , "don't escape horizontal inside paragraph" =: para "foo ---- bar" =?> "foo ---- bar"
          , "escape nonbreaking space" =: para "~~" =?> "<verbatim>~~</verbatim>"
          , "escape > in the beginning of line" =: para "> foo bar" =?> "<verbatim></verbatim>> foo bar"
          , "escape string with > and space in the beginning of line" =: para (str "> foo bar") =?> "<verbatim></verbatim>> foo bar"
          , testGroup "tables"
            [ "table without header" =:
              let rows = [[para "Para 1.1", para "Para 1.2"]
                         ,[para "Para 2.1", para "Para 2.2"]]
                  toRow = Row nullAttr . map simpleCell
              in table emptyCaption
                       [(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)]
                       (TableHead nullAttr [toRow [mempty, mempty]])
                       [TableBody nullAttr 0 [] $ map toRow rows]
                       (TableFoot nullAttr [])
              =?>
              unlines [ " Para 1.1 | Para 1.2"
                      , " Para 2.1 | Para 2.2"
                      ]
            , "table with header" =:
              let headers = [plain "header 1", plain "header 2"]
                  rows = [[para "Para 1.1", para "Para 1.2"]
                         ,[para "Para 2.1", para "Para 2.2"]]
              in simpleTable headers rows
              =?>
              unlines [ " header 1 || header 2"
                      , " Para 1.1 |  Para 1.2"
                      , " Para 2.1 |  Para 2.2"
                      ]
            , "table with header and caption" =:
              let capt = simpleCaption $ plain "Table 1"
                  toRow = Row nullAttr . map simpleCell
                  headers = [toRow [plain "header 1", plain "header 2"]]
                  rows = map toRow [[para "Para 1.1", para  "Para 1.2"]
                                   ,[para "Para 2.1", para  "Para 2.2"]]
              in table capt
                       [(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)]
                       (TableHead nullAttr headers)
                       [TableBody nullAttr 0 [] rows]
                       (TableFoot nullAttr [])
              =?> unlines [ " header 1 || header 2"
                          , " Para 1.1 |  Para 1.2"
                          , " Para 2.1 |  Para 2.2"
                          , " |+ Table 1 +|"
                          ]
            , "table inside bullet list" =:
              bulletList [simpleTable [] [[para "foo", para "bar"]
                                         ,[para "bat", para "baz"]]]
              =?> unlines [ " - foo | bar"
                          , "   bat | baz"
                          ]
            , "table with one column" =:
              let headers = []
                  rows = [[para "Para 1"]
                         ,[para "Para 2"]]
              in simpleTable headers rows
              =?>
              unlines [ "+--------+"
                      , "| Para 1 |"
                      , "+--------+"
                      , "| Para 2 |"
                      , "+--------+"
                      ]
            ]
          , "div with bullet list" =:
            divWith nullAttr (bulletList [para "foo"]) =?>
            unlines [ " - foo" ] -- Making sure bullets are indented
          ]
        , testGroup "inline elements"
          [ testGroup "string"
            [ "string" =: str "foo" =?> "foo"
            , "escape footnote" =: str "[1]" =?> "<verbatim>[1]</verbatim>"
            , "escape secondary note" =: str "{1}" =?> "<verbatim>{1}</verbatim>"
            , "do not escape brackets" =: str "[12ab]" =?> "[12ab]"
            , "escape verbatim close tag" =: str "foo</verbatim>bar"
               =?> "<verbatim>foo<</verbatim><verbatim>/verbatim>bar</verbatim>"
            , "escape link-like text" =: str "[[https://www.example.org]]"
               =?> "<verbatim>[[https://www.example.org]]</verbatim>"
            , "escape pipe to avoid accidental tables" =: str "foo | bar"
               =?> "<verbatim>foo | bar</verbatim>"
            , "escape hash to avoid accidental anchors" =: text "#foo bar"
              =?> "<verbatim>#foo</verbatim> bar"
            , "escape definition list markers" =: str "::" =?> "<verbatim>::</verbatim>"
            , "normalize strings before escaping" =: fromList [Str ":", Str ":"] =?> "<verbatim>::</verbatim>"
            -- We don't want colons to be escaped if they can't be confused
            -- with definition list item markers.
            , "do not escape colon" =: str ":" =?> ":"
            , "escape - to avoid accidental unordered lists" =: text " - foo" =?> "<verbatim></verbatim> - foo"
            , "escape - inside a list to avoid accidental nested unordered lists" =:
              bulletList [ para "foo" <>
                           para "- bar"
                         ] =?>
              unlines [ " - foo"
                      , ""
                      , "   <verbatim></verbatim>- bar"
                      ]
            , "escape strings starting with - inside a list" =:
              bulletList [ para (str "foo") <>
                           para (str "- bar")
                         ] =?>
              unlines [ " - foo"
                      , ""
                      , "   <verbatim></verbatim>- bar"
                      ]
            , "escape - inside a note" =:
              note (para "- foo") =?>
              unlines [ "[1]"
                      , ""
                      , "[1] <verbatim></verbatim>- foo"
                      ]
            , "escape - after softbreak in note" =:
              note (para (str "foo" <> softbreak <> str "- bar")) =?>
              unlines [ "[1]"
                      , ""
                      , "[1] foo"
                      , "    <verbatim></verbatim>- bar"
                      ]
            , "escape ; to avoid accidental comments" =: text "; foo" =?> "<verbatim></verbatim>; foo"
            , "escape strings starting with ; and space" =: str "; foo" =?> "<verbatim></verbatim>; foo"
            , "escape ; after softbreak" =: "foo" <> softbreak <> "; bar" =?> "foo\n<verbatim></verbatim>; bar"
            , "escape ; after linebreak" =: "foo" <> linebreak <> "; bar" =?> "foo<br>\n<verbatim></verbatim>; bar"
            , "do not escape ; inside paragraph" =: text "foo ; bar" =?> "foo ; bar"
            , "escape newlines" =: str "foo\nbar" =?> "foo bar"
            ]
          , testGroup "emphasis"
            [ "emphasis" =: emph "foo" =?> "*foo*"
            , "emphasis inside word" =: "foo" <> emph "bar" <> "baz" =?> "foo<em>bar</em>baz"
            , "emphasis before comma" =: emph "foo" <> ", bar" =?> "*foo*, bar"
            , "emphasis before period" =: emph "foobar" <> "." =?> "*foobar*."
            , "empty emphasis" =: emph mempty =?> "<em></em>"
            , "empty strong" =: strong mempty =?> "<strong></strong>"
            , "empty strong emphasis" =: strong (emph mempty) =?> "**<em></em>**"
            , "empty emphasized strong" =: emph (strong mempty) =?> "*<strong></strong>*"
            , "emphasized empty string" =: emph (str "") =?> "<em></em>"
            , "strong empty string" =: strong (str "") =?> "<strong></strong>"
            , "strong emphasized empty string" =: strong (emph (str "")) =?> "**<em></em>**"
            , "emphasized strong empty string" =: emph (strong (str "")) =?> "*<strong></strong>*"
            , "emphasized string with space" =: emph (str " ") =?> "<em> </em>"
            , "emphasized string ending with space" =: emph (str "foo ") =?> "<em>foo </em>"
            , "emphasized string with tab" =: emph (str "\t") =?> "<em>\t</em>"
            , "emphasized space between empty strings" =: emph (str "" <> space <> str "") =?> "<em> </em>"
            , "strong" =: strong "foo" =?> "**foo**"
            , "strong inside word" =: "foo" <> strong "bar" <> "baz" =?> "foo<strong>bar</strong>baz"
            , "strong emphasis" =: strong (emph "foo") =?> "***foo***"
            , "strong after emphasis" =: emph "foo" <> strong "bar" =?> "*foo*<strong>bar</strong>"
            , "strong emphasis after emphasis" =: emph "foo" <> strong (emph "bar") =?> "*foo*<strong>*bar*</strong>"
            , "strong in the end of emphasis" =: emph ("foo" <> strong "bar") =?> "*foo<strong>bar</strong>*"
            , "switch to lightweight markup after <em> tag" =:
              strong (str "foo") <> emph (str "bar") <> strong (str "baz") =?>
              "**foo**<em>bar</em>**baz**"
            , "strikeout" =: strikeout "foo" =?> "<del>foo</del>"
            , "space at the beginning of emphasis" =: emph " foo" =?> "<em> foo</em>"
            , "space at the end of emphasis" =: emph "foo " =?> "<em>foo </em>"
            , "space at the beginning of strong" =: strong " foo" =?> "<strong> foo</strong>"
            , "space at the end of strong" =: strong "foo " =?> "<strong>foo </strong>"
            , "space at the beginning of strong emphasis" =: strong (emph " foo") =?> "**<em> foo</em>**"
            , "space at the end of strong emphasis" =: strong (emph "foo ") =?> "**<em>foo </em>**"
            , "space at the beginning of emphasiszed strong" =: emph (strong " foo") =?> "*<strong> foo</strong>*"
            , "space at the end of emphasized strong" =: emph (strong "foo ") =?> "*<strong>foo </strong>*"
            ]
          , "superscript" =: superscript "foo" =?> "<sup>foo</sup>"
          , "subscript" =: subscript "foo" =?> "<sub>foo</sub>"
          , "smallcaps" =: smallcaps "foo" =?> "*foo*"
          , "smallcaps near emphasis" =: emph (str "foo") <> smallcaps (str "bar") =?> "*foobar*"
          , "single quoted" =: singleQuoted "foo" =?> "‘foo’"
          , "double quoted" =: doubleQuoted "foo" =?> "“foo”"
          -- Cite is trivial
          , testGroup "code"
            [ "simple" =: code "foo" =?> "=foo="
            , "empty" =: code "" =?> "<code></code>"
            , "space" =: code " " =?> "<code> </code>"
            , "space at the beginning" =: code " foo" =?> "<code> foo</code>"
            , "space at the end" =: code "foo " =?> "<code>foo </code>"
            , "use tags for =" =: code "foo = bar" =?> "<code>foo = bar</code>"
            , "escape tag" =: code "<code>foo = bar</code> baz" =?> "<code><code>foo = bar<</code><code>/code> baz</code>"
            , "normalization with attributes" =: codeWith ("",["haskell"],[]) "foo" <> code "bar" =?> "=foobar="
            , "code tag" =: code "<code>foo</code>" =?> "=<code>foo</code>="
            , "normalization" =: code "</co" <> code "de>" <> code "=" =?> "<code><</code><code>/code>=</code>"
            , "normalization with empty string" =: code "</co" <> str "" <> code "de>" <> code "=" =?> "<code><</code><code>/code>=</code>"
            , "emphasized code" =: emph (code "foo") =?> "*=foo=*"
            , "strong code" =: strong (code "foo") =?> "**=foo=**"
            ]
          , testGroup "spaces"
            [ "space" =: "a" <> space <> "b" =?> "a b"
            , "soft break" =: "a" <> softbreak <> "b" =?> "a\nb"
            , test (museWithOpts def{ writerWrapText = WrapNone })
                   "remove soft break" $ "a" <> softbreak <> "b"
                   =?> ("a b" :: String)
            , "line break" =: "a" <> linebreak <> "b" =?> "a<br>\nb"
            , "line break at the end" =: "a" <> linebreak =?> "a<br>"
            , "no newline after line break in header" =: header 1 ("a" <> linebreak <> "b") =?> "* a<br>b"
            , "no softbreak in header" =: header 1 ("a" <> softbreak <> "b") =?> "* a b"
            ]
          , testGroup "math"
            [ "inline math" =: math "2^3" =?> "2<sup>3</sup>"
            , "display math" =: displayMath "2^3" =?> "2<sup>3</sup>"
            , "multiple letters in inline math" =: math "abc" =?> "*abc*"
            , "expand math before normalization" =: math "[" <> str "2]" =?> "<verbatim>[2]</verbatim>"
            , "multiple math expressions inside one inline list" =: math "5_4" <> ", " <> displayMath "3^2" =?> "5<sub>4</sub>, 3<sup>2</sup>"
            ]
          , "raw inline"
            =: rawInline "html" "<mark>marked text</mark>"
            =?> "<literal style=\"html\"><mark>marked text</mark></literal>"
          , testGroup "links"
            [ "link with description" =: link "https://example.com" "" (str "Link 1")
                                      =?> "[[https://example.com][Link 1]]"
            , "link without description" =: link "https://example.com" "" (str "https://example.com")
                                         =?> "[[https://example.com]]"
            -- Internal links in Muse include '#'
            , "link to anchor" =: link "#intro" "" (str "Introduction")
                               =?> "[[#intro][Introduction]]"
            -- According to Emacs Muse manual, links to images should be prefixed with "URL:"
            , "link to image with description" =: link "1.png" "" (str "Link to image")
                                               =?> "[[URL:1.png][Link to image]]"
            , "link to image without description" =: link "1.png" "" (str "1.png")
                                                  =?> "[[URL:1.png]]"

            , testGroup "escape brackets in links"
              [ "link with description"
                =: link "https://example.com/foo].txt" "" (str "Description")
                =?> "[[https://example.com/foo%5D.txt][Description]]"
              , "link without description"
                =: link "https://example.com/foo].txt" "" (str "https://example.com/foo].txt")
                =?> "[[https://example.com/foo%5D.txt][<verbatim>https://example.com/foo].txt</verbatim>]]"
              , "image link with description"
                =: link "foo]bar.png" "" (str "Image link")
                =?> "[[URL:foo%5Dbar.png][Image link]]"
              , "image link without description"
                =: link "foo]bar.png" "" (str "foo]bar.png")
                =?> "[[URL:foo%5Dbar.png][<verbatim>foo]bar.png</verbatim>]]"
              ]
            ]
          , "image" =: image "image.png" "Image 1" (str "") =?> "[[image.png][Image 1]]"
          , "image with width" =:
            imageWith ("", [], [("width", "60%")]) "image.png" "Image" (str "") =?>
            "[[image.png 60][Image]]"
          , "left-aligned image with width" =:
            imageWith ("", ["align-left"], [("width", "60%")]) "image.png" "Image" (str "") =?>
            "[[image.png 60 l][Image]]"
          , "right-aligned image with width" =:
            imageWith ("", ["align-right"], [("width", "60%")]) "image.png" "Image" (str "") =?>
            "[[image.png 60 r][Image]]"
          , "escape brackets in image title" =: image "image.png" "Foo]bar" (str "") =?> "[[image.png][<verbatim>Foo]bar</verbatim>]]"
          , "note" =: note (plain "Foo")
                   =?> unlines [ "[1]"
                               , ""
                               , "[1] Foo"
                               ]
          , noteLocationTests
          , "span with class" =: spanWith ("",["foobar"],[]) "Some text"
                              =?> "<class name=\"foobar\">Some text</class>"
          , "span without class" =: spanWith ("",[],[]) "Some text"
                                 =?> "<class>Some text</class>"
          , "span with anchor" =: spanWith ("anchor", [], []) mempty <> "Foo bar"
                               =?> "#anchor Foo bar"
          , "empty span with anchor" =: spanWith ("anchor", [], []) mempty
                                     =?> "#anchor"
          , "empty span without class and anchor" =: spanWith ("", [], []) mempty
                                                  =?> "<class></class>"
          , "span with class and anchor" =: spanWith ("anchor", ["foo"], []) "bar"
                                         =?> "#anchor <class name=\"foo\">bar</class>"
          , "adjacent spans" =: spanWith ("", ["syllable"], []) (str "wa") <>
                                spanWith ("", ["syllable"], []) (str "ter")
                             =?> "<class name=\"syllable\">wa</class><class name=\"syllable\">ter</class>"
          , testGroup "RTL"
            [ "RTL span" =: spanWith ("",[],[("dir", "rtl")]) (text "foo bar") =?> "<<<foo bar>>>"
            , "LTR span" =: spanWith ("",[],[("dir", "ltr")]) (text "foo bar") =?> ">>>foo bar<<<"
            , "RTL span with a class" =: spanWith ("",["foobar"],[("dir", "rtl")]) (text "foo bar") =?> "<class name=\"foobar\"><<<foo bar>>></class>"
            , "LTR span with a class" =: spanWith ("",["foobar"],[("dir", "ltr")]) (text "foo bar") =?> "<class name=\"foobar\">>>>foo bar<<<</class>"
            , "Escape <<< and >>>" =: plain (text "<<< foo bar >>>") =?> "<verbatim><<<</verbatim> foo bar <verbatim>>>></verbatim>"
            ]
          , testGroup "combined"
            [ "emph word before" =:
                para ("foo" <> emph "bar") =?>
                    "foo<em>bar</em>"
            , "emph word after" =:
                para (emph "foo" <> "bar") =?>
                    "<em>foo</em>bar"
            , "emph quoted" =:
                para (doubleQuoted (emph "foo")) =?>
                    "“*foo*”"
            , "strong word before" =:
                para ("foo" <> strong "bar") =?>
                    "foo<strong>bar</strong>"
            , "strong word after" =:
                para (strong "foo" <> "bar") =?>
                    "<strong>foo</strong>bar"
            , "strong quoted" =:
                para (singleQuoted (strong "foo")) =?>
                    "‘**foo**’"
            ]
         ]
       ]
