Testing Rich Markdown Rendering Implementation in Textualize/rich
This test suite validates Rich’s Markdown rendering capabilities by verifying text formatting, layout structures, and styling features. It ensures proper handling of various Markdown elements including headings, lists, code blocks, and tables.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
textualize/rich
tests/test_markdown.py
# coding=utf-8
MARKDOWN = """Heading
=======
Sub-heading
-----------
### Heading
#### H4 Heading
##### H5 Heading
###### H6 Heading
Paragraphs are separated
by a blank line.
Two spaces at the end of a line
produces a line break.
Text attributes _italic_,
**bold**, `monospace`.
Horizontal rule:
---
Bullet list:
* apples
* oranges
* pears
Numbered list:
1. lather
2. rinse
3. repeat
An [example](http://example.com).
> Markdown uses email-style > characters for blockquoting.
>
> Lorem ipsum

```
a=1
```
```python
import this
```
```somelang
foobar
```
import this
1. List item
Code block
"""
import io
import re
from rich.console import Console, RenderableType
from rich.markdown import Markdown
re_link_ids = re.compile(r"id=[\d\.\-]*?;.*?\x1b")
def replace_link_ids(render: str) -> str:
"""Link IDs have a random ID and system path which is a problem for
reproducible tests.
"""
return re_link_ids.sub("id=0;foo\x1b", render)
def render(renderable: RenderableType) -> str:
console = Console(
width=100, file=io.StringIO(), color_system="truecolor", legacy_windows=False
)
console.print(renderable)
output = replace_link_ids(console.file.getvalue())
print(repr(output))
return output
def test_markdown_render():
markdown = Markdown(MARKDOWN)
rendered_markdown = render(markdown)
expected = "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ \x1b[1mHeading\x1b[0m ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
\x1b[1;4mSub-heading\x1b[0m
\x1b[1mHeading\x1b[0m
\x1b[1;2mH4 Heading\x1b[0m
\x1b[4mH5 Heading\x1b[0m
\x1b[3mH6 Heading\x1b[0m
Paragraphs are separated by a blank line.
Two spaces at the end of a line produces a line break.
Text attributes \x1b[3mitalic\x1b[0m, \x1b[1mbold\x1b[0m, \x1b[1;36;40mmonospace\x1b[0m.
Horizontal rule:
\x1b[33m────────────────────────────────────────────────────────────────────────────────────────────────────\x1b[0m
Bullet list:
\x1b[1;33m • \x1b[0mapples
\x1b[1;33m • \x1b[0moranges
\x1b[1;33m • \x1b[0mpears
Numbered list:
\x1b[1;33m 1 \x1b[0mlather
\x1b[1;33m 2 \x1b[0mrinse
\x1b[1;33m 3 \x1b[0mrepeat
An \x1b]8;id=0;foo\x1b\\\x1b[4;34mexample\x1b[0m\x1b]8;;\x1b\\.
\x1b[35m▌ \x1b[0m\x1b[35mMarkdown uses email-style > characters for blockquoting.\x1b[0m\x1b[35m \x1b[0m
\x1b[35m▌ \x1b[0m\x1b[35mLorem ipsum\x1b[0m\x1b[35m \x1b[0m
🌆 \x1b]8;id=0;foo\x1b\\progress\x1b]8;;\x1b\\
\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34ma=1\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m\x1b[38;2;255;70;137;48;2;39;40;34mimport\x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mthis\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mfoobar\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mimport this\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\x1b[48;2;39;40;34m \x1b[0m
\x1b[48;2;39;40;34m \x1b[0m
\x1b[1;33m 1 \x1b[0mList item
\x1b[1;33m \x1b[0m\x1b[48;2;39;40;34m \x1b[0m
\x1b[1;33m \x1b[0m\x1b[48;2;39;40;34m \x1b[0m\x1b[38;2;248;248;242;48;2;39;40;34mCode block\x1b[0m\x1b[48;2;39;40;34m \x1b[0m\x1b[48;2;39;40;34m \x1b[0m
\x1b[1;33m \x1b[0m\x1b[48;2;39;40;34m \x1b[0m
"
assert rendered_markdown == expected
def test_inline_code():
markdown = Markdown(
"inline `import this` code",
inline_code_lexer="python",
inline_code_theme="emacs",
)
result = render(markdown)
expected = "inline \x1b[1;38;2;170;34;255;48;2;248;248;248mimport\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;0;255;48;2;248;248;248mthis\x1b[0m code
"
print(result)
print(repr(result))
assert result == expected
def test_markdown_table():
markdown = Markdown(
"""\
| Year | Title | Director | Box Office (USD) |
|------|:------------------------------------------------:|:------------------|------------------:|
| 1982 | *E.T. the Extra-Terrestrial* | Steven Spielberg | $792.9 million |
| 1980 | Star Wars: Episode V – The Empire Strikes Back | Irvin Kershner | $538.4 million |
| 1983 | Star Wars: Episode VI – Return of the Jedi | Richard Marquand | $475.1 million |
| 1981 | Raiders of the Lost Ark | Steven Spielberg | $389.9 million |
| 1984 | Indiana Jones and the Temple of Doom | Steven Spielberg | $333.1 million |
"""
)
result = render(markdown)
expected = "
\x1b[1m \x1b[0m\x1b[1mYear\x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1m Title \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1mDirector \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1mBox Office (USD)\x1b[0m\x1b[1m \x1b[0m
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1982 \x1b[3mE.T. the Extra-Terrestrial\x1b[0m Steven Spielberg $792.9 million
1980 Star Wars: Episode V – The Empire Strikes Back Irvin Kershner $538.4 million
1983 Star Wars: Episode VI – Return of the Jedi Richard Marquand $475.1 million
1981 Raiders of the Lost Ark Steven Spielberg $389.9 million
1984 Indiana Jones and the Temple of Doom Steven Spielberg $333.1 million
"
assert result == expected
def test_inline_styles_in_table():
"""Regression test for https://github.com/Textualize/rich/issues/3115"""
markdown = Markdown(
"""\
| Year | This **column** displays _the_ movie _title_ ~~description~~ | Director | Box Office (USD) |
|------|:----------------------------------------------------------:|:------------------|------------------:|
| 1982 | *E.T. the Extra-Terrestrial* ([Wikipedia article](https://en.wikipedia.org/wiki/E.T._the_Extra-Terrestrial)) | Steven Spielberg | $792.9 million |
| 1980 | Star Wars: Episode V – The *Empire* **Strikes** ~~Back~~ | Irvin Kershner | $538.4 million |
"""
)
result = render(markdown)
expected = "
\x1b[1m \x1b[0m\x1b[1mYear\x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1mThis \x1b[0m\x1b[1mcolumn\x1b[0m\x1b[1m displays \x1b[0m\x1b[1;3mthe\x1b[0m\x1b[1m movie \x1b[0m\x1b[1;3mtitle\x1b[0m\x1b[1m \x1b[0m\x1b[1;9mdescription\x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1mDirector \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1mBox Office (USD)\x1b[0m\x1b[1m \x1b[0m
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1982 \x1b[3mE.T. the Extra-Terrestrial\x1b[0m (\x1b]8;id=0;foo\x1b\\\x1b[4;34mWikipedia article\x1b[0m\x1b]8;;\x1b\\) Steven Spielberg $792.9 million
1980 Star Wars: Episode V – The \x1b[3mEmpire\x1b[0m \x1b[1mStrikes\x1b[0m \x1b[9mBack\x1b[0m Irvin Kershner $538.4 million
"
assert result == expected
def test_inline_styles_with_justification():
"""Regression test for https://github.com/Textualize/rich/issues/3115
In particular, this tests the interaction between the change that was made to fix
#3115 and column text justification.
"""
markdown = Markdown(
"""\
| left | center | right |
| :- | :-: | -: |
| This is a long row | because it contains | a fairly long sentence. |
| a*b* _c_ ~~d~~ e | a*b* _c_ ~~d~~ e | a*b* _c_ ~~d~~ e |"""
)
result = render(markdown)
expected = "
\x1b[1m \x1b[0m\x1b[1mleft \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1m center \x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1m right\x1b[0m\x1b[1m \x1b[0m
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
This is a long row because it contains a fairly long sentence.
a\x1b[3mb\x1b[0m \x1b[3mc\x1b[0m \x1b[9md\x1b[0m e a\x1b[3mb\x1b[0m \x1b[3mc\x1b[0m \x1b[9md\x1b[0m e a\x1b[3mb\x1b[0m \x1b[3mc\x1b[0m \x1b[9md\x1b[0m e
"
assert result == expected
def test_partial_table():
markdown = Markdown("| Simple | Table |
| ------ | ----- ")
result = render(markdown)
print(repr(result))
expected = "
\x1b[1m \x1b[0m\x1b[1mSimple\x1b[0m\x1b[1m \x1b[0m \x1b[1m \x1b[0m\x1b[1mTable\x1b[0m\x1b[1m \x1b[0m
━━━━━━━━━━━━━━━━
"
assert result == expected
def test_table_with_empty_cells() -> None:
"""Test a table with empty cells is rendered without extra newlines above.
Regression test for #3027 https://github.com/Textualize/rich/issues/3027
"""
complete_table = Markdown(
"""\
| First Header | Second Header |
| ------------- | ------------- |
| Content Cell | Content Cell |
| Content Cell | Content Cell |
"""
)
table_with_empty_cells = Markdown(
"""\
| First Header | |
| ------------- | ------------- |
| Content Cell | Content Cell |
| | Content Cell |
"""
)
result = len(render(table_with_empty_cells).splitlines())
expected = len(render(complete_table).splitlines())
assert result == expected
if __name__ == "__main__":
markdown = Markdown(MARKDOWN)
rendered = render(markdown)
print(rendered)
print(repr(rendered))