Skip to content

Commit 613ad50

Browse files
authored
Ensure permalinks and ankorlinks are not restricted by toc_depth
This fixes a regression which was introduced with support for toc_depth. Relevant tests have been moved and updated to the new framework. Fixes Python-Markdown#1107. The test framework also received an addition. The assertMarkdownRenders method now accepts a new keyword expected_attrs which consists of a dict of attrs and expected values. Each is checked against the attr of the Markdown instance. This was needed to check the value of md.toc and md.toc_tokens in some of the included tests.
1 parent 1858c1b commit 613ad50

File tree

6 files changed

+426
-217
lines changed

6 files changed

+426
-217
lines changed

docs/change_log/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Under development: version 3.3.4 (a bug-fix release).
1010
* Properly parse code spans in md_in_html (#1069).
1111
* Preserve text immediately before an admonition (#1092).
1212
* Simplified regex for HTML placeholders (#928) addressing (#932).
13+
* Ensure `permalinks` and `ankorlinks` are not restricted by `toc_depth` (#1107).
1314

1415
Oct 25, 2020: version 3.3.3 (a bug-fix release).
1516

docs/test_tools.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@ Properties
2222
test. The defaults can be overridden on individual tests.
2323

2424
Methods
25-
: `assertMarkdownRenders`: accepts the source text, the expected output,
26-
and any keywords to pass to Markdown. The `default_kwargs` defined on the
27-
class are used except where overridden by keyword arguments. The output and
28-
expected output are passed to `TestCase.assertMultiLineEqual`. An
29-
`AssertionError` is raised with a diff if the actual output does not equal the
30-
expected output.
25+
: `assertMarkdownRenders`: accepts the source text, the expected output, an optional
26+
dictionary of `expected_attrs`, and any keywords to pass to Markdown. The
27+
`default_kwargs` defined on the class are used except where overridden by
28+
keyword arguments. The output and expected output are passed to
29+
`TestCase.assertMultiLineEqual`. An `AssertionError` is raised with a diff
30+
if the actual output does not equal the expected output. The optional
31+
keyword `expected_attrs` accepts a dictionary of attribute names as keys with
32+
expected values. Each value is checked against the attribute of that
33+
name on the instance of the `Markdown` class using `TestCase.assertEqual`. An
34+
`AssertionError` is raised if any value does not match the expected value.
3135

3236
: `dedent`: Dedent triple-quoted strings.
3337

markdown/extensions/toc.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -269,23 +269,22 @@ def run(self, doc):
269269
for el in doc.iter():
270270
if isinstance(el.tag, str) and self.header_rgx.match(el.tag):
271271
self.set_level(el)
272-
if int(el.tag[-1]) < self.toc_top or int(el.tag[-1]) > self.toc_bottom:
273-
continue
274272
text = get_name(el)
275273

276274
# Do not override pre-existing ids
277275
if "id" not in el.attrib:
278276
innertext = unescape(stashedHTML2text(text, self.md))
279277
el.attrib["id"] = unique(self.slugify(innertext, self.sep), used_ids)
280278

281-
toc_tokens.append({
282-
'level': int(el.tag[-1]),
283-
'id': el.attrib["id"],
284-
'name': unescape(stashedHTML2text(
285-
code_escape(el.attrib.get('data-toc-label', text)),
286-
self.md, strip_entities=False
287-
))
288-
})
279+
if int(el.tag[-1]) >= self.toc_top and int(el.tag[-1]) <= self.toc_bottom:
280+
toc_tokens.append({
281+
'level': int(el.tag[-1]),
282+
'id': el.attrib["id"],
283+
'name': unescape(stashedHTML2text(
284+
code_escape(el.attrib.get('data-toc-label', text)),
285+
self.md, strip_entities=False
286+
))
287+
})
289288

290289
# Remove the data-toc-label attribute as it is no longer needed
291290
if 'data-toc-label' in el.attrib:

markdown/test_tools.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import sys
2424
import unittest
2525
import textwrap
26-
from . import markdown, util
26+
from . import markdown, Markdown, util
2727

2828
try:
2929
import tidylib
@@ -54,15 +54,25 @@ class TestCase(unittest.TestCase):
5454

5555
default_kwargs = {}
5656

57-
def assertMarkdownRenders(self, source, expected, **kwargs):
57+
def assertMarkdownRenders(self, source, expected, expected_attrs=None, **kwargs):
5858
"""
5959
Test that source Markdown text renders to expected output with given keywords.
60+
61+
`expected_attrs` accepts a dict. Each key should be the name of an attribute
62+
on the `Markdown` instance and the value should be the expected value after
63+
the source text is parsed by Markdown. After the expected output is tested,
64+
the expected value for each attribute is compared against the actual
65+
attribute of the `Markdown` instance using `TestCase.assertEqual`.
6066
"""
6167

68+
expected_attrs = expected_attrs or {}
6269
kws = self.default_kwargs.copy()
6370
kws.update(kwargs)
64-
output = markdown(source, **kws)
71+
md = Markdown(**kws)
72+
output = md.convert(source)
6573
self.assertMultiLineEqual(output, expected)
74+
for key, value in expected_attrs.items():
75+
self.assertEqual(getattr(md, key), value)
6676

6777
def dedent(self, text):
6878
"""

tests/test_extensions.py

Lines changed: 0 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -536,90 +536,6 @@ def testHeaderInlineMarkup(self):
536536
{'level': 1, 'id': 'some-header-with-markup', 'name': 'Some Header with markup.', 'children': []},
537537
])
538538

539-
def testAnchorLink(self):
540-
""" Test TOC Anchorlink. """
541-
md = markdown.Markdown(
542-
extensions=[markdown.extensions.toc.TocExtension(anchorlink=True)]
543-
)
544-
text = '# Header 1\n\n## Header *2*'
545-
self.assertEqual(
546-
md.convert(text),
547-
'<h1 id="header-1"><a class="toclink" href="#header-1">Header 1</a></h1>\n'
548-
'<h2 id="header-2"><a class="toclink" href="#header-2">Header <em>2</em></a></h2>'
549-
)
550-
551-
def testAnchorLinkWithSingleInlineCode(self):
552-
""" Test TOC Anchorlink with single inline code. """
553-
md = markdown.Markdown(
554-
extensions=[markdown.extensions.toc.TocExtension(anchorlink=True)]
555-
)
556-
text = '# This is `code`.'
557-
self.assertEqual(
558-
md.convert(text),
559-
'<h1 id="this-is-code">' # noqa
560-
'<a class="toclink" href="#this-is-code">' # noqa
561-
'This is <code>code</code>.' # noqa
562-
'</a>' # noqa
563-
'</h1>' # noqa
564-
)
565-
566-
def testAnchorLinkWithDoubleInlineCode(self):
567-
""" Test TOC Anchorlink with double inline code. """
568-
md = markdown.Markdown(
569-
extensions=[markdown.extensions.toc.TocExtension(anchorlink=True)]
570-
)
571-
text = '# This is `code` and `this` too.'
572-
self.assertEqual(
573-
md.convert(text),
574-
'<h1 id="this-is-code-and-this-too">' # noqa
575-
'<a class="toclink" href="#this-is-code-and-this-too">' # noqa
576-
'This is <code>code</code> and <code>this</code> too.' # noqa
577-
'</a>' # noqa
578-
'</h1>' # noqa
579-
)
580-
581-
def testPermalink(self):
582-
""" Test TOC Permalink. """
583-
md = markdown.Markdown(
584-
extensions=[markdown.extensions.toc.TocExtension(permalink=True)]
585-
)
586-
text = '# Header'
587-
self.assertEqual(
588-
md.convert(text),
589-
'<h1 id="header">' # noqa
590-
'Header' # noqa
591-
'<a class="headerlink" href="#header" title="Permanent link">&para;</a>' # noqa
592-
'</h1>' # noqa
593-
)
594-
595-
def testPermalinkWithSingleInlineCode(self):
596-
""" Test TOC Permalink with single inline code. """
597-
md = markdown.Markdown(
598-
extensions=[markdown.extensions.toc.TocExtension(permalink=True)]
599-
)
600-
text = '# This is `code`.'
601-
self.assertEqual(
602-
md.convert(text),
603-
'<h1 id="this-is-code">' # noqa
604-
'This is <code>code</code>.' # noqa
605-
'<a class="headerlink" href="#this-is-code" title="Permanent link">&para;</a>' # noqa
606-
'</h1>' # noqa
607-
)
608-
609-
def testPermalinkWithDoubleInlineCode(self):
610-
""" Test TOC Permalink with double inline code. """
611-
md = markdown.Markdown(
612-
extensions=[markdown.extensions.toc.TocExtension(permalink=True)]
613-
)
614-
text = '# This is `code` and `this` too.'
615-
self.assertEqual(
616-
md.convert(text),
617-
'<h1 id="this-is-code-and-this-too">' # noqa
618-
'This is <code>code</code> and <code>this</code> too.' # noqa
619-
'<a class="headerlink" href="#this-is-code-and-this-too" title="Permanent link">&para;</a>' # noqa
620-
'</h1>' # noqa
621-
)
622-
623539
def testTitle(self):
624540
""" Test TOC Title. """
625541
md = markdown.Markdown(
@@ -714,115 +630,6 @@ def testTocInHeaders(self):
714630
'<h1 id="toc"><em>[TOC]</em></h1>' # noqa
715631
)
716632

717-
def testMinMaxLevel(self):
718-
""" Test toc_height setting """
719-
md = markdown.Markdown(
720-
extensions=[markdown.extensions.toc.TocExtension(toc_depth='3-4')]
721-
)
722-
text = '# Header 1 not in TOC\n\n## Header 2 not in TOC\n\n### Header 3\n\n####Header 4'
723-
self.assertEqual(
724-
md.convert(text),
725-
'<h1>Header 1 not in TOC</h1>\n'
726-
'<h2>Header 2 not in TOC</h2>\n'
727-
'<h3 id="header-3">Header 3</h3>\n'
728-
'<h4 id="header-4">Header 4</h4>'
729-
)
730-
self.assertEqual(
731-
md.toc,
732-
'<div class="toc">\n'
733-
'<ul>\n' # noqa
734-
'<li><a href="#header-3">Header 3</a>' # noqa
735-
'<ul>\n' # noqa
736-
'<li><a href="#header-4">Header 4</a></li>\n' # noqa
737-
'</ul>\n' # noqa
738-
'</li>\n' # noqa
739-
'</ul>\n' # noqa
740-
'</div>\n'
741-
)
742-
743-
self.assertNotIn("Header 1", md.toc)
744-
745-
def testMaxLevel(self):
746-
""" Test toc_depth setting """
747-
md = markdown.Markdown(
748-
extensions=[markdown.extensions.toc.TocExtension(toc_depth=2)]
749-
)
750-
text = '# Header 1\n\n## Header 2\n\n###Header 3 not in TOC'
751-
self.assertEqual(
752-
md.convert(text),
753-
'<h1 id="header-1">Header 1</h1>\n'
754-
'<h2 id="header-2">Header 2</h2>\n'
755-
'<h3>Header 3 not in TOC</h3>'
756-
)
757-
self.assertEqual(
758-
md.toc,
759-
'<div class="toc">\n'
760-
'<ul>\n' # noqa
761-
'<li><a href="#header-1">Header 1</a>' # noqa
762-
'<ul>\n' # noqa
763-
'<li><a href="#header-2">Header 2</a></li>\n' # noqa
764-
'</ul>\n' # noqa
765-
'</li>\n' # noqa
766-
'</ul>\n' # noqa
767-
'</div>\n'
768-
)
769-
770-
self.assertNotIn("Header 3", md.toc)
771-
772-
def testMinMaxLevelwithBaseLevel(self):
773-
""" Test toc_height setting together with baselevel """
774-
md = markdown.Markdown(
775-
extensions=[markdown.extensions.toc.TocExtension(toc_depth='4-6',
776-
baselevel=3)]
777-
)
778-
text = '# First Header\n\n## Second Level\n\n### Third Level'
779-
self.assertEqual(
780-
md.convert(text),
781-
'<h3>First Header</h3>\n'
782-
'<h4 id="second-level">Second Level</h4>\n'
783-
'<h5 id="third-level">Third Level</h5>'
784-
)
785-
self.assertEqual(
786-
md.toc,
787-
'<div class="toc">\n'
788-
'<ul>\n' # noqa
789-
'<li><a href="#second-level">Second Level</a>' # noqa
790-
'<ul>\n' # noqa
791-
'<li><a href="#third-level">Third Level</a></li>\n' # noqa
792-
'</ul>\n' # noqa
793-
'</li>\n' # noqa
794-
'</ul>\n' # noqa
795-
'</div>\n'
796-
)
797-
self.assertNotIn("First Header", md.toc)
798-
799-
def testMaxLevelwithBaseLevel(self):
800-
""" Test toc_depth setting together with baselevel """
801-
md = markdown.Markdown(
802-
extensions=[markdown.extensions.toc.TocExtension(toc_depth=3,
803-
baselevel=2)]
804-
)
805-
text = '# Some Header\n\n## Next Level\n\n### Too High'
806-
self.assertEqual(
807-
md.convert(text),
808-
'<h2 id="some-header">Some Header</h2>\n'
809-
'<h3 id="next-level">Next Level</h3>\n'
810-
'<h4>Too High</h4>'
811-
)
812-
self.assertEqual(
813-
md.toc,
814-
'<div class="toc">\n'
815-
'<ul>\n' # noqa
816-
'<li><a href="#some-header">Some Header</a>' # noqa
817-
'<ul>\n' # noqa
818-
'<li><a href="#next-level">Next Level</a></li>\n' # noqa
819-
'</ul>\n' # noqa
820-
'</li>\n' # noqa
821-
'</ul>\n' # noqa
822-
'</div>\n'
823-
)
824-
self.assertNotIn("Too High", md.toc)
825-
826633

827634
class TestSmarty(unittest.TestCase):
828635
def setUp(self):

0 commit comments

Comments
 (0)