Coverage for tests/test_extension.py: 100.00%
78 statements
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 18:59 +0200
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 18:59 +0200
1"""Tests for the extension module."""
3from __future__ import annotations
5import re
6import sys
7from textwrap import dedent
8from typing import TYPE_CHECKING
10import pytest
12if TYPE_CHECKING:
13 from markdown import Markdown
15 from mkdocstrings.plugin import MkdocstringsPlugin
18@pytest.mark.parametrize("ext_markdown", [{"markdown_extensions": [{"footnotes": {}}]}], indirect=["ext_markdown"])
19def test_multiple_footnotes(ext_markdown: Markdown) -> None:
20 """Assert footnotes don't get added to subsequent docstrings."""
21 output = ext_markdown.convert(
22 dedent(
23 """
24 Top.[^aaa]
26 ::: tests.fixtures.footnotes.func_a
28 ::: tests.fixtures.footnotes.func_b
30 ::: tests.fixtures.footnotes.func_c
32 [^aaa]: Top footnote
33 """,
34 ),
35 )
36 assert output.count("Footnote A") == 1
37 assert output.count("Footnote B") == 1
38 assert output.count("Top footnote") == 1
41def test_markdown_heading_level(ext_markdown: Markdown) -> None:
42 """Assert that Markdown headings' level doesn't exceed heading_level."""
43 output = ext_markdown.convert("::: tests.fixtures.headings\n options:\n show_root_heading: true")
44 assert ">Foo</h3>" in output
45 assert ">Bar</h5>" in output
46 assert ">Baz</h6>" in output
49def test_keeps_preceding_text(ext_markdown: Markdown) -> None:
50 """Assert that autodoc is recognized in the middle of a block and preceding text is kept."""
51 output = ext_markdown.convert("**preceding**\n::: tests.fixtures.headings")
52 assert "<strong>preceding</strong>" in output
53 assert ">Foo</h2>" in output
54 assert ":::" not in output
57def test_reference_inside_autodoc(ext_markdown: Markdown) -> None:
58 """Assert cross-reference Markdown extension works correctly."""
59 output = ext_markdown.convert("::: tests.fixtures.cross_reference")
60 assert re.search(r"Link to <.*something\.Else.*>something\.Else<.*>\.", output)
63@pytest.mark.skipif(sys.version_info < (3, 8), reason="typing.Literal requires Python 3.8")
64def test_quote_inside_annotation(ext_markdown: Markdown) -> None:
65 """Assert that inline highlighting doesn't double-escape HTML."""
66 output = ext_markdown.convert("::: tests.fixtures.string_annotation.Foo")
67 assert ";hi&" in output
68 assert "&" not in output
71def test_html_inside_heading(ext_markdown: Markdown) -> None:
72 """Assert that headings don't double-escape HTML."""
73 output = ext_markdown.convert("::: tests.fixtures.html_tokens")
74 assert "'<" in output
75 assert "&" not in output
78@pytest.mark.parametrize(
79 ("ext_markdown", "expect_permalink"),
80 [
81 ({"markdown_extensions": [{"toc": {"permalink": "@@@"}}]}, "@@@"),
82 ({"markdown_extensions": [{"toc": {"permalink": "TeSt"}}]}, "TeSt"),
83 ({"markdown_extensions": [{"toc": {"permalink": True}}]}, "¶"),
84 ],
85 indirect=["ext_markdown"],
86)
87def test_no_double_toc(ext_markdown: Markdown, expect_permalink: str) -> None:
88 """Assert that the 'toc' extension doesn't apply its modification twice."""
89 output = ext_markdown.convert(
90 dedent(
91 """
92 # aa
94 ::: tests.fixtures.headings
95 options:
96 show_root_toc_entry: false
98 # bb
99 """,
100 ),
101 )
102 assert output.count(expect_permalink) == 5
103 assert 'id="tests.fixtures.headings--foo"' in output
104 assert ext_markdown.toc_tokens == [ # type: ignore[attr-defined] # the member gets populated only with 'toc' extension
105 {
106 "level": 1,
107 "id": "aa",
108 "html": "aa",
109 "name": "aa",
110 "data-toc-label": "",
111 "children": [
112 {
113 "level": 2,
114 "id": "tests.fixtures.headings--foo",
115 "html": "Foo",
116 "name": "Foo",
117 "data-toc-label": "",
118 "children": [
119 {
120 "level": 4,
121 "id": "tests.fixtures.headings--bar",
122 "html": "Bar",
123 "name": "Bar",
124 "data-toc-label": "",
125 "children": [
126 {
127 "level": 6,
128 "id": "tests.fixtures.headings--baz",
129 "html": "Baz",
130 "name": "Baz",
131 "data-toc-label": "",
132 "children": [],
133 },
134 ],
135 },
136 ],
137 },
138 ],
139 },
140 {
141 "level": 1,
142 "id": "bb",
143 "html": "bb",
144 "name": "bb",
145 "data-toc-label": "",
146 "children": [],
147 },
148 ]
151def test_use_custom_handler(ext_markdown: Markdown) -> None:
152 """Assert that we use the custom handler declared in an individual autodoc instruction."""
153 with pytest.raises(ModuleNotFoundError):
154 ext_markdown.convert("::: tests.fixtures.headings\n handler: not_here")
157def test_dont_register_every_identifier_as_anchor(plugin: MkdocstringsPlugin, ext_markdown: Markdown) -> None:
158 """Assert that we don't preemptively register all identifiers of a rendered object."""
159 handler = plugin._handlers.get_handler("python") # type: ignore[union-attr]
160 ids = ("id1", "id2", "id3")
161 handler.get_anchors = lambda _: ids # type: ignore[method-assign]
162 ext_markdown.convert("::: tests.fixtures.headings")
163 autorefs = ext_markdown.parser.blockprocessors["mkdocstrings"]._autorefs # type: ignore[attr-defined]
164 for identifier in ids:
165 assert identifier not in autorefs._url_map
166 assert identifier not in autorefs._abs_url_map
169def test_use_options_yaml_key(ext_markdown: Markdown) -> None:
170 """Check that using the 'options' YAML key works as expected."""
171 assert "h1" in ext_markdown.convert("::: tests.fixtures.headings\n options:\n heading_level: 1")
172 assert "h1" not in ext_markdown.convert("::: tests.fixtures.headings\n options:\n heading_level: 2")
175def test_use_yaml_options_after_blank_line(ext_markdown: Markdown) -> None:
176 """Check that YAML options are detected even after a blank line."""
177 assert "h1" not in ext_markdown.convert("::: tests.fixtures.headings\n\n options:\n heading_level: 2")
180@pytest.mark.parametrize("ext_markdown", [{"markdown_extensions": [{"admonition": {}}]}], indirect=["ext_markdown"])
181def test_removing_duplicated_headings(ext_markdown: Markdown) -> None:
182 """Assert duplicated headings are removed from the output."""
183 output = ext_markdown.convert(
184 dedent(
185 """
186 ::: tests.fixtures.headings_many.heading_1
188 !!! note
190 ::: tests.fixtures.headings_many.heading_2
192 ::: tests.fixtures.headings_many.heading_3
193 """,
194 ),
195 )
196 assert output.count(">Heading one<") == 1
197 assert output.count(">Heading two<") == 1
198 assert output.count(">Heading three<") == 1
199 assert output.count('class="mkdocstrings') == 0
202def _assert_contains_in_order(items: list[str], string: str) -> None:
203 index = 0
204 for item in items:
205 assert item in string[index:]
206 index = string.index(item, index) + len(item)
209@pytest.mark.parametrize("ext_markdown", [{"markdown_extensions": [{"attr_list": {}}]}], indirect=["ext_markdown"])
210def test_backup_of_anchors(ext_markdown: Markdown) -> None:
211 """Anchors with empty `href` are backed up."""
212 output = ext_markdown.convert("::: tests.fixtures.markdown_anchors")
214 # Anchors with id and no href have been backed up and updated.
215 _assert_contains_in_order(
216 [
217 'id="anchor"',
218 'id="tests.fixtures.markdown_anchors--anchor"',
219 'id="heading-anchor-1"',
220 'id="tests.fixtures.markdown_anchors--heading-anchor-1"',
221 'id="heading-anchor-2"',
222 'id="tests.fixtures.markdown_anchors--heading-anchor-2"',
223 'id="heading-anchor-3"',
224 'id="tests.fixtures.markdown_anchors--heading-anchor-3"',
225 ],
226 output,
227 )
229 # Anchors with href and with or without id have been updated but not backed up.
230 _assert_contains_in_order(
231 [
232 'id="tests.fixtures.markdown_anchors--with-id"',
233 ],
234 output,
235 )
236 assert 'id="with-id"' not in output
238 _assert_contains_in_order(
239 [
240 'href="#tests.fixtures.markdown_anchors--has-href1"',
241 'href="#tests.fixtures.markdown_anchors--has-href2"',
242 ],
243 output,
244 )
245 assert 'href="#has-href1"' not in output
246 assert 'href="#has-href2"' not in output