diff options
Diffstat (limited to 'tests/test_syntax')
29 files changed, 9967 insertions, 0 deletions
diff --git a/tests/test_syntax/__init__.py b/tests/test_syntax/__init__.py new file mode 100644 index 0000000..564ba3b --- /dev/null +++ b/tests/test_syntax/__init__.py @@ -0,0 +1,20 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" diff --git a/tests/test_syntax/blocks/__init__.py b/tests/test_syntax/blocks/__init__.py new file mode 100644 index 0000000..564ba3b --- /dev/null +++ b/tests/test_syntax/blocks/__init__.py @@ -0,0 +1,20 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" diff --git a/tests/test_syntax/blocks/test_blockquotes.py b/tests/test_syntax/blocks/test_blockquotes.py new file mode 100644 index 0000000..3422f9f --- /dev/null +++ b/tests/test_syntax/blocks/test_blockquotes.py @@ -0,0 +1,51 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2020 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase, recursionlimit + + +class TestBlockquoteBlocks(TestCase): + + # TODO: Move legacy tests here + + def test_nesting_limit(self): + # Test that the nesting limit is within 100 levels of recursion limit. Future code changes could cause the + # recursion limit to need adjusted here. We need to account for all of Markdown's internal calls. Finally, we + # need to account for the 100 level cushion which we are testing. + with recursionlimit(120): + self.assertMarkdownRenders( + '>>>>>>>>>>', + self.dedent( + """ + <blockquote> + <blockquote> + <blockquote> + <blockquote> + <blockquote> + <p>>>>>></p> + </blockquote> + </blockquote> + </blockquote> + </blockquote> + </blockquote> + """ + ) + ) diff --git a/tests/test_syntax/blocks/test_code_blocks.py b/tests/test_syntax/blocks/test_code_blocks.py new file mode 100644 index 0000000..54b6860 --- /dev/null +++ b/tests/test_syntax/blocks/test_code_blocks.py @@ -0,0 +1,88 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestCodeBlocks(TestCase): + + def test_spaced_codeblock(self): + self.assertMarkdownRenders( + ' # A code block.', + + self.dedent( + """ + <pre><code># A code block. + </code></pre> + """ + ) + ) + + def test_tabbed_codeblock(self): + self.assertMarkdownRenders( + '\t# A code block.', + + self.dedent( + """ + <pre><code># A code block. + </code></pre> + """ + ) + ) + + def test_multiline_codeblock(self): + self.assertMarkdownRenders( + ' # Line 1\n # Line 2\n', + + self.dedent( + """ + <pre><code># Line 1 + # Line 2 + </code></pre> + """ + ) + ) + + def test_codeblock_with_blankline(self): + self.assertMarkdownRenders( + ' # Line 1\n\n # Line 2\n', + + self.dedent( + """ + <pre><code># Line 1 + + # Line 2 + </code></pre> + """ + ) + ) + + def test_codeblock_escape(self): + self.assertMarkdownRenders( + ' <foo & bar>', + + self.dedent( + """ + <pre><code><foo & bar> + </code></pre> + """ + ) + ) diff --git a/tests/test_syntax/blocks/test_headers.py b/tests/test_syntax/blocks/test_headers.py new file mode 100644 index 0000000..ca065a5 --- /dev/null +++ b/tests/test_syntax/blocks/test_headers.py @@ -0,0 +1,729 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +import unittest +from markdown.test_tools import TestCase + + +class TestSetextHeaders(TestCase): + + def test_setext_h1(self): + self.assertMarkdownRenders( + self.dedent( + """ + This is an H1 + ============= + """ + ), + + '<h1>This is an H1</h1>' + ) + + def test_setext_h2(self): + self.assertMarkdownRenders( + self.dedent( + """ + This is an H2 + ------------- + """ + ), + + '<h2>This is an H2</h2>' + ) + + def test_setext_h1_mismatched_length(self): + self.assertMarkdownRenders( + self.dedent( + """ + This is an H1 + === + """ + ), + + '<h1>This is an H1</h1>' + ) + + def test_setext_h2_mismatched_length(self): + self.assertMarkdownRenders( + self.dedent( + """ + This is an H2 + --- + """ + ), + + '<h2>This is an H2</h2>' + ) + + def test_setext_h1_followed_by_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + This is an H1 + ============= + Followed by a Paragraph with no blank line. + """ + ), + self.dedent( + """ + <h1>This is an H1</h1> + <p>Followed by a Paragraph with no blank line.</p> + """ + ) + ) + + def test_setext_h2_followed_by_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + This is an H2 + ------------- + Followed by a Paragraph with no blank line. + """ + ), + self.dedent( + """ + <h2>This is an H2</h2> + <p>Followed by a Paragraph with no blank line.</p> + """ + ) + ) + + # TODO: fix this + # see https://johnmacfarlane.net/babelmark2/?normalize=1&text=Paragraph%0AAn+H1%0A%3D%3D%3D%3D%3D + @unittest.skip('This is broken in Python-Markdown') + def test_p_followed_by_setext_h1(self): + self.assertMarkdownRenders( + self.dedent( + """ + This is a Paragraph. + Followed by an H1 with no blank line. + ===================================== + """ + ), + self.dedent( + """ + <p>This is a Paragraph.</p> + <h1>Followed by an H1 with no blank line.</h1> + """ + ) + ) + + # TODO: fix this + # see https://johnmacfarlane.net/babelmark2/?normalize=1&text=Paragraph%0AAn+H2%0A----- + @unittest.skip('This is broken in Python-Markdown') + def test_p_followed_by_setext_h2(self): + self.assertMarkdownRenders( + self.dedent( + """ + This is a Paragraph. + Followed by an H2 with no blank line. + ------------------------------------- + """ + ), + self.dedent( + """ + <p>This is a Paragraph.</p> + <h2>Followed by an H2 with no blank line.</h2> + """ + ) + ) + + +class TestHashHeaders(TestCase): + + def test_hash_h1_open(self): + self.assertMarkdownRenders( + '# This is an H1', + + '<h1>This is an H1</h1>' + ) + + def test_hash_h2_open(self): + self.assertMarkdownRenders( + '## This is an H2', + + '<h2>This is an H2</h2>' + ) + + def test_hash_h3_open(self): + self.assertMarkdownRenders( + '### This is an H3', + + '<h3>This is an H3</h3>' + ) + + def test_hash_h4_open(self): + self.assertMarkdownRenders( + '#### This is an H4', + + '<h4>This is an H4</h4>' + ) + + def test_hash_h5_open(self): + self.assertMarkdownRenders( + '##### This is an H5', + + '<h5>This is an H5</h5>' + ) + + def test_hash_h6_open(self): + self.assertMarkdownRenders( + '###### This is an H6', + + '<h6>This is an H6</h6>' + ) + + def test_hash_gt6_open(self): + self.assertMarkdownRenders( + '####### This is an H6', + + '<h6># This is an H6</h6>' + ) + + def test_hash_h1_open_missing_space(self): + self.assertMarkdownRenders( + '#This is an H1', + + '<h1>This is an H1</h1>' + ) + + def test_hash_h2_open_missing_space(self): + self.assertMarkdownRenders( + '##This is an H2', + + '<h2>This is an H2</h2>' + ) + + def test_hash_h3_open_missing_space(self): + self.assertMarkdownRenders( + '###This is an H3', + + '<h3>This is an H3</h3>' + ) + + def test_hash_h4_open_missing_space(self): + self.assertMarkdownRenders( + '####This is an H4', + + '<h4>This is an H4</h4>' + ) + + def test_hash_h5_open_missing_space(self): + self.assertMarkdownRenders( + '#####This is an H5', + + '<h5>This is an H5</h5>' + ) + + def test_hash_h6_open_missing_space(self): + self.assertMarkdownRenders( + '######This is an H6', + + '<h6>This is an H6</h6>' + ) + + def test_hash_gt6_open_missing_space(self): + self.assertMarkdownRenders( + '#######This is an H6', + + '<h6>#This is an H6</h6>' + ) + + def test_hash_h1_closed(self): + self.assertMarkdownRenders( + '# This is an H1 #', + + '<h1>This is an H1</h1>' + ) + + def test_hash_h2_closed(self): + self.assertMarkdownRenders( + '## This is an H2 ##', + + '<h2>This is an H2</h2>' + ) + + def test_hash_h3_closed(self): + self.assertMarkdownRenders( + '### This is an H3 ###', + + '<h3>This is an H3</h3>' + ) + + def test_hash_h4_closed(self): + self.assertMarkdownRenders( + '#### This is an H4 ####', + + '<h4>This is an H4</h4>' + ) + + def test_hash_h5_closed(self): + self.assertMarkdownRenders( + '##### This is an H5 #####', + + '<h5>This is an H5</h5>' + ) + + def test_hash_h6_closed(self): + self.assertMarkdownRenders( + '###### This is an H6 ######', + + '<h6>This is an H6</h6>' + ) + + def test_hash_gt6_closed(self): + self.assertMarkdownRenders( + '####### This is an H6 #######', + + '<h6># This is an H6</h6>' + ) + + def test_hash_h1_closed_missing_space(self): + self.assertMarkdownRenders( + '#This is an H1#', + + '<h1>This is an H1</h1>' + ) + + def test_hash_h2_closed_missing_space(self): + self.assertMarkdownRenders( + '##This is an H2##', + + '<h2>This is an H2</h2>' + ) + + def test_hash_h3_closed_missing_space(self): + self.assertMarkdownRenders( + '###This is an H3###', + + '<h3>This is an H3</h3>' + ) + + def test_hash_h4_closed_missing_space(self): + self.assertMarkdownRenders( + '####This is an H4####', + + '<h4>This is an H4</h4>' + ) + + def test_hash_h5_closed_missing_space(self): + self.assertMarkdownRenders( + '#####This is an H5#####', + + '<h5>This is an H5</h5>' + ) + + def test_hash_h6_closed_missing_space(self): + self.assertMarkdownRenders( + '######This is an H6######', + + '<h6>This is an H6</h6>' + ) + + def test_hash_gt6_closed_missing_space(self): + self.assertMarkdownRenders( + '#######This is an H6#######', + + '<h6>#This is an H6</h6>' + ) + + def test_hash_h1_closed_mismatch(self): + self.assertMarkdownRenders( + '# This is an H1 ##', + + '<h1>This is an H1</h1>' + ) + + def test_hash_h2_closed_mismatch(self): + self.assertMarkdownRenders( + '## This is an H2 #', + + '<h2>This is an H2</h2>' + ) + + def test_hash_h3_closed_mismatch(self): + self.assertMarkdownRenders( + '### This is an H3 #', + + '<h3>This is an H3</h3>' + ) + + def test_hash_h4_closed_mismatch(self): + self.assertMarkdownRenders( + '#### This is an H4 #', + + '<h4>This is an H4</h4>' + ) + + def test_hash_h5_closed_mismatch(self): + self.assertMarkdownRenders( + '##### This is an H5 #', + + '<h5>This is an H5</h5>' + ) + + def test_hash_h6_closed_mismatch(self): + self.assertMarkdownRenders( + '###### This is an H6 #', + + '<h6>This is an H6</h6>' + ) + + def test_hash_gt6_closed_mismatch(self): + self.assertMarkdownRenders( + '####### This is an H6 ##################', + + '<h6># This is an H6</h6>' + ) + + def test_hash_h1_followed_by_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + # This is an H1 + Followed by a Paragraph with no blank line. + """ + ), + self.dedent( + """ + <h1>This is an H1</h1> + <p>Followed by a Paragraph with no blank line.</p> + """ + ) + ) + + def test_hash_h2_followed_by_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + ## This is an H2 + Followed by a Paragraph with no blank line. + """ + ), + self.dedent( + """ + <h2>This is an H2</h2> + <p>Followed by a Paragraph with no blank line.</p> + """ + ) + ) + + def test_hash_h3_followed_by_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + ### This is an H3 + Followed by a Paragraph with no blank line. + """ + ), + self.dedent( + """ + <h3>This is an H3</h3> + <p>Followed by a Paragraph with no blank line.</p> + """ + ) + ) + + def test_hash_h4_followed_by_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + #### This is an H4 + Followed by a Paragraph with no blank line. + """ + ), + self.dedent( + """ + <h4>This is an H4</h4> + <p>Followed by a Paragraph with no blank line.</p> + """ + ) + ) + + def test_hash_h5_followed_by_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + ##### This is an H5 + Followed by a Paragraph with no blank line. + """ + ), + self.dedent( + """ + <h5>This is an H5</h5> + <p>Followed by a Paragraph with no blank line.</p> + """ + ) + ) + + def test_hash_h6_followed_by_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + ###### This is an H6 + Followed by a Paragraph with no blank line. + """ + ), + self.dedent( + """ + <h6>This is an H6</h6> + <p>Followed by a Paragraph with no blank line.</p> + """ + ) + ) + + def test_hash_h1_leading_space(self): + self.assertMarkdownRenders( + ' # This is an H1', + + '<p># This is an H1</p>' + ) + + def test_hash_h2_leading_space(self): + self.assertMarkdownRenders( + ' ## This is an H2', + + '<p>## This is an H2</p>' + ) + + def test_hash_h3_leading_space(self): + self.assertMarkdownRenders( + ' ### This is an H3', + + '<p>### This is an H3</p>' + ) + + def test_hash_h4_leading_space(self): + self.assertMarkdownRenders( + ' #### This is an H4', + + '<p>#### This is an H4</p>' + ) + + def test_hash_h5_leading_space(self): + self.assertMarkdownRenders( + ' ##### This is an H5', + + '<p>##### This is an H5</p>' + ) + + def test_hash_h6_leading_space(self): + self.assertMarkdownRenders( + ' ###### This is an H6', + + '<p>###### This is an H6</p>' + ) + + def test_hash_h1_open_trailing_space(self): + self.assertMarkdownRenders( + '# This is an H1 ', + + '<h1>This is an H1</h1>' + ) + + def test_hash_h2_open_trailing_space(self): + self.assertMarkdownRenders( + '## This is an H2 ', + + '<h2>This is an H2</h2>' + ) + + def test_hash_h3_open_trailing_space(self): + self.assertMarkdownRenders( + '### This is an H3 ', + + '<h3>This is an H3</h3>' + ) + + def test_hash_h4_open_trailing_space(self): + self.assertMarkdownRenders( + '#### This is an H4 ', + + '<h4>This is an H4</h4>' + ) + + def test_hash_h5_open_trailing_space(self): + self.assertMarkdownRenders( + '##### This is an H5 ', + + '<h5>This is an H5</h5>' + ) + + def test_hash_h6_open_trailing_space(self): + self.assertMarkdownRenders( + '###### This is an H6 ', + + '<h6>This is an H6</h6>' + ) + + def test_hash_gt6_open_trailing_space(self): + self.assertMarkdownRenders( + '####### This is an H6 ', + + '<h6># This is an H6</h6>' + ) + + # TODO: Possibly change the following behavior. While this follows the behavior + # of markdown.pl, it is rather uncommon and not necessarily intuitive. + # See: https://johnmacfarlane.net/babelmark2/?normalize=1&text=%23+This+is+an+H1+%23+ + def test_hash_h1_closed_trailing_space(self): + self.assertMarkdownRenders( + '# This is an H1 # ', + + '<h1>This is an H1 #</h1>' + ) + + def test_hash_h2_closed_trailing_space(self): + self.assertMarkdownRenders( + '## This is an H2 ## ', + + '<h2>This is an H2 ##</h2>' + ) + + def test_hash_h3_closed_trailing_space(self): + self.assertMarkdownRenders( + '### This is an H3 ### ', + + '<h3>This is an H3 ###</h3>' + ) + + def test_hash_h4_closed_trailing_space(self): + self.assertMarkdownRenders( + '#### This is an H4 #### ', + + '<h4>This is an H4 ####</h4>' + ) + + def test_hash_h5_closed_trailing_space(self): + self.assertMarkdownRenders( + '##### This is an H5 ##### ', + + '<h5>This is an H5 #####</h5>' + ) + + def test_hash_h6_closed_trailing_space(self): + self.assertMarkdownRenders( + '###### This is an H6 ###### ', + + '<h6>This is an H6 ######</h6>' + ) + + def test_hash_gt6_closed_trailing_space(self): + self.assertMarkdownRenders( + '####### This is an H6 ####### ', + + '<h6># This is an H6 #######</h6>' + ) + + def test_no_blank_lines_between_hashs(self): + self.assertMarkdownRenders( + self.dedent( + """ + # This is an H1 + ## This is an H2 + """ + ), + self.dedent( + """ + <h1>This is an H1</h1> + <h2>This is an H2</h2> + """ + ) + ) + + def test_random_hash_levels(self): + self.assertMarkdownRenders( + self.dedent( + """ + ### H3 + ###### H6 + # H1 + ##### H5 + #### H4 + ## H2 + ### H3 + """ + ), + self.dedent( + """ + <h3>H3</h3> + <h6>H6</h6> + <h1>H1</h1> + <h5>H5</h5> + <h4>H4</h4> + <h2>H2</h2> + <h3>H3</h3> + """ + ) + ) + + def test_hash_followed_by_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + # This is an H1 + Followed by a Paragraph with no blank line. + """ + ), + self.dedent( + """ + <h1>This is an H1</h1> + <p>Followed by a Paragraph with no blank line.</p> + """ + ) + ) + + def test_p_followed_by_hash(self): + self.assertMarkdownRenders( + self.dedent( + """ + This is a Paragraph. + # Followed by an H1 with no blank line. + """ + ), + self.dedent( + """ + <p>This is a Paragraph.</p> + <h1>Followed by an H1 with no blank line.</h1> + """ + ) + ) + + def test_escaped_hash(self): + self.assertMarkdownRenders( + "### H3 \\###", + self.dedent( + """ + <h3>H3 #</h3> + """ + ) + ) + + def test_unescaped_hash(self): + self.assertMarkdownRenders( + "### H3 \\\\###", + self.dedent( + """ + <h3>H3 \\</h3> + """ + ) + ) diff --git a/tests/test_syntax/blocks/test_hr.py b/tests/test_syntax/blocks/test_hr.py new file mode 100644 index 0000000..85a51b3 --- /dev/null +++ b/tests/test_syntax/blocks/test_hr.py @@ -0,0 +1,402 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestHorizontalRules(TestCase): + + def test_hr_asterisks(self): + self.assertMarkdownRenders( + '***', + + '<hr />' + ) + + def test_hr_asterisks_spaces(self): + self.assertMarkdownRenders( + '* * *', + + '<hr />' + ) + + def test_hr_asterisks_long(self): + self.assertMarkdownRenders( + '*******', + + '<hr />' + ) + + def test_hr_asterisks_spaces_long(self): + self.assertMarkdownRenders( + '* * * * * * *', + + '<hr />' + ) + + def test_hr_asterisks_1_indent(self): + self.assertMarkdownRenders( + ' ***', + + '<hr />' + ) + + def test_hr_asterisks_spaces_1_indent(self): + self.assertMarkdownRenders( + ' * * *', + + '<hr />' + ) + + def test_hr_asterisks_2_indent(self): + self.assertMarkdownRenders( + ' ***', + + '<hr />' + ) + + def test_hr_asterisks_spaces_2_indent(self): + self.assertMarkdownRenders( + ' * * *', + + '<hr />' + ) + + def test_hr_asterisks_3_indent(self): + self.assertMarkdownRenders( + ' ***', + + '<hr />' + ) + + def test_hr_asterisks_spaces_3_indent(self): + self.assertMarkdownRenders( + ' * * *', + + '<hr />' + ) + + def test_hr_asterisks_trailing_space(self): + self.assertMarkdownRenders( + '*** ', + + '<hr />' + ) + + def test_hr_asterisks_spaces_trailing_space(self): + self.assertMarkdownRenders( + '* * * ', + + '<hr />' + ) + + def test_hr_hyphens(self): + self.assertMarkdownRenders( + '---', + + '<hr />' + ) + + def test_hr_hyphens_spaces(self): + self.assertMarkdownRenders( + '- - -', + + '<hr />' + ) + + def test_hr_hyphens_long(self): + self.assertMarkdownRenders( + '-------', + + '<hr />' + ) + + def test_hr_hyphens_spaces_long(self): + self.assertMarkdownRenders( + '- - - - - - -', + + '<hr />' + ) + + def test_hr_hyphens_1_indent(self): + self.assertMarkdownRenders( + ' ---', + + '<hr />' + ) + + def test_hr_hyphens_spaces_1_indent(self): + self.assertMarkdownRenders( + ' - - -', + + '<hr />' + ) + + def test_hr_hyphens_2_indent(self): + self.assertMarkdownRenders( + ' ---', + + '<hr />' + ) + + def test_hr_hyphens_spaces_2_indent(self): + self.assertMarkdownRenders( + ' - - -', + + '<hr />' + ) + + def test_hr_hyphens_3_indent(self): + self.assertMarkdownRenders( + ' ---', + + '<hr />' + ) + + def test_hr_hyphens_spaces_3_indent(self): + self.assertMarkdownRenders( + ' - - -', + + '<hr />' + ) + + def test_hr_hyphens_trailing_space(self): + self.assertMarkdownRenders( + '--- ', + + '<hr />' + ) + + def test_hr_hyphens_spaces_trailing_space(self): + self.assertMarkdownRenders( + '- - - ', + + '<hr />' + ) + + def test_hr_underscores(self): + self.assertMarkdownRenders( + '___', + + '<hr />' + ) + + def test_hr_underscores_spaces(self): + self.assertMarkdownRenders( + '_ _ _', + + '<hr />' + ) + + def test_hr_underscores_long(self): + self.assertMarkdownRenders( + '_______', + + '<hr />' + ) + + def test_hr_underscores_spaces_long(self): + self.assertMarkdownRenders( + '_ _ _ _ _ _ _', + + '<hr />' + ) + + def test_hr_underscores_1_indent(self): + self.assertMarkdownRenders( + ' ___', + + '<hr />' + ) + + def test_hr_underscores_spaces_1_indent(self): + self.assertMarkdownRenders( + ' _ _ _', + + '<hr />' + ) + + def test_hr_underscores_2_indent(self): + self.assertMarkdownRenders( + ' ___', + + '<hr />' + ) + + def test_hr_underscores_spaces_2_indent(self): + self.assertMarkdownRenders( + ' _ _ _', + + '<hr />' + ) + + def test_hr_underscores_3_indent(self): + self.assertMarkdownRenders( + ' ___', + + '<hr />' + ) + + def test_hr_underscores_spaces_3_indent(self): + self.assertMarkdownRenders( + ' _ _ _', + + '<hr />' + ) + + def test_hr_underscores_trailing_space(self): + self.assertMarkdownRenders( + '___ ', + + '<hr />' + ) + + def test_hr_underscores_spaces_trailing_space(self): + self.assertMarkdownRenders( + '_ _ _ ', + + '<hr />' + ) + + def test_hr_before_paragraph(self): + self.assertMarkdownRenders( + self.dedent( + """ + *** + An HR followed by a paragraph with no blank line. + """ + ), + self.dedent( + """ + <hr /> + <p>An HR followed by a paragraph with no blank line.</p> + """ + ) + ) + + def test_hr_after_paragraph(self): + self.assertMarkdownRenders( + self.dedent( + """ + A paragraph followed by an HR with no blank line. + *** + """ + ), + self.dedent( + """ + <p>A paragraph followed by an HR with no blank line.</p> + <hr /> + """ + ) + ) + + def test_hr_after_emstrong(self): + self.assertMarkdownRenders( + self.dedent( + """ + ***text*** + *** + """ + ), + self.dedent( + """ + <p><strong><em>text</em></strong></p> + <hr /> + """ + ) + ) + + def test_not_hr_2_asterisks(self): + self.assertMarkdownRenders( + '**', + + '<p>**</p>' + ) + + def test_not_hr_2_asterisks_spaces(self): + self.assertMarkdownRenders( + '* *', + + self.dedent( + """ + <ul> + <li>*</li> + </ul> + """ + ) + ) + + def test_not_hr_2_hyphens(self): + self.assertMarkdownRenders( + '--', + + '<p>--</p>' + ) + + def test_not_hr_2_hyphens_spaces(self): + self.assertMarkdownRenders( + '- -', + + self.dedent( + """ + <ul> + <li>-</li> + </ul> + """ + ) + ) + + def test_not_hr_2_underscores(self): + self.assertMarkdownRenders( + '__', + + '<p>__</p>' + ) + + def test_not_hr_2_underscores_spaces(self): + self.assertMarkdownRenders( + '_ _', + + '<p>_ _</p>' + ) + + def test_2_consecutive_hr(self): + self.assertMarkdownRenders( + self.dedent( + """ + - - - + - - - + """ + ), + self.dedent( + """ + <hr /> + <hr /> + """ + ) + ) + + def test_not_hr_end_in_char(self): + self.assertMarkdownRenders( + '--------------------------------------c', + + '<p>--------------------------------------c</p>' + ) diff --git a/tests/test_syntax/blocks/test_html_blocks.py b/tests/test_syntax/blocks/test_html_blocks.py new file mode 100644 index 0000000..9ec0668 --- /dev/null +++ b/tests/test_syntax/blocks/test_html_blocks.py @@ -0,0 +1,1619 @@ +# -*- coding: utf-8 -*- +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase +import markdown + + +class TestHTMLBlocks(TestCase): + + def test_raw_paragraph(self): + self.assertMarkdownRenders( + '<p>A raw paragraph.</p>', + '<p>A raw paragraph.</p>' + ) + + def test_raw_skip_inline_markdown(self): + self.assertMarkdownRenders( + '<p>A *raw* paragraph.</p>', + '<p>A *raw* paragraph.</p>' + ) + + def test_raw_indent_one_space(self): + self.assertMarkdownRenders( + ' <p>A *raw* paragraph.</p>', + '<p>A *raw* paragraph.</p>' + ) + + def test_raw_indent_two_spaces(self): + self.assertMarkdownRenders( + ' <p>A *raw* paragraph.</p>', + '<p>A *raw* paragraph.</p>' + ) + + def test_raw_indent_three_spaces(self): + self.assertMarkdownRenders( + ' <p>A *raw* paragraph.</p>', + '<p>A *raw* paragraph.</p>' + ) + + def test_raw_indent_four_spaces(self): + self.assertMarkdownRenders( + ' <p>code block</p>', + self.dedent( + """ + <pre><code><p>code block</p> + </code></pre> + """ + ) + ) + + def test_raw_span(self): + self.assertMarkdownRenders( + '<span>*inline*</span>', + '<p><span><em>inline</em></span></p>' + ) + + def test_code_span(self): + self.assertMarkdownRenders( + '`<p>code span</p>`', + '<p><code><p>code span</p></code></p>' + ) + + def test_code_span_open_gt(self): + self.assertMarkdownRenders( + '*bar* `<` *foo*', + '<p><em>bar</em> <code><</code> <em>foo</em></p>' + ) + + def test_raw_empty(self): + self.assertMarkdownRenders( + '<p></p>', + '<p></p>' + ) + + def test_raw_empty_space(self): + self.assertMarkdownRenders( + '<p> </p>', + '<p> </p>' + ) + + def test_raw_empty_newline(self): + self.assertMarkdownRenders( + '<p>\n</p>', + '<p>\n</p>' + ) + + def test_raw_empty_blank_line(self): + self.assertMarkdownRenders( + '<p>\n\n</p>', + '<p>\n\n</p>' + ) + + def test_raw_uppercase(self): + self.assertMarkdownRenders( + '<DIV>*foo*</DIV>', + '<DIV>*foo*</DIV>' + ) + + def test_raw_uppercase_multiline(self): + self.assertMarkdownRenders( + self.dedent( + """ + <DIV> + *foo* + </DIV> + """ + ), + self.dedent( + """ + <DIV> + *foo* + </DIV> + """ + ) + ) + + def test_multiple_raw_single_line(self): + self.assertMarkdownRenders( + '<p>*foo*</p><div>*bar*</div>', + self.dedent( + """ + <p>*foo*</p> + <div>*bar*</div> + """ + ) + ) + + def test_multiple_raw_single_line_with_pi(self): + self.assertMarkdownRenders( + "<p>*foo*</p><?php echo '>'; ?>", + self.dedent( + """ + <p>*foo*</p> + <?php echo '>'; ?> + """ + ) + ) + + def test_multiline_raw(self): + self.assertMarkdownRenders( + self.dedent( + """ + <p> + A raw paragraph + with multiple lines. + </p> + """ + ), + self.dedent( + """ + <p> + A raw paragraph + with multiple lines. + </p> + """ + ) + ) + + def test_blank_lines_in_raw(self): + self.assertMarkdownRenders( + self.dedent( + """ + <p> + + A raw paragraph... + + with many blank lines. + + </p> + """ + ), + self.dedent( + """ + <p> + + A raw paragraph... + + with many blank lines. + + </p> + """ + ) + ) + + def test_raw_surrounded_by_Markdown(self): + self.assertMarkdownRenders( + self.dedent( + """ + Some *Markdown* text. + + <p>*Raw* HTML.</p> + + More *Markdown* text. + """ + ), + self.dedent( + """ + <p>Some <em>Markdown</em> text.</p> + <p>*Raw* HTML.</p> + + <p>More <em>Markdown</em> text.</p> + """ + ) + ) + + def test_raw_surrounded_by_text_without_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + Some *Markdown* text. + <p>*Raw* HTML.</p> + More *Markdown* text. + """ + ), + self.dedent( + """ + <p>Some <em>Markdown</em> text.</p> + <p>*Raw* HTML.</p> + <p>More <em>Markdown</em> text.</p> + """ + ) + ) + + def test_multiline_markdown_with_code_span(self): + self.assertMarkdownRenders( + self.dedent( + """ + A paragraph with a block-level + `<p>code span</p>`, which is + at the start of a line. + """ + ), + self.dedent( + """ + <p>A paragraph with a block-level + <code><p>code span</p></code>, which is + at the start of a line.</p> + """ + ) + ) + + def test_raw_block_preceded_by_markdown_code_span_with_unclosed_block_tag(self): + self.assertMarkdownRenders( + self.dedent( + """ + A paragraph with a block-level code span: `<div>`. + + <p>*not markdown*</p> + + This is *markdown* + """ + ), + self.dedent( + """ + <p>A paragraph with a block-level code span: <code><div></code>.</p> + <p>*not markdown*</p> + + <p>This is <em>markdown</em></p> + """ + ) + ) + + def test_raw_one_line_followed_by_text(self): + self.assertMarkdownRenders( + '<p>*foo*</p>*bar*', + self.dedent( + """ + <p>*foo*</p> + <p><em>bar</em></p> + """ + ) + ) + + def test_raw_one_line_followed_by_span(self): + self.assertMarkdownRenders( + "<p>*foo*</p><span>*bar*</span>", + self.dedent( + """ + <p>*foo*</p> + <p><span><em>bar</em></span></p> + """ + ) + ) + + def test_raw_with_markdown_blocks(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div> + Not a Markdown paragraph. + + * Not a list item. + * Another non-list item. + + Another non-Markdown paragraph. + </div> + """ + ), + self.dedent( + """ + <div> + Not a Markdown paragraph. + + * Not a list item. + * Another non-list item. + + Another non-Markdown paragraph. + </div> + """ + ) + ) + + def test_adjacent_raw_blocks(self): + self.assertMarkdownRenders( + self.dedent( + """ + <p>A raw paragraph.</p> + <p>A second raw paragraph.</p> + """ + ), + self.dedent( + """ + <p>A raw paragraph.</p> + <p>A second raw paragraph.</p> + """ + ) + ) + + def test_adjacent_raw_blocks_with_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + <p>A raw paragraph.</p> + + <p>A second raw paragraph.</p> + """ + ), + self.dedent( + """ + <p>A raw paragraph.</p> + + <p>A second raw paragraph.</p> + """ + ) + ) + + def test_nested_raw_one_line(self): + self.assertMarkdownRenders( + '<div><p>*foo*</p></div>', + '<div><p>*foo*</p></div>' + ) + + def test_nested_raw_block(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div> + <p>A raw paragraph.</p> + </div> + """ + ), + self.dedent( + """ + <div> + <p>A raw paragraph.</p> + </div> + """ + ) + ) + + def test_nested_indented_raw_block(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div> + <p>A raw paragraph.</p> + </div> + """ + ), + self.dedent( + """ + <div> + <p>A raw paragraph.</p> + </div> + """ + ) + ) + + def test_nested_raw_blocks(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div> + <p>A raw paragraph.</p> + <p>A second raw paragraph.</p> + </div> + """ + ), + self.dedent( + """ + <div> + <p>A raw paragraph.</p> + <p>A second raw paragraph.</p> + </div> + """ + ) + ) + + def test_nested_raw_blocks_with_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div> + + <p>A raw paragraph.</p> + + <p>A second raw paragraph.</p> + + </div> + """ + ), + self.dedent( + """ + <div> + + <p>A raw paragraph.</p> + + <p>A second raw paragraph.</p> + + </div> + """ + ) + ) + + def test_nested_inline_one_line(self): + self.assertMarkdownRenders( + '<p><em>foo</em><br></p>', + '<p><em>foo</em><br></p>' + ) + + def test_raw_nested_inline(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div> + <p> + <span>*text*</span> + </p> + </div> + """ + ), + self.dedent( + """ + <div> + <p> + <span>*text*</span> + </p> + </div> + """ + ) + ) + + def test_raw_nested_inline_with_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div> + + <p> + + <span>*text*</span> + + </p> + + </div> + """ + ), + self.dedent( + """ + <div> + + <p> + + <span>*text*</span> + + </p> + + </div> + """ + ) + ) + + def test_raw_html5(self): + self.assertMarkdownRenders( + self.dedent( + """ + <section> + <header> + <hgroup> + <h1>Hello :-)</h1> + </hgroup> + </header> + <figure> + <img src="image.png" alt="" /> + <figcaption>Caption</figcaption> + </figure> + <footer> + <p>Some footer</p> + </footer> + </section> + """ + ), + self.dedent( + """ + <section> + <header> + <hgroup> + <h1>Hello :-)</h1> + </hgroup> + </header> + <figure> + <img src="image.png" alt="" /> + <figcaption>Caption</figcaption> + </figure> + <footer> + <p>Some footer</p> + </footer> + </section> + """ + ) + ) + + def test_raw_pre_tag(self): + self.assertMarkdownRenders( + self.dedent( + """ + Preserve whitespace in raw html + + <pre> + class Foo(): + bar = 'bar' + + @property + def baz(self): + return self.bar + </pre> + """ + ), + self.dedent( + """ + <p>Preserve whitespace in raw html</p> + <pre> + class Foo(): + bar = 'bar' + + @property + def baz(self): + return self.bar + </pre> + """ + ) + ) + + def test_raw_pre_tag_nested_escaped_html(self): + self.assertMarkdownRenders( + self.dedent( + """ + <pre> + <p>foo</p> + </pre> + """ + ), + self.dedent( + """ + <pre> + <p>foo</p> + </pre> + """ + ) + ) + + def test_raw_p_no_end_tag(self): + self.assertMarkdownRenders( + '<p>*text*', + '<p>*text*' + ) + + def test_raw_multiple_p_no_end_tag(self): + self.assertMarkdownRenders( + self.dedent( + """ + <p>*text*' + + <p>more *text* + """ + ), + self.dedent( + """ + <p>*text*' + + <p>more *text* + """ + ) + ) + + def test_raw_p_no_end_tag_followed_by_blank_line(self): + self.assertMarkdownRenders( + self.dedent( + """ + <p>*raw text*' + + Still part of *raw* text. + """ + ), + self.dedent( + """ + <p>*raw text*' + + Still part of *raw* text. + """ + ) + ) + + def test_raw_nested_p_no_end_tag(self): + self.assertMarkdownRenders( + '<div><p>*text*</div>', + '<div><p>*text*</div>' + ) + + def test_raw_open_bracket_only(self): + self.assertMarkdownRenders( + '<', + '<p><</p>' + ) + + def test_raw_open_bracket_followed_by_space(self): + self.assertMarkdownRenders( + '< foo', + '<p>< foo</p>' + ) + + def test_raw_missing_close_bracket(self): + self.assertMarkdownRenders( + '<foo', + '<p><foo</p>' + ) + + def test_raw_unclosed_tag_in_code_span(self): + self.assertMarkdownRenders( + self.dedent( + """ + `<div`. + + <div> + hello + </div> + """ + ), + self.dedent( + """ + <p><code><div</code>.</p> + <div> + hello + </div> + """ + ) + ) + + def test_raw_unclosed_tag_in_code_span_space(self): + self.assertMarkdownRenders( + self.dedent( + """ + ` <div `. + + <div> + hello + </div> + """ + ), + self.dedent( + """ + <p><code><div</code>.</p> + <div> + hello + </div> + """ + ) + ) + + def test_raw_attributes(self): + self.assertMarkdownRenders( + '<p id="foo", class="bar baz", style="margin: 15px; line-height: 1.5; text-align: center;">text</p>', + '<p id="foo", class="bar baz", style="margin: 15px; line-height: 1.5; text-align: center;">text</p>' + ) + + def test_raw_attributes_nested(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div id="foo, class="bar", style="background: #ffe7e8; border: 2px solid #e66465;"> + <p id="baz", style="margin: 15px; line-height: 1.5; text-align: center;"> + <img scr="../foo.jpg" title="with 'quoted' text." valueless_attr weirdness="<i>foo</i>" /> + </p> + </div> + """ + ), + self.dedent( + """ + <div id="foo, class="bar", style="background: #ffe7e8; border: 2px solid #e66465;"> + <p id="baz", style="margin: 15px; line-height: 1.5; text-align: center;"> + <img scr="../foo.jpg" title="with 'quoted' text." valueless_attr weirdness="<i>foo</i>" /> + </p> + </div> + """ + ) + ) + + def test_raw_comment_one_line(self): + self.assertMarkdownRenders( + '<!-- *foo* -->', + '<!-- *foo* -->' + ) + + def test_raw_comment_one_line_with_tag(self): + self.assertMarkdownRenders( + '<!-- <tag> -->', + '<!-- <tag> -->' + ) + + def test_comment_in_code_span(self): + self.assertMarkdownRenders( + '`<!-- *foo* -->`', + '<p><code><!-- *foo* --></code></p>' + ) + + def test_raw_comment_one_line_followed_by_text(self): + self.assertMarkdownRenders( + '<!-- *foo* -->*bar*', + self.dedent( + """ + <!-- *foo* --> + <p><em>bar</em></p> + """ + ) + ) + + def test_raw_comment_one_line_followed_by_html(self): + self.assertMarkdownRenders( + '<!-- *foo* --><p>*bar*</p>', + self.dedent( + """ + <!-- *foo* --> + <p>*bar*</p> + """ + ) + ) + + # Note: Trailing (insignificant) whitespace is not preserved, which does not match the + # reference implementation. However, it is not a change in behavior for Python-Markdown. + def test_raw_comment_trailing_whitespace(self): + self.assertMarkdownRenders( + '<!-- *foo* --> ', + '<!-- *foo* -->' + ) + + # Note: this is a change in behavior for Python-Markdown, which does *not* match the reference + # implementation. However, it does match the HTML5 spec. Declarations must start with either + # `<!DOCTYPE` or `<![`. Anything else that starts with `<!` is a comment. According to the + # HTML5 spec, a comment without the hyphens is a "bogus comment", but a comment nonetheless. + # See https://www.w3.org/TR/html52/syntax.html#markup-declaration-open-state. + # If we wanted to change this behavior, we could override `HTMLParser.parse_bogus_comment()`. + def test_bogus_comment(self): + self.assertMarkdownRenders( + '<!*foo*>', + '<!--*foo*-->' + ) + + def test_raw_multiline_comment(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- + *foo* + --> + """ + ), + self.dedent( + """ + <!-- + *foo* + --> + """ + ) + ) + + def test_raw_multiline_comment_with_tag(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- + <tag> + --> + """ + ), + self.dedent( + """ + <!-- + <tag> + --> + """ + ) + ) + + def test_raw_multiline_comment_first_line(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- *foo* + --> + """ + ), + self.dedent( + """ + <!-- *foo* + --> + """ + ) + ) + + def test_raw_multiline_comment_last_line(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- + *foo* --> + """ + ), + self.dedent( + """ + <!-- + *foo* --> + """ + ) + ) + + def test_raw_comment_with_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- + + *foo* + + --> + """ + ), + self.dedent( + """ + <!-- + + *foo* + + --> + """ + ) + ) + + def test_raw_comment_with_blank_lines_with_tag(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- + + <tag> + + --> + """ + ), + self.dedent( + """ + <!-- + + <tag> + + --> + """ + ) + ) + + def test_raw_comment_with_blank_lines_first_line(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- *foo* + + --> + """ + ), + self.dedent( + """ + <!-- *foo* + + --> + """ + ) + ) + + def test_raw_comment_with_blank_lines_last_line(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- + + *foo* --> + """ + ), + self.dedent( + """ + <!-- + + *foo* --> + """ + ) + ) + + def test_raw_comment_indented(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- + + *foo* + + --> + """ + ), + self.dedent( + """ + <!-- + + *foo* + + --> + """ + ) + ) + + def test_raw_comment_indented_with_tag(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- + + <tag> + + --> + """ + ), + self.dedent( + """ + <!-- + + <tag> + + --> + """ + ) + ) + + def test_raw_comment_nested(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div> + <!-- *foo* --> + </div> + """ + ), + self.dedent( + """ + <div> + <!-- *foo* --> + </div> + """ + ) + ) + + def test_comment_in_code_block(self): + self.assertMarkdownRenders( + ' <!-- *foo* -->', + self.dedent( + """ + <pre><code><!-- *foo* --> + </code></pre> + """ + ) + ) + + # Note: This is a change in behavior. Previously, Python-Markdown interpreted this in the same manner + # as browsers and all text after the opening comment tag was considered to be in a comment. However, + # that did not match the reference implementation. The new behavior does. + def test_unclosed_comment_(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!-- unclosed comment + + *not* a comment + """ + ), + self.dedent( + """ + <p><!-- unclosed comment</p> + <p><em>not</em> a comment</p> + """ + ) + ) + + def test_raw_processing_instruction_one_line(self): + self.assertMarkdownRenders( + "<?php echo '>'; ?>", + "<?php echo '>'; ?>" + ) + + # This is a change in behavior and does not match the reference implementation. + # We have no way to determine if text is on the same line, so we get this. TODO: reevaluate! + def test_raw_processing_instruction_one_line_followed_by_text(self): + self.assertMarkdownRenders( + "<?php echo '>'; ?>*bar*", + self.dedent( + """ + <?php echo '>'; ?> + <p><em>bar</em></p> + """ + ) + ) + + def test_raw_multiline_processing_instruction(self): + self.assertMarkdownRenders( + self.dedent( + """ + <?php + echo '>'; + ?> + """ + ), + self.dedent( + """ + <?php + echo '>'; + ?> + """ + ) + ) + + def test_raw_processing_instruction_with_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + <?php + + echo '>'; + + ?> + """ + ), + self.dedent( + """ + <?php + + echo '>'; + + ?> + """ + ) + ) + + def test_raw_processing_instruction_indented(self): + self.assertMarkdownRenders( + self.dedent( + """ + <?php + + echo '>'; + + ?> + """ + ), + self.dedent( + """ + <?php + + echo '>'; + + ?> + """ + ) + ) + + def test_raw_processing_instruction_code_span(self): + self.assertMarkdownRenders( + self.dedent( + """ + `<?php` + + <div> + foo + </div> + """ + ), + self.dedent( + """ + <p><code><?php</code></p> + <div> + foo + </div> + """ + ) + ) + + def test_raw_declaration_one_line(self): + self.assertMarkdownRenders( + '<!DOCTYPE html>', + '<!DOCTYPE html>' + ) + + # This is a change in behavior and does not match the reference implementation. + # We have no way to determine if text is on the same line, so we get this. TODO: reevaluate! + def test_raw_declaration_one_line_followed_by_text(self): + self.assertMarkdownRenders( + '<!DOCTYPE html>*bar*', + self.dedent( + """ + <!DOCTYPE html> + <p><em>bar</em></p> + """ + ) + ) + + def test_raw_multiline_declaration(self): + self.assertMarkdownRenders( + self.dedent( + """ + <!DOCTYPE html PUBLIC + "-//W3C//DTD XHTML 1.1//EN" + "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> + """ + ), + self.dedent( + """ + <!DOCTYPE html PUBLIC + "-//W3C//DTD XHTML 1.1//EN" + "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> + """ + ) + ) + + def test_raw_declaration_code_span(self): + self.assertMarkdownRenders( + self.dedent( + """ + `<!` + + <div> + foo + </div> + """ + ), + self.dedent( + """ + <p><code><!</code></p> + <div> + foo + </div> + """ + ) + ) + + def test_raw_cdata_one_line(self): + self.assertMarkdownRenders( + '<![CDATA[ document.write(">"); ]]>', + '<![CDATA[ document.write(">"); ]]>' + ) + + # Note: this is a change. Neither previous output nor this match reference implementation. + def test_raw_cdata_one_line_followed_by_text(self): + self.assertMarkdownRenders( + '<![CDATA[ document.write(">"); ]]>*bar*', + self.dedent( + """ + <![CDATA[ document.write(">"); ]]> + <p><em>bar</em></p> + """ + ) + ) + + def test_raw_multiline_cdata(self): + self.assertMarkdownRenders( + self.dedent( + """ + <![CDATA[ + document.write(">"); + ]]> + """ + ), + self.dedent( + """ + <![CDATA[ + document.write(">"); + ]]> + """ + ) + ) + + def test_raw_cdata_with_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + <![CDATA[ + + document.write(">"); + + ]]> + """ + ), + self.dedent( + """ + <![CDATA[ + + document.write(">"); + + ]]> + """ + ) + ) + + def test_raw_cdata_indented(self): + self.assertMarkdownRenders( + self.dedent( + """ + <![CDATA[ + + document.write(">"); + + ]]> + """ + ), + self.dedent( + """ + <![CDATA[ + + document.write(">"); + + ]]> + """ + ) + ) + + def test_raw_cdata_code_span(self): + self.assertMarkdownRenders( + self.dedent( + """ + `<![` + + <div> + foo + </div> + """ + ), + self.dedent( + """ + <p><code><![</code></p> + <div> + foo + </div> + """ + ) + ) + + def test_charref(self): + self.assertMarkdownRenders( + '§', + '<p>§</p>' + ) + + def test_nested_charref(self): + self.assertMarkdownRenders( + '<p>§</p>', + '<p>§</p>' + ) + + def test_entityref(self): + self.assertMarkdownRenders( + '§', + '<p>§</p>' + ) + + def test_nested_entityref(self): + self.assertMarkdownRenders( + '<p>§</p>', + '<p>§</p>' + ) + + def test_amperstand(self): + self.assertMarkdownRenders( + 'AT&T & AT&T', + '<p>AT&T & AT&T</p>' + ) + + def test_startendtag(self): + self.assertMarkdownRenders( + '<hr>', + '<hr>' + ) + + def test_startendtag_with_attrs(self): + self.assertMarkdownRenders( + '<hr id="foo" class="bar">', + '<hr id="foo" class="bar">' + ) + + def test_startendtag_with_space(self): + self.assertMarkdownRenders( + '<hr >', + '<hr >' + ) + + def test_closed_startendtag(self): + self.assertMarkdownRenders( + '<hr />', + '<hr />' + ) + + def test_closed_startendtag_without_space(self): + self.assertMarkdownRenders( + '<hr/>', + '<hr/>' + ) + + def test_closed_startendtag_with_attrs(self): + self.assertMarkdownRenders( + '<hr id="foo" class="bar" />', + '<hr id="foo" class="bar" />' + ) + + def test_nested_startendtag(self): + self.assertMarkdownRenders( + '<div><hr></div>', + '<div><hr></div>' + ) + + def test_nested_closed_startendtag(self): + self.assertMarkdownRenders( + '<div><hr /></div>', + '<div><hr /></div>' + ) + + def test_auto_links_dont_break_parser(self): + self.assertMarkdownRenders( + self.dedent( + """ + <https://example.com> + + <email@example.com> + """ + ), + '<p><a href="https://example.com">https://example.com</a></p>\n' + '<p><a href="mailto:em' + 'ail@example' + '.com">email@e' + 'xample.com</a></p>' + ) + + def test_text_links_ignored(self): + self.assertMarkdownRenders( + self.dedent( + """ + https://example.com + + email@example.com + """ + ), + self.dedent( + """ + <p>https://example.com</p> + <p>email@example.com</p> + """ + ), + ) + + def text_invalid_tags(self): + self.assertMarkdownRenders( + self.dedent( + """ + <some [weird](http://example.com) stuff> + + <some>> <<unbalanced>> <<brackets> + """ + ), + self.dedent( + """ + <p><some <a href="http://example.com">weird</a> stuff></p> + <p><some>> <<unbalanced>> <<brackets></p> + """ + ) + ) + + def test_script_tags(self): + self.assertMarkdownRenders( + self.dedent( + """ + <script> + *random stuff* <div> & + </script> + + <style> + **more stuff** + </style> + """ + ), + self.dedent( + """ + <script> + *random stuff* <div> & + </script> + + <style> + **more stuff** + </style> + """ + ) + ) + + def test_unclosed_script_tag(self): + # Ensure we have a working fix for https://bugs.python.org/issue41989 + self.assertMarkdownRenders( + self.dedent( + """ + <script> + *random stuff* <div> & + + Still part of the *script* tag + """ + ), + self.dedent( + """ + <script> + *random stuff* <div> & + + Still part of the *script* tag + """ + ) + ) + + def test_inline_script_tags(self): + # Ensure inline script tags doesn't cause the parser to eat content (see #1036). + self.assertMarkdownRenders( + self.dedent( + """ + Text `<script>` more *text*. + + <div> + *foo* + </div> + + <div> + + bar + + </div> + + A new paragraph with a closing `</script>` tag. + """ + ), + self.dedent( + """ + <p>Text <code><script></code> more <em>text</em>.</p> + <div> + *foo* + </div> + + <div> + + bar + + </div> + + <p>A new paragraph with a closing <code></script></code> tag.</p> + """ + ) + ) + + def test_hr_only_start(self): + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + <hr> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em></p> + <hr> + <p><em>emphasis2</em></p> + """ + ) + ) + + def test_hr_self_close(self): + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + <hr/> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em></p> + <hr/> + <p><em>emphasis2</em></p> + """ + ) + ) + + def test_hr_start_and_end(self): + # Browsers ignore ending hr tags, so we don't try to do anything to handle them special. + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + <hr></hr> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em></p> + <hr> + <p></hr> + <em>emphasis2</em></p> + """ + ) + ) + + def test_hr_only_end(self): + # Browsers ignore ending hr tags, so we don't try to do anything to handle them special. + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + </hr> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em> + </hr> + <em>emphasis2</em></p> + """ + ) + ) + + def test_hr_with_content(self): + # Browsers ignore ending hr tags, so we don't try to do anything to handle them special. + # Content is not allowed and will be treated as normal content between two hr tags. + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + <hr> + **content** + </hr> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em></p> + <hr> + <p><strong>content</strong> + </hr> + <em>emphasis2</em></p> + """ + ) + ) + + def test_placeholder_in_source(self): + # This should never occur, but third party extensions could create weird edge cases. + md = markdown.Markdown() + # Ensure there is an htmlstash so relevant code (nested in `if replacements`) is run. + md.htmlStash.store('foo') + # Run with a placeholder which is not in the stash + placeholder = md.htmlStash.get_placeholder(md.htmlStash.html_counter + 1) + result = md.postprocessors['raw_html'].run(placeholder) + self.assertEqual(placeholder, result) diff --git a/tests/test_syntax/blocks/test_paragraphs.py b/tests/test_syntax/blocks/test_paragraphs.py new file mode 100644 index 0000000..9b7ba03 --- /dev/null +++ b/tests/test_syntax/blocks/test_paragraphs.py @@ -0,0 +1,229 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestParagraphBlocks(TestCase): + + def test_simple_paragraph(self): + self.assertMarkdownRenders( + 'A simple paragraph.', + + '<p>A simple paragraph.</p>' + ) + + def test_blank_line_before_paragraph(self): + self.assertMarkdownRenders( + '\nA paragraph preceded by a blank line.', + + '<p>A paragraph preceded by a blank line.</p>' + ) + + def test_multiline_paragraph(self): + self.assertMarkdownRenders( + self.dedent( + """ + This is a paragraph + on multiple lines + with hard returns. + """ + ), + self.dedent( + """ + <p>This is a paragraph + on multiple lines + with hard returns.</p> + """ + ) + ) + + def test_paragraph_long_line(self): + self.assertMarkdownRenders( + 'A very long long long long long long long long long long long long long long long long long long long ' + 'long long long long long long long long long long long long long paragraph on 1 line.', + + '<p>A very long long long long long long long long long long long long long long long long long long ' + 'long long long long long long long long long long long long long long paragraph on 1 line.</p>' + ) + + def test_2_paragraphs_long_line(self): + self.assertMarkdownRenders( + 'A very long long long long long long long long long long long long long long long long long long long ' + 'long long long long long long long long long long long long long paragraph on 1 line.\n\n' + + 'A new long long long long long long long long long long long long long long long ' + 'long paragraph on 1 line.', + + '<p>A very long long long long long long long long long long long long long long long long long long ' + 'long long long long long long long long long long long long long long paragraph on 1 line.</p>\n' + '<p>A new long long long long long long long long long long long long long long long ' + 'long paragraph on 1 line.</p>' + ) + + def test_consecutive_paragraphs(self): + self.assertMarkdownRenders( + self.dedent( + """ + Paragraph 1. + + Paragraph 2. + """ + ), + self.dedent( + """ + <p>Paragraph 1.</p> + <p>Paragraph 2.</p> + """ + ) + ) + + def test_consecutive_paragraphs_tab(self): + self.assertMarkdownRenders( + self.dedent( + """ + Paragraph followed by a line with a tab only. + \t + Paragraph after a line with a tab only. + """ + ), + self.dedent( + """ + <p>Paragraph followed by a line with a tab only.</p> + <p>Paragraph after a line with a tab only.</p> + """ + ) + ) + + def test_consecutive_paragraphs_space(self): + self.assertMarkdownRenders( + self.dedent( + """ + Paragraph followed by a line with a space only. + + Paragraph after a line with a space only. + """ + ), + self.dedent( + """ + <p>Paragraph followed by a line with a space only.</p> + <p>Paragraph after a line with a space only.</p> + """ + ) + ) + + def test_consecutive_multiline_paragraphs(self): + self.assertMarkdownRenders( + self.dedent( + """ + Paragraph 1, line 1. + Paragraph 1, line 2. + + Paragraph 2, line 1. + Paragraph 2, line 2. + """ + ), + self.dedent( + """ + <p>Paragraph 1, line 1. + Paragraph 1, line 2.</p> + <p>Paragraph 2, line 1. + Paragraph 2, line 2.</p> + """ + ) + ) + + def test_paragraph_leading_space(self): + self.assertMarkdownRenders( + ' A paragraph with 1 leading space.', + + '<p>A paragraph with 1 leading space.</p>' + ) + + def test_paragraph_2_leading_spaces(self): + self.assertMarkdownRenders( + ' A paragraph with 2 leading spaces.', + + '<p>A paragraph with 2 leading spaces.</p>' + ) + + def test_paragraph_3_leading_spaces(self): + self.assertMarkdownRenders( + ' A paragraph with 3 leading spaces.', + + '<p>A paragraph with 3 leading spaces.</p>' + ) + + def test_paragraph_trailing_leading_space(self): + self.assertMarkdownRenders( + ' A paragraph with 1 trailing and 1 leading space. ', + + '<p>A paragraph with 1 trailing and 1 leading space. </p>' + ) + + def test_paragraph_trailing_tab(self): + self.assertMarkdownRenders( + 'A paragraph with 1 trailing tab.\t', + + '<p>A paragraph with 1 trailing tab. </p>' + ) + + def test_paragraphs_CR(self): + self.assertMarkdownRenders( + 'Paragraph 1, line 1.\rParagraph 1, line 2.\r\rParagraph 2, line 1.\rParagraph 2, line 2.\r', + + self.dedent( + """ + <p>Paragraph 1, line 1. + Paragraph 1, line 2.</p> + <p>Paragraph 2, line 1. + Paragraph 2, line 2.</p> + """ + ) + ) + + def test_paragraphs_LF(self): + self.assertMarkdownRenders( + 'Paragraph 1, line 1.\nParagraph 1, line 2.\n\nParagraph 2, line 1.\nParagraph 2, line 2.\n', + + self.dedent( + """ + <p>Paragraph 1, line 1. + Paragraph 1, line 2.</p> + <p>Paragraph 2, line 1. + Paragraph 2, line 2.</p> + """ + ) + ) + + def test_paragraphs_CR_LF(self): + self.assertMarkdownRenders( + 'Paragraph 1, line 1.\r\nParagraph 1, line 2.\r\n\r\nParagraph 2, line 1.\r\nParagraph 2, line 2.\r\n', + + self.dedent( + """ + <p>Paragraph 1, line 1. + Paragraph 1, line 2.</p> + <p>Paragraph 2, line 1. + Paragraph 2, line 2.</p> + """ + ) + ) diff --git a/tests/test_syntax/extensions/__init__.py b/tests/test_syntax/extensions/__init__.py new file mode 100644 index 0000000..564ba3b --- /dev/null +++ b/tests/test_syntax/extensions/__init__.py @@ -0,0 +1,20 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" diff --git a/tests/test_syntax/extensions/test_abbr.py b/tests/test_syntax/extensions/test_abbr.py new file mode 100644 index 0000000..64388c2 --- /dev/null +++ b/tests/test_syntax/extensions/test_abbr.py @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestAbbr(TestCase): + + default_kwargs = {'extensions': ['abbr']} + + def test_abbr_upper(self): + self.assertMarkdownRenders( + self.dedent( + """ + ABBR + + *[ABBR]: Abbreviation + """ + ), + self.dedent( + """ + <p><abbr title="Abbreviation">ABBR</abbr></p> + """ + ) + ) + + def test_abbr_lower(self): + self.assertMarkdownRenders( + self.dedent( + """ + abbr + + *[abbr]: Abbreviation + """ + ), + self.dedent( + """ + <p><abbr title="Abbreviation">abbr</abbr></p> + """ + ) + ) + + def test_abbr_multiple(self): + self.assertMarkdownRenders( + self.dedent( + """ + The HTML specification + is maintained by the W3C. + + *[HTML]: Hyper Text Markup Language + *[W3C]: World Wide Web Consortium + """ + ), + self.dedent( + """ + <p>The <abbr title="Hyper Text Markup Language">HTML</abbr> specification + is maintained by the <abbr title="World Wide Web Consortium">W3C</abbr>.</p> + """ + ) + ) + + def test_abbr_override(self): + self.assertMarkdownRenders( + self.dedent( + """ + ABBR + + *[ABBR]: Ignored + *[ABBR]: The override + """ + ), + self.dedent( + """ + <p><abbr title="The override">ABBR</abbr></p> + """ + ) + ) + + def test_abbr_no_blank_Lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + ABBR + *[ABBR]: Abbreviation + ABBR + """ + ), + self.dedent( + """ + <p><abbr title="Abbreviation">ABBR</abbr></p> + <p><abbr title="Abbreviation">ABBR</abbr></p> + """ + ) + ) + + def test_abbr_no_space(self): + self.assertMarkdownRenders( + self.dedent( + """ + ABBR + + *[ABBR]:Abbreviation + """ + ), + self.dedent( + """ + <p><abbr title="Abbreviation">ABBR</abbr></p> + """ + ) + ) + + def test_abbr_extra_space(self): + self.assertMarkdownRenders( + self.dedent( + """ + ABBR + + *[ABBR] : Abbreviation + """ + ), + self.dedent( + """ + <p><abbr title="Abbreviation">ABBR</abbr></p> + """ + ) + ) + + def test_abbr_line_break(self): + self.assertMarkdownRenders( + self.dedent( + """ + ABBR + + *[ABBR]: + Abbreviation + """ + ), + self.dedent( + """ + <p><abbr title="Abbreviation">ABBR</abbr></p> + """ + ) + ) + + def test_abbr_ignore_unmatched_case(self): + self.assertMarkdownRenders( + self.dedent( + """ + ABBR abbr + + *[ABBR]: Abbreviation + """ + ), + self.dedent( + """ + <p><abbr title="Abbreviation">ABBR</abbr> abbr</p> + """ + ) + ) + + def test_abbr_partial_word(self): + self.assertMarkdownRenders( + self.dedent( + """ + ABBR ABBREVIATION + + *[ABBR]: Abbreviation + """ + ), + self.dedent( + """ + <p><abbr title="Abbreviation">ABBR</abbr> ABBREVIATION</p> + """ + ) + ) + + def test_abbr_unused(self): + self.assertMarkdownRenders( + self.dedent( + """ + foo bar + + *[ABBR]: Abbreviation + """ + ), + self.dedent( + """ + <p>foo bar</p> + """ + ) + ) + + def test_abbr_double_quoted(self): + self.assertMarkdownRenders( + self.dedent( + """ + ABBR + + *[ABBR]: "Abbreviation" + """ + ), + self.dedent( + """ + <p><abbr title=""Abbreviation"">ABBR</abbr></p> + """ + ) + ) + + def test_abbr_single_quoted(self): + self.assertMarkdownRenders( + self.dedent( + """ + ABBR + + *[ABBR]: 'Abbreviation' + """ + ), + self.dedent( + """ + <p><abbr title="'Abbreviation'">ABBR</abbr></p> + """ + ) + ) diff --git a/tests/test_syntax/extensions/test_admonition.py b/tests/test_syntax/extensions/test_admonition.py new file mode 100644 index 0000000..44c70d1 --- /dev/null +++ b/tests/test_syntax/extensions/test_admonition.py @@ -0,0 +1,245 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2019 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestAdmonition(TestCase): + + def test_with_lists(self): + self.assertMarkdownRenders( + self.dedent( + ''' + - List + + !!! note "Admontion" + + - Paragraph + + Paragraph + ''' + ), + self.dedent( + ''' + <ul> + <li> + <p>List</p> + <div class="admonition note"> + <p class="admonition-title">Admontion</p> + <ul> + <li> + <p>Paragraph</p> + <p>Paragraph</p> + </li> + </ul> + </div> + </li> + </ul> + ''' + ), + extensions=['admonition'] + ) + + def test_with_big_lists(self): + self.assertMarkdownRenders( + self.dedent( + ''' + - List + + !!! note "Admontion" + + - Paragraph + + Paragraph + + - Paragraph + + paragraph + ''' + ), + self.dedent( + ''' + <ul> + <li> + <p>List</p> + <div class="admonition note"> + <p class="admonition-title">Admontion</p> + <ul> + <li> + <p>Paragraph</p> + <p>Paragraph</p> + </li> + <li> + <p>Paragraph</p> + <p>paragraph</p> + </li> + </ul> + </div> + </li> + </ul> + ''' + ), + extensions=['admonition'] + ) + + def test_with_complex_lists(self): + self.assertMarkdownRenders( + self.dedent( + ''' + - List + + !!! note "Admontion" + + - Paragraph + + !!! note "Admontion" + + 1. Paragraph + + Paragraph + ''' + ), + self.dedent( + ''' + <ul> + <li> + <p>List</p> + <div class="admonition note"> + <p class="admonition-title">Admontion</p> + <ul> + <li> + <p>Paragraph</p> + <div class="admonition note"> + <p class="admonition-title">Admontion</p> + <ol> + <li> + <p>Paragraph</p> + <p>Paragraph</p> + </li> + </ol> + </div> + </li> + </ul> + </div> + </li> + </ul> + ''' + ), + extensions=['admonition'] + ) + + def test_definition_list(self): + self.assertMarkdownRenders( + self.dedent( + ''' + - List + + !!! note "Admontion" + + Term + + : Definition + + More text + + : Another + definition + + Even more text + ''' + ), + self.dedent( + ''' + <ul> + <li> + <p>List</p> + <div class="admonition note"> + <p class="admonition-title">Admontion</p> + <dl> + <dt>Term</dt> + <dd> + <p>Definition</p> + <p>More text</p> + </dd> + <dd> + <p>Another + definition</p> + <p>Even more text</p> + </dd> + </dl> + </div> + </li> + </ul> + ''' + ), + extensions=['admonition', 'def_list'] + ) + + def test_with_preceding_text(self): + self.assertMarkdownRenders( + self.dedent( + ''' + foo + **foo** + !!! note "Admonition" + ''' + ), + self.dedent( + ''' + <p>foo + <strong>foo</strong></p> + <div class="admonition note"> + <p class="admonition-title">Admonition</p> + </div> + ''' + ), + extensions=['admonition'] + ) + + def test_admontion_detabbing(self): + self.assertMarkdownRenders( + self.dedent( + ''' + !!! note "Admonition" + - Parent 1 + + - Child 1 + - Child 2 + ''' + ), + self.dedent( + ''' + <div class="admonition note"> + <p class="admonition-title">Admonition</p> + <ul> + <li> + <p>Parent 1</p> + <ul> + <li>Child 1</li> + <li>Child 2</li> + </ul> + </li> + </ul> + </div> + ''' + ), + extensions=['admonition'] + ) diff --git a/tests/test_syntax/extensions/test_attr_list.py b/tests/test_syntax/extensions/test_attr_list.py new file mode 100644 index 0000000..6baaafb --- /dev/null +++ b/tests/test_syntax/extensions/test_attr_list.py @@ -0,0 +1,80 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2020 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestAttrList(TestCase): + + maxDiff = None + + # TODO: Move the rest of the attr_list tests here. + + def test_empty_list(self): + self.assertMarkdownRenders( + '*foo*{ }', + '<p><em>foo</em>{ }</p>', + extensions=['attr_list'] + ) + + def test_table_td(self): + self.assertMarkdownRenders( + self.dedent( + """ + | A { .foo } | *B*{ .foo } | C { } | D{ .foo } | E { .foo } F | + |-------------|-------------|-------|---------------|--------------| + | a { .foo } | *b*{ .foo } | c { } | d{ .foo } | e { .foo } f | + | valid on td | inline | empty | missing space | not at end | + """ + ), + self.dedent( + """ + <table> + <thead> + <tr> + <th class="foo">A</th> + <th><em class="foo">B</em></th> + <th>C { }</th> + <th>D{ .foo }</th> + <th>E { .foo } F</th> + </tr> + </thead> + <tbody> + <tr> + <td class="foo">a</td> + <td><em class="foo">b</em></td> + <td>c { }</td> + <td>d{ .foo }</td> + <td>e { .foo } f</td> + </tr> + <tr> + <td>valid on td</td> + <td>inline</td> + <td>empty</td> + <td>missing space</td> + <td>not at end</td> + </tr> + </tbody> + </table> + """ + ), + extensions=['attr_list', 'tables'] + ) diff --git a/tests/test_syntax/extensions/test_code_hilite.py b/tests/test_syntax/extensions/test_code_hilite.py new file mode 100644 index 0000000..09dd523 --- /dev/null +++ b/tests/test_syntax/extensions/test_code_hilite.py @@ -0,0 +1,764 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2019 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase +from markdown.extensions.codehilite import CodeHiliteExtension, CodeHilite +import os + +try: + import pygments # noqa + has_pygments = True +except ImportError: + has_pygments = False + +# The version required by the tests is the version specified and installed in the 'pygments' tox env. +# In any environment where the PYGMENTS_VERSION environment variable is either not defined or doesn't +# match the version of Pygments installed, all tests which rely in pygments will be skipped. +required_pygments_version = os.environ.get('PYGMENTS_VERSION', '') + + +class TestCodeHiliteClass(TestCase): + """ Test the markdown.extensions.codehilite.CodeHilite class. """ + + def setUp(self): + if has_pygments and pygments.__version__ != required_pygments_version: + self.skipTest(f'Pygments=={required_pygments_version} is required') + + maxDiff = None + + def assertOutputEquals(self, source, expected, **options): + """ + Test that source code block results in the expected output with given options. + """ + + output = CodeHilite(source, **options).hilite() + self.assertMultiLineEqual(output.strip(), expected) + + def test_codehilite_defaults(self): + if has_pygments: + # Odd result as no lang given and a single comment is not enough for guessing. + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="err"># A Code Comment</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code># A Code Comment\n' + '</code></pre>' + ) + self.assertOutputEquals('# A Code Comment', expected) + + def test_codehilite_guess_lang(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="cp"><?php</span> ' + '<span class="k">print</span><span class="p">(</span><span class="s2">"Hello World"</span>' + '<span class="p">);</span> <span class="cp">?></span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code><?php print("Hello World"); ?>\n' + '</code></pre>' + ) + # Use PHP as the the starting `<?php` tag ensures an accurate guess. + self.assertOutputEquals('<?php print("Hello World"); ?>', expected, guess_lang=True) + + def test_codehilite_guess_lang_plain_text(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="err">plain text</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code>plain text\n' + '</code></pre>' + ) + # This will be difficult to guess. + self.assertOutputEquals('plain text', expected, guess_lang=True) + + def test_codehilite_set_lang(self): + if has_pygments: + # Note an extra `<span class="x">` is added to end of code block when lang explicitly set. + # Compare with expected output for `test_guess_lang`. Not sure why this happens. + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="cp"><?php</span> ' + '<span class="k">print</span><span class="p">(</span><span class="s2">"Hello World"</span>' + '<span class="p">);</span> <span class="cp">?></span><span class="x"></span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-php"><?php print("Hello World"); ?>\n' + '</code></pre>' + ) + self.assertOutputEquals('<?php print("Hello World"); ?>', expected, lang='php') + + def test_codehilite_bad_lang(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="cp"><?php</span> ' + '<span class="k">print</span><span class="p">(</span><span class="s2">' + '"Hello World"</span><span class="p">);</span> <span class="cp">?></span>\n' + '</code></pre></div>' + ) + else: + # Note that without pygments there is no way to check that the language name is bad. + expected = ( + '<pre class="codehilite"><code class="language-unkown">' + '<?php print("Hello World"); ?>\n' + '</code></pre>' + ) + # The starting `<?php` tag ensures an accurate guess. + self.assertOutputEquals('<?php print("Hello World"); ?>', expected, lang='unkown') + + def test_codehilite_use_pygments_false(self): + expected = ( + '<pre class="codehilite"><code class="language-php"><?php print("Hello World"); ?>\n' + '</code></pre>' + ) + self.assertOutputEquals('<?php print("Hello World"); ?>', expected, lang='php', use_pygments=False) + + def test_codehilite_lang_prefix_empty(self): + expected = ( + '<pre class="codehilite"><code class="php"><?php print("Hello World"); ?>\n' + '</code></pre>' + ) + self.assertOutputEquals( + '<?php print("Hello World"); ?>', expected, lang='php', use_pygments=False, lang_prefix='' + ) + + def test_codehilite_lang_prefix(self): + expected = ( + '<pre class="codehilite"><code class="lang-php"><?php print("Hello World"); ?>\n' + '</code></pre>' + ) + self.assertOutputEquals( + '<?php print("Hello World"); ?>', expected, lang='php', use_pygments=False, lang_prefix='lang-' + ) + + def test_codehilite_linenos_true(self): + if has_pygments: + expected = ( + '<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div>' + '</td><td class="code"><div class="codehilite"><pre><span></span><code>plain text\n' + '</code></pre></div>\n' + '</td></tr></table>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text linenums">plain text\n' + '</code></pre>' + ) + self.assertOutputEquals('plain text', expected, lang='text', linenos=True) + + def test_codehilite_linenos_false(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code>plain text\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text">plain text\n' + '</code></pre>' + ) + self.assertOutputEquals('plain text', expected, lang='text', linenos=False) + + def test_codehilite_linenos_none(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code>plain text\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text">plain text\n' + '</code></pre>' + ) + self.assertOutputEquals('plain text', expected, lang='text', linenos=None) + + def test_codehilite_linenos_table(self): + if has_pygments: + expected = ( + '<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div>' + '</td><td class="code"><div class="codehilite"><pre><span></span><code>plain text\n' + '</code></pre></div>\n' + '</td></tr></table>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text linenums">plain text\n' + '</code></pre>' + ) + self.assertOutputEquals('plain text', expected, lang='text', linenos='table') + + def test_codehilite_linenos_inline(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="linenos">1</span>plain text\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text linenums">plain text\n' + '</code></pre>' + ) + self.assertOutputEquals('plain text', expected, lang='text', linenos='inline') + + def test_codehilite_linenums_true(self): + if has_pygments: + expected = ( + '<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div>' + '</td><td class="code"><div class="codehilite"><pre><span></span><code>plain text\n' + '</code></pre></div>\n' + '</td></tr></table>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text linenums">plain text\n' + '</code></pre>' + ) + self.assertOutputEquals('plain text', expected, lang='text', linenums=True) + + def test_codehilite_set_cssclass(self): + if has_pygments: + expected = ( + '<div class="override"><pre><span></span><code>plain text\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="override"><code class="language-text">plain text\n' + '</code></pre>' + ) + self.assertOutputEquals('plain text', expected, lang='text', cssclass='override') + + def test_codehilite_set_css_class(self): + if has_pygments: + expected = ( + '<div class="override"><pre><span></span><code>plain text\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="override"><code class="language-text">plain text\n' + '</code></pre>' + ) + self.assertOutputEquals('plain text', expected, lang='text', css_class='override') + + def test_codehilite_linenostart(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="linenos">42</span>plain text\n' + '</code></pre></div>' + ) + else: + # TODO: Implement linenostart for no-pygments. Will need to check what JS libs look for. + expected = ( + '<pre class="codehilite"><code class="language-text linenums">plain text\n' + '</code></pre>' + ) + self.assertOutputEquals('plain text', expected, lang='text', linenos='inline', linenostart=42) + + def test_codehilite_linenos_hl_lines(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code>' + '<span class="linenos">1</span><span class="hll">line 1\n' + '</span><span class="linenos">2</span>line 2\n' + '<span class="linenos">3</span><span class="hll">line 3\n' + '</span></code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text linenums">line 1\n' + 'line 2\n' + 'line 3\n' + '</code></pre>' + ) + self.assertOutputEquals('line 1\nline 2\nline 3', expected, lang='text', linenos='inline', hl_lines=[1, 3]) + + def test_codehilite_linenos_linenostep(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="linenos"> </span>line 1\n' + '<span class="linenos">2</span>line 2\n' + '<span class="linenos"> </span>line 3\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text linenums">line 1\n' + 'line 2\n' + 'line 3\n' + '</code></pre>' + ) + self.assertOutputEquals('line 1\nline 2\nline 3', expected, lang='text', linenos='inline', linenostep=2) + + def test_codehilite_linenos_linenospecial(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="linenos">1</span>line 1\n' + '<span class="linenos special">2</span>line 2\n' + '<span class="linenos">3</span>line 3\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text linenums">line 1\n' + 'line 2\n' + 'line 3\n' + '</code></pre>' + ) + self.assertOutputEquals('line 1\nline 2\nline 3', expected, lang='text', linenos='inline', linenospecial=2) + + def test_codehilite_startinline(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="k">print</span><span class="p">(</span>' + '<span class="s2">"Hello World"</span><span class="p">);</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-php">print("Hello World");\n' + '</code></pre>' + ) + self.assertOutputEquals('print("Hello World");', expected, lang='php', startinline=True) + + +class TestCodeHiliteExtension(TestCase): + """ Test codehilite extension. """ + + def setUp(self): + if has_pygments and pygments.__version__ != required_pygments_version: + self.skipTest(f'Pygments=={required_pygments_version} is required') + + # Define a custom Pygments formatter (same example in the documentation) + if has_pygments: + class CustomAddLangHtmlFormatter(pygments.formatters.HtmlFormatter): + def __init__(self, lang_str='', **options): + super().__init__(**options) + self.lang_str = lang_str + + def _wrap_code(self, source): + yield 0, f'<code class="{self.lang_str}">' + yield from source + yield 0, '</code>' + else: + CustomAddLangHtmlFormatter = None + + self.custom_pygments_formatter = CustomAddLangHtmlFormatter + + maxDiff = None + + def testBasicCodeHilite(self): + if has_pygments: + # Odd result as no lang given and a single comment is not enough for guessing. + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="err"># A Code Comment</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code># A Code Comment\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + '\t# A Code Comment', + expected, + extensions=['codehilite'] + ) + + def testLinenumsTrue(self): + if has_pygments: + expected = ( + '<table class="codehilitetable"><tr>' + '<td class="linenos"><div class="linenodiv"><pre>1</pre></div></td>' + '<td class="code"><div class="codehilite"><pre><span></span>' + '<code><span class="err"># A Code Comment</span>\n' + '</code></pre></div>\n' + '</td></tr></table>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="linenums"># A Code Comment\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + '\t# A Code Comment', + expected, + extensions=[CodeHiliteExtension(linenums=True)] + ) + + def testLinenumsFalse(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="c1"># A Code Comment</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-python"># A Code Comment\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + ( + '\t#!Python\n' + '\t# A Code Comment' + ), + expected, + extensions=[CodeHiliteExtension(linenums=False)] + ) + + def testLinenumsNone(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="err"># A Code Comment</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code># A Code Comment\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + '\t# A Code Comment', + expected, + extensions=[CodeHiliteExtension(linenums=None)] + ) + + def testLinenumsNoneWithShebang(self): + if has_pygments: + expected = ( + '<table class="codehilitetable"><tr>' + '<td class="linenos"><div class="linenodiv"><pre>1</pre></div></td>' + '<td class="code"><div class="codehilite"><pre><span></span>' + '<code><span class="c1"># A Code Comment</span>\n' + '</code></pre></div>\n' + '</td></tr></table>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-python linenums"># A Code Comment\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + ( + '\t#!Python\n' + '\t# A Code Comment' + ), + expected, + extensions=[CodeHiliteExtension(linenums=None)] + ) + + def testLinenumsNoneWithColon(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="c1"># A Code Comment</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-python"># A Code Comment\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + ( + '\t:::Python\n' + '\t# A Code Comment' + ), + expected, + extensions=[CodeHiliteExtension(linenums=None)] + ) + + def testHighlightLinesWithColon(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="hll"><span class="c1">#line 1</span>\n' + '</span><span class="c1">#line 2</span>\n' + '<span class="c1">#line 3</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-python">#line 1\n' + '#line 2\n' + '#line 3\n' + '</code></pre>' + ) + # Double quotes + self.assertMarkdownRenders( + ( + '\t:::Python hl_lines="1"\n' + '\t#line 1\n' + '\t#line 2\n' + '\t#line 3' + ), + expected, + extensions=['codehilite'] + ) + # Single quotes + self.assertMarkdownRenders( + ( + "\t:::Python hl_lines='1'\n" + '\t#line 1\n' + '\t#line 2\n' + '\t#line 3' + ), + expected, + extensions=['codehilite'] + ) + + def testUsePygmentsFalse(self): + self.assertMarkdownRenders( + ( + '\t:::Python\n' + '\t# A Code Comment' + ), + ( + '<pre class="codehilite"><code class="language-python"># A Code Comment\n' + '</code></pre>' + ), + extensions=[CodeHiliteExtension(use_pygments=False)] + ) + + def testLangPrefixEmpty(self): + self.assertMarkdownRenders( + ( + '\t:::Python\n' + '\t# A Code Comment' + ), + ( + '<pre class="codehilite"><code class="python"># A Code Comment\n' + '</code></pre>' + ), + extensions=[CodeHiliteExtension(use_pygments=False, lang_prefix='')] + ) + + def testLangPrefix(self): + self.assertMarkdownRenders( + ( + '\t:::Python\n' + '\t# A Code Comment' + ), + ( + '<pre class="codehilite"><code class="lang-python"># A Code Comment\n' + '</code></pre>' + ), + extensions=[CodeHiliteExtension(use_pygments=False, lang_prefix='lang-')] + ) + + def testDoubleEscape(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre>' + '<span></span>' + '<code><span class="p"><</span><span class="nt">span</span><span class="p">></span>' + 'This<span class="ni">&amp;</span>That' + '<span class="p"></</span><span class="nt">span</span><span class="p">></span>' + '\n</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-html">' + '<span>This&amp;That</span>\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + ( + '\t:::html\n' + '\t<span>This&That</span>' + ), + expected, + extensions=['codehilite'] + ) + + def testEntitiesIntact(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre>' + '<span></span>' + '<code>< &lt; and > &gt;' + '\n</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text">' + '< &lt; and > &gt;\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + ( + '\t:::text\n' + '\t< < and > >' + ), + expected, + extensions=['codehilite'] + ) + + def testHighlightAmps(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code>&\n' + '&amp;\n' + '&amp;amp;\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-text">&\n' + '&amp;\n' + '&amp;amp;\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + ( + '\t:::text\n' + '\t&\n' + '\t&\n' + '\t&amp;' + ), + expected, + extensions=['codehilite'] + ) + + def testUnknownOption(self): + if has_pygments: + # Odd result as no lang given and a single comment is not enough for guessing. + expected = ( + '<div class="codehilite"><pre><span></span><code><span class="err"># A Code Comment</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code># A Code Comment\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + '\t# A Code Comment', + expected, + extensions=[CodeHiliteExtension(unknown='some value')], + ) + + def testMultipleBlocksSameStyle(self): + if has_pygments: + # See also: https://github.com/Python-Markdown/markdown/issues/1240 + expected = ( + '<div class="codehilite" style="background: #202020"><pre style="line-height: 125%; margin: 0;">' + '<span></span><code><span style="color: #999999; font-style: italic"># First Code Block</span>\n' + '</code></pre></div>\n\n' + '<p>Normal paragraph</p>\n' + '<div class="codehilite" style="background: #202020"><pre style="line-height: 125%; margin: 0;">' + '<span></span><code><span style="color: #999999; font-style: italic"># Second Code Block</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-python"># First Code Block\n' + '</code></pre>\n\n' + '<p>Normal paragraph</p>\n' + '<pre class="codehilite"><code class="language-python"># Second Code Block\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + ( + '\t:::Python\n' + '\t# First Code Block\n\n' + 'Normal paragraph\n\n' + '\t:::Python\n' + '\t# Second Code Block' + ), + expected, + extensions=[CodeHiliteExtension(pygments_style="native", noclasses=True)] + ) + + def testFormatterLangStr(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code class="language-python">' + '<span class="c1"># A Code Comment</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-python"># A Code Comment\n' + '</code></pre>' + ) + + self.assertMarkdownRenders( + '\t:::Python\n' + '\t# A Code Comment', + expected, + extensions=[ + CodeHiliteExtension( + guess_lang=False, + pygments_formatter=self.custom_pygments_formatter + ) + ] + ) + + def testFormatterLangStrGuessLang(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span>' + '<code class="language-js+php"><span class="cp"><?php</span> ' + '<span class="k">print</span><span class="p">(</span>' + '<span class="s2">"Hello World"</span>' + '<span class="p">);</span> <span class="cp">?></span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code><?php print("Hello World"); ?>\n' + '</code></pre>' + ) + # Use PHP as the the starting `<?php` tag ensures an accurate guess. + self.assertMarkdownRenders( + '\t<?php print("Hello World"); ?>', + expected, + extensions=[CodeHiliteExtension(pygments_formatter=self.custom_pygments_formatter)] + ) + + def testFormatterLangStrEmptyLang(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span>' + '<code class="language-text"># A Code Comment\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code># A Code Comment\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + '\t# A Code Comment', + expected, + extensions=[ + CodeHiliteExtension( + guess_lang=False, + pygments_formatter=self.custom_pygments_formatter, + ) + ] + ) diff --git a/tests/test_syntax/extensions/test_def_list.py b/tests/test_syntax/extensions/test_def_list.py new file mode 100644 index 0000000..8273410 --- /dev/null +++ b/tests/test_syntax/extensions/test_def_list.py @@ -0,0 +1,323 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2019 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestDefList(TestCase): + + def test_def_list_with_ol(self): + self.assertMarkdownRenders( + self.dedent( + ''' + + term + + : this is a definition for term. it has + multiple lines in the first paragraph. + + 1. first thing + + first thing details in a second paragraph. + + 1. second thing + + second thing details in a second paragraph. + + 1. third thing + + third thing details in a second paragraph. + ''' + ), + self.dedent( + ''' + <dl> + <dt>term</dt> + <dd> + <p>this is a definition for term. it has + multiple lines in the first paragraph.</p> + <ol> + <li> + <p>first thing</p> + <p>first thing details in a second paragraph.</p> + </li> + <li> + <p>second thing</p> + <p>second thing details in a second paragraph.</p> + </li> + <li> + <p>third thing</p> + <p>third thing details in a second paragraph.</p> + </li> + </ol> + </dd> + </dl> + ''' + ), + extensions=['def_list'] + ) + + def test_def_list_with_ul(self): + self.assertMarkdownRenders( + self.dedent( + ''' + + term + + : this is a definition for term. it has + multiple lines in the first paragraph. + + - first thing + + first thing details in a second paragraph. + + - second thing + + second thing details in a second paragraph. + + - third thing + + third thing details in a second paragraph. + ''' + ), + self.dedent( + ''' + <dl> + <dt>term</dt> + <dd> + <p>this is a definition for term. it has + multiple lines in the first paragraph.</p> + <ul> + <li> + <p>first thing</p> + <p>first thing details in a second paragraph.</p> + </li> + <li> + <p>second thing</p> + <p>second thing details in a second paragraph.</p> + </li> + <li> + <p>third thing</p> + <p>third thing details in a second paragraph.</p> + </li> + </ul> + </dd> + </dl> + ''' + ), + extensions=['def_list'] + ) + + def test_def_list_with_nesting(self): + self.assertMarkdownRenders( + self.dedent( + ''' + + term + + : this is a definition for term. it has + multiple lines in the first paragraph. + + 1. first thing + + first thing details in a second paragraph. + + - first nested thing + + second nested thing details + ''' + ), + self.dedent( + ''' + <dl> + <dt>term</dt> + <dd> + <p>this is a definition for term. it has + multiple lines in the first paragraph.</p> + <ol> + <li> + <p>first thing</p> + <p>first thing details in a second paragraph.</p> + <ul> + <li> + <p>first nested thing</p> + <p>second nested thing details</p> + </li> + </ul> + </li> + </ol> + </dd> + </dl> + ''' + ), + extensions=['def_list'] + ) + + def test_def_list_with_nesting_self(self): + self.assertMarkdownRenders( + self.dedent( + ''' + + term + + : this is a definition for term. it has + multiple lines in the first paragraph. + + inception + + : this is a definition for term. it has + multiple lines in the first paragraph. + + - bullet point + + another paragraph + ''' + ), + self.dedent( + ''' + <dl> + <dt>term</dt> + <dd> + <p>this is a definition for term. it has + multiple lines in the first paragraph.</p> + <dl> + <dt>inception</dt> + <dd> + <p>this is a definition for term. it has + multiple lines in the first paragraph.</p> + <ul> + <li>bullet point</li> + </ul> + <p>another paragraph</p> + </dd> + </dl> + </dd> + </dl> + ''' + ), + extensions=['def_list'] + ) + + def test_def_list_unreasonable_nesting(self): + self.assertMarkdownRenders( + self.dedent( + ''' + + turducken + + : this is a definition for term. it has + multiple lines in the first paragraph. + + 1. ordered list + + - nested list + + term + + : definition + + - item 1 paragraph 1 + + item 1 paragraph 2 + ''' + ), + self.dedent( + ''' + <dl> + <dt>turducken</dt> + <dd> + <p>this is a definition for term. it has + multiple lines in the first paragraph.</p> + <ol> + <li> + <p>ordered list</p> + <ul> + <li> + <p>nested list</p> + <dl> + <dt>term</dt> + <dd> + <p>definition</p> + <ul> + <li> + <p>item 1 paragraph 1</p> + <p>item 1 paragraph 2</p> + </li> + </ul> + </dd> + </dl> + </li> + </ul> + </li> + </ol> + </dd> + </dl> + ''' + ), + extensions=['def_list'] + ) + + def test_def_list_nested_admontions(self): + self.assertMarkdownRenders( + self.dedent( + ''' + term + + : definition + + !!! note "Admontion" + + term + + : definition + + 1. list + + continue + ''' + ), + self.dedent( + ''' + <dl> + <dt>term</dt> + <dd> + <p>definition</p> + <div class="admonition note"> + <p class="admonition-title">Admontion</p> + <dl> + <dt>term</dt> + <dd> + <p>definition</p> + <ol> + <li> + <p>list</p> + <p>continue</p> + </li> + </ol> + </dd> + </dl> + </div> + </dd> + </dl> + ''' + ), + extensions=['def_list', 'admonition'] + ) diff --git a/tests/test_syntax/extensions/test_fenced_code.py b/tests/test_syntax/extensions/test_fenced_code.py new file mode 100644 index 0000000..be3c215 --- /dev/null +++ b/tests/test_syntax/extensions/test_fenced_code.py @@ -0,0 +1,1020 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2019 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase +import markdown +import markdown.extensions.codehilite +import os + +try: + import pygments # noqa + import pygments.formatters # noqa + has_pygments = True +except ImportError: + has_pygments = False + +# The version required by the tests is the version specified and installed in the 'pygments' tox env. +# In any environment where the PYGMENTS_VERSION environment variable is either not defined or doesn't +# match the version of Pygments installed, all tests which rely in pygments will be skipped. +required_pygments_version = os.environ.get('PYGMENTS_VERSION', '') + + +class TestFencedCode(TestCase): + + def testBasicFence(self): + self.assertMarkdownRenders( + self.dedent( + ''' + A paragraph before a fenced code block: + + ``` + Fenced code block + ``` + ''' + ), + self.dedent( + ''' + <p>A paragraph before a fenced code block:</p> + <pre><code>Fenced code block + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testNestedFence(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ```` + + ``` + ```` + ''' + ), + self.dedent( + ''' + <pre><code> + ``` + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testFencedTildes(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ~~~ + # Arbitrary code + ``` # these backticks will not close the block + ~~~ + ''' + ), + self.dedent( + ''' + <pre><code># Arbitrary code + ``` # these backticks will not close the block + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testFencedLanguageNoDot(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` python + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testFencedLanguageWithDot(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` .python + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def test_fenced_code_in_raw_html(self): + self.assertMarkdownRenders( + self.dedent( + """ + <details> + ``` + Begone placeholders! + ``` + </details> + """ + ), + self.dedent( + """ + <details> + + <pre><code>Begone placeholders! + </code></pre> + + </details> + """ + ), + extensions=['fenced_code'] + ) + + def testFencedLanguageInAttr(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` {.python} + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testFencedMultipleClassesInAttr(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` {.python .foo .bar} + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre class="foo bar"><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testFencedIdInAttr(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { #foo } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre id="foo"><code># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testFencedIdAndLangInAttr(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python #foo } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre id="foo"><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testFencedIdAndLangAndClassInAttr(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python #foo .bar } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre id="foo" class="bar"><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testFencedLanguageIdAndPygmentsDisabledInAttrNoCodehilite(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python #foo use_pygments=False } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre id="foo"><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testFencedLanguageIdAndPygmentsEnabledInAttrNoCodehilite(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python #foo use_pygments=True } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre id="foo"><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code'] + ) + + def testFencedLanguageNoCodehiliteWithAttrList(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python foo=bar } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre><code class="language-python" foo="bar"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code', 'attr_list'] + ) + + def testFencedLanguagePygmentsDisabledInAttrNoCodehiliteWithAttrList(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python foo=bar use_pygments=False } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre><code class="language-python" foo="bar"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code', 'attr_list'] + ) + + def testFencedLanguagePygmentsEnabledInAttrNoCodehiliteWithAttrList(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python foo=bar use_pygments=True } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code', 'attr_list'] + ) + + def testFencedLanguageNoPrefix(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` python + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre><code class="python"># Some python code + </code></pre> + ''' + ), + extensions=[markdown.extensions.fenced_code.FencedCodeExtension(lang_prefix='')] + ) + + def testFencedLanguageAltPrefix(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` python + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre><code class="lang-python"># Some python code + </code></pre> + ''' + ), + extensions=[markdown.extensions.fenced_code.FencedCodeExtension(lang_prefix='lang-')] + ) + + def testFencedCodeEscapedAttrs(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { ."weird #"foo bar=">baz } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre id=""foo"><code class="language-"weird" bar="">baz"># Some python code + </code></pre> + ''' + ), + extensions=['fenced_code', 'attr_list'] + ) + + +class TestFencedCodeWithCodehilite(TestCase): + + def setUp(self): + if has_pygments and pygments.__version__ != required_pygments_version: + self.skipTest(f'Pygments=={required_pygments_version} is required') + + def test_shebang(self): + + if has_pygments: + expected = ''' + <div class="codehilite"><pre><span></span><code>#!test + </code></pre></div> + ''' + else: + expected = ''' + <pre class="codehilite"><code>#!test + </code></pre> + ''' + + self.assertMarkdownRenders( + self.dedent( + ''' + ``` + #!test + ``` + ''' + ), + self.dedent( + expected + ), + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension(linenums=None, guess_lang=False), + 'fenced_code' + ] + ) + + def testFencedCodeWithHighlightLines(self): + if has_pygments: + expected = self.dedent( + ''' + <div class="codehilite"><pre><span></span><code><span class="hll">line 1 + </span>line 2 + <span class="hll">line 3 + </span></code></pre></div> + ''' + ) + else: + expected = self.dedent( + ''' + <pre class="codehilite"><code>line 1 + line 2 + line 3 + </code></pre> + ''' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ```hl_lines="1 3" + line 1 + line 2 + line 3 + ``` + ''' + ), + expected, + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension(linenums=None, guess_lang=False), + 'fenced_code' + ] + ) + + def testFencedLanguageAndHighlightLines(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code>' + '<span class="hll"><span class="n">line</span> <span class="mi">1</span>\n' + '</span><span class="n">line</span> <span class="mi">2</span>\n' + '<span class="hll"><span class="n">line</span> <span class="mi">3</span>\n' + '</span></code></pre></div>' + ) + else: + expected = self.dedent( + ''' + <pre class="codehilite"><code class="language-python">line 1 + line 2 + line 3 + </code></pre> + ''' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ``` .python hl_lines="1 3" + line 1 + line 2 + line 3 + ``` + ''' + ), + expected, + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension(linenums=None, guess_lang=False), + 'fenced_code' + ] + ) + + def testFencedLanguageAndPygmentsDisabled(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` .python + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension(use_pygments=False), + 'fenced_code' + ] + ) + + def testFencedLanguageDoubleEscape(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code>' + '<span class="p"><</span><span class="nt">span</span>' + '<span class="p">></span>This<span class="ni">&amp;</span>' + 'That<span class="p"></</span><span class="nt">span</span>' + '<span class="p">></span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-html">' + '<span>This&amp;That</span>\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ```html + <span>This&That</span> + ``` + ''' + ), + expected, + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension(), + 'fenced_code' + ] + ) + + def testFencedAmps(self): + if has_pygments: + expected = self.dedent( + ''' + <div class="codehilite"><pre><span></span><code>& + &amp; + &amp;amp; + </code></pre></div> + ''' + ) + else: + expected = self.dedent( + ''' + <pre class="codehilite"><code class="language-text">& + &amp; + &amp;amp; + </code></pre> + ''' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ```text + & + & + &amp; + ``` + ''' + ), + expected, + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension(), + 'fenced_code' + ] + ) + + def testFencedCodeWithHighlightLinesInAttr(self): + if has_pygments: + expected = self.dedent( + ''' + <div class="codehilite"><pre><span></span><code><span class="hll">line 1 + </span>line 2 + <span class="hll">line 3 + </span></code></pre></div> + ''' + ) + else: + expected = self.dedent( + ''' + <pre class="codehilite"><code>line 1 + line 2 + line 3 + </code></pre> + ''' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ```{ hl_lines="1 3" } + line 1 + line 2 + line 3 + ``` + ''' + ), + expected, + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension(linenums=None, guess_lang=False), + 'fenced_code' + ] + ) + + def testFencedLanguageAndHighlightLinesInAttr(self): + if has_pygments: + expected = ( + '<div class="codehilite"><pre><span></span><code>' + '<span class="hll"><span class="n">line</span> <span class="mi">1</span>\n' + '</span><span class="n">line</span> <span class="mi">2</span>\n' + '<span class="hll"><span class="n">line</span> <span class="mi">3</span>\n' + '</span></code></pre></div>' + ) + else: + expected = self.dedent( + ''' + <pre class="codehilite"><code class="language-python">line 1 + line 2 + line 3 + </code></pre> + ''' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python hl_lines="1 3" } + line 1 + line 2 + line 3 + ``` + ''' + ), + expected, + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension(linenums=None, guess_lang=False), + 'fenced_code' + ] + ) + + def testFencedLanguageIdInAttrAndPygmentsDisabled(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python #foo } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre id="foo"><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension(use_pygments=False), + 'fenced_code' + ] + ) + + def testFencedLanguageIdAndPygmentsDisabledInAttr(self): + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python #foo use_pygments=False } + # Some python code + ``` + ''' + ), + self.dedent( + ''' + <pre id="foo"><code class="language-python"># Some python code + </code></pre> + ''' + ), + extensions=['codehilite', 'fenced_code'] + ) + + def testFencedLanguageAttrCssclass(self): + if has_pygments: + expected = self.dedent( + ''' + <div class="pygments"><pre><span></span><code><span class="c1"># Some python code</span> + </code></pre></div> + ''' + ) + else: + expected = ( + '<pre class="pygments"><code class="language-python"># Some python code\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python css_class='pygments' } + # Some python code + ``` + ''' + ), + expected, + extensions=['codehilite', 'fenced_code'] + ) + + def testFencedLanguageAttrLinenums(self): + if has_pygments: + expected = ( + '<table class="codehilitetable"><tr>' + '<td class="linenos"><div class="linenodiv"><pre>1</pre></div></td>' + '<td class="code"><div class="codehilite"><pre><span></span>' + '<code><span class="c1"># Some python code</span>\n' + '</code></pre></div>\n' + '</td></tr></table>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-python linenums"># Some python code\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python linenums=True } + # Some python code + ``` + ''' + ), + expected, + extensions=['codehilite', 'fenced_code'] + ) + + def testFencedLanguageAttrGuesslang(self): + if has_pygments: + expected = self.dedent( + ''' + <div class="codehilite"><pre><span></span><code># Some python code + </code></pre></div> + ''' + ) + else: + expected = ( + '<pre class="codehilite"><code># Some python code\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { guess_lang=False } + # Some python code + ``` + ''' + ), + expected, + extensions=['codehilite', 'fenced_code'] + ) + + def testFencedLanguageAttrNoclasses(self): + if has_pygments: + expected = ( + '<div class="codehilite" style="background: #f8f8f8">' + '<pre style="line-height: 125%; margin: 0;"><span></span><code>' + '<span style="color: #408080; font-style: italic"># Some python code</span>\n' + '</code></pre></div>' + ) + else: + expected = ( + '<pre class="codehilite"><code class="language-python"># Some python code\n' + '</code></pre>' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python noclasses=True } + # Some python code + ``` + ''' + ), + expected, + extensions=['codehilite', 'fenced_code'] + ) + + def testFencedMultipleBlocksSameStyle(self): + if has_pygments: + # See also: https://github.com/Python-Markdown/markdown/issues/1240 + expected = ( + '<div class="codehilite" style="background: #202020"><pre style="line-height: 125%; margin: 0;">' + '<span></span><code><span style="color: #999999; font-style: italic"># First Code Block</span>\n' + '</code></pre></div>\n\n' + '<p>Normal paragraph</p>\n' + '<div class="codehilite" style="background: #202020"><pre style="line-height: 125%; margin: 0;">' + '<span></span><code><span style="color: #999999; font-style: italic"># Second Code Block</span>\n' + '</code></pre></div>' + ) + else: + expected = ''' + <pre class="codehilite"><code class="language-python"># First Code Block + </code></pre> + + <p>Normal paragraph</p> + <pre class="codehilite"><code class="language-python"># Second Code Block + </code></pre> + ''' + + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .python } + # First Code Block + ``` + + Normal paragraph + + ``` { .python } + # Second Code Block + ``` + ''' + ), + self.dedent( + expected + ), + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension(pygments_style="native", noclasses=True), + 'fenced_code' + ] + ) + + def testCustomPygmentsFormatter(self): + if has_pygments: + class CustomFormatter(pygments.formatters.HtmlFormatter): + def wrap(self, source, outfile): + return self._wrap_div(self._wrap_code(source)) + + def _wrap_code(self, source): + yield 0, '<code>' + for i, t in source: + if i == 1: + t += '<br>' + yield i, t + yield 0, '</code>' + + expected = ''' + <div class="codehilite"><code>hello world + <br>hello another world + <br></code></div> + ''' + + else: + CustomFormatter = None + expected = ''' + <pre class="codehilite"><code>hello world + hello another world + </code></pre> + ''' + + self.assertMarkdownRenders( + self.dedent( + ''' + ``` + hello world + hello another world + ``` + ''' + ), + self.dedent( + expected + ), + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension( + pygments_formatter=CustomFormatter, + guess_lang=False, + ), + 'fenced_code' + ] + ) + + def testPygmentsAddLangClassFormatter(self): + if has_pygments: + class CustomAddLangHtmlFormatter(pygments.formatters.HtmlFormatter): + def __init__(self, lang_str='', **options): + super().__init__(**options) + self.lang_str = lang_str + + def _wrap_code(self, source): + yield 0, f'<code class="{self.lang_str}">' + yield from source + yield 0, '</code>' + + expected = ''' + <div class="codehilite"><pre><span></span><code class="language-text">hello world + hello another world + </code></pre></div> + ''' + else: + CustomAddLangHtmlFormatter = None + expected = ''' + <pre class="codehilite"><code class="language-text">hello world + hello another world + </code></pre> + ''' + + self.assertMarkdownRenders( + self.dedent( + ''' + ```text + hello world + hello another world + ``` + ''' + ), + self.dedent( + expected + ), + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension( + guess_lang=False, + pygments_formatter=CustomAddLangHtmlFormatter, + ), + 'fenced_code' + ] + ) + + def testSvgCustomPygmentsFormatter(self): + if has_pygments: + expected = ''' + <?xml version="1.0"?> + <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> + <svg xmlns="http://www.w3.org/2000/svg"> + <g font-family="monospace" font-size="14px"> + <text x="0" y="14" xml:space="preserve">hello world</text> + <text x="0" y="33" xml:space="preserve">hello another world</text> + <text x="0" y="52" xml:space="preserve"></text></g></svg> + ''' + + else: + expected = ''' + <pre class="codehilite"><code>hello world + hello another world + </code></pre> + ''' + + self.assertMarkdownRenders( + self.dedent( + ''' + ``` + hello world + hello another world + ``` + ''' + ), + self.dedent( + expected + ), + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension( + pygments_formatter='svg', + linenos=False, + guess_lang=False, + ), + 'fenced_code' + ] + ) + + def testInvalidCustomPygmentsFormatter(self): + if has_pygments: + expected = ''' + <div class="codehilite"><pre><span></span><code>hello world + hello another world + </code></pre></div> + ''' + + else: + expected = ''' + <pre class="codehilite"><code>hello world + hello another world + </code></pre> + ''' + + self.assertMarkdownRenders( + self.dedent( + ''' + ``` + hello world + hello another world + ``` + ''' + ), + self.dedent( + expected + ), + extensions=[ + markdown.extensions.codehilite.CodeHiliteExtension( + pygments_formatter='invalid', + guess_lang=False, + ), + 'fenced_code' + ] + ) diff --git a/tests/test_syntax/extensions/test_footnotes.py b/tests/test_syntax/extensions/test_footnotes.py new file mode 100644 index 0000000..9a6b32a --- /dev/null +++ b/tests/test_syntax/extensions/test_footnotes.py @@ -0,0 +1,338 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestFootnotes(TestCase): + + default_kwargs = {'extensions': ['footnotes']} + maxDiff = None + + def test_basic_footnote(self): + self.assertMarkdownRenders( + self.dedent( + """ + paragraph[^1] + + [^1]: A Footnote + """ + ), + '<p>paragraph<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>A Footnote <a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote 1 in the text">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>' + ) + + def test_multiple_footnotes(self): + self.assertMarkdownRenders( + self.dedent( + """ + foo[^1] + + bar[^2] + + [^1]: Footnote 1 + [^2]: Footnote 2 + """ + ), + '<p>foo<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '<p>bar<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>Footnote 1 <a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote 1 in the text">↩</a></p>\n' + '</li>\n' + '<li id="fn:2">\n' + '<p>Footnote 2 <a class="footnote-backref" href="#fnref:2"' + ' title="Jump back to footnote 2 in the text">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>' + ) + + def test_multiple_footnotes_multiline(self): + self.assertMarkdownRenders( + self.dedent( + """ + foo[^1] + + bar[^2] + + [^1]: Footnote 1 + line 2 + [^2]: Footnote 2 + """ + ), + '<p>foo<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '<p>bar<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>Footnote 1\nline 2 <a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote 1 in the text">↩</a></p>\n' + '</li>\n' + '<li id="fn:2">\n' + '<p>Footnote 2 <a class="footnote-backref" href="#fnref:2"' + ' title="Jump back to footnote 2 in the text">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>' + ) + + def test_footnote_multi_line(self): + self.assertMarkdownRenders( + self.dedent( + """ + paragraph[^1] + [^1]: A Footnote + line 2 + """ + ), + '<p>paragraph<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>A Footnote\nline 2 <a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote 1 in the text">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>' + ) + + def test_footnote_multi_line_lazy_indent(self): + self.assertMarkdownRenders( + self.dedent( + """ + paragraph[^1] + [^1]: A Footnote + line 2 + """ + ), + '<p>paragraph<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>A Footnote\nline 2 <a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote 1 in the text">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>' + ) + + def test_footnote_multi_line_complex(self): + self.assertMarkdownRenders( + self.dedent( + """ + paragraph[^1] + + [^1]: + + A Footnote + line 2 + + * list item + + > blockquote + """ + ), + '<p>paragraph<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>A Footnote\nline 2</p>\n' + '<ul>\n<li>list item</li>\n</ul>\n' + '<blockquote>\n<p>blockquote</p>\n</blockquote>\n' + '<p><a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote 1 in the text">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>' + ) + + def test_footnote_multple_complex(self): + self.assertMarkdownRenders( + self.dedent( + """ + foo[^1] + + bar[^2] + + [^1]: + + A Footnote + line 2 + + * list item + + > blockquote + + [^2]: Second footnote + + paragraph 2 + """ + ), + '<p>foo<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '<p>bar<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>A Footnote\nline 2</p>\n' + '<ul>\n<li>list item</li>\n</ul>\n' + '<blockquote>\n<p>blockquote</p>\n</blockquote>\n' + '<p><a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote 1 in the text">↩</a></p>\n' + '</li>\n' + '<li id="fn:2">\n' + '<p>Second footnote</p>\n' + '<p>paragraph 2 <a class="footnote-backref" href="#fnref:2"' + ' title="Jump back to footnote 2 in the text">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>' + ) + + def test_footnote_multple_complex_no_blank_line_between(self): + self.assertMarkdownRenders( + self.dedent( + """ + foo[^1] + + bar[^2] + + [^1]: + + A Footnote + line 2 + + * list item + + > blockquote + [^2]: Second footnote + + paragraph 2 + """ + ), + '<p>foo<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '<p>bar<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>A Footnote\nline 2</p>\n' + '<ul>\n<li>list item</li>\n</ul>\n' + '<blockquote>\n<p>blockquote</p>\n</blockquote>\n' + '<p><a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote 1 in the text">↩</a></p>\n' + '</li>\n' + '<li id="fn:2">\n' + '<p>Second footnote</p>\n' + '<p>paragraph 2 <a class="footnote-backref" href="#fnref:2"' + ' title="Jump back to footnote 2 in the text">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>' + ) + + def test_backlink_text(self): + """Test backlink configuration.""" + + self.assertMarkdownRenders( + 'paragraph[^1]\n\n[^1]: A Footnote', + '<p>paragraph<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>A Footnote <a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote 1 in the text">back</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>', + extension_configs={'footnotes': {'BACKLINK_TEXT': 'back'}} + ) + + def test_footnote_separator(self): + """Test separator configuration.""" + + self.assertMarkdownRenders( + 'paragraph[^1]\n\n[^1]: A Footnote', + '<p>paragraph<sup id="fnref-1"><a class="footnote-ref" href="#fn-1">1</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn-1">\n' + '<p>A Footnote <a class="footnote-backref" href="#fnref-1"' + ' title="Jump back to footnote 1 in the text">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>', + extension_configs={'footnotes': {'SEPARATOR': '-'}} + ) + + def test_backlink_title(self): + """Test backlink title configuration without placeholder.""" + + self.assertMarkdownRenders( + 'paragraph[^1]\n\n[^1]: A Footnote', + '<p>paragraph<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>A Footnote <a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>', + extension_configs={'footnotes': {'BACKLINK_TITLE': 'Jump back to footnote'}} + ) + + def test_superscript_text(self): + """Test superscript text configuration.""" + + self.assertMarkdownRenders( + 'paragraph[^1]\n\n[^1]: A Footnote', + '<p>paragraph<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">[1]</a></sup></p>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>A Footnote <a class="footnote-backref" href="#fnref:1"' + ' title="Jump back to footnote 1 in the text">↩</a></p>\n' + '</li>\n' + '</ol>\n' + '</div>', + extension_configs={'footnotes': {'SUPERSCRIPT_TEXT': '[{}]'}} + ) diff --git a/tests/test_syntax/extensions/test_legacy_attrs.py b/tests/test_syntax/extensions/test_legacy_attrs.py new file mode 100644 index 0000000..b4ba5e7 --- /dev/null +++ b/tests/test_syntax/extensions/test_legacy_attrs.py @@ -0,0 +1,67 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestLegacyAtrributes(TestCase): + + maxDiff = None + + def testLegacyAttrs(self): + self.assertMarkdownRenders( + self.dedent(""" + # Header {@id=inthebeginning} + + Now, let's try something *inline{@class=special}*, to see if it works + + @id=TABLE.OF.CONTENTS} + + + * {@id=TABLEOFCONTENTS} + + + Or in the middle of the text {@id=TABLEOFCONTENTS} + + {@id=tableofcontents} + + [![{@style=float: left; margin: 10px; border: + none;}](http://fourthought.com/images/ftlogo.png "Fourthought + logo")](http://fourthought.com/) + + ![img{@id=foo}][img] + + [img]: http://example.com/i.jpg + """), + self.dedent(""" + <h1 id="inthebeginning">Header </h1> + <p>Now, let's try something <em class="special">inline</em>, to see if it works</p> + <p>@id=TABLE.OF.CONTENTS}</p> + <ul> + <li id="TABLEOFCONTENTS"></li> + </ul> + <p id="TABLEOFCONTENTS">Or in the middle of the text </p> + <p id="tableofcontents"></p> + <p><a href="http://fourthought.com/"><img alt="" src="http://fourthought.com/images/ftlogo.png" style="float: left; margin: 10px; border: none;" title="Fourthought logo" /></a></p> + <p><img alt="img" id="foo" src="http://example.com/i.jpg" /></p> + """), # noqa: E501 + extensions=['legacy_attrs'] + ) diff --git a/tests/test_syntax/extensions/test_legacy_em.py b/tests/test_syntax/extensions/test_legacy_em.py new file mode 100644 index 0000000..c5daf1c --- /dev/null +++ b/tests/test_syntax/extensions/test_legacy_em.py @@ -0,0 +1,66 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestLegacyEm(TestCase): + def test_legacy_emphasis(self): + self.assertMarkdownRenders( + '_connected_words_', + '<p><em>connected</em>words_</p>', + extensions=['legacy_em'] + ) + + def test_legacy_strong(self): + self.assertMarkdownRenders( + '__connected__words__', + '<p><strong>connected</strong>words__</p>', + extensions=['legacy_em'] + ) + + def test_complex_emphasis_underscore(self): + self.assertMarkdownRenders( + 'This is text __bold _italic bold___ with more text', + '<p>This is text <strong>bold <em>italic bold</em></strong> with more text</p>', + extensions=['legacy_em'] + ) + + def test_complex_emphasis_underscore_mid_word(self): + self.assertMarkdownRenders( + 'This is text __bold_italic bold___ with more text', + '<p>This is text <strong>bold<em>italic bold</em></strong> with more text</p>', + extensions=['legacy_em'] + ) + + def test_complex_multple_underscore_type(self): + + self.assertMarkdownRenders( + 'traced ___along___ bla __blocked__ if other ___or___', + '<p>traced <strong><em>along</em></strong> bla <strong>blocked</strong> if other <strong><em>or</em></strong></p>' # noqa: E501 + ) + + def test_complex_multple_underscore_type_variant2(self): + + self.assertMarkdownRenders( + 'on the __1-4 row__ of the AP Combat Table ___and___ receive', + '<p>on the <strong>1-4 row</strong> of the AP Combat Table <strong><em>and</em></strong> receive</p>' + ) diff --git a/tests/test_syntax/extensions/test_md_in_html.py b/tests/test_syntax/extensions/test_md_in_html.py new file mode 100644 index 0000000..6c13f11 --- /dev/null +++ b/tests/test_syntax/extensions/test_md_in_html.py @@ -0,0 +1,1216 @@ +# -*- coding: utf-8 -*- +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from unittest import TestSuite +from markdown.test_tools import TestCase +from ..blocks.test_html_blocks import TestHTMLBlocks +from markdown import Markdown +from xml.etree.ElementTree import Element + + +class TestMarkdownInHTMLPostProcessor(TestCase): + """ Ensure any remaining elements in HTML stash are properly serialized. """ + + def test_stash_to_string(self): + # There should be no known cases where this actually happens so we need to + # forcefully pass an etree Element to the method to ensure proper behavior. + element = Element('div') + element.text = 'Foo bar.' + md = Markdown(extensions=['md_in_html']) + result = md.postprocessors['raw_html'].stash_to_string(element) + self.assertEqual(result, '<div>Foo bar.</div>') + + +class TestDefaultwMdInHTML(TestHTMLBlocks): + """ Ensure the md_in_html extension does not break the default behavior. """ + + default_kwargs = {'extensions': ['md_in_html']} + + +class TestMdInHTML(TestCase): + + default_kwargs = {'extensions': ['md_in_html']} + + def test_md1_paragraph(self): + self.assertMarkdownRenders( + '<p markdown="1">*foo*</p>', + '<p><em>foo</em></p>' + ) + + def test_md1_p_linebreaks(self): + self.assertMarkdownRenders( + self.dedent( + """ + <p markdown="1"> + *foo* + </p> + """ + ), + self.dedent( + """ + <p> + <em>foo</em> + </p> + """ + ) + ) + + def test_md1_p_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + <p markdown="1"> + + *foo* + + </p> + """ + ), + self.dedent( + """ + <p> + + <em>foo</em> + + </p> + """ + ) + ) + + def test_md1_div(self): + self.assertMarkdownRenders( + '<div markdown="1">*foo*</div>', + self.dedent( + """ + <div> + <p><em>foo</em></p> + </div> + """ + ) + ) + + def test_md1_div_linebreaks(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + *foo* + </div> + """ + ), + self.dedent( + """ + <div> + <p><em>foo</em></p> + </div> + """ + ) + ) + + def test_md1_code_span(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + `<h1>code span</h1>` + </div> + """ + ), + self.dedent( + """ + <div> + <p><code><h1>code span</h1></code></p> + </div> + """ + ) + ) + + def test_md1_code_span_oneline(self): + self.assertMarkdownRenders( + '<div markdown="1">`<h1>code span</h1>`</div>', + self.dedent( + """ + <div> + <p><code><h1>code span</h1></code></p> + </div> + """ + ) + ) + + def test_md1_code_span_unclosed(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + `<p>` + </div> + """ + ), + self.dedent( + """ + <div> + <p><code><p></code></p> + </div> + """ + ) + ) + + def test_md1_code_span_script_tag(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + `<script>` + </div> + """ + ), + self.dedent( + """ + <div> + <p><code><script></code></p> + </div> + """ + ) + ) + + def test_md1_div_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + + *foo* + + </div> + """ + ), + self.dedent( + """ + <div> + <p><em>foo</em></p> + </div> + """ + ) + ) + + def test_md1_div_multi(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + + *foo* + + __bar__ + + </div> + """ + ), + self.dedent( + """ + <div> + <p><em>foo</em></p> + <p><strong>bar</strong></p> + </div> + """ + ) + ) + + def test_md1_div_nested(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + + <div markdown="1"> + *foo* + </div> + + </div> + """ + ), + self.dedent( + """ + <div> + <div> + <p><em>foo</em></p> + </div> + </div> + """ + ) + ) + + def test_md1_div_multi_nest(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + + <div markdown="1"> + <p markdown="1">*foo*</p> + </div> + + </div> + """ + ), + self.dedent( + """ + <div> + <div> + <p><em>foo</em></p> + </div> + </div> + """ + ) + ) + + def text_md1_details(self): + self.assertMarkdownRenders( + self.dedent( + """ + <details markdown="1"> + <summary>Click to expand</summary> + *foo* + </details> + """ + ), + self.dedent( + """ + <details> + <summary>Click to expand</summary> + <p><em>foo</em></p> + </details> + """ + ) + ) + + def test_md1_mix(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + A _Markdown_ paragraph before a raw child. + + <p markdown="1">A *raw* child.</p> + + A _Markdown_ tail to the raw child. + </div> + """ + ), + self.dedent( + """ + <div> + <p>A <em>Markdown</em> paragraph before a raw child.</p> + <p>A <em>raw</em> child.</p> + <p>A <em>Markdown</em> tail to the raw child.</p> + </div> + """ + ) + ) + + def test_md1_deep_mix(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + + A _Markdown_ paragraph before a raw child. + + A second Markdown paragraph + with two lines. + + <div markdown="1"> + + A *raw* child. + + <p markdown="1">*foo*</p> + + Raw child tail. + + </div> + + A _Markdown_ tail to the raw child. + + A second tail item + with two lines. + + <p markdown="1">More raw.</p> + + </div> + """ + ), + self.dedent( + """ + <div> + <p>A <em>Markdown</em> paragraph before a raw child.</p> + <p>A second Markdown paragraph + with two lines.</p> + <div> + <p>A <em>raw</em> child.</p> + <p><em>foo</em></p> + <p>Raw child tail.</p> + </div> + <p>A <em>Markdown</em> tail to the raw child.</p> + <p>A second tail item + with two lines.</p> + <p>More raw.</p> + </div> + """ + ) + ) + + def test_md1_div_raw_inline(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + + <em>foo</em> + + </div> + """ + ), + self.dedent( + """ + <div> + <p><em>foo</em></p> + </div> + """ + ) + ) + + def test_no_md1_paragraph(self): + self.assertMarkdownRenders( + '<p>*foo*</p>', + '<p>*foo*</p>' + ) + + def test_no_md1_nest(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + A _Markdown_ paragraph before a raw child. + + <p>A *raw* child.</p> + + A _Markdown_ tail to the raw child. + </div> + """ + ), + self.dedent( + """ + <div> + <p>A <em>Markdown</em> paragraph before a raw child.</p> + <p>A *raw* child.</p> + <p>A <em>Markdown</em> tail to the raw child.</p> + </div> + """ + ) + ) + + def test_md1_nested_empty(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + A _Markdown_ paragraph before a raw empty tag. + + <img src="image.png" alt="An image" /> + + A _Markdown_ tail to the raw empty tag. + </div> + """ + ), + self.dedent( + """ + <div> + <p>A <em>Markdown</em> paragraph before a raw empty tag.</p> + <p><img src="image.png" alt="An image" /></p> + <p>A <em>Markdown</em> tail to the raw empty tag.</p> + </div> + """ + ) + ) + + def test_md1_nested_empty_block(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + A _Markdown_ paragraph before a raw empty tag. + + <hr /> + + A _Markdown_ tail to the raw empty tag. + </div> + """ + ), + self.dedent( + """ + <div> + <p>A <em>Markdown</em> paragraph before a raw empty tag.</p> + <hr /> + <p>A <em>Markdown</em> tail to the raw empty tag.</p> + </div> + """ + ) + ) + + def test_empty_tags(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + <div></div> + </div> + """ + ), + self.dedent( + """ + <div> + <div></div> + </div> + """ + ) + ) + + def test_orphan_end_tag_in_raw_html(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + <div> + Test + + </pre> + + Test + </div> + </div> + """ + ), + self.dedent( + """ + <div> + <div> + Test + + </pre> + + Test + </div> + </div> + """ + ) + ) + + def test_complex_nested_case(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + **test** + <div> + **test** + <img src=""/> + <code>Test</code> + <span>**test**</span> + <p>Test 2</p> + </div> + </div> + """ + ), + self.dedent( + """ + <div> + <p><strong>test</strong></p> + <div> + **test** + <img src=""/> + <code>Test</code> + <span>**test**</span> + <p>Test 2</p> + </div> + </div> + """ + ) + ) + + def test_complex_nested_case_whitespace(self): + self.assertMarkdownRenders( + self.dedent( + """ + Text with space\t + <div markdown="1">\t + \t + <div> + **test** + <img src=""/> + <code>Test</code> + <span>**test**</span> + <div>With whitespace</div> + <p>Test 2</p> + </div> + **test** + </div> + """ + ), + self.dedent( + """ + <p>Text with space </p> + <div> + <div> + **test** + <img src=""/> + <code>Test</code> + <span>**test**</span> + <div>With whitespace</div> + <p>Test 2</p> + </div> + <p><strong>test</strong></p> + </div> + """ + ) + ) + + def test_md1_intail_md1(self): + self.assertMarkdownRenders( + '<div markdown="1">*foo*</div><div markdown="1">*bar*</div>', + self.dedent( + """ + <div> + <p><em>foo</em></p> + </div> + <div> + <p><em>bar</em></p> + </div> + """ + ) + ) + + def test_md1_no_blank_line_before(self): + self.assertMarkdownRenders( + self.dedent( + """ + A _Markdown_ paragraph with no blank line after. + <div markdown="1"> + A _Markdown_ paragraph in an HTML block with no blank line before. + </div> + """ + ), + self.dedent( + """ + <p>A <em>Markdown</em> paragraph with no blank line after.</p> + <div> + <p>A <em>Markdown</em> paragraph in an HTML block with no blank line before.</p> + </div> + """ + ) + ) + + def test_md1_no_line_break(self): + # The div here is parsed as a span-level element. Bad input equals bad output! + self.assertMarkdownRenders( + 'A _Markdown_ paragraph with <div markdown="1">no _line break_.</div>', + '<p>A <em>Markdown</em> paragraph with <div markdown="1">no <em>line break</em>.</div></p>' + ) + + def test_md1_in_tail(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div></div><div markdown="1"> + A _Markdown_ paragraph in an HTML block in tail of previous element. + </div> + """ + ), + self.dedent( + """ + <div></div> + <div> + <p>A <em>Markdown</em> paragraph in an HTML block in tail of previous element.</p> + </div> + """ + ) + ) + + def test_md1_PI_oneliner(self): + self.assertMarkdownRenders( + '<div markdown="1"><?php print("foo"); ?></div>', + self.dedent( + """ + <div> + <?php print("foo"); ?> + </div> + """ + ) + ) + + def test_md1_PI_multiline(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + <?php print("foo"); ?> + </div> + """ + ), + self.dedent( + """ + <div> + <?php print("foo"); ?> + </div> + """ + ) + ) + + def test_md1_PI_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + + <?php print("foo"); ?> + + </div> + """ + ), + self.dedent( + """ + <div> + <?php print("foo"); ?> + </div> + """ + ) + ) + + def test_md_span_paragraph(self): + self.assertMarkdownRenders( + '<p markdown="span">*foo*</p>', + '<p><em>foo</em></p>' + ) + + def test_md_block_paragraph(self): + self.assertMarkdownRenders( + '<p markdown="block">*foo*</p>', + self.dedent( + """ + <p> + <p><em>foo</em></p> + </p> + """ + ) + ) + + def test_md_span_div(self): + self.assertMarkdownRenders( + '<div markdown="span">*foo*</div>', + '<div><em>foo</em></div>' + ) + + def test_md_block_div(self): + self.assertMarkdownRenders( + '<div markdown="block">*foo*</div>', + self.dedent( + """ + <div> + <p><em>foo</em></p> + </div> + """ + ) + ) + + def test_md_span_nested_in_block(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="block"> + <div markdown="span">*foo*</div> + </div> + """ + ), + self.dedent( + """ + <div> + <div><em>foo</em></div> + </div> + """ + ) + ) + + def test_md_block_nested_in_span(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="span"> + <div markdown="block">*foo*</div> + </div> + """ + ), + self.dedent( + """ + <div> + <div><em>foo</em></div> + </div> + """ + ) + ) + + def test_md_block_after_span_nested_in_block(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="block"> + <div markdown="span">*foo*</div> + <div markdown="block">*bar*</div> + </div> + """ + ), + self.dedent( + """ + <div> + <div><em>foo</em></div> + <div> + <p><em>bar</em></p> + </div> + </div> + """ + ) + ) + + def test_nomd_nested_in_md1(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + *foo* + <div> + *foo* + <p>*bar*</p> + *baz* + </div> + *bar* + </div> + """ + ), + self.dedent( + """ + <div> + <p><em>foo</em></p> + <div> + *foo* + <p>*bar*</p> + *baz* + </div> + <p><em>bar</em></p> + </div> + """ + ) + ) + + def test_md1_nested_in_nomd(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div> + <div markdown="1">*foo*</div> + </div> + """ + ), + self.dedent( + """ + <div> + <div markdown="1">*foo*</div> + </div> + """ + ) + ) + + def test_md1_single_quotes(self): + self.assertMarkdownRenders( + "<p markdown='1'>*foo*</p>", + '<p><em>foo</em></p>' + ) + + def test_md1_no_quotes(self): + self.assertMarkdownRenders( + '<p markdown=1>*foo*</p>', + '<p><em>foo</em></p>' + ) + + def test_md_no_value(self): + self.assertMarkdownRenders( + '<p markdown>*foo*</p>', + '<p><em>foo</em></p>' + ) + + def test_md1_preserve_attrs(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1" id="parent"> + + <div markdown="1" class="foo"> + <p markdown="1" class="bar baz">*foo*</p> + </div> + + </div> + """ + ), + self.dedent( + """ + <div id="parent"> + <div class="foo"> + <p class="bar baz"><em>foo</em></p> + </div> + </div> + """ + ) + ) + + def test_md1_unclosed_div(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + + _foo_ + + <div class="unclosed"> + + _bar_ + + </div> + """ + ), + self.dedent( + """ + <div> + <p><em>foo</em></p> + <div class="unclosed"> + + _bar_ + + </div> + </div> + """ + ) + ) + + def test_md1_orphan_endtag(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + + _foo_ + + </p> + + _bar_ + + </div> + """ + ), + self.dedent( + """ + <div> + <p><em>foo</em></p> + </p> + <p><em>bar</em></p> + </div> + """ + ) + ) + + def test_md1_unclosed_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + <p markdown="1">_foo_ + <p markdown="1">_bar_ + """ + ), + self.dedent( + """ + <p><em>foo</em> + </p> + <p><em>bar</em> + + </p> + """ + ) + ) + + def test_md1_nested_unclosed_p(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + <p markdown="1">_foo_ + <p markdown="1">_bar_ + </div> + """ + ), + self.dedent( + """ + <div> + <p><em>foo</em> + </p> + <p><em>bar</em> + </p> + </div> + """ + ) + ) + + def test_md1_nested_comment(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + A *Markdown* paragraph. + <!-- foobar --> + A *Markdown* paragraph. + </div> + """ + ), + self.dedent( + """ + <div> + <p>A <em>Markdown</em> paragraph.</p> + <!-- foobar --> + <p>A <em>Markdown</em> paragraph.</p> + </div> + """ + ) + ) + + def test_md1_nested_link_ref(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + [link]: http://example.com + <div markdown="1"> + [link][link] + </div> + </div> + """ + ), + self.dedent( + """ + <div> + <div> + <p><a href="http://example.com">link</a></p> + </div> + </div> + """ + ) + ) + + def test_md1_hr_only_start(self): + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + <hr markdown="1"> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em></p> + <hr> + <p><em>emphasis2</em></p> + """ + ) + ) + + def test_md1_hr_self_close(self): + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + <hr markdown="1" /> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em></p> + <hr> + <p><em>emphasis2</em></p> + """ + ) + ) + + def test_md1_hr_start_and_end(self): + # Browsers ignore ending hr tags, so we don't try to do anything to handle them special. + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + <hr markdown="1"></hr> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em></p> + <hr> + <p></hr> + <em>emphasis2</em></p> + """ + ) + ) + + def test_md1_hr_only_end(self): + # Browsers ignore ending hr tags, so we don't try to do anything to handle them special. + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + </hr> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em> + </hr> + <em>emphasis2</em></p> + """ + ) + ) + + def test_md1_hr_with_content(self): + # Browsers ignore ending hr tags, so we don't try to do anything to handle them special. + # Content is not allowed and will be treated as normal content between two hr tags + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + <hr markdown="1"> + **content** + </hr> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em></p> + <hr> + <p><strong>content</strong> + </hr> + <em>emphasis2</em></p> + """ + ) + ) + + def test_no_md1_hr_with_content(self): + # Browsers ignore ending hr tags, so we don't try to do anything to handle them special. + # Content is not allowed and will be treated as normal content between two hr tags + self.assertMarkdownRenders( + self.dedent( + """ + *emphasis1* + <hr> + **content** + </hr> + *emphasis2* + """ + ), + self.dedent( + """ + <p><em>emphasis1</em></p> + <hr> + <p><strong>content</strong> + </hr> + <em>emphasis2</em></p> + """ + ) + ) + + def test_md1_nested_abbr_ref(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + *[abbr]: Abbreviation + <div markdown="1"> + abbr + </div> + </div> + """ + ), + self.dedent( + """ + <div> + <div> + <p><abbr title="Abbreviation">abbr</abbr></p> + </div> + </div> + """ + ), + extensions=['md_in_html', 'abbr'] + ) + + def test_md1_nested_footnote_ref(self): + self.assertMarkdownRenders( + self.dedent( + """ + <div markdown="1"> + [^1]: The footnote. + <div markdown="1"> + Paragraph with a footnote.[^1] + </div> + </div> + """ + ), + '<div>\n' + '<div>\n' + '<p>Paragraph with a footnote.<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n' + '</div>\n' + '</div>\n' + '<div class="footnote">\n' + '<hr />\n' + '<ol>\n' + '<li id="fn:1">\n' + '<p>The footnote. ' + '<a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a>' + '</p>\n' + '</li>\n' + '</ol>\n' + '</div>', + extensions=['md_in_html', 'footnotes'] + ) + + +def load_tests(loader, tests, pattern): + ''' Ensure TestHTMLBlocks doesn't get run twice by excluding it here. ''' + suite = TestSuite() + for test_class in [TestDefaultwMdInHTML, TestMdInHTML, TestMarkdownInHTMLPostProcessor]: + tests = loader.loadTestsFromTestCase(test_class) + suite.addTests(tests) + return suite diff --git a/tests/test_syntax/extensions/test_smarty.py b/tests/test_syntax/extensions/test_smarty.py new file mode 100644 index 0000000..fc635ad --- /dev/null +++ b/tests/test_syntax/extensions/test_smarty.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2022 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestSmarty(TestCase): + + default_kwargs = {'extensions': ['smarty']} + + def test_escaped_attr(self): + self.assertMarkdownRenders( + '![x\"x](x)', + '<p><img alt="x"x" src="x" /></p>' + ) + + # TODO: Move rest of smarty tests here. diff --git a/tests/test_syntax/extensions/test_tables.py b/tests/test_syntax/extensions/test_tables.py new file mode 100644 index 0000000..cd3fbe4 --- /dev/null +++ b/tests/test_syntax/extensions/test_tables.py @@ -0,0 +1,922 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase +from markdown.extensions.tables import TableExtension + + +class TestTableBlocks(TestCase): + + def test_empty_cells(self): + """Empty cells (nbsp).""" + + text = """ + | Second Header +------------- | ------------- + | Content Cell +Content Cell | +""" + + self.assertMarkdownRenders( + text, + self.dedent( + """ + <table> + <thead> + <tr> + <th> </th> + <th>Second Header</th> + </tr> + </thead> + <tbody> + <tr> + <td> </td> + <td>Content Cell</td> + </tr> + <tr> + <td>Content Cell</td> + <td> </td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_no_sides(self): + self.assertMarkdownRenders( + self.dedent( + """ + First Header | Second Header + ------------- | ------------- + Content Cell | Content Cell + Content Cell | Content Cell + """ + ), + self.dedent( + """ + <table> + <thead> + <tr> + <th>First Header</th> + <th>Second Header</th> + </tr> + </thead> + <tbody> + <tr> + <td>Content Cell</td> + <td>Content Cell</td> + </tr> + <tr> + <td>Content Cell</td> + <td>Content Cell</td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_both_sides(self): + self.assertMarkdownRenders( + self.dedent( + """ + | First Header | Second Header | + | ------------- | ------------- | + | Content Cell | Content Cell | + | Content Cell | Content Cell | + """ + ), + self.dedent( + """ + <table> + <thead> + <tr> + <th>First Header</th> + <th>Second Header</th> + </tr> + </thead> + <tbody> + <tr> + <td>Content Cell</td> + <td>Content Cell</td> + </tr> + <tr> + <td>Content Cell</td> + <td>Content Cell</td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_align_columns(self): + self.assertMarkdownRenders( + self.dedent( + """ + | Item | Value | + | :-------- | -----:| + | Computer | $1600 | + | Phone | $12 | + | Pipe | $1 | + """ + ), + self.dedent( + """ + <table> + <thead> + <tr> + <th style="text-align: left;">Item</th> + <th style="text-align: right;">Value</th> + </tr> + </thead> + <tbody> + <tr> + <td style="text-align: left;">Computer</td> + <td style="text-align: right;">$1600</td> + </tr> + <tr> + <td style="text-align: left;">Phone</td> + <td style="text-align: right;">$12</td> + </tr> + <tr> + <td style="text-align: left;">Pipe</td> + <td style="text-align: right;">$1</td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_styles_in_tables(self): + self.assertMarkdownRenders( + self.dedent( + """ + | Function name | Description | + | ------------- | ------------------------------ | + | `help()` | Display the help window. | + | `destroy()` | **Destroy your computer!** | + """ + ), + self.dedent( + """ + <table> + <thead> + <tr> + <th>Function name</th> + <th>Description</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>help()</code></td> + <td>Display the help window.</td> + </tr> + <tr> + <td><code>destroy()</code></td> + <td><strong>Destroy your computer!</strong></td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_align_three(self): + self.assertMarkdownRenders( + self.dedent( + """ + |foo|bar|baz| + |:--|:-:|--:| + | | Q | | + |W | | W| + """ + ), + self.dedent( + """ + <table> + <thead> + <tr> + <th style="text-align: left;">foo</th> + <th style="text-align: center;">bar</th> + <th style="text-align: right;">baz</th> + </tr> + </thead> + <tbody> + <tr> + <td style="text-align: left;"></td> + <td style="text-align: center;">Q</td> + <td style="text-align: right;"></td> + </tr> + <tr> + <td style="text-align: left;">W</td> + <td style="text-align: center;"></td> + <td style="text-align: right;">W</td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_three_columns(self): + self.assertMarkdownRenders( + self.dedent( + """ + foo|bar|baz + ---|---|--- + | Q | + W | | W + """ + ), + self.dedent( + """ + <table> + <thead> + <tr> + <th>foo</th> + <th>bar</th> + <th>baz</th> + </tr> + </thead> + <tbody> + <tr> + <td></td> + <td>Q</td> + <td></td> + </tr> + <tr> + <td>W</td> + <td></td> + <td>W</td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_three_spaces_prefix(self): + self.assertMarkdownRenders( + self.dedent( + """ + Three spaces in front of a table: + + First Header | Second Header + ------------ | ------------- + Content Cell | Content Cell + Content Cell | Content Cell + + | First Header | Second Header | + | ------------ | ------------- | + | Content Cell | Content Cell | + | Content Cell | Content Cell | + """), + self.dedent( + """ + <p>Three spaces in front of a table:</p> + <table> + <thead> + <tr> + <th>First Header</th> + <th>Second Header</th> + </tr> + </thead> + <tbody> + <tr> + <td>Content Cell</td> + <td>Content Cell</td> + </tr> + <tr> + <td>Content Cell</td> + <td>Content Cell</td> + </tr> + </tbody> + </table> + <table> + <thead> + <tr> + <th>First Header</th> + <th>Second Header</th> + </tr> + </thead> + <tbody> + <tr> + <td>Content Cell</td> + <td>Content Cell</td> + </tr> + <tr> + <td>Content Cell</td> + <td>Content Cell</td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_code_block_table(self): + self.assertMarkdownRenders( + self.dedent( + """ + Four spaces is a code block: + + First Header | Second Header + ------------ | ------------- + Content Cell | Content Cell + Content Cell | Content Cell + + | First Header | Second Header | + | ------------ | ------------- | + """), + self.dedent( + """ + <p>Four spaces is a code block:</p> + <pre><code>First Header | Second Header + ------------ | ------------- + Content Cell | Content Cell + Content Cell | Content Cell + </code></pre> + <table> + <thead> + <tr> + <th>First Header</th> + <th>Second Header</th> + </tr> + </thead> + <tbody> + <tr> + <td></td> + <td></td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_inline_code_blocks(self): + self.assertMarkdownRenders( + self.dedent( + """ + More inline code block tests + + Column 1 | Column 2 | Column 3 + ---------|----------|--------- + word 1 | word 2 | word 3 + word 1 | `word 2` | word 3 + word 1 | \\`word 2 | word 3 + word 1 | `word 2 | word 3 + word 1 | `word |2` | word 3 + words |`` some | code `` | more words + words |``` some | code ``` | more words + words |```` some | code ```` | more words + words |`` some ` | ` code `` | more words + words |``` some ` | ` code ``` | more words + words |```` some ` | ` code ```` | more words + """), + self.dedent( + """ + <p>More inline code block tests</p> + <table> + <thead> + <tr> + <th>Column 1</th> + <th>Column 2</th> + <th>Column 3</th> + </tr> + </thead> + <tbody> + <tr> + <td>word 1</td> + <td>word 2</td> + <td>word 3</td> + </tr> + <tr> + <td>word 1</td> + <td><code>word 2</code></td> + <td>word 3</td> + </tr> + <tr> + <td>word 1</td> + <td>`word 2</td> + <td>word 3</td> + </tr> + <tr> + <td>word 1</td> + <td>`word 2</td> + <td>word 3</td> + </tr> + <tr> + <td>word 1</td> + <td><code>word |2</code></td> + <td>word 3</td> + </tr> + <tr> + <td>words</td> + <td><code>some | code</code></td> + <td>more words</td> + </tr> + <tr> + <td>words</td> + <td><code>some | code</code></td> + <td>more words</td> + </tr> + <tr> + <td>words</td> + <td><code>some | code</code></td> + <td>more words</td> + </tr> + <tr> + <td>words</td> + <td><code>some ` | ` code</code></td> + <td>more words</td> + </tr> + <tr> + <td>words</td> + <td><code>some ` | ` code</code></td> + <td>more words</td> + </tr> + <tr> + <td>words</td> + <td><code>some ` | ` code</code></td> + <td>more words</td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_issue_440(self): + self.assertMarkdownRenders( + self.dedent( + """ + A test for issue #440: + + foo | bar + --- | --- + foo | (`bar`) and `baz`. + """), + self.dedent( + """ + <p>A test for issue #440:</p> + <table> + <thead> + <tr> + <th>foo</th> + <th>bar</th> + </tr> + </thead> + <tbody> + <tr> + <td>foo</td> + <td>(<code>bar</code>) and <code>baz</code>.</td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_lists_not_tables(self): + self.assertMarkdownRenders( + self.dedent( + """ + Lists are not tables + + - this | should | not + - be | a | table + """), + self.dedent( + """ + <p>Lists are not tables</p> + <ul> + <li>this | should | not</li> + <li>be | a | table</li> + </ul> + """ + ), + extensions=['tables'] + ) + + def test_issue_449(self): + self.assertMarkdownRenders( + self.dedent( + r""" + Add tests for issue #449 + + Odd backticks | Even backticks + ------------ | ------------- + ``[!\"\#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~]`` | ``[!\"\#$%&'()*+,\-./:;<=>?@\[\\\]^`_`{|}~]`` + + Escapes | More Escapes + ------- | ------ + `` `\`` | `\` + + Only the first backtick can be escaped + + Escaped | Bacticks + ------- | ------ + \`` \` | \`\` + + Test escaped pipes + + Column 1 | Column 2 + -------- | -------- + `|` \| | Pipes are okay in code and escaped. \| + + | Column 1 | Column 2 | + | -------- | -------- | + | row1 | row1 \| + | row2 | row2 | + + Test header escapes + + | `` `\`` \| | `\` \| + | ---------- | ---- | + | row1 | row1 | + | row2 | row2 | + + Escaped pipes in format row should not be a table + + | Column1 | Column2 | + | ------- \|| ------- | + | row1 | row1 | + | row2 | row2 | + + Test escaped code in Table + + Should not be code | Should be code + ------------------ | -------------- + \`Not code\` | \\`code` + \\\`Not code\\\` | \\\\`code` + """), + self.dedent( + """ + <p>Add tests for issue #449</p> + <table> + <thead> + <tr> + <th>Odd backticks</th> + <th>Even backticks</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>[!\\"\\#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~]</code></td> + <td><code>[!\\"\\#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^`_`{|}~]</code></td> + </tr> + </tbody> + </table> + <table> + <thead> + <tr> + <th>Escapes</th> + <th>More Escapes</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>`\\</code></td> + <td><code>\\</code></td> + </tr> + </tbody> + </table> + <p>Only the first backtick can be escaped</p> + <table> + <thead> + <tr> + <th>Escaped</th> + <th>Bacticks</th> + </tr> + </thead> + <tbody> + <tr> + <td>`<code>\\</code></td> + <td>``</td> + </tr> + </tbody> + </table> + <p>Test escaped pipes</p> + <table> + <thead> + <tr> + <th>Column 1</th> + <th>Column 2</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>|</code> |</td> + <td>Pipes are okay in code and escaped. |</td> + </tr> + </tbody> + </table> + <table> + <thead> + <tr> + <th>Column 1</th> + <th>Column 2</th> + </tr> + </thead> + <tbody> + <tr> + <td>row1</td> + <td>row1 |</td> + </tr> + <tr> + <td>row2</td> + <td>row2</td> + </tr> + </tbody> + </table> + <p>Test header escapes</p> + <table> + <thead> + <tr> + <th><code>`\\</code> |</th> + <th><code>\\</code> |</th> + </tr> + </thead> + <tbody> + <tr> + <td>row1</td> + <td>row1</td> + </tr> + <tr> + <td>row2</td> + <td>row2</td> + </tr> + </tbody> + </table> + <p>Escaped pipes in format row should not be a table</p> + <p>| Column1 | Column2 | + | ------- || ------- | + | row1 | row1 | + | row2 | row2 |</p> + <p>Test escaped code in Table</p> + <table> + <thead> + <tr> + <th>Should not be code</th> + <th>Should be code</th> + </tr> + </thead> + <tbody> + <tr> + <td>`Not code`</td> + <td>\\<code>code</code></td> + </tr> + <tr> + <td>\\`Not code\\`</td> + <td>\\\\<code>code</code></td> + </tr> + </tbody> + </table> + """ + ), + extensions=['tables'] + ) + + def test_single_column_tables(self): + self.assertMarkdownRenders( + self.dedent( + """ + Single column tables + + | Is a Table | + | ---------- | + + | Is a Table + | ---------- + + Is a Table | + ---------- | + + | Is a Table | + | ---------- | + | row | + + | Is a Table + | ---------- + | row + + Is a Table | + ---------- | + row | + + | Is not a Table + -------------- + | row + + Is not a Table | + -------------- + row | + + | Is not a Table + | -------------- + row + + Is not a Table | + -------------- | + row + """), + self.dedent( + """ + <p>Single column tables</p> + <table> + <thead> + <tr> + <th>Is a Table</th> + </tr> + </thead> + <tbody> + <tr> + <td></td> + </tr> + </tbody> + </table> + <table> + <thead> + <tr> + <th>Is a Table</th> + </tr> + </thead> + <tbody> + <tr> + <td></td> + </tr> + </tbody> + </table> + <table> + <thead> + <tr> + <th>Is a Table</th> + </tr> + </thead> + <tbody> + <tr> + <td></td> + </tr> + </tbody> + </table> + <table> + <thead> + <tr> + <th>Is a Table</th> + </tr> + </thead> + <tbody> + <tr> + <td>row</td> + </tr> + </tbody> + </table> + <table> + <thead> + <tr> + <th>Is a Table</th> + </tr> + </thead> + <tbody> + <tr> + <td>row</td> + </tr> + </tbody> + </table> + <table> + <thead> + <tr> + <th>Is a Table</th> + </tr> + </thead> + <tbody> + <tr> + <td>row</td> + </tr> + </tbody> + </table> + <h2>| Is not a Table</h2> + <p>| row</p> + <h2>Is not a Table |</h2> + <p>row |</p> + <p>| Is not a Table + | -------------- + row</p> + <p>Is not a Table | + -------------- | + row</p> + """ + ), + extensions=['tables'] + ) + + def test_align_columns_legacy(self): + self.assertMarkdownRenders( + self.dedent( + """ + | Item | Value | + | :-------- | -----:| + | Computer | $1600 | + | Phone | $12 | + | Pipe | $1 | + """ + ), + self.dedent( + """ + <table> + <thead> + <tr> + <th align="left">Item</th> + <th align="right">Value</th> + </tr> + </thead> + <tbody> + <tr> + <td align="left">Computer</td> + <td align="right">$1600</td> + </tr> + <tr> + <td align="left">Phone</td> + <td align="right">$12</td> + </tr> + <tr> + <td align="left">Pipe</td> + <td align="right">$1</td> + </tr> + </tbody> + </table> + """ + ), + extensions=[TableExtension(use_align_attribute=True)] + ) + + def test_align_three_legacy(self): + self.assertMarkdownRenders( + self.dedent( + """ + |foo|bar|baz| + |:--|:-:|--:| + | | Q | | + |W | | W| + """ + ), + self.dedent( + """ + <table> + <thead> + <tr> + <th align="left">foo</th> + <th align="center">bar</th> + <th align="right">baz</th> + </tr> + </thead> + <tbody> + <tr> + <td align="left"></td> + <td align="center">Q</td> + <td align="right"></td> + </tr> + <tr> + <td align="left">W</td> + <td align="center"></td> + <td align="right">W</td> + </tr> + </tbody> + </table> + """ + ), + extensions=[TableExtension(use_align_attribute=True)] + ) diff --git a/tests/test_syntax/extensions/test_toc.py b/tests/test_syntax/extensions/test_toc.py new file mode 100644 index 0000000..d879f6e --- /dev/null +++ b/tests/test_syntax/extensions/test_toc.py @@ -0,0 +1,614 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2019 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase +from markdown.extensions.toc import TocExtension +from markdown.extensions.nl2br import Nl2BrExtension + + +class TestTOC(TestCase): + maxDiff = None + + # TODO: Move the rest of the TOC tests here. + + def testAnchorLink(self): + self.assertMarkdownRenders( + self.dedent( + ''' + # Header 1 + + ## Header *2* + ''' + ), + self.dedent( + ''' + <h1 id="header-1"><a class="toclink" href="#header-1">Header 1</a></h1> + <h2 id="header-2"><a class="toclink" href="#header-2">Header <em>2</em></a></h2> + ''' + ), + extensions=[TocExtension(anchorlink=True)] + ) + + def testAnchorLinkWithSingleInlineCode(self): + self.assertMarkdownRenders( + '# This is `code`.', + '<h1 id="this-is-code">' # noqa + '<a class="toclink" href="#this-is-code">' # noqa + 'This is <code>code</code>.' # noqa + '</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(anchorlink=True)] + ) + + def testAnchorLinkWithDoubleInlineCode(self): + self.assertMarkdownRenders( + '# This is `code` and `this` too.', + '<h1 id="this-is-code-and-this-too">' # noqa + '<a class="toclink" href="#this-is-code-and-this-too">' # noqa + 'This is <code>code</code> and <code>this</code> too.' # noqa + '</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(anchorlink=True)] + ) + + def testPermalink(self): + self.assertMarkdownRenders( + '# Header', + '<h1 id="header">' # noqa + 'Header' # noqa + '<a class="headerlink" href="#header" title="Permanent link">¶</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink=True)] + ) + + def testPermalinkWithSingleInlineCode(self): + self.assertMarkdownRenders( + '# This is `code`.', + '<h1 id="this-is-code">' # noqa + 'This is <code>code</code>.' # noqa + '<a class="headerlink" href="#this-is-code" title="Permanent link">¶</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink=True)] + ) + + def testPermalinkWithDoubleInlineCode(self): + self.assertMarkdownRenders( + '# This is `code` and `this` too.', + '<h1 id="this-is-code-and-this-too">' # noqa + 'This is <code>code</code> and <code>this</code> too.' # noqa + '<a class="headerlink" href="#this-is-code-and-this-too" title="Permanent link">¶</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink=True)] + ) + + def testMinMaxLevel(self): + self.assertMarkdownRenders( + self.dedent( + ''' + # Header 1 not in TOC + + ## Header 2 not in TOC + + ### Header 3 + + #### Header 4 + + ##### Header 5 not in TOC + ''' + ), + self.dedent( + ''' + <h1 id="header-1-not-in-toc">Header 1 not in TOC</h1> + <h2 id="header-2-not-in-toc">Header 2 not in TOC</h2> + <h3 id="header-3">Header 3</h3> + <h4 id="header-4">Header 4</h4> + <h5 id="header-5-not-in-toc">Header 5 not in TOC</h5> + ''' + ), + expected_attrs={ + 'toc': ( + '<div class="toc">\n' + '<ul>\n' # noqa + '<li><a href="#header-3">Header 3</a>' # noqa + '<ul>\n' # noqa + '<li><a href="#header-4">Header 4</a></li>\n' # noqa + '</ul>\n' # noqa + '</li>\n' # noqa + '</ul>\n' # noqa + '</div>\n' # noqa + ), + 'toc_tokens': [ + { + 'level': 3, + 'id': 'header-3', + 'name': 'Header 3', + 'children': [ + { + 'level': 4, + 'id': 'header-4', + 'name': 'Header 4', + 'children': [] + } + ] + } + ] + }, + extensions=[TocExtension(toc_depth='3-4')] + ) + + def testMaxLevel(self): + self.assertMarkdownRenders( + self.dedent( + ''' + # Header 1 + + ## Header 2 + + ### Header 3 not in TOC + ''' + ), + self.dedent( + ''' + <h1 id="header-1">Header 1</h1> + <h2 id="header-2">Header 2</h2> + <h3 id="header-3-not-in-toc">Header 3 not in TOC</h3> + ''' + ), + expected_attrs={ + 'toc': ( + '<div class="toc">\n' + '<ul>\n' # noqa + '<li><a href="#header-1">Header 1</a>' # noqa + '<ul>\n' # noqa + '<li><a href="#header-2">Header 2</a></li>\n' # noqa + '</ul>\n' # noqa + '</li>\n' # noqa + '</ul>\n' # noqa + '</div>\n' # noqa + ), + 'toc_tokens': [ + { + 'level': 1, + 'id': 'header-1', + 'name': 'Header 1', + 'children': [ + { + 'level': 2, + 'id': 'header-2', + 'name': 'Header 2', + 'children': [] + } + ] + } + ] + }, + extensions=[TocExtension(toc_depth=2)] + ) + + def testMinMaxLevelwithAnchorLink(self): + self.assertMarkdownRenders( + self.dedent( + ''' + # Header 1 not in TOC + + ## Header 2 not in TOC + + ### Header 3 + + #### Header 4 + + ##### Header 5 not in TOC + ''' + ), + '<h1 id="header-1-not-in-toc">' # noqa + '<a class="toclink" href="#header-1-not-in-toc">Header 1 not in TOC</a></h1>\n' # noqa + '<h2 id="header-2-not-in-toc">' # noqa + '<a class="toclink" href="#header-2-not-in-toc">Header 2 not in TOC</a></h2>\n' # noqa + '<h3 id="header-3">' # noqa + '<a class="toclink" href="#header-3">Header 3</a></h3>\n' # noqa + '<h4 id="header-4">' # noqa + '<a class="toclink" href="#header-4">Header 4</a></h4>\n' # noqa + '<h5 id="header-5-not-in-toc">' # noqa + '<a class="toclink" href="#header-5-not-in-toc">Header 5 not in TOC</a></h5>', # noqa + expected_attrs={ + 'toc': ( + '<div class="toc">\n' + '<ul>\n' # noqa + '<li><a href="#header-3">Header 3</a>' # noqa + '<ul>\n' # noqa + '<li><a href="#header-4">Header 4</a></li>\n' # noqa + '</ul>\n' # noqa + '</li>\n' # noqa + '</ul>\n' # noqa + '</div>\n' # noqa + ), + 'toc_tokens': [ + { + 'level': 3, + 'id': 'header-3', + 'name': 'Header 3', + 'children': [ + { + 'level': 4, + 'id': 'header-4', + 'name': 'Header 4', + 'children': [] + } + ] + } + ] + }, + extensions=[TocExtension(toc_depth='3-4', anchorlink=True)] + ) + + def testMinMaxLevelwithPermalink(self): + self.assertMarkdownRenders( + self.dedent( + ''' + # Header 1 not in TOC + + ## Header 2 not in TOC + + ### Header 3 + + #### Header 4 + + ##### Header 5 not in TOC + ''' + ), + '<h1 id="header-1-not-in-toc">Header 1 not in TOC' # noqa + '<a class="headerlink" href="#header-1-not-in-toc" title="Permanent link">¶</a></h1>\n' # noqa + '<h2 id="header-2-not-in-toc">Header 2 not in TOC' # noqa + '<a class="headerlink" href="#header-2-not-in-toc" title="Permanent link">¶</a></h2>\n' # noqa + '<h3 id="header-3">Header 3' # noqa + '<a class="headerlink" href="#header-3" title="Permanent link">¶</a></h3>\n' # noqa + '<h4 id="header-4">Header 4' # noqa + '<a class="headerlink" href="#header-4" title="Permanent link">¶</a></h4>\n' # noqa + '<h5 id="header-5-not-in-toc">Header 5 not in TOC' # noqa + '<a class="headerlink" href="#header-5-not-in-toc" title="Permanent link">¶</a></h5>', # noqa + expected_attrs={ + 'toc': ( + '<div class="toc">\n' + '<ul>\n' # noqa + '<li><a href="#header-3">Header 3</a>' # noqa + '<ul>\n' # noqa + '<li><a href="#header-4">Header 4</a></li>\n' # noqa + '</ul>\n' # noqa + '</li>\n' # noqa + '</ul>\n' # noqa + '</div>\n' # noqa + ), + 'toc_tokens': [ + { + 'level': 3, + 'id': 'header-3', + 'name': 'Header 3', + 'children': [ + { + 'level': 4, + 'id': 'header-4', + 'name': 'Header 4', + 'children': [] + } + ] + } + ] + }, + extensions=[TocExtension(toc_depth='3-4', permalink=True)] + ) + + def testMinMaxLevelwithBaseLevel(self): + self.assertMarkdownRenders( + self.dedent( + ''' + # First Header + + ## Second Level + + ### Third Level + + #### Forth Level + ''' + ), + self.dedent( + ''' + <h3 id="first-header">First Header</h3> + <h4 id="second-level">Second Level</h4> + <h5 id="third-level">Third Level</h5> + <h6 id="forth-level">Forth Level</h6> + ''' + ), + expected_attrs={ + 'toc': ( + '<div class="toc">\n' + '<ul>\n' # noqa + '<li><a href="#second-level">Second Level</a>' # noqa + '<ul>\n' # noqa + '<li><a href="#third-level">Third Level</a></li>\n' # noqa + '</ul>\n' # noqa + '</li>\n' # noqa + '</ul>\n' # noqa + '</div>\n' # noqa + ), + 'toc_tokens': [ + { + 'level': 4, + 'id': 'second-level', + 'name': 'Second Level', + 'children': [ + { + 'level': 5, + 'id': 'third-level', + 'name': 'Third Level', + 'children': [] + } + ] + } + ] + }, + extensions=[TocExtension(toc_depth='4-5', baselevel=3)] + ) + + def testMaxLevelwithBaseLevel(self): + self.assertMarkdownRenders( + self.dedent( + ''' + # Some Header + + ## Next Level + + ### Too High + ''' + ), + self.dedent( + ''' + <h2 id="some-header">Some Header</h2> + <h3 id="next-level">Next Level</h3> + <h4 id="too-high">Too High</h4> + ''' + ), + expected_attrs={ + 'toc': ( + '<div class="toc">\n' + '<ul>\n' # noqa + '<li><a href="#some-header">Some Header</a>' # noqa + '<ul>\n' # noqa + '<li><a href="#next-level">Next Level</a></li>\n' # noqa + '</ul>\n' # noqa + '</li>\n' # noqa + '</ul>\n' # noqa + '</div>\n' # noqa + ), + 'toc_tokens': [ + { + 'level': 2, + 'id': 'some-header', + 'name': 'Some Header', + 'children': [ + { + 'level': 3, + 'id': 'next-level', + 'name': 'Next Level', + 'children': [] + } + ] + } + ] + }, + extensions=[TocExtension(toc_depth=3, baselevel=2)] + ) + + def test_escaped_code(self): + self.assertMarkdownRenders( + self.dedent( + ''' + [TOC] + + # `<test>` + ''' + ), + self.dedent( + ''' + <div class="toc"> + <ul> + <li><a href="#test"><test></a></li> + </ul> + </div> + <h1 id="test"><code><test></code></h1> + ''' + ), + extensions=['toc'] + ) + + def test_escaped_char_in_id(self): + self.assertMarkdownRenders( + r'# escaped\_character', + '<h1 id="escaped_character">escaped_character</h1>', + extensions=['toc'] + ) + + def testAnchorLinkWithCustomClass(self): + self.assertMarkdownRenders( + self.dedent( + ''' + # Header 1 + + ## Header *2* + ''' + ), + self.dedent( + ''' + <h1 id="header-1"><a class="custom" href="#header-1">Header 1</a></h1> + <h2 id="header-2"><a class="custom" href="#header-2">Header <em>2</em></a></h2> + ''' + ), + extensions=[TocExtension(anchorlink=True, anchorlink_class="custom")] + ) + + def testAnchorLinkWithCustomClasses(self): + self.assertMarkdownRenders( + self.dedent( + ''' + # Header 1 + + ## Header *2* + ''' + ), + self.dedent( + ''' + <h1 id="header-1"><a class="custom1 custom2" href="#header-1">Header 1</a></h1> + <h2 id="header-2"><a class="custom1 custom2" href="#header-2">Header <em>2</em></a></h2> + ''' + ), + extensions=[TocExtension(anchorlink=True, anchorlink_class="custom1 custom2")] + ) + + def testPermalinkWithEmptyText(self): + self.assertMarkdownRenders( + '# Header', + '<h1 id="header">' # noqa + 'Header' # noqa + '<a class="headerlink" href="#header" title="Permanent link"></a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink="")] + ) + + def testPermalinkWithCustomClass(self): + self.assertMarkdownRenders( + '# Header', + '<h1 id="header">' # noqa + 'Header' # noqa + '<a class="custom" href="#header" title="Permanent link">¶</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink=True, permalink_class="custom")] + ) + + def testPermalinkWithCustomClasses(self): + self.assertMarkdownRenders( + '# Header', + '<h1 id="header">' # noqa + 'Header' # noqa + '<a class="custom1 custom2" href="#header" title="Permanent link">¶</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink=True, permalink_class="custom1 custom2")] + ) + + def testPermalinkWithCustomTitle(self): + self.assertMarkdownRenders( + '# Header', + '<h1 id="header">' # noqa + 'Header' # noqa + '<a class="headerlink" href="#header" title="custom">¶</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink=True, permalink_title="custom")] + ) + + def testPermalinkWithEmptyTitle(self): + self.assertMarkdownRenders( + '# Header', + '<h1 id="header">' # noqa + 'Header' # noqa + '<a class="headerlink" href="#header">¶</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink=True, permalink_title="")] + ) + + def testPermalinkWithUnicodeInID(self): + from markdown.extensions.toc import slugify_unicode + self.assertMarkdownRenders( + '# Unicode ヘッダー', + '<h1 id="unicode-ヘッダー">' # noqa + 'Unicode ヘッダー' # noqa + '<a class="headerlink" href="#unicode-ヘッダー" title="Permanent link">¶</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink=True, slugify=slugify_unicode)] + ) + + def testPermalinkWithUnicodeTitle(self): + from markdown.extensions.toc import slugify_unicode + self.assertMarkdownRenders( + '# Unicode ヘッダー', + '<h1 id="unicode-ヘッダー">' # noqa + 'Unicode ヘッダー' # noqa + '<a class="headerlink" href="#unicode-ヘッダー" title="パーマリンク">¶</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink=True, permalink_title="パーマリンク", slugify=slugify_unicode)] + ) + + def testPermalinkWithExtendedLatinInID(self): + self.assertMarkdownRenders( + '# Théâtre', + '<h1 id="theatre">' # noqa + 'Théâtre' # noqa + '<a class="headerlink" href="#theatre" title="Permanent link">¶</a>' # noqa + '</h1>', # noqa + extensions=[TocExtension(permalink=True)] + ) + + def testNl2brCompatibility(self): + self.assertMarkdownRenders( + '[TOC]\ntext', + '<p>[TOC]<br />\ntext</p>', + extensions=[TocExtension(), Nl2BrExtension()] + ) + + def testTOCWithCustomClass(self): + + self.assertMarkdownRenders( + self.dedent( + ''' + [TOC] + # Header + ''' + ), + self.dedent( + ''' + <div class="custom"> + <ul> + <li><a href="#header">Header</a></li> + </ul> + </div> + <h1 id="header">Header</h1> + ''' + ), + extensions=[TocExtension(toc_class="custom")] + ) + + def testTOCWithCustomClasses(self): + self.assertMarkdownRenders( + self.dedent( + ''' + [TOC] + # Header + ''' + ), + self.dedent( + ''' + <div class="custom1 custom2"> + <ul> + <li><a href="#header">Header</a></li> + </ul> + </div> + <h1 id="header">Header</h1> + ''' + ), + extensions=[TocExtension(toc_class="custom1 custom2")] + ) diff --git a/tests/test_syntax/inline/__init__.py b/tests/test_syntax/inline/__init__.py new file mode 100644 index 0000000..564ba3b --- /dev/null +++ b/tests/test_syntax/inline/__init__.py @@ -0,0 +1,20 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" diff --git a/tests/test_syntax/inline/test_autolinks.py b/tests/test_syntax/inline/test_autolinks.py new file mode 100644 index 0000000..b6bd1cf --- /dev/null +++ b/tests/test_syntax/inline/test_autolinks.py @@ -0,0 +1,63 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2021 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestAutomaticLinks(TestCase): + + def test_email_address(self): + self.assertMarkdownRenders( + 'asdfasdfadsfasd <yuri@freewisdom.org> or you can say ', + '<p>asdfasdfadsfasd <a href="mailto:yur' + 'i@freewisdom.or' + 'g">yuri@freewisd' + 'om.org</a> or you can say </p>' + ) + + def test_mailto_email_address(self): + self.assertMarkdownRenders( + 'instead <mailto:yuri@freewisdom.org>', + '<p>instead <a href="mailto:yuri@' + 'freewisdom.org">' + 'yuri@freewisdom' + '.org</a></p>' + ) + + def test_email_address_with_ampersand(self): + self.assertMarkdownRenders( + '<bob&sue@example.com>', + '<p><a href="mailto:bob&sue' + '@example.com">bob&' + 'sue@example.com</a></p>' + ) + + def test_invalid_email_address_local_part(self): + self.assertMarkdownRenders( + 'Missing local-part <@domain>', + '<p>Missing local-part <@domain></p>' + ) + + def test_invalid_email_address_domain(self): + self.assertMarkdownRenders( + 'Missing domain <local-part@>', + '<p>Missing domain <local-part@></p>' + ) diff --git a/tests/test_syntax/inline/test_emphasis.py b/tests/test_syntax/inline/test_emphasis.py new file mode 100644 index 0000000..1e7fafa --- /dev/null +++ b/tests/test_syntax/inline/test_emphasis.py @@ -0,0 +1,130 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2019 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestNotEmphasis(TestCase): + + def test_standalone_asterisk(self): + self.assertMarkdownRenders( + '*', + '<p>*</p>' + ) + + def test_standalone_understore(self): + self.assertMarkdownRenders( + '_', + '<p>_</p>' + ) + + def test_standalone_asterisk_in_text(self): + self.assertMarkdownRenders( + 'foo * bar', + '<p>foo * bar</p>' + ) + + def test_standalone_understore_in_text(self): + self.assertMarkdownRenders( + 'foo _ bar', + '<p>foo _ bar</p>' + ) + + def test_standalone_asterisks_in_text(self): + self.assertMarkdownRenders( + 'foo * bar * baz', + '<p>foo * bar * baz</p>' + ) + + def test_standalone_understores_in_text(self): + self.assertMarkdownRenders( + 'foo _ bar _ baz', + '<p>foo _ bar _ baz</p>' + ) + + def test_standalone_asterisks_with_newlines(self): + self.assertMarkdownRenders( + 'foo\n* bar *\nbaz', + '<p>foo\n* bar *\nbaz</p>' + ) + + def test_standalone_understores_with_newlines(self): + self.assertMarkdownRenders( + 'foo\n_ bar _\nbaz', + '<p>foo\n_ bar _\nbaz</p>' + ) + + def test_standalone_asterisks_at_end(self): + self.assertMarkdownRenders( + 'foo * bar *', + '<p>foo * bar *</p>' + ) + + def test_standalone_understores_at_begin_end(self): + self.assertMarkdownRenders( + '_ bar _', + '<p>_ bar _</p>' + ) + + def test_complex_emphasis_asterisk(self): + self.assertMarkdownRenders( + 'This is text **bold *italic bold*** with more text', + '<p>This is text <strong>bold <em>italic bold</em></strong> with more text</p>' + ) + + def test_complex_emphasis_asterisk_mid_word(self): + self.assertMarkdownRenders( + 'This is text **bold*italic bold*** with more text', + '<p>This is text <strong>bold<em>italic bold</em></strong> with more text</p>' + ) + + def test_complex_emphasis_smart_underscore(self): + self.assertMarkdownRenders( + 'This is text __bold _italic bold___ with more text', + '<p>This is text <strong>bold <em>italic bold</em></strong> with more text</p>' + ) + + def test_complex_emphasis_smart_underscore_mid_word(self): + self.assertMarkdownRenders( + 'This is text __bold_italic bold___ with more text', + '<p>This is text __bold_italic bold___ with more text</p>' + ) + + def test_nested_emphasis(self): + + self.assertMarkdownRenders( + 'This text is **bold *italic* *italic* bold**', + '<p>This text is <strong>bold <em>italic</em> <em>italic</em> bold</strong></p>' + ) + + def test_complex_multple_emphasis_type(self): + + self.assertMarkdownRenders( + 'traced ***along*** bla **blocked** if other ***or***', + '<p>traced <strong><em>along</em></strong> bla <strong>blocked</strong> if other <strong><em>or</em></strong></p>' # noqa: E501 + ) + + def test_complex_multple_emphasis_type_variant2(self): + + self.assertMarkdownRenders( + 'on the **1-4 row** of the AP Combat Table ***and*** receive', + '<p>on the <strong>1-4 row</strong> of the AP Combat Table <strong><em>and</em></strong> receive</p>' + ) diff --git a/tests/test_syntax/inline/test_entities.py b/tests/test_syntax/inline/test_entities.py new file mode 100644 index 0000000..34cc2e7 --- /dev/null +++ b/tests/test_syntax/inline/test_entities.py @@ -0,0 +1,43 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestEntities(TestCase): + + def test_named_entities(self): + self.assertMarkdownRenders("&", "<p>&</p>") + self.assertMarkdownRenders("²", "<p>²</p>") + self.assertMarkdownRenders("Á", "<p>Á</p>") + + def test_decimal_entities(self): + self.assertMarkdownRenders("&", "<p>&</p>") + self.assertMarkdownRenders("²", "<p>²</p>") + + def test_hexadecimal_entities(self): + self.assertMarkdownRenders("&", "<p>&</p>") + self.assertMarkdownRenders("²", "<p>²</p>") + + def test_false_entities(self): + self.assertMarkdownRenders("¬ an entity;", "<p>&not an entity;</p>") + self.assertMarkdownRenders("&#B2;", "<p>&#B2;</p>") + self.assertMarkdownRenders("&#xnothex;", "<p>&#xnothex;</p>") diff --git a/tests/test_syntax/inline/test_images.py b/tests/test_syntax/inline/test_images.py new file mode 100644 index 0000000..c9c7cb8 --- /dev/null +++ b/tests/test_syntax/inline/test_images.py @@ -0,0 +1,184 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestAdvancedImages(TestCase): + + def test_nested_square_brackets(self): + self.assertMarkdownRenders( + """![Text[[[[[[[]]]]]]][]](http://link.com/image.png) more text""", + """<p><img alt="Text[[[[[[[]]]]]]][]" src="http://link.com/image.png" /> more text</p>""" + ) + + def test_nested_round_brackets(self): + self.assertMarkdownRenders( + """![Text](http://link.com/(((((((()))))))()).png) more text""", + """<p><img alt="Text" src="http://link.com/(((((((()))))))()).png" /> more text</p>""" + ) + + def test_uneven_brackets_with_titles1(self): + self.assertMarkdownRenders( + """![Text](http://link.com/(.png"title") more text""", + """<p><img alt="Text" src="http://link.com/(.png" title="title" /> more text</p>""" + ) + + def test_uneven_brackets_with_titles2(self): + self.assertMarkdownRenders( + """![Text](http://link.com/('.png"title") more text""", + """<p><img alt="Text" src="http://link.com/('.png" title="title" /> more text</p>""" + ) + + def test_uneven_brackets_with_titles3(self): + self.assertMarkdownRenders( + """![Text](http://link.com/(.png"title)") more text""", + """<p><img alt="Text" src="http://link.com/(.png" title="title)" /> more text</p>""" + ) + + def test_uneven_brackets_with_titles4(self): + self.assertMarkdownRenders( + """![Text](http://link.com/(.png "title") more text""", + """<p><img alt="Text" src="http://link.com/(.png" title="title" /> more text</p>""" + ) + + def test_uneven_brackets_with_titles5(self): + self.assertMarkdownRenders( + """![Text](http://link.com/(.png "title)") more text""", + """<p><img alt="Text" src="http://link.com/(.png" title="title)" /> more text</p>""" + ) + + def test_mixed_title_quotes1(self): + self.assertMarkdownRenders( + """![Text](http://link.com/'.png"title") more text""", + """<p><img alt="Text" src="http://link.com/'.png" title="title" /> more text</p>""" + ) + + def test_mixed_title_quotes2(self): + self.assertMarkdownRenders( + """![Text](http://link.com/".png'title') more text""", + """<p><img alt="Text" src="http://link.com/".png" title="title" /> more text</p>""" + ) + + def test_mixed_title_quotes3(self): + self.assertMarkdownRenders( + """![Text](http://link.com/with spaces.png'"and quotes" 'and title') more text""", + """<p><img alt="Text" src="http://link.com/with spaces.png" title=""and quotes" 'and title" />""" + """ more text</p>""" + ) + + def test_mixed_title_quotes4(self): + self.assertMarkdownRenders( + """![Text](http://link.com/with spaces'.png"and quotes" 'and title") more text""", + """<p><img alt="Text" src="http://link.com/with spaces'.png" title="and quotes" 'and title" />""" + """ more text</p>""" + ) + + def test_mixed_title_quotes5(self): + self.assertMarkdownRenders( + """![Text](http://link.com/with spaces .png'"and quotes" 'and title') more text""", + """<p><img alt="Text" src="http://link.com/with spaces .png" title=""and quotes"""" + """ 'and title" /> more text</p>""" + ) + + def test_mixed_title_quotes6(self): + self.assertMarkdownRenders( + """![Text](http://link.com/with spaces "and quotes".png 'and title') more text""", + """<p><img alt="Text" src="http://link.com/with spaces "and quotes".png" title="and title" />""" + """ more text</p>""" + ) + + def test_single_quote(self): + self.assertMarkdownRenders( + """![test](link"notitle.png)""", + """<p><img alt="test" src="link"notitle.png" /></p>""" + ) + + def test_angle_with_mixed_title_quotes(self): + self.assertMarkdownRenders( + """![Text](<http://link.com/with spaces '"and quotes".png> 'and title') more text""", + """<p><img alt="Text" src="http://link.com/with spaces '"and quotes".png" title="and title" />""" + """ more text</p>""" + ) + + def test_misc(self): + self.assertMarkdownRenders( + """![Poster](http://humane_man.jpg "The most humane man.")""", + """<p><img alt="Poster" src="http://humane_man.jpg" title="The most humane man." /></p>""" + ) + + def test_misc_ref(self): + self.assertMarkdownRenders( + self.dedent( + """ + ![Poster][] + + [Poster]:http://humane_man.jpg "The most humane man." + """ + ), + self.dedent( + """ + <p><img alt="Poster" src="http://humane_man.jpg" title="The most humane man." /></p> + """ + ) + ) + + def test_misc_blank(self): + self.assertMarkdownRenders( + """![Blank]()""", + """<p><img alt="Blank" src="" /></p>""" + ) + + def test_misc_img_title(self): + self.assertMarkdownRenders( + """![Image](http://humane man.jpg "The most humane man.")""", + """<p><img alt="Image" src="http://humane man.jpg" title="The most humane man." /></p>""" + ) + + def test_misc_img(self): + self.assertMarkdownRenders( + """![Image](http://humane man.jpg)""", + """<p><img alt="Image" src="http://humane man.jpg" /></p>""" + ) + + def test_short_ref(self): + self.assertMarkdownRenders( + self.dedent( + """ + ![ref] + + [ref]: ./image.jpg + """ + ), + '<p><img alt="ref" src="./image.jpg" /></p>' + ) + + def test_short_ref_in_link(self): + self.assertMarkdownRenders( + self.dedent( + """ + [![img ref]](http://example.com/) + + [img ref]: ./image.jpg + """ + ), + '<p><a href="http://example.com/"><img alt="img ref" src="./image.jpg" /></a></p>' + ) diff --git a/tests/test_syntax/inline/test_links.py b/tests/test_syntax/inline/test_links.py new file mode 100644 index 0000000..0458756 --- /dev/null +++ b/tests/test_syntax/inline/test_links.py @@ -0,0 +1,386 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestInlineLinks(TestCase): + + def test_nested_square_brackets(self): + self.assertMarkdownRenders( + """[Text[[[[[[[]]]]]]][]](http://link.com) more text""", + """<p><a href="http://link.com">Text[[[[[[[]]]]]]][]</a> more text</p>""" + ) + + def test_nested_round_brackets(self): + self.assertMarkdownRenders( + """[Text](http://link.com/(((((((()))))))())) more text""", + """<p><a href="http://link.com/(((((((()))))))())">Text</a> more text</p>""" + ) + + def test_uneven_brackets_with_titles1(self): + self.assertMarkdownRenders( + """[Text](http://link.com/("title") more text""", + """<p><a href="http://link.com/(" title="title">Text</a> more text</p>""" + ) + + def test_uneven_brackets_with_titles2(self): + self.assertMarkdownRenders( + """[Text](http://link.com/('"title") more text""", + """<p><a href="http://link.com/('" title="title">Text</a> more text</p>""" + ) + + def test_uneven_brackets_with_titles3(self): + self.assertMarkdownRenders( + """[Text](http://link.com/("title)") more text""", + """<p><a href="http://link.com/(" title="title)">Text</a> more text</p>""" + ) + + def test_uneven_brackets_with_titles4(self): + self.assertMarkdownRenders( + """[Text](http://link.com/( "title") more text""", + """<p><a href="http://link.com/(" title="title">Text</a> more text</p>""" + ) + + def test_uneven_brackets_with_titles5(self): + self.assertMarkdownRenders( + """[Text](http://link.com/( "title)") more text""", + """<p><a href="http://link.com/(" title="title)">Text</a> more text</p>""" + ) + + def test_mixed_title_quotes1(self): + self.assertMarkdownRenders( + """[Text](http://link.com/'"title") more text""", + """<p><a href="http://link.com/'" title="title">Text</a> more text</p>""" + ) + + def test_mixed_title_quotes2(self): + self.assertMarkdownRenders( + """[Text](http://link.com/"'title') more text""", + """<p><a href="http://link.com/"" title="title">Text</a> more text</p>""" + ) + + def test_mixed_title_quotes3(self): + self.assertMarkdownRenders( + """[Text](http://link.com/with spaces'"and quotes" 'and title') more text""", + """<p><a href="http://link.com/with spaces" title=""and quotes" 'and title">""" + """Text</a> more text</p>""" + ) + + def test_mixed_title_quotes4(self): + self.assertMarkdownRenders( + """[Text](http://link.com/with spaces'"and quotes" 'and title") more text""", + """<p><a href="http://link.com/with spaces'" title="and quotes" 'and title">Text</a> more text</p>""" + ) + + def test_mixed_title_quotes5(self): + self.assertMarkdownRenders( + """[Text](http://link.com/with spaces '"and quotes" 'and title') more text""", + """<p><a href="http://link.com/with spaces" title=""and quotes" 'and title">""" + """Text</a> more text</p>""" + ) + + def test_mixed_title_quotes6(self): + self.assertMarkdownRenders( + """[Text](http://link.com/with spaces "and quotes" 'and title') more text""", + """<p><a href="http://link.com/with spaces "and quotes"" title="and title">""" + """Text</a> more text</p>""" + ) + + def test_single_quote(self): + self.assertMarkdownRenders( + """[test](link"notitle)""", + """<p><a href="link"notitle">test</a></p>""" + ) + + def test_angle_with_mixed_title_quotes(self): + self.assertMarkdownRenders( + """[Text](<http://link.com/with spaces '"and quotes"> 'and title') more text""", + """<p><a href="http://link.com/with spaces '"and quotes"" title="and title">""" + """Text</a> more text</p>""" + ) + + def test_amp_in_url(self): + """Test amp in URLs.""" + + self.assertMarkdownRenders( + '[link](http://www.freewisdom.org/this&that)', + '<p><a href="http://www.freewisdom.org/this&that">link</a></p>' + ) + self.assertMarkdownRenders( + '[title](http://example.com/?a=1&b=2)', + '<p><a href="http://example.com/?a=1&b=2">title</a></p>' + ) + self.assertMarkdownRenders( + '[title](http://example.com/?a=1&b=2)', + '<p><a href="http://example.com/?a=1&b=2">title</a></p>' + ) + + +class TestReferenceLinks(TestCase): + + def test_ref_link(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: http://example.com + """ + ), + """<p><a href="http://example.com">Text</a></p>""" + ) + + def test_ref_link_angle_brackets(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: <http://example.com> + """ + ), + """<p><a href="http://example.com">Text</a></p>""" + ) + + def test_ref_link_no_space(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]:http://example.com + """ + ), + """<p><a href="http://example.com">Text</a></p>""" + ) + + def test_ref_link_angle_brackets_no_space(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]:<http://example.com> + """ + ), + """<p><a href="http://example.com">Text</a></p>""" + ) + + def test_ref_link_angle_brackets_title(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: <http://example.com> "title" + """ + ), + """<p><a href="http://example.com" title="title">Text</a></p>""" + ) + + def test_ref_link_title(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: http://example.com "title" + """ + ), + """<p><a href="http://example.com" title="title">Text</a></p>""" + ) + + def test_ref_link_angle_brackets_title_no_space(self): + # TODO: Maybe reevaluate this? + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: <http://example.com>"title" + """ + ), + """<p><a href="http://example.com>"title"">Text</a></p>""" + ) + + def test_ref_link_title_no_space(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: http://example.com"title" + """ + ), + """<p><a href="http://example.com"title"">Text</a></p>""" + ) + + def test_ref_link_single_quoted_title(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: http://example.com 'title' + """ + ), + """<p><a href="http://example.com" title="title">Text</a></p>""" + ) + + def test_ref_link_title_nested_quote(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: http://example.com "title'" + """ + ), + """<p><a href="http://example.com" title="title'">Text</a></p>""" + ) + + def test_ref_link_single_quoted_title_nested_quote(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: http://example.com 'title"' + """ + ), + """<p><a href="http://example.com" title="title"">Text</a></p>""" + ) + + def test_ref_link_override(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: http://example.com 'ignore' + [Text]: https://example.com 'override' + """ + ), + """<p><a href="https://example.com" title="override">Text</a></p>""" + ) + + def test_ref_link_title_no_blank_lines(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + [Text]: http://example.com "title" + [Text] + """ + ), + self.dedent( + """ + <p><a href="http://example.com" title="title">Text</a></p> + <p><a href="http://example.com" title="title">Text</a></p> + """ + ) + ) + + def test_ref_link_multi_line(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text] + + [Text]: + http://example.com + "title" + """ + ), + """<p><a href="http://example.com" title="title">Text</a></p>""" + ) + + def test_reference_newlines(self): + """Test reference id whitespace cleanup.""" + + self.assertMarkdownRenders( + self.dedent( + """ + Two things: + + - I would like to tell you about the [code of + conduct][] we are using in this project. + - Only one in fact. + + [code of conduct]: https://github.com/Python-Markdown/markdown/blob/master/CODE_OF_CONDUCT.md + """ + ), + '<p>Two things:</p>\n<ul>\n<li>I would like to tell you about the ' + '<a href="https://github.com/Python-Markdown/markdown/blob/master/CODE_OF_CONDUCT.md">code of\n' + ' conduct</a> we are using in this project.</li>\n<li>Only one in fact.</li>\n</ul>' + ) + + def test_reference_across_blocks(self): + """Test references across blocks.""" + + self.assertMarkdownRenders( + self.dedent( + """ + I would like to tell you about the [code of + + conduct][] we are using in this project. + + [code of conduct]: https://github.com/Python-Markdown/markdown/blob/master/CODE_OF_CONDUCT.md + """ + ), + '<p>I would like to tell you about the [code of</p>\n' + '<p>conduct][] we are using in this project.</p>' + ) + + def test_ref_link_nested_left_bracket(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text[] + + [Text[]: http://example.com + """ + ), + self.dedent( + """ + <p>[Text[]</p> + <p>[Text[]: http://example.com</p> + """ + ) + ) + + def test_ref_link_nested_right_bracket(self): + self.assertMarkdownRenders( + self.dedent( + """ + [Text]] + + [Text]]: http://example.com + """ + ), + self.dedent( + """ + <p>[Text]]</p> + <p>[Text]]: http://example.com</p> + """ + ) + ) diff --git a/tests/test_syntax/inline/test_raw_html.py b/tests/test_syntax/inline/test_raw_html.py new file mode 100644 index 0000000..a9c4857 --- /dev/null +++ b/tests/test_syntax/inline/test_raw_html.py @@ -0,0 +1,30 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestRawHtml(TestCase): + def test_inline_html_angle_brackets(self): + self.assertMarkdownRenders("<span>e<c</span>", "<p><span>e<c</span></p>") + self.assertMarkdownRenders("<span>e>c</span>", "<p><span>e>c</span></p>") + self.assertMarkdownRenders("<span>e < c</span>", "<p><span>e < c</span></p>") + self.assertMarkdownRenders("<span>e > c</span>", "<p><span>e > c</span></p>") |