Skip to content

Commit a6d1e0f

Browse files
committed
Add comprehensive tests for footnotes extension
1 parent 1d13ae4 commit a6d1e0f

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

tests/test_extensions.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,204 @@ def testCustomSubstitutions(self):
302302
is the ‚mdash‘: \u2014
303303
Must not be confused with &sbquo;ndash&lsquo; (\u2013) \u2026 ]</p>"""
304304
self.assertEqual(self.md.convert(text), correct)
305+
306+
307+
class TestFootnotes(unittest.TestCase):
308+
"""Test Footnotes Extension."""
309+
310+
def setUp(self):
311+
self.md = markdown.Markdown(extensions=["footnotes"])
312+
313+
def testBasicFootnote(self):
314+
""" Test basic footnote syntax. """
315+
text = "This is a footnote reference[^1].\n\n[^1]: This is the footnote."
316+
317+
expected = (
318+
'<p>This is a footnote reference<sup id="fnref:1">'
319+
'<a class="footnote-ref" href="#fn:1">1</a></sup>.</p>\n'
320+
'<div class="footnote">\n'
321+
"<hr />\n"
322+
"<ol>\n"
323+
'<li id="fn:1">\n'
324+
"<p>This is the footnote.&#160;"
325+
'<a class="footnote-backref" href="#fnref:1" title="Jump back to '
326+
'footnote 1 in the text">&#8617;</a></p>\n'
327+
"</li>\n"
328+
"</ol>\n"
329+
"</div>"
330+
)
331+
332+
self.assertEqual(self.md.convert(text), expected)
333+
334+
def testFootnoteOrder(self):
335+
""" Test that footnotes are ordered correctly. """
336+
text = (
337+
"First footnote reference[^first]. Second footnote reference[^last].\n\n"
338+
"[^last]: Second footnote.\n[^first]: First footnote."
339+
)
340+
341+
expected = (
342+
'<p>First footnote reference<sup id="fnref:first"><a class="footnote-ref" '
343+
'href="#fn:first">1</a></sup>. Second footnote reference<sup id="fnref:last">'
344+
'<a class="footnote-ref" href="#fn:last">2</a></sup>.</p>\n'
345+
'<div class="footnote">\n'
346+
"<hr />\n"
347+
"<ol>\n"
348+
'<li id="fn:first">\n'
349+
'<p>First footnote.&#160;<a class="footnote-backref" href="#fnref:first" '
350+
'title="Jump back to footnote 1 in the text">&#8617;</a></p>\n'
351+
"</li>\n"
352+
'<li id="fn:last">\n'
353+
'<p>Second footnote.&#160;<a class="footnote-backref" href="#fnref:last" '
354+
'title="Jump back to footnote 2 in the text">&#8617;</a></p>\n'
355+
"</li>\n"
356+
"</ol>\n"
357+
"</div>"
358+
)
359+
360+
self.assertEqual(self.md.convert(text), expected)
361+
362+
def testFootnoteReferenceWithinCodeSpan(self):
363+
""" Test footnote reference within a code span. """
364+
365+
text = "A `code span with a footnote[^1] reference`."
366+
expected = "<p>A <code>code span with a footnote[^1] reference</code>.</p>"
367+
368+
self.assertEqual(self.md.convert(text), expected)
369+
370+
def testFootnoteReferenceInLink(self):
371+
""" Test footnote reference within a link. """
372+
373+
text = "A [link with a footnote[^1] reference](http://example.com)."
374+
expected = '<p>A <a href="http://example.com">link with a footnote[^1] reference</a>.</p>'
375+
376+
self.assertEqual(self.md.convert(text), expected)
377+
378+
def testDuplicateFootnoteReferences(self):
379+
""" Test multiple references to the same footnote. """
380+
text = "First[^dup] and second[^dup] reference.\n\n[^dup]: Duplicate footnote."
381+
382+
expected = (
383+
'<p>First<sup id="fnref:dup">'
384+
'<a class="footnote-ref" href="#fn:dup">1</a></sup> and second<sup id="fnref2:dup">'
385+
'<a class="footnote-ref" href="#fn:dup">1</a></sup> reference.</p>\n'
386+
'<div class="footnote">\n'
387+
"<hr />\n"
388+
"<ol>\n"
389+
'<li id="fn:dup">\n'
390+
"<p>Duplicate footnote.&#160;"
391+
'<a class="footnote-backref" href="#fnref:dup" '
392+
'title="Jump back to footnote 1 in the text">&#8617;</a>'
393+
'<a class="footnote-backref" href="#fnref2:dup" '
394+
'title="Jump back to footnote 1 in the text">&#8617;</a></p>\n'
395+
"</li>\n"
396+
"</ol>\n"
397+
"</div>"
398+
)
399+
400+
self.assertEqual(self.md.convert(text), expected)
401+
402+
def testFootnoteReferenceWithoutDefinition(self):
403+
""" Test footnote reference without corresponding definition. """
404+
text = "This has a missing footnote[^missing]."
405+
expected = "<p>This has a missing footnote[^missing].</p>"
406+
407+
self.assertEqual(self.md.convert(text), expected)
408+
409+
def testFootnoteDefinitionWithoutReference(self):
410+
""" Test footnote definition without corresponding reference. """
411+
text = "No reference here.\n\n[^orphan]: Orphaned footnote."
412+
413+
self.assertIn("fn:orphan", self.md.convert(text))
414+
415+
# For the opposite behavior:
416+
# self.assertNotIn("fn:orphan", self.md.convert(text))
417+
418+
def testMultilineFootnote(self):
419+
""" Test footnote definition spanning multiple lines. """
420+
421+
text = (
422+
"Multi-line footnote[^multi].\n\n"
423+
"[^multi]: This is a footnote\n"
424+
" that spans multiple lines\n"
425+
" with proper indentation."
426+
)
427+
428+
expected = (
429+
'<p>Multi-line footnote<sup id="fnref:multi"><a class="footnote-ref" href="#fn:multi">1</a></sup>.</p>\n'
430+
'<div class="footnote">\n'
431+
'<hr />\n'
432+
'<ol>\n'
433+
'<li id="fn:multi">\n'
434+
'<p>This is a footnote\n'
435+
'that spans multiple lines\n'
436+
'with proper indentation.&#160;<a class="footnote-backref" href="#fnref:multi" '
437+
'title="Jump back to footnote 1 in the text">&#8617;</a></p>\n'
438+
'</li>\n'
439+
'</ol>\n'
440+
'</div>'
441+
)
442+
self.assertEqual(self.md.convert(text), expected)
443+
444+
def testFootnoteInBlockquote(self):
445+
""" Test footnote reference within a blockquote. """
446+
text = "> This is a quote with a footnote[^quote].\n\n[^quote]: Quote footnote."
447+
448+
result = self.md.convert(text)
449+
self.assertIn("<blockquote>", result)
450+
self.assertIn("fnref:quote", result)
451+
452+
def testFootnoteInList(self):
453+
""" Test footnote reference within a list item. """
454+
text = "1. First item with footnote[^note]\n1. Second item\n\n[^note]: List footnote."
455+
456+
result = self.md.convert(text)
457+
self.assertIn("<ol>", result)
458+
self.assertIn("fnref:note", result)
459+
460+
def testNestedFootnotes(self):
461+
""" Test footnote definition containing another footnote reference. """
462+
text = (
463+
"Main footnote[^main].\n\n"
464+
"[^main]: This footnote references another[^nested].\n"
465+
"[^nested]: Nested footnote."
466+
)
467+
result = self.md.convert(text)
468+
469+
self.assertIn("fnref:main", result)
470+
self.assertIn("fnref:nested", result)
471+
self.assertIn("fn:main", result)
472+
self.assertIn("fn:nested", result)
473+
474+
def testFootnoteReset(self):
475+
""" Test that footnotes are properly reset between documents. """
476+
text1 = "First doc[^1].\n\n[^1]: First footnote."
477+
text2 = "Second doc[^1].\n\n[^1]: Different footnote."
478+
479+
result1 = self.md.convert(text1)
480+
self.md.reset()
481+
result2 = self.md.convert(text2)
482+
483+
self.assertIn("First footnote", result1)
484+
self.assertIn("Different footnote", result2)
485+
self.assertNotIn("Different footnote", result1)
486+
487+
def testFootnoteIdWithSpecialChars(self):
488+
""" Test footnote id containing special and unicode characters. """
489+
text = "Unicode footnote id[^!#¤%/()=?+}{§øé].\n\n[^!#¤%/()=?+}{§øé]: Footnote with unicode id."
490+
491+
self.assertIn("fnref:!#¤%/()=?+}{§øé", self.md.convert(text))
492+
493+
def testFootnoteRefInHtml(self):
494+
""" Test footnote reference within HTML tags. """
495+
text = "A <span>footnote reference[^1] in an HTML</span>.\n\n[^1]: The footnote."
496+
497+
self.assertIn("fnref:1", self.md.convert(text))
498+
499+
def testFootnoteWithHtmlAndMarkdown(self):
500+
""" Test footnote containing HTML and markdown elements. """
501+
text = "A footnote with style[^html].\n\n[^html]: Has *emphasis* and <strong>bold</strong>."
502+
503+
result = self.md.convert(text)
504+
self.assertIn("<em>emphasis</em>", result)
505+
self.assertIn("<strong>bold</strong>", result)

0 commit comments

Comments
 (0)