ruby/test/rdoc/test_rdoc_markdown.rb
Hartley McGuire d0c17cbd09
Require space between hash/content in ATX heading (#1140)
While writing some Markdown documentation for Rails, I came across an
interesting case where trying to link to an instance method at the start
of a line would instead parse as an H1 heading:

```markdown
#response_body=
```

Expected:

```html
<a href=""><code>#response_body=</code></a>
```

Actual:

```html
<h1>response_body=</h1>
```

According to the CommonMark spec:

> At least one space or tab is required between the # characters and the
> heading’s contents, unless the heading is empty. Note that many
> implementations currently do not require the space. However, the space
> was required by the original ATX implementation, and it helps prevent
> things like the following from being parsed as headings:
>
> Example 64

So while some implementations do not follow this requirement, I believe
RDoc should because it makes it easy to write text similar to Example 64
(which was used in the new test) and it also enables automatically
linking to instance methods at the start of a line.
2024-07-18 09:40:01 +09:00

1122 lines
21 KiB
Ruby

# coding: UTF-8
# frozen_string_literal: true
require_relative 'helper'
require_relative '../../lib/rdoc/markup/block_quote'
require_relative '../../lib/rdoc/markdown'
class TestRDocMarkdown < RDoc::TestCase
def setup
super
@RM = RDoc::Markup
@parser = RDoc::Markdown.new
@to_html = RDoc::Markup::ToHtml.new(RDoc::Options.new, nil)
end
def test_class_parse
doc = RDoc::Markdown.parse "hello\n\nworld"
expected = doc(para("hello"), para("world"))
assert_equal expected, doc
end
def test_emphasis
assert_equal '_word_', @parser.emphasis('word')
assert_equal '<em>two words</em>', @parser.emphasis('two words')
assert_equal '<em>*bold*</em>', @parser.emphasis('*bold*')
end
def test_parse_auto_link_email
doc = parse "Autolink: <nobody-0+_./!%~$@example>"
expected = doc(para("Autolink: mailto:nobody-0+_./!%~$@example"))
assert_equal expected, doc
end
def test_parse_auto_link_url
doc = parse "Autolink: <http://example>"
expected = doc(para("Autolink: http://example"))
assert_equal expected, doc
end
def test_parse_block_quote
doc = parse <<-BLOCK_QUOTE
> this is
> a block quote
BLOCK_QUOTE
expected =
doc(
block(
para("this is\na block quote")))
assert_equal expected, doc
end
def test_parse_block_quote_continue
doc = parse <<-BLOCK_QUOTE
> this is
a block quote
BLOCK_QUOTE
expected =
doc(
block(
para("this is\na block quote")))
assert_equal expected, doc
end
def test_parse_block_quote_list
doc = parse <<-BLOCK_QUOTE
> text
>
> * one
> * two
BLOCK_QUOTE
expected =
doc(
block(
para("text"),
list(:BULLET,
item(nil, para("one")),
item(nil, para("two")))))
assert_equal expected, doc
end
def test_parse_block_quote_newline
doc = parse <<-BLOCK_QUOTE
> this is
a block quote
BLOCK_QUOTE
expected =
doc(
block(
para("this is\na block quote")))
assert_equal expected, doc
end
def test_parse_block_quote_separate
doc = parse <<-BLOCK_QUOTE
> this is
a block quote
> that continues
BLOCK_QUOTE
expected =
doc(
block(
para("this is\na block quote"),
para("that continues")))
assert_equal expected, doc
end
def test_parse_char_entity
doc = parse '&pi; &nn;'
expected = doc(para('π &nn;'))
assert_equal expected, doc
end
def test_parse_code
doc = parse "Code: `text`"
expected = doc(para("Code: <code>text</code>"))
assert_equal expected, doc
end
def test_parse_code_github
doc = <<-MD
Example:
```
code goes here
```
MD
expected =
doc(
para("Example:"),
verb("code goes here\n"))
assert_equal expected, parse(doc)
assert_equal expected, parse(doc.sub(/^\n/, ''))
@parser.github = false
expected =
doc(para("Example:"),
para("<code>\n""code goes here\n</code>"))
assert_equal expected, parse(doc)
expected =
doc(para("Example:\n<code>\n""code goes here\n</code>"))
assert_equal expected, parse(doc.sub(/^\n/, ''))
end
def test_parse_code_github_format
doc = <<-MD
Example:
``` ruby
code goes here
```
MD
code = verb("code goes here\n")
code.format = :ruby
expected =
doc(
para("Example:"),
code)
assert_equal expected, parse(doc)
assert_equal expected, parse(doc.sub(/^\n/, ''))
@parser.github = false
expected =
doc(para("Example:"),
para("<code>ruby\n""code goes here\n</code>"))
assert_equal expected, parse(doc)
expected =
doc(para("Example:\n<code>ruby\n""code goes here\n</code>"))
assert_equal expected, parse(doc.sub(/^\n/, ''))
end
def test_parse_definition_list
doc = parse <<-MD
one
: This is a definition
two
: This is another definition
MD
expected = doc(
list(:NOTE,
item(%w[one], para("This is a definition")),
item(%w[two], para("This is another definition"))))
assert_equal expected, doc
end
def test_parse_definition_list_indents
doc = parse <<-MD
zero
: Indented zero characters
one
: Indented one characters
two
: Indented two characters
three
: Indented three characters
four
: Indented four characters
MD
expected = doc(
list(:NOTE,
item(%w[zero], para("Indented zero characters")),
item(%w[one], para("Indented one characters")),
item(%w[two], para("Indented two characters")),
item(%w[three], para("Indented three characters"))),
para("four\n : Indented four characters"))
assert_equal expected, doc
end
def test_parse_definition_list_multi_description
doc = parse <<-MD
label
: This is a definition
: This is another definition
MD
expected = doc(
list(:NOTE,
item(%w[label], para("This is a definition")),
item(nil, para("This is another definition"))))
assert_equal expected, doc
end
def test_parse_definition_list_multi_label
doc = parse <<-MD
one
two
: This is a definition
MD
expected = doc(
list(:NOTE,
item(%w[one two], para("This is a definition"))))
assert_equal expected, doc
end
def test_parse_definition_list_multi_line
doc = parse <<-MD
one
: This is a definition
that extends to two lines
two
: This is another definition
that also extends to two lines
MD
expected = doc(
list(:NOTE,
item(%w[one],
para("This is a definition\nthat extends to two lines")),
item(%w[two],
para("This is another definition\nthat also extends to two lines"))))
assert_equal expected, doc
end
def test_parse_definition_list_rich_label
doc = parse <<-MD
`one`
: This is a definition
**two**
: This is another definition
MD
expected = doc(
list(:NOTE,
item(%w[<code>one</code>],
para("This is a definition")),
item(%w[*two*],
para("This is another definition"))))
assert_equal expected, doc
end
def test_parse_definition_list_no
@parser.definition_lists = false
doc = parse <<-MD
one
: This is a definition
two
: This is another definition
MD
expected = doc(
para("one\n: This is a definition"),
para("two\n: This is another definition"))
assert_equal expected, doc
end
def test_parse_entity_dec
doc = parse "Entity: &#65;"
expected = doc(para("Entity: A"))
assert_equal expected, doc
end
def test_parse_entity_hex
doc = parse "Entity: &#x41;"
expected = doc(para("Entity: A"))
assert_equal expected, doc
end
def test_parse_entity_named
doc = parse "Entity: &pi;"
expected = doc(para("Entity: π"))
assert_equal expected, doc
end
def test_parse_emphasis_star
doc = parse "it *works*\n"
expected = @RM::Document.new(
@RM::Paragraph.new("it _works_"))
assert_equal expected, doc
end
def test_parse_emphasis_underscore
doc = parse "it _works_\n"
expected =
doc(
para("it _works_"))
assert_equal expected, doc
end
def test_parse_emphasis_underscore_embedded
doc = parse "foo_bar bar_baz\n"
expected =
doc(
para("foo_bar bar_baz"))
assert_equal expected, doc
end
def test_parse_emphasis_underscore_in_word
doc = parse "it foo_bar_baz\n"
expected =
doc(
para("it foo_bar_baz"))
assert_equal expected, doc
end
def test_parse_escape
assert_equal doc(para("Backtick: `")), parse("Backtick: \\`")
assert_equal doc(para("Backslash: \\")), parse("Backslash: \\\\")
assert_equal doc(para("Colon: :")), parse("Colon: \\:")
end
def test_parse_heading_atx
# CommonMark Example 62
(1..6).each do |level|
doc = parse "#{"#" * level} heading\n"
expected = @RM::Document.new(
@RM::Heading.new(level, "heading"))
assert_equal expected, doc
end
# CommonMark Example 64
doc = parse "#5 bolt\n\n#hashtag\n"
expected = @RM::Document.new(
para("#5 bolt"),
para("#hashtag"),
)
assert_equal expected, doc
end
def test_parse_heading_setext_dash
doc = parse <<-MD
heading
---
MD
expected = @RM::Document.new(
@RM::Heading.new(2, "heading"))
assert_equal expected, doc
end
def test_parse_heading_setext_equals
doc = parse <<-MD
heading
===
MD
expected = @RM::Document.new(
@RM::Heading.new(1, "heading"))
assert_equal expected, doc
end
def test_parse_html
@parser.html = true
doc = parse "<address>Links here</address>\n"
expected = doc(
@RM::Raw.new("<address>Links here</address>"))
assert_equal expected, doc
end
def test_parse_html_hr
@parser.html = true
doc = parse "<hr>\n"
expected = doc(raw("<hr>"))
assert_equal expected, doc
end
def test_parse_html_no_html
@parser.html = false
doc = parse "<address>Links here</address>\n"
expected = doc()
assert_equal expected, doc
end
def test_parse_image
doc = parse "image ![alt text](path/to/image.jpg)"
expected = doc(para("image rdoc-image:path/to/image.jpg"))
assert_equal expected, doc
end
def test_parse_image_link
@parser.html = true
doc = parse "[![alt text](path/to/image.jpg)](http://example.com)"
expected =
doc(
para('{rdoc-image:path/to/image.jpg}[http://example.com]'))
assert_equal expected, doc
end
def test_parse_line_break
doc = parse "Some text \nwith extra lines"
expected = doc(
para("Some text", hard_break, "with extra lines"))
assert_equal expected, doc
end
def test_parse_link_reference_id
doc = parse <<-MD
This is [an example][id] reference-style link.
[id]: http://example.com "Optional Title Here"
MD
expected = doc(
para("This is {an example}[http://example.com] reference-style link."))
assert_equal expected, doc
end
def test_parse_link_reference_id_adjacent
doc = parse <<-MD
[this] [this] should work
[this]: example
MD
expected = doc(
para("{this}[example] should work"))
assert_equal expected, doc
end
def test_parse_link_reference_id_eof
doc = parse <<-MD.chomp
This is [an example][id] reference-style link.
[id]: http://example.com "Optional Title Here"
MD
expected = doc(
para("This is {an example}[http://example.com] reference-style link."))
assert_equal expected, doc
end
def test_parse_link_reference_id_many
doc = parse <<-MD
This is [an example][id] reference-style link.
And [another][id].
[id]: http://example.com "Optional Title Here"
MD
expected = doc(
para("This is {an example}[http://example.com] reference-style link."),
para("And {another}[http://example.com]."))
assert_equal expected, doc
end
def test_parse_link_reference_implicit
doc = parse <<-MD
This is [an example][] reference-style link.
[an example]: http://example.com "Optional Title Here"
MD
expected = doc(
para("This is {an example}[http://example.com] reference-style link."))
assert_equal expected, doc
end
def test_parse_list_bullet
doc = parse <<-MD
* one
* two
MD
expected = doc(
list(:BULLET,
item(nil, para("one")),
item(nil, para("two"))))
assert_equal expected, doc
end
def test_parse_list_bullet_auto_link
doc = parse <<-MD
* <http://example/>
MD
expected = doc(
list(:BULLET,
item(nil, para("http://example/"))))
assert_equal expected, doc
end
def test_parse_list_bullet_continue
doc = parse <<-MD
* one
* two
MD
expected = doc(
list(:BULLET,
item(nil, para("one")),
item(nil, para("two"))))
assert_equal expected, doc
end
def test_parse_list_bullet_multiline
doc = parse <<-MD
* one
two
MD
expected = doc(
list(:BULLET,
item(nil, para("one\n two"))))
assert_equal expected, doc
end
def test_parse_list_bullet_nest
doc = parse <<-MD
* outer
* inner
MD
expected = doc(
list(:BULLET,
item(nil,
para("outer"),
list(:BULLET,
item(nil,
para("inner"))))))
assert_equal expected, doc
end
def test_parse_list_bullet_nest_loose
doc = parse <<-MD
* outer
* inner
MD
expected = doc(
list(:BULLET,
item(nil,
para("outer"),
list(:BULLET,
item(nil, para("inner"))))))
assert_equal expected, doc
end
def test_parse_list_bullet_nest_continue
doc = parse <<-MD
* outer
* inner
continue inner
* outer 2
MD
expected = doc(
list(:BULLET,
item(nil,
para("outer"),
list(:BULLET,
item(nil,
para("inner\n continue inner")))),
item(nil,
para("outer 2"))))
assert_equal expected, doc
end
def test_parse_list_number
doc = parse <<-MD
1. one
1. two
MD
expected = doc(
list(:NUMBER,
item(nil, para("one")),
item(nil, para("two"))))
assert_equal expected, doc
end
def test_parse_list_number_continue
doc = parse <<-MD
1. one
1. two
MD
expected = doc(
list(:NUMBER,
item(nil, para("one")),
item(nil, para("two"))))
assert_equal expected, doc
end
def test_parse_note
@parser.notes = true
doc = parse <<-MD
Some text.[^1]
[^1]: With a footnote
MD
expected = doc(
para("Some text.{*1}[rdoc-label:foottext-1:footmark-1]"),
@RM::Rule.new(1),
para("{^1}[rdoc-label:footmark-1:foottext-1] With a footnote"))
assert_equal expected, doc
end
def test_parse_note_indent
@parser.notes = true
doc = parse <<-MD
Some text.[^1]
[^1]: With a footnote
more
MD
expected = doc(
para("Some text.{*1}[rdoc-label:foottext-1:footmark-1]"),
rule(1),
para("{^1}[rdoc-label:footmark-1:foottext-1] With a footnote\n\nmore"))
assert_equal expected, doc
end
def test_parse_note_inline
@parser.notes = true
doc = parse <<-MD
Some text. ^[With a footnote]
MD
expected = doc(
para("Some text. {*1}[rdoc-label:foottext-1:footmark-1]"),
@RM::Rule.new(1),
para("{^1}[rdoc-label:footmark-1:foottext-1] With a footnote"))
assert_equal expected, doc
end
def test_parse_note_no_notes
@parser.notes = false
assert_raise RDoc::Markdown::ParseError do
parse "Some text.[^1]"
end
end
def test_parse_note_multiple
@parser.notes = true
doc = parse <<-MD
Some text[^1]
with inline notes^[like this]
and an extra note.[^2]
[^1]: With a footnote
[^2]: Which should be numbered correctly
MD
expected = doc(
para("Some text{*1}[rdoc-label:foottext-1:footmark-1]\n" +
"with inline notes{*2}[rdoc-label:foottext-2:footmark-2]\n" +
"and an extra note.{*3}[rdoc-label:foottext-3:footmark-3]"),
rule(1),
para("{^1}[rdoc-label:footmark-1:foottext-1] With a footnote"),
para("{^2}[rdoc-label:footmark-2:foottext-2] like this"),
para("{^3}[rdoc-label:footmark-3:foottext-3] " +
"Which should be numbered correctly"))
assert_equal expected, doc
end
def test_parse_paragraph
doc = parse "it worked\n"
expected = doc(para("it worked"))
assert_equal expected, doc
end
def test_parse_paragraph_break_on_newline
@parser.break_on_newline = true
doc = parse "one\ntwo\n"
expected = doc(para("one", hard_break, "two"))
assert_equal expected, doc
doc = parse "one \ntwo\nthree\n"
expected = doc(para("one", hard_break, "two", hard_break, "three"))
assert_equal expected, doc
end
def test_parse_paragraph_stars
doc = parse "it worked ****\n"
expected = @RM::Document.new(
@RM::Paragraph.new("it worked ****"))
assert_equal expected, doc
end
def test_parse_paragraph_html
@parser.html = true
doc = parse "<address>Links here</address>"
expected = doc(raw("<address>Links here</address>"))
assert_equal expected, doc
end
def test_parse_paragraph_html_no_html
@parser.html = false
doc = parse "<address>Links here</address>"
expected = doc()
assert_equal expected, doc
end
def test_parse_paragraph_indent_one
doc = parse <<-MD
text
MD
expected = doc(para("text"))
assert_equal expected, doc
end
def test_parse_paragraph_indent_two
doc = parse <<-MD
text
MD
expected = doc(para("text"))
assert_equal expected, doc
end
def test_parse_paragraph_indent_three
doc = parse <<-MD
text
MD
expected = doc(para("text"))
assert_equal expected, doc
end
def test_parse_paragraph_multiline
doc = parse "one\ntwo"
expected = doc(para("one\ntwo"))
assert_equal expected, doc
end
def test_parse_paragraph_two
doc = parse "one\n\ntwo"
expected = @RM::Document.new(
@RM::Paragraph.new("one"),
@RM::Paragraph.new("two"))
assert_equal expected, doc
end
def test_parse_plain
doc = parse "it worked"
expected = @RM::Document.new(
@RM::Paragraph.new("it worked"))
assert_equal expected, doc
end
def test_parse_reference_link_embedded_bracket
doc = parse "With [embedded [brackets]] [b].\n\n[b]: /url/\n"
expected =
doc(
para("With {embedded [brackets]}[/url/]."))
assert_equal expected, doc
end
def test_parse_rule_dash
doc = parse "- - -\n\n"
expected = @RM::Document.new(@RM::Rule.new(1))
assert_equal expected, doc
end
def test_parse_rule_underscore
doc = parse "_ _ _\n\n"
expected = @RM::Document.new(@RM::Rule.new(1))
assert_equal expected, doc
end
def test_parse_rule_star
doc = parse "* * *\n\n"
expected = @RM::Document.new(@RM::Rule.new(1))
assert_equal expected, doc
end
def test_parse_strong_star
doc = parse "it **works**\n"
expected = @RM::Document.new(
@RM::Paragraph.new("it *works*"))
assert_equal expected, doc
end
def test_parse_strong_underscore
doc = parse "it __works__\n"
expected = @RM::Document.new(
@RM::Paragraph.new("it *works*"))
assert_equal expected, doc
end
def test_parse_strong_emphasis_star
doc = parse "it ***works***\n"
expected = @RM::Document.new(
@RM::Paragraph.new("it <b>_works_</b>"))
assert_equal expected, doc
end
def test_parse_strong_emphasis_underscore
doc = parse "it ___works___\n"
expected = @RM::Document.new(
@RM::Paragraph.new("it <b>_works_</b>"))
assert_equal expected, doc
end
def test_parse_strike_tilde
doc = parse "it ~~works~~\n"
expected = @RM::Document.new(
@RM::Paragraph.new("it ~works~"))
assert_equal expected, doc
end
def test_parse_strike_words_tilde
doc = parse "it ~~works fine~~\n"
expected = @RM::Document.new(
@RM::Paragraph.new("it <s>works fine</s>"))
assert_equal expected, doc
end
def test_parse_strike_tilde_no
@parser.strike = false
doc = parse "it ~~works fine~~\n"
expected = @RM::Document.new(
@RM::Paragraph.new("it ~~works fine~~"))
assert_equal expected, doc
end
def test_parse_style
@parser.css = true
doc = parse "<style>h1 { color: red }</style>\n"
expected = doc(
@RM::Raw.new("<style>h1 { color: red }</style>"))
assert_equal expected, doc
end
def test_parse_style_disabled
doc = parse "<style>h1 { color: red }</style>\n"
expected = doc()
assert_equal expected, doc
end
def test_parse_verbatim
doc = parse <<-MD
text
MD
expected = doc(verb("text\n"))
assert_equal expected, doc
end
def test_parse_verbatim_eof
doc = parse " text"
expected = doc(verb("text\n"))
assert_equal expected, doc
end
def test_strong
assert_equal '*word*', @parser.strong('word')
assert_equal '<b>two words</b>', @parser.strong('two words')
assert_equal '<b>_emphasis_</b>', @parser.strong('_emphasis_')
end
def test_code_fence_with_unintended_array
doc = parse '```<ruby>```'
expected = doc(verb('<ruby>'))
assert_equal expected, doc
end
def test_gfm_table
doc = parse <<~MD
| | |compare-ruby|built-ruby|
|------|:----------------|-----------:|---------:|
|test | 1 | 11.618M| 10.757M|
| | | 1.08x| -|
|test | 10 | 1.849M| 1.723M|
| | | 1.07x| -|
MD
head = ["", "", "compare-ruby", "built-ruby"]
align = [nil, :left, :right, :right]
body = [
["test", "1", "11.618M", "10.757M"],
["", "", "1.08x", "-"],
["test", "10", "1.849M", "1.723M"],
["", "", "1.07x", "-"],
]
expected = doc(@RM::Table.new(head, align, body))
assert_equal expected, doc
end
def test_gfm_table_2
doc = parse <<~'MD'
| Cmd | Returns | Meaning
----- | :-----: | -------
|"b" | boolean | True if file1 is a block device
"c" | boolean | True if file1 is a character device
|"\|" | boolean | escaped bar \| test
MD
head = %w[Cmd Returns Meaning]
align = [nil, :center, nil]
body = [
['"b"', 'boolean', 'True if file1 is a block device'],
['"c"', 'boolean', 'True if file1 is a character device'],
['"|"', 'boolean', 'escaped bar | test'],
]
expected = doc(@RM::Table.new(head, align, body))
assert_equal expected, doc
end
def parse text
@parser.parse text
end
end