Skip to content

Commit

Permalink
Ensure permalinks and ankorlinks are not restricted by toc_depth
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
waylan authored Feb 24, 2021
1 parent 1858c1b commit 613ad50
Show file tree
Hide file tree
Showing 6 changed files with 426 additions and 217 deletions.
1 change: 1 addition & 0 deletions docs/change_log/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Under development: version 3.3.4 (a bug-fix release).
* Properly parse code spans in md_in_html (#1069).
* Preserve text immediately before an admonition (#1092).
* Simplified regex for HTML placeholders (#928) addressing (#932).
* Ensure `permalinks` and `ankorlinks` are not restricted by `toc_depth` (#1107).

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

Expand Down
16 changes: 10 additions & 6 deletions docs/test_tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ Properties
test. The defaults can be overridden on individual tests.

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

: `dedent`: Dedent triple-quoted strings.

Expand Down
19 changes: 9 additions & 10 deletions markdown/extensions/toc.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,23 +269,22 @@ def run(self, doc):
for el in doc.iter():
if isinstance(el.tag, str) and self.header_rgx.match(el.tag):
self.set_level(el)
if int(el.tag[-1]) < self.toc_top or int(el.tag[-1]) > self.toc_bottom:
continue
text = get_name(el)

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

toc_tokens.append({
'level': int(el.tag[-1]),
'id': el.attrib["id"],
'name': unescape(stashedHTML2text(
code_escape(el.attrib.get('data-toc-label', text)),
self.md, strip_entities=False
))
})
if int(el.tag[-1]) >= self.toc_top and int(el.tag[-1]) <= self.toc_bottom:
toc_tokens.append({
'level': int(el.tag[-1]),
'id': el.attrib["id"],
'name': unescape(stashedHTML2text(
code_escape(el.attrib.get('data-toc-label', text)),
self.md, strip_entities=False
))
})

# Remove the data-toc-label attribute as it is no longer needed
if 'data-toc-label' in el.attrib:
Expand Down
16 changes: 13 additions & 3 deletions markdown/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import sys
import unittest
import textwrap
from . import markdown, util
from . import markdown, Markdown, util

try:
import tidylib
Expand Down Expand Up @@ -54,15 +54,25 @@ class TestCase(unittest.TestCase):

default_kwargs = {}

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

expected_attrs = expected_attrs or {}
kws = self.default_kwargs.copy()
kws.update(kwargs)
output = markdown(source, **kws)
md = Markdown(**kws)
output = md.convert(source)
self.assertMultiLineEqual(output, expected)
for key, value in expected_attrs.items():
self.assertEqual(getattr(md, key), value)

def dedent(self, text):
"""
Expand Down
193 changes: 0 additions & 193 deletions tests/test_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,90 +536,6 @@ def testHeaderInlineMarkup(self):
{'level': 1, 'id': 'some-header-with-markup', 'name': 'Some Header with markup.', 'children': []},
])

def testAnchorLink(self):
""" Test TOC Anchorlink. """
md = markdown.Markdown(
extensions=[markdown.extensions.toc.TocExtension(anchorlink=True)]
)
text = '# Header 1\n\n## Header *2*'
self.assertEqual(
md.convert(text),
'<h1 id="header-1"><a class="toclink" href="#header-1">Header 1</a></h1>\n'
'<h2 id="header-2"><a class="toclink" href="#header-2">Header <em>2</em></a></h2>'
)

def testAnchorLinkWithSingleInlineCode(self):
""" Test TOC Anchorlink with single inline code. """
md = markdown.Markdown(
extensions=[markdown.extensions.toc.TocExtension(anchorlink=True)]
)
text = '# This is `code`.'
self.assertEqual(
md.convert(text),
'<h1 id="this-is-code">' # noqa
'<a class="toclink" href="#this-is-code">' # noqa
'This is <code>code</code>.' # noqa
'</a>' # noqa
'</h1>' # noqa
)

def testAnchorLinkWithDoubleInlineCode(self):
""" Test TOC Anchorlink with double inline code. """
md = markdown.Markdown(
extensions=[markdown.extensions.toc.TocExtension(anchorlink=True)]
)
text = '# This is `code` and `this` too.'
self.assertEqual(
md.convert(text),
'<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
)

def testPermalink(self):
""" Test TOC Permalink. """
md = markdown.Markdown(
extensions=[markdown.extensions.toc.TocExtension(permalink=True)]
)
text = '# Header'
self.assertEqual(
md.convert(text),
'<h1 id="header">' # noqa
'Header' # noqa
'<a class="headerlink" href="#header" title="Permanent link">&para;</a>' # noqa
'</h1>' # noqa
)

def testPermalinkWithSingleInlineCode(self):
""" Test TOC Permalink with single inline code. """
md = markdown.Markdown(
extensions=[markdown.extensions.toc.TocExtension(permalink=True)]
)
text = '# This is `code`.'
self.assertEqual(
md.convert(text),
'<h1 id="this-is-code">' # noqa
'This is <code>code</code>.' # noqa
'<a class="headerlink" href="#this-is-code" title="Permanent link">&para;</a>' # noqa
'</h1>' # noqa
)

def testPermalinkWithDoubleInlineCode(self):
""" Test TOC Permalink with double inline code. """
md = markdown.Markdown(
extensions=[markdown.extensions.toc.TocExtension(permalink=True)]
)
text = '# This is `code` and `this` too.'
self.assertEqual(
md.convert(text),
'<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">&para;</a>' # noqa
'</h1>' # noqa
)

def testTitle(self):
""" Test TOC Title. """
md = markdown.Markdown(
Expand Down Expand Up @@ -714,115 +630,6 @@ def testTocInHeaders(self):
'<h1 id="toc"><em>[TOC]</em></h1>' # noqa
)

def testMinMaxLevel(self):
""" Test toc_height setting """
md = markdown.Markdown(
extensions=[markdown.extensions.toc.TocExtension(toc_depth='3-4')]
)
text = '# Header 1 not in TOC\n\n## Header 2 not in TOC\n\n### Header 3\n\n####Header 4'
self.assertEqual(
md.convert(text),
'<h1>Header 1 not in TOC</h1>\n'
'<h2>Header 2 not in TOC</h2>\n'
'<h3 id="header-3">Header 3</h3>\n'
'<h4 id="header-4">Header 4</h4>'
)
self.assertEqual(
md.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'
)

self.assertNotIn("Header 1", md.toc)

def testMaxLevel(self):
""" Test toc_depth setting """
md = markdown.Markdown(
extensions=[markdown.extensions.toc.TocExtension(toc_depth=2)]
)
text = '# Header 1\n\n## Header 2\n\n###Header 3 not in TOC'
self.assertEqual(
md.convert(text),
'<h1 id="header-1">Header 1</h1>\n'
'<h2 id="header-2">Header 2</h2>\n'
'<h3>Header 3 not in TOC</h3>'
)
self.assertEqual(
md.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'
)

self.assertNotIn("Header 3", md.toc)

def testMinMaxLevelwithBaseLevel(self):
""" Test toc_height setting together with baselevel """
md = markdown.Markdown(
extensions=[markdown.extensions.toc.TocExtension(toc_depth='4-6',
baselevel=3)]
)
text = '# First Header\n\n## Second Level\n\n### Third Level'
self.assertEqual(
md.convert(text),
'<h3>First Header</h3>\n'
'<h4 id="second-level">Second Level</h4>\n'
'<h5 id="third-level">Third Level</h5>'
)
self.assertEqual(
md.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'
)
self.assertNotIn("First Header", md.toc)

def testMaxLevelwithBaseLevel(self):
""" Test toc_depth setting together with baselevel """
md = markdown.Markdown(
extensions=[markdown.extensions.toc.TocExtension(toc_depth=3,
baselevel=2)]
)
text = '# Some Header\n\n## Next Level\n\n### Too High'
self.assertEqual(
md.convert(text),
'<h2 id="some-header">Some Header</h2>\n'
'<h3 id="next-level">Next Level</h3>\n'
'<h4>Too High</h4>'
)
self.assertEqual(
md.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'
)
self.assertNotIn("Too High", md.toc)


class TestSmarty(unittest.TestCase):
def setUp(self):
Expand Down
Loading

0 comments on commit 613ad50

Please sign in to comment.