# This file is a part of Julia. License is MIT: https://julialang.org/license using Base.Markdown import Base.Markdown: MD, Paragraph, Header, Italic, Bold, LineBreak, plain, term, html, rst, Table, Code, LaTeX, Footnote import Base: show # Basics # Equality is checked by making sure the HTML output is # the same – the structure itself may be different. @test md"foo" == MD(Paragraph("foo")) @test md"foo *bar* baz" == MD(Paragraph(["foo ", Italic("bar"), " baz"])) @test md"""foo bar""" == MD(Paragraph(["foo bar"])) @test md"""foo\ bar""" == MD(Paragraph(["foo", LineBreak(), "bar"])) @test md"#no title" == MD(Paragraph(["#no title"])) @test md"# title" == MD(Header{1}("title")) @test md""" # empty """ == MD(Header{1}(""), Paragraph("empty")) @test md"## section" == MD(Header{2}("section")) @test md"# title *foo* `bar` **baz**" == MD(Header{1}(["title ", Italic("foo")," ",Code("bar")," ",Bold("baz")])) @test md""" h1 ===""" == md"# h1" @test md""" h2 ---""" == md"## h2" @test md"**foo *bar* baz**" == MD(Paragraph(Bold(["foo ", Italic("bar"), " baz"]))) @test md"*foo **bar** baz*" == MD(Paragraph(Italic(["foo ", Bold("bar"), " baz"]))) @test md"""```julia foo ``` """ == MD(Code("julia", "foo")) @test md"foo ``bar`` baz" == MD(Paragraph(["foo ", LaTeX("bar"), " baz"])) @test md""" ```math ... ``` """ == MD(LaTeX("...")) code_in_code = md""" ```` ``` ```` """ @test code_in_code == MD(Code("```")) @test plain(code_in_code) == "````\n```\n````\n" let text = "Foo ```bar` ``baz`` ```\n", md = Markdown.parse(text) @test text == Markdown.plain(md) end @test isempty(Markdown.parse("\r")) @test Markdown.parse("hello\r") == MD(Paragraph(["hello"])) @test Markdown.parse("hello\r*julia*") == MD(Paragraph(Any["hello ", Italic(Any["julia"])])) @test md"A footnote [^foo]." == MD(Paragraph(["A footnote ", Footnote("foo", nothing), "."])) @test md"[^foo]: footnote" == MD([Footnote("foo", Any[Paragraph(Any["footnote"])])]) let text = """ A paragraph with some footnotes,[^1] and another.[^note] [^1]: Footnote text for the first. [^note]: A longer footnote: Indented paragraphs are part of the footnote. some.code And *another* paragraph. This isn't part of the footnote. """, md = Markdown.parse(text) @test length(md.content) == 4 @test isa(md.content[1], Markdown.Paragraph) @test isa(md.content[2], Markdown.Footnote) @test isa(md.content[3], Markdown.Footnote) @test isa(md.content[4], Markdown.Paragraph) @test md.content[2].id == "1" @test md.content[3].id == "note" @test length(md.content[3].text) == 4 let expected = """ A paragraph with some footnotes,[^1] and another.[^note] [^1]: Footnote text for the first. [^note]: A longer footnote: Indented paragraphs are part of the footnote. ``` some.code ``` And *another* paragraph. This isn't part of the footnote. """ @test Markdown.plain(md) == expected end let expected = """ A paragraph with some footnotes,[1]_ and another.[note]_ .. [1] Footnote text for the first. .. [note] A longer footnote: Indented paragraphs are part of the footnote. .. code-block:: julia some.code And *another* paragraph. This isn't part of the footnote. """ @test Markdown.rst(md) == expected end let html = Markdown.html(md) @test contains(html, ",[1]") @test contains(html, ".[note]") @test contains(html, "

1

") @test contains(html, "

note

") end let latex = Markdown.latex(md) @test contains(latex, ",\\footnotemark[1]") @test contains(latex, ".\\footnotemark[note]") @test contains(latex, "\n\\footnotetext[1]{Footnote text for") @test contains(latex, "\n\\footnotetext[note]{A longer footnote:\n") end end let doc = md""" * one * two 1. pirate 2. ninja 3. zombie""" @test isa(doc.content[1], Markdown.List) @test isa(doc.content[2], Markdown.List) @test doc.content[1].items[1][1].content[1] == "one" @test doc.content[1].items[2][1].content[1] == "two" @test doc.content[2].items[1][1].content[1] == "pirate" @test doc.content[2].items[2][1].content[1] == "ninja" @test doc.content[2].items[3][1].content[1] == "zombie" end let doc = Markdown.parse( """ A paragraph... - one - two * three * four ... another paragraph. """ ) @test length(doc.content) === 3 @test isa(doc.content[1], Markdown.Paragraph) @test isa(doc.content[2], Markdown.List) @test isa(doc.content[3], Markdown.Paragraph) @test length(doc.content[2].items) === 2 @test doc.content[2].items[1][1].content[1] == "one" @test length(doc.content[2].items[2]) == 2 @test doc.content[2].items[2][1].content[1] == "two" @test isa(doc.content[2].items[2][2], Markdown.List) @test length(doc.content[2].items[2][2].items) === 2 @test doc.content[2].items[2][2].items[1][1].content[1] == "three" @test doc.content[2].items[2][2].items[2][1].content[1] == "four" end @test md"Foo [bar]" == MD(Paragraph("Foo [bar]")) @test md"Foo [bar](baz)" != MD(Paragraph("Foo [bar](baz)")) @test md"Foo \[bar](baz)" == MD(Paragraph("Foo [bar](baz)")) # Basic plain (markdown) output @test md"foo" |> plain == "foo\n" @test md"foo *bar* baz" |> plain == "foo *bar* baz\n" @test md"# title" |> plain == "# title\n" @test md"## section" |> plain == "## section\n" @test md"## section `foo`" |> plain == "## section `foo`\n" @test md"""Hello --- World""" |> plain == "Hello\n\n---\n\nWorld\n" @test md"[*a*](b)" |> plain == "[*a*](b)\n" @test md""" > foo > > * bar > > ``` > baz > ```""" |> plain == """> foo\n>\n> * bar\n>\n> ```\n> baz\n> ```\n\n""" # Terminal (markdown) output # multiple whitespace is ignored @test sprint(term, md"a b") == " a b\n" @test sprint(term, md"[x](https://julialang.org)") == " x\n" @test sprint(term, md"![x](https://julialang.org)") == " (Image: x)\n" # enumeration is normalized let doc = Markdown.parse( """ 1. a 3. b """ ) @test contains(sprint(term, doc), "1. ") @test contains(sprint(term, doc), "2. ") @test !contains(sprint(term, doc), "3. ") end # HTML output @test md"foo *bar* baz" |> html == "

foo bar baz

\n" @test md"something ***" |> html == "

something ***

\n" @test md"# h1## " |> html == "

h1##

\n" @test md"## h2 ### " |> html == "

h2

\n" @test md"###### h6" |> html == "
h6
\n" @test md"####### h7" |> html == "

####### h7

\n" @test md" >" |> html == "
\n
\n" @test md"1. Hello" |> html == "
    \n
  1. Hello

    \n
  2. \n
\n" @test md"* World" |> html == "\n" @test md"# title *blah*" |> html == "

title blah

\n" @test md"## title *blah*" |> html == "

title blah

\n" @test md"" |> html == """

https://julialang.org

\n""" @test md"" |> html == """

mailto://a@example.com

\n""" @test md"" |> html == "

<https://julialang.org/not a link>

\n" @test md"""""" |> html == "

<https://julialang.org/nota link>

\n" @test md"""Hello --- World""" |> html == "

Hello

\n
\n

World

\n" @test md"`escape`" |> html == "

escape</code>

\n" @test md""" code1 code2 """ |> html == "
code1\n\ncode2
\n" # single code block # @test md""" # - Foo # --- # - Bar""" |> html == "
    \n
  • Foo
  • \n
\n
\n
    \n
  • Bar
  • \n
\n" @test md""" h1 === h2 --- not == =""" |> html == "

h1

\n

h2

\n

not == =

\n" # Latex output book = md""" # Title Some discussion > A quote ## Section *important* Some **bolded** - list1 - list2 """ @test latex(book) == "\\section{Title}\nSome discussion\n\n\\begin{quote}\nA quote\n\n\\end{quote}\n\\subsection{Section \\emph{important}}\nSome \\textbf{bolded}\n\n\\begin{itemize}\n\\item list1\n\n\n\\item list2\n\n\\end{itemize}\n" # mime output let out = """ # Title Some discussion > A quote ## Section *important* Some **bolded** * list1 * list2 """ @test sprint(show, "text/plain", book) == out @test sprint(show, "text/markdown", book) == out end let out = """

Title

Some discussion

A quote

Section important

Some bolded

  • list1

  • list2

""" @test sprint(show, "text/html", book) == out end let out = """ \\section{Title} Some discussion \\begin{quote} A quote \\end{quote} \\subsection{Section \\emph{important}} Some \\textbf{bolded} \\begin{itemize} \\item list1 \\item list2 \\end{itemize} """ @test sprint(show, "text/latex", book) == out end let out = """ Title ***** Some discussion A quote Section *important* =================== Some **bolded** * list1 * list2 """ @test sprint(show, "text/rst", book) == out end # rst rendering for (input, output) in ( md"foo *bar* baz" => "foo *bar* baz\n", md"something ***" => "something ***\n", md"# h1## " => "h1##\n****\n\n", md"## h2 ### " => "h2\n==\n\n", md"###### h6" => "h6\n..\n\n", md"####### h7" => "####### h7\n", md" >" => " \n\n", md"1. Hello" => "1. Hello\n", md"* World" => "* World\n", md"``x + y``" => ":math:`x + y`\n", md"# title *blah*" => "title *blah*\n************\n\n", md"## title *blah*" => "title *blah*\n============\n\n", md"[`x`](:func:`x`)" => ":func:`x`\n", md"[`x`](:obj:`x`)" => ":obj:`x`\n", md"[`x`](:ref:`x`)" => ":ref:`x`\n", md"[`x`](:exc:`x`)" => ":exc:`x`\n", md"[`x`](:class:`x`)" => ":class:`x`\n", md"[`x`](:const:`x`)" => ":const:`x`\n", md"[`x`](:data:`x`)" => ":data:`x`\n", md"[`x`](:???:`x`)" => "```x`` <:???:`x`>`_\n", md"[x](y)" => "`x `_\n", ) @test rst(input) == output end # Interpolation / Custom types mutable struct Reference ref end ref(x) = Reference(x) ref(mean) show(io::IO, m::MIME"text/plain", r::Reference) = print(io, "$(r.ref) (see Julia docs)") mean_ref = md"Behaves like $(ref(mean))" @test plain(mean_ref) == "Behaves like mean (see Julia docs)\n" @test html(mean_ref) == "

Behaves like mean (see Julia docs)

\n" show(io::IO, m::MIME"text/html", r::Reference) = Markdown.withtag(io, :a, :href=>"test") do Markdown.htmlesc(io, Markdown.plaininline(r)) end @test html(mean_ref) == "

Behaves like mean (see Julia docs)

\n" @test md""" ````julia foo() ````""" == md""" ```julia foo() ```""" # GH tables @test md""" a | b ---|--- 1 | 2""" == MD(Table(Any[["a","b"], ["1","2"]], [:r, :r])) @test md""" | a | b | c | | :-- | --: | --- | | d`gh`hg | hgh**jhj**ge | f |""" == MD(Table(Any[["a","b","c"], Any[["d",Code("gh"),"hg"], ["hgh",Bold("jhj"),"ge"], "f"]], [:l, :r, :r])) @test md""" no|table no error """ == MD([Paragraph(Any["no|table no error"])]) let t = """a | b :-- | --: 1 | 2 """ @test Markdown.parse(t) == MD(Table(Any[Any["a", "b"], Any["1", "2"]], [:l, :r])) end let text = """ | a | b | |:--- | ---:| | 1 | 2 | """, table = Markdown.parse(text) @test text == Markdown.plain(table) end let text = """ | Markdown | Table | Test | |:-------- |:-----:| -----:| | foo | `bar` | *baz* | | `bar` | baz | *foo* | """, table = Markdown.parse(text) @test text == Markdown.plain(table) end let text = """ | a | b | |:-------- | ---:| | `x \\| y` | 2 | """, table = Markdown.parse(text) @test text == Markdown.plain(table) end # LaTeX extension let in_dollars = """ We have \$x^2 < x\$ whenever: \$|x| < 1\$ etc. """, in_backticks = """ We have ``x^2 < x`` whenever: ```math |x| < 1 ``` etc. """, out_plain = """ We have \$x^2 < x\$ whenever: \$\$ |x| < 1 \$\$ etc. """, out_rst = """ We have :math:`x^2 < x` whenever: .. math:: |x| < 1 etc. """, out_latex = """ We have \$x^2 < x\$ whenever: \$\$|x| < 1\$\$ etc. """, dollars = Markdown.parse(in_dollars), backticks = Markdown.parse(in_backticks), latex_doc = MD( Any[Paragraph(Any["We have ", LaTeX("x^2 < x"), " whenever:"]), LaTeX("|x| < 1"), Paragraph(Any["etc."]) ]) @test out_plain == Markdown.plain(dollars) @test out_plain == Markdown.plain(backticks) @test out_rst == Markdown.rst(dollars) @test out_rst == Markdown.rst(backticks) @test out_latex == Markdown.latex(dollars) @test out_latex == Markdown.latex(backticks) @test latex_doc == dollars @test latex_doc == backticks end # Nested backticks for inline code and math. let t_1 = "`code` ``math`` ```code``` ````math```` `````code`````", t_2 = "`` `math` `` ``` `code` ``code`` ``` ```` `math` ``math`` ```math``` ````", t_3 = "`` ` `` ``` `` ` `` ` ` ```", t_4 = """`code over several lines` ``math over several lines`` ``math with ` some extra ` ` backticks` ``""", t_5 = "``code at end of string`", t_6 = "```math at end of string``" @test Markdown.parse(t_1) == MD(Paragraph([ Code("code"), " ", LaTeX("math"), " ", Code("code"), " ", LaTeX("math"), " ", Code("code"), ])) @test Markdown.parse(t_2) == MD(Paragraph([ LaTeX("`math`"), " ", Code("`code` ``code``"), " ", LaTeX("`math` ``math`` ```math```"), ])) @test Markdown.parse(t_3) == MD(Paragraph([ LaTeX("`"), " ", Code("`` ` `` ` `"), ])) @test Markdown.parse(t_4) == MD(Paragraph([ Code("code over several lines"), " ", LaTeX("math over several lines"), " ", LaTeX("math with ` some extra ` ` backticks`") ])) @test Markdown.parse(t_5) == MD(Paragraph([ "`", Code("code at end of string"), ])) @test Markdown.parse(t_6) == MD(Paragraph([ "`", LaTeX("math at end of string"), ])) end # Admonitions. let t_1 = """ # Foo !!! note !!! warning "custom title" ## Bar !!! danger "" !!! """, t_2 = """ !!! note foo bar baz !!! warning "custom title" - foo - bar - baz foo bar baz !!! danger "" ``` foo ``` bar # baz """, m_1 = Markdown.parse(t_1), m_2 = Markdown.parse(t_2) # Content Tests. @test isa(m_1.content[2], Markdown.Admonition) @test m_1.content[2].category == "note" @test m_1.content[2].title == "Note" @test m_1.content[2].content == [] @test isa(m_1.content[3], Markdown.Admonition) @test m_1.content[3].category == "warning" @test m_1.content[3].title == "custom title" @test m_1.content[3].content == [] @test isa(m_1.content[5], Markdown.Admonition) @test m_1.content[5].category == "danger" @test m_1.content[5].title == "" @test m_1.content[5].content == [] @test isa(m_1.content[6], Markdown.Paragraph) @test isa(m_2.content[1], Markdown.Admonition) @test m_2.content[1].category == "note" @test m_2.content[1].title == "Note" @test isa(m_2.content[1].content[1], Markdown.Paragraph) @test isa(m_2.content[2], Markdown.Admonition) @test m_2.content[2].category == "warning" @test m_2.content[2].title == "custom title" @test isa(m_2.content[2].content[1], Markdown.List) @test isa(m_2.content[2].content[2], Markdown.Paragraph) @test isa(m_2.content[3], Markdown.Admonition) @test m_2.content[3].category == "danger" @test m_2.content[3].title == "" @test isa(m_2.content[3].content[1], Markdown.Code) @test isa(m_2.content[3].content[2], Markdown.Code) @test isa(m_2.content[3].content[3], Markdown.Header{1}) # Rendering Tests. let out = Markdown.plain(m_1), expected = """ # Foo !!! note \n\n !!! warning "custom title" \n\n ## Bar !!! danger "" \n\n !!! """ @test out == expected end let out = Markdown.rst(m_1), expected = """ Foo *** \n .. note:: \n\n .. warning:: custom title \n\n Bar === \n .. danger:: \n\n !!! """ @test out == expected end let out = Markdown.latex(m_1), expected = """ \\section{Foo} \\begin{quote} \\textbf{note} Note \\end{quote} \\begin{quote} \\textbf{warning} custom title \\end{quote} \\subsection{Bar} \\begin{quote} \\textbf{danger} \n\n \\end{quote} !!! """ @test out == expected end let out = Markdown.html(m_1), expected = """

Foo

Note

custom title

Bar

!!!

""" @test out == expected end let out = Markdown.plain(m_2), expected = """ !!! note foo bar baz !!! warning "custom title" * foo * bar * baz foo bar baz !!! danger "" ``` foo ``` ``` bar ``` # baz """ @test out == expected end let out = Markdown.rst(m_2), expected = """ .. note:: foo bar baz .. warning:: custom title * foo * bar * baz foo bar baz .. danger:: .. code-block:: julia foo .. code-block:: julia bar baz *** """ @test out == expected end end # Nested Lists. let text = """ 1. A paragraph with two lines. indented code > A block quote. - one two - one two - baz + ``` foo ``` 1. foo 2. bar 3. baz """, md = Markdown.parse(text) # Content and structure tests. @test length(md.content) == 6 @test length(md.content[1].items) == 1 @test length(md.content[1].items[1]) == 3 @test isa(md.content[1].items[1][1], Markdown.Paragraph) @test isa(md.content[1].items[1][2], Markdown.Code) @test isa(md.content[1].items[1][3], Markdown.BlockQuote) @test length(md.content[2].items) == 1 @test isa(md.content[2].items[1][1], Markdown.Paragraph) @test isa(md.content[3], Markdown.Paragraph) @test length(md.content[4].items) == 1 @test isa(md.content[4].items[1][1], Paragraph) @test isa(md.content[4].items[1][2], Paragraph) @test length(md.content[5].items) == 2 @test isa(md.content[5].items[1][1], Markdown.Paragraph) @test isa(md.content[5].items[2][1], Markdown.Code) @test length(md.content[6].items) == 3 @test md.content[6].items[1][1].content[1] == "foo" @test md.content[6].items[2][1].content[1] == "bar" @test md.content[6].items[3][1].content[1] == "baz" # Rendering tests. let expected = """ 1. A paragraph with two lines. ``` indented code ``` > A block quote. * one two * one two * baz * ``` foo ``` 1. foo 2. bar 3. baz """ @test expected == Markdown.plain(md) end let expected = """
  1. A paragraph with two lines.

    indented code

    A block quote.

  • one

two

  • one

    two

  • baz

  • foo
  1. foo

  2. bar

  3. baz

""" @test expected == Markdown.html(md) end let expected = """ 1. A paragraph with two lines. .. code-block:: julia indented code A block quote. * one two * one two * baz * .. code-block:: julia foo 1. foo 2. bar 3. baz """ @test expected == Markdown.rst(md) end end # Ordered list starting number. let text = """ 42. foo 43. bar 1. foo 2. bar - foo - bar """, md = Markdown.parse(text) @test md.content[1].ordered == 42 @test md.content[2].ordered == 1 @test md.content[3].ordered == -1 let expected = """
  1. foo

  2. bar

  1. foo

  2. bar

  • foo

  • bar

""" @test expected == Markdown.html(md) end let expected = """ \\begin{itemize} \\item[42. ] foo \\item[43. ] bar \\end{itemize} \\begin{itemize} \\item[1. ] foo \\item[2. ] bar \\end{itemize} \\begin{itemize} \\item foo \\item bar \\end{itemize} """ @test expected == Markdown.latex(md) end end