1""" 2 test_build_html 3 ~~~~~~~~~~~~~~~ 4 5 Test the HTML builder and check output against XPath. 6 7 :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9""" 10 11import os 12import re 13from distutils.version import LooseVersion 14from itertools import chain, cycle 15from unittest.mock import ANY, call, patch 16 17import pygments 18import pytest 19from html5lib import HTMLParser 20 21from sphinx.builders.html import validate_html_extra_path, validate_html_static_path 22from sphinx.errors import ConfigError 23from sphinx.testing.util import strip_escseq 24from sphinx.util import docutils, md5 25from sphinx.util.inventory import InventoryFile 26 27ENV_WARNINGS = """\ 28%(root)s/autodoc_fodder.py:docstring of autodoc_fodder.MarkupError:\\d+: \ 29WARNING: Explicit markup ends without a blank line; unexpected unindent. 30%(root)s/index.rst:\\d+: WARNING: Encoding 'utf-8-sig' used for reading included \ 31file '%(root)s/wrongenc.inc' seems to be wrong, try giving an :encoding: option 32%(root)s/index.rst:\\d+: WARNING: invalid single index entry '' 33%(root)s/index.rst:\\d+: WARNING: image file not readable: foo.png 34%(root)s/index.rst:\\d+: WARNING: download file not readable: %(root)s/nonexisting.png 35%(root)s/undecodable.rst:\\d+: WARNING: undecodable source characters, replacing \ 36with "\\?": b?'here: >>>(\\\\|/)xbb<<<((\\\\|/)r)?' 37""" 38 39HTML_WARNINGS = ENV_WARNINGS + """\ 40%(root)s/index.rst:\\d+: WARNING: unknown option: &option 41%(root)s/index.rst:\\d+: WARNING: citation not found: missing 42%(root)s/index.rst:\\d+: WARNING: a suitable image for html builder not found: foo.\\* 43%(root)s/index.rst:\\d+: WARNING: Could not lex literal_block as "c". Highlighting skipped. 44""" 45 46 47etree_cache = {} 48 49 50@pytest.fixture(scope='module') 51def cached_etree_parse(): 52 def parse(fname): 53 if fname in etree_cache: 54 return etree_cache[fname] 55 with (fname).open('rb') as fp: 56 etree = HTMLParser(namespaceHTMLElements=False).parse(fp) 57 etree_cache.clear() 58 etree_cache[fname] = etree 59 return etree 60 yield parse 61 etree_cache.clear() 62 63 64def flat_dict(d): 65 return chain.from_iterable( 66 [ 67 zip(cycle([fname]), values) 68 for fname, values in d.items() 69 ] 70 ) 71 72 73def tail_check(check): 74 rex = re.compile(check) 75 76 def checker(nodes): 77 for node in nodes: 78 if node.tail and rex.search(node.tail): 79 return True 80 assert False, '%r not found in tail of any nodes %s' % (check, nodes) 81 return checker 82 83 84def check_xpath(etree, fname, path, check, be_found=True): 85 nodes = list(etree.findall(path)) 86 if check is None: 87 assert nodes == [], ('found any nodes matching xpath ' 88 '%r in file %s' % (path, fname)) 89 return 90 else: 91 assert nodes != [], ('did not find any node matching xpath ' 92 '%r in file %s' % (path, fname)) 93 if hasattr(check, '__call__'): 94 check(nodes) 95 elif not check: 96 # only check for node presence 97 pass 98 else: 99 def get_text(node): 100 if node.text is not None: 101 # the node has only one text 102 return node.text 103 else: 104 # the node has tags and text; gather texts just under the node 105 return ''.join(n.tail or '' for n in node) 106 107 rex = re.compile(check) 108 if be_found: 109 if any(rex.search(get_text(node)) for node in nodes): 110 return 111 else: 112 if all(not rex.search(get_text(node)) for node in nodes): 113 return 114 115 assert False, ('%r not found in any node matching ' 116 'path %s in %s: %r' % (check, path, fname, 117 [node.text for node in nodes])) 118 119 120@pytest.mark.sphinx('html', testroot='warnings') 121def test_html_warnings(app, warning): 122 app.build() 123 html_warnings = strip_escseq(re.sub(re.escape(os.sep) + '{1,2}', '/', warning.getvalue())) 124 html_warnings_exp = HTML_WARNINGS % { 125 'root': re.escape(app.srcdir.replace(os.sep, '/'))} 126 assert re.match(html_warnings_exp + '$', html_warnings), \ 127 'Warnings don\'t match:\n' + \ 128 '--- Expected (regex):\n' + html_warnings_exp + \ 129 '--- Got:\n' + html_warnings 130 131 132@pytest.mark.sphinx('html', confoverrides={'html4_writer': True}) 133def test_html4_output(app, status, warning): 134 app.build() 135 136 137@pytest.mark.parametrize("fname,expect", flat_dict({ 138 'images.html': [ 139 (".//img[@src='_images/img.png']", ''), 140 (".//img[@src='_images/img1.png']", ''), 141 (".//img[@src='_images/simg.png']", ''), 142 (".//img[@src='_images/svgimg.svg']", ''), 143 (".//a[@href='_sources/images.txt']", ''), 144 ], 145 'subdir/images.html': [ 146 (".//img[@src='../_images/img1.png']", ''), 147 (".//img[@src='../_images/rimg.png']", ''), 148 ], 149 'subdir/includes.html': [ 150 (".//a[@class='reference download internal']", ''), 151 (".//img[@src='../_images/img.png']", ''), 152 (".//p", 'This is an include file.'), 153 (".//pre/span", 'line 1'), 154 (".//pre/span", 'line 2'), 155 ], 156 'includes.html': [ 157 (".//pre", 'Max Strauß'), 158 (".//a[@class='reference download internal']", ''), 159 (".//pre/span", '"quotes"'), 160 (".//pre/span", "'included'"), 161 (".//pre/span[@class='s2']", 'üöä'), 162 (".//div[@class='inc-pyobj1 highlight-text notranslate']//pre", 163 r'^class Foo:\n pass\n\s*$'), 164 (".//div[@class='inc-pyobj2 highlight-text notranslate']//pre", 165 r'^ def baz\(\):\n pass\n\s*$'), 166 (".//div[@class='inc-lines highlight-text notranslate']//pre", 167 r'^class Foo:\n pass\nclass Bar:\n$'), 168 (".//div[@class='inc-startend highlight-text notranslate']//pre", 169 '^foo = "Including Unicode characters: üöä"\\n$'), 170 (".//div[@class='inc-preappend highlight-text notranslate']//pre", 171 r'(?m)^START CODE$'), 172 (".//div[@class='inc-pyobj-dedent highlight-python notranslate']//span", 173 r'def'), 174 (".//div[@class='inc-tab3 highlight-text notranslate']//pre", 175 r'-| |-'), 176 (".//div[@class='inc-tab8 highlight-python notranslate']//pre/span", 177 r'-| |-'), 178 ], 179 'autodoc.html': [ 180 (".//dl[@class='py class']/dt[@id='autodoc_target.Class']", ''), 181 (".//dl[@class='py function']/dt[@id='autodoc_target.function']/em/span/span", r'\*\*'), 182 (".//dl[@class='py function']/dt[@id='autodoc_target.function']/em/span/span", r'kwds'), 183 (".//dd/p", r'Return spam\.'), 184 ], 185 'extapi.html': [ 186 (".//strong", 'from class: Bar'), 187 ], 188 'markup.html': [ 189 (".//title", 'set by title directive'), 190 (".//p/em", 'Section author: Georg Brandl'), 191 (".//p/em", 'Module author: Georg Brandl'), 192 # created by the meta directive 193 (".//meta[@name='author'][@content='Me']", ''), 194 (".//meta[@name='keywords'][@content='docs, sphinx']", ''), 195 # a label created by ``.. _label:`` 196 (".//div[@id='label']", ''), 197 # code with standard code blocks 198 (".//pre", '^some code$'), 199 # an option list 200 (".//span[@class='option']", '--help'), 201 # admonitions 202 (".//p[@class='admonition-title']", 'My Admonition'), 203 (".//div[@class='admonition note']/p", 'Note text.'), 204 (".//div[@class='admonition warning']/p", 'Warning text.'), 205 # inline markup 206 (".//li/p/strong", r'^command\\n$'), 207 (".//li/p/strong", r'^program\\n$'), 208 (".//li/p/em", r'^dfn\\n$'), 209 (".//li/p/kbd", r'^kbd\\n$'), 210 (".//li/p/span", 'File \N{TRIANGULAR BULLET} Close'), 211 (".//li/p/code/span[@class='pre']", '^a/$'), 212 (".//li/p/code/em/span[@class='pre']", '^varpart$'), 213 (".//li/p/code/em/span[@class='pre']", '^i$'), 214 (".//a[@href='https://www.python.org/dev/peps/pep-0008']" 215 "[@class='pep reference external']/strong", 'PEP 8'), 216 (".//a[@href='https://www.python.org/dev/peps/pep-0008']" 217 "[@class='pep reference external']/strong", 218 'Python Enhancement Proposal #8'), 219 (".//a[@href='https://tools.ietf.org/html/rfc1.html']" 220 "[@class='rfc reference external']/strong", 'RFC 1'), 221 (".//a[@href='https://tools.ietf.org/html/rfc1.html']" 222 "[@class='rfc reference external']/strong", 'Request for Comments #1'), 223 (".//a[@href='objects.html#envvar-HOME']" 224 "[@class='reference internal']/code/span[@class='pre']", 'HOME'), 225 (".//a[@href='#with']" 226 "[@class='reference internal']/code/span[@class='pre']", '^with$'), 227 (".//a[@href='#grammar-token-try_stmt']" 228 "[@class='reference internal']/code/span", '^statement$'), 229 (".//a[@href='#some-label'][@class='reference internal']/span", '^here$'), 230 (".//a[@href='#some-label'][@class='reference internal']/span", '^there$'), 231 (".//a[@href='subdir/includes.html']" 232 "[@class='reference internal']/span", 'Including in subdir'), 233 (".//a[@href='objects.html#cmdoption-python-c']" 234 "[@class='reference internal']/code/span[@class='pre']", '-c'), 235 # abbreviations 236 (".//abbr[@title='abbreviation']", '^abbr$'), 237 # version stuff 238 (".//div[@class='versionadded']/p/span", 'New in version 0.6: '), 239 (".//div[@class='versionadded']/p/span", 240 tail_check('First paragraph of versionadded')), 241 (".//div[@class='versionchanged']/p/span", 242 tail_check('First paragraph of versionchanged')), 243 (".//div[@class='versionchanged']/p", 244 'Second paragraph of versionchanged'), 245 # footnote reference 246 (".//a[@class='footnote-reference brackets']", r'1'), 247 # created by reference lookup 248 (".//a[@href='index.html#ref1']", ''), 249 # ``seealso`` directive 250 (".//div/p[@class='admonition-title']", 'See also'), 251 # a ``hlist`` directive 252 (".//table[@class='hlist']/tbody/tr/td/ul/li/p", '^This$'), 253 # a ``centered`` directive 254 (".//p[@class='centered']/strong", 'LICENSE'), 255 # a glossary 256 (".//dl/dt[@id='term-boson']", 'boson'), 257 (".//dl/dt[@id='term-boson']/a", '¶'), 258 # a production list 259 (".//pre/strong", 'try_stmt'), 260 (".//pre/a[@href='#grammar-token-try1_stmt']/code/span", 'try1_stmt'), 261 # tests for ``only`` directive 262 (".//p", 'A global substitution!'), 263 (".//p", 'In HTML.'), 264 (".//p", 'In both.'), 265 (".//p", 'Always present'), 266 # tests for ``any`` role 267 (".//a[@href='#with']/span", 'headings'), 268 (".//a[@href='objects.html#func_without_body']/code/span", 'objects'), 269 # tests for numeric labels 270 (".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'), 271 # tests for smartypants 272 (".//li/p", 'Smart “quotes” in English ‘text’.'), 273 (".//li/p", 'Smart — long and – short dashes.'), 274 (".//li/p", 'Ellipsis…'), 275 (".//li/p/code/span[@class='pre']", 'foo--"bar"...'), 276 (".//p", 'Этот «абзац» должен использовать „русские“ кавычки.'), 277 (".//p", 'Il dit : « C’est “super” ! »'), 278 ], 279 'objects.html': [ 280 (".//dt[@id='mod.Cls.meth1']", ''), 281 (".//dt[@id='errmod.Error']", ''), 282 (".//dt/code/span", r'long\(parameter,'), 283 (".//dt/code/span", r'list\)'), 284 (".//dt/code/span", 'another'), 285 (".//dt/code/span", 'one'), 286 (".//a[@href='#mod.Cls'][@class='reference internal']", ''), 287 (".//dl[@class='std userdesc']", ''), 288 (".//dt[@id='userdesc-myobj']", ''), 289 (".//a[@href='#userdesc-myobj'][@class='reference internal']", ''), 290 # docfields 291 (".//a[@class='reference internal'][@href='#TimeInt']/em", 'TimeInt'), 292 (".//a[@class='reference internal'][@href='#Time']", 'Time'), 293 (".//a[@class='reference internal'][@href='#errmod.Error']/strong", 'Error'), 294 # C references 295 (".//span[@class='pre']", 'CFunction()'), 296 (".//a[@href='#c.Sphinx_DoSomething']", ''), 297 (".//a[@href='#c.SphinxStruct.member']", ''), 298 (".//a[@href='#c.SPHINX_USE_PYTHON']", ''), 299 (".//a[@href='#c.SphinxType']", ''), 300 (".//a[@href='#c.sphinx_global']", ''), 301 # test global TOC created by toctree() 302 (".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='#']", 303 'Testing object descriptions'), 304 (".//li[@class='toctree-l1']/a[@href='markup.html']", 305 'Testing various markup'), 306 # test unknown field names 307 (".//dt[@class='field-odd']", 'Field_name'), 308 (".//dt[@class='field-even']", 'Field_name all lower'), 309 (".//dt[@class='field-odd']", 'FIELD_NAME'), 310 (".//dt[@class='field-even']", 'FIELD_NAME ALL CAPS'), 311 (".//dt[@class='field-odd']", 'Field_Name'), 312 (".//dt[@class='field-even']", 'Field_Name All Word Caps'), 313 (".//dt[@class='field-odd']", 'Field_name'), 314 (".//dt[@class='field-even']", 'Field_name First word cap'), 315 (".//dt[@class='field-odd']", 'FIELd_name'), 316 (".//dt[@class='field-even']", 'FIELd_name PARTial caps'), 317 # custom sidebar 318 (".//h4", 'Custom sidebar'), 319 # docfields 320 (".//dd[@class='field-odd']/p/strong", '^moo$'), 321 (".//dd[@class='field-odd']/p/strong", tail_check(r'\(Moo\) .* Moo')), 322 (".//dd[@class='field-odd']/ul/li/p/strong", '^hour$'), 323 (".//dd[@class='field-odd']/ul/li/p/em", '^DuplicateType$'), 324 (".//dd[@class='field-odd']/ul/li/p/em", tail_check(r'.* Some parameter')), 325 # others 326 (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span", 327 'perl'), 328 (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span", 329 '\\+p'), 330 (".//a[@class='reference internal'][@href='#cmdoption-perl-ObjC']/code/span", 331 '--ObjC\\+\\+'), 332 (".//a[@class='reference internal'][@href='#cmdoption-perl-plugin.option']/code/span", 333 '--plugin.option'), 334 (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-create-auth-token']" 335 "/code/span", 336 'create-auth-token'), 337 (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-arg']/code/span", 338 'arg'), 339 (".//a[@class='reference internal'][@href='#cmdoption-perl-j']/code/span", 340 '-j'), 341 (".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span", 342 'hg'), 343 (".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span", 344 'commit'), 345 (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", 346 'git'), 347 (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", 348 'commit'), 349 (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", 350 '-p'), 351 ], 352 'index.html': [ 353 (".//meta[@name='hc'][@content='hcval']", ''), 354 (".//meta[@name='hc_co'][@content='hcval_co']", ''), 355 (".//dt[@class='label']/span[@class='brackets']", r'Ref1'), 356 (".//dt[@class='label']", ''), 357 (".//li[@class='toctree-l1']/a", 'Testing various markup'), 358 (".//li[@class='toctree-l2']/a", 'Inline markup'), 359 (".//title", 'Sphinx <Tests>'), 360 (".//div[@class='footer']", 'Georg Brandl & Team'), 361 (".//a[@href='http://python.org/']" 362 "[@class='reference external']", ''), 363 (".//li/p/a[@href='genindex.html']/span", 'Index'), 364 (".//li/p/a[@href='py-modindex.html']/span", 'Module Index'), 365 # custom sidebar only for contents 366 (".//h4", 'Contents sidebar'), 367 # custom JavaScript 368 (".//script[@src='file://moo.js']", ''), 369 # URL in contents 370 (".//a[@class='reference external'][@href='http://sphinx-doc.org/']", 371 'http://sphinx-doc.org/'), 372 (".//a[@class='reference external'][@href='http://sphinx-doc.org/latest/']", 373 'Latest reference'), 374 # Indirect hyperlink targets across files 375 (".//a[@href='markup.html#some-label'][@class='reference internal']/span", 376 '^indirect hyperref$'), 377 ], 378 'bom.html': [ 379 (".//title", " File with UTF-8 BOM"), 380 ], 381 'extensions.html': [ 382 (".//a[@href='http://python.org/dev/']", "http://python.org/dev/"), 383 (".//a[@href='http://bugs.python.org/issue1000']", "issue 1000"), 384 (".//a[@href='http://bugs.python.org/issue1042']", "explicit caption"), 385 ], 386 'genindex.html': [ 387 # index entries 388 (".//a/strong", "Main"), 389 (".//a/strong", "[1]"), 390 (".//a/strong", "Other"), 391 (".//a", "entry"), 392 (".//li/a", "double"), 393 ], 394 'footnote.html': [ 395 (".//a[@class='footnote-reference brackets'][@href='#id9'][@id='id1']", r"1"), 396 (".//a[@class='footnote-reference brackets'][@href='#id10'][@id='id2']", r"2"), 397 (".//a[@class='footnote-reference brackets'][@href='#foo'][@id='id3']", r"3"), 398 (".//a[@class='reference internal'][@href='#bar'][@id='id4']/span", r"\[bar\]"), 399 (".//a[@class='reference internal'][@href='#baz-qux'][@id='id5']/span", r"\[baz_qux\]"), 400 (".//a[@class='footnote-reference brackets'][@href='#id11'][@id='id6']", r"4"), 401 (".//a[@class='footnote-reference brackets'][@href='#id12'][@id='id7']", r"5"), 402 (".//a[@class='fn-backref'][@href='#id1']", r"1"), 403 (".//a[@class='fn-backref'][@href='#id2']", r"2"), 404 (".//a[@class='fn-backref'][@href='#id3']", r"3"), 405 (".//a[@class='fn-backref'][@href='#id4']", r"bar"), 406 (".//a[@class='fn-backref'][@href='#id5']", r"baz_qux"), 407 (".//a[@class='fn-backref'][@href='#id6']", r"4"), 408 (".//a[@class='fn-backref'][@href='#id7']", r"5"), 409 (".//a[@class='fn-backref'][@href='#id8']", r"6"), 410 ], 411 'otherext.html': [ 412 (".//h1", "Generated section"), 413 (".//a[@href='_sources/otherext.foo.txt']", ''), 414 ] 415})) 416@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 417 reason='docutils-0.13 or above is required') 418@pytest.mark.sphinx('html', tags=['testtag'], 419 confoverrides={'html_context.hckey_co': 'hcval_co'}) 420@pytest.mark.test_params(shared_result='test_build_html_output') 421def test_html5_output(app, cached_etree_parse, fname, expect): 422 app.build() 423 print(app.outdir / fname) 424 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 425 426 427@pytest.mark.sphinx('html', parallel=2) 428def test_html_parallel(app): 429 app.build() 430 431 432@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 433 reason='docutils-0.13 or above is required') 434@pytest.mark.sphinx('html') 435@pytest.mark.test_params(shared_result='test_build_html_output') 436def test_html_download(app): 437 app.build() 438 439 # subdir/includes.html 440 result = (app.outdir / 'subdir' / 'includes.html').read_text() 441 pattern = ('<a class="reference download internal" download="" ' 442 'href="../(_downloads/.*/img.png)">') 443 matched = re.search(pattern, result) 444 assert matched 445 assert (app.outdir / matched.group(1)).exists() 446 filename = matched.group(1) 447 448 # includes.html 449 result = (app.outdir / 'includes.html').read_text() 450 pattern = ('<a class="reference download internal" download="" ' 451 'href="(_downloads/.*/img.png)">') 452 matched = re.search(pattern, result) 453 assert matched 454 assert (app.outdir / matched.group(1)).exists() 455 assert matched.group(1) == filename 456 457 458@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 459 reason='docutils-0.13 or above is required') 460@pytest.mark.sphinx('html', testroot='roles-download') 461def test_html_download_role(app, status, warning): 462 app.build() 463 digest = md5(b'dummy.dat').hexdigest() 464 assert (app.outdir / '_downloads' / digest / 'dummy.dat').exists() 465 digest_another = md5(b'another/dummy.dat').hexdigest() 466 assert (app.outdir / '_downloads' / digest_another / 'dummy.dat').exists() 467 468 content = (app.outdir / 'index.html').read_text() 469 assert (('<li><p><a class="reference download internal" download="" ' 470 'href="_downloads/%s/dummy.dat">' 471 '<code class="xref download docutils literal notranslate">' 472 '<span class="pre">dummy.dat</span></code></a></p></li>' % digest) 473 in content) 474 assert (('<li><p><a class="reference download internal" download="" ' 475 'href="_downloads/%s/dummy.dat">' 476 '<code class="xref download docutils literal notranslate">' 477 '<span class="pre">another/dummy.dat</span></code></a></p></li>' % 478 digest_another) in content) 479 assert ('<li><p><code class="xref download docutils literal notranslate">' 480 '<span class="pre">not_found.dat</span></code></p></li>' in content) 481 assert ('<li><p><a class="reference download external" download="" ' 482 'href="http://www.sphinx-doc.org/en/master/_static/sphinxheader.png">' 483 '<code class="xref download docutils literal notranslate">' 484 '<span class="pre">Sphinx</span> <span class="pre">logo</span>' 485 '</code></a></p></li>' in content) 486 487 488@pytest.mark.sphinx('html', testroot='build-html-translator') 489def test_html_translator(app): 490 app.build() 491 assert app.builder.docwriter.visitor.depart_with_node == 10 492 493 494@pytest.mark.parametrize("fname,expect", flat_dict({ 495 'index.html': [ 496 (".//li[@class='toctree-l3']/a", '1.1.1. Foo A1', True), 497 (".//li[@class='toctree-l3']/a", '1.2.1. Foo B1', True), 498 (".//li[@class='toctree-l3']/a", '2.1.1. Bar A1', False), 499 (".//li[@class='toctree-l3']/a", '2.2.1. Bar B1', False), 500 ], 501 'foo.html': [ 502 (".//h1", 'Foo', True), 503 (".//h2", 'Foo A', True), 504 (".//h3", 'Foo A1', True), 505 (".//h2", 'Foo B', True), 506 (".//h3", 'Foo B1', True), 507 508 (".//h1//span[@class='section-number']", '1. ', True), 509 (".//h2//span[@class='section-number']", '1.1. ', True), 510 (".//h3//span[@class='section-number']", '1.1.1. ', True), 511 (".//h2//span[@class='section-number']", '1.2. ', True), 512 (".//h3//span[@class='section-number']", '1.2.1. ', True), 513 514 (".//div[@class='sphinxsidebarwrapper']//li/a", '1.1. Foo A', True), 515 (".//div[@class='sphinxsidebarwrapper']//li/a", '1.1.1. Foo A1', True), 516 (".//div[@class='sphinxsidebarwrapper']//li/a", '1.2. Foo B', True), 517 (".//div[@class='sphinxsidebarwrapper']//li/a", '1.2.1. Foo B1', True), 518 ], 519 'bar.html': [ 520 (".//h1", 'Bar', True), 521 (".//h2", 'Bar A', True), 522 (".//h2", 'Bar B', True), 523 (".//h3", 'Bar B1', True), 524 (".//h1//span[@class='section-number']", '2. ', True), 525 (".//h2//span[@class='section-number']", '2.1. ', True), 526 (".//h2//span[@class='section-number']", '2.2. ', True), 527 (".//h3//span[@class='section-number']", '2.2.1. ', True), 528 (".//div[@class='sphinxsidebarwrapper']//li/a", '2. Bar', True), 529 (".//div[@class='sphinxsidebarwrapper']//li/a", '2.1. Bar A', True), 530 (".//div[@class='sphinxsidebarwrapper']//li/a", '2.2. Bar B', True), 531 (".//div[@class='sphinxsidebarwrapper']//li/a", '2.2.1. Bar B1', False), 532 ], 533 'baz.html': [ 534 (".//h1", 'Baz A', True), 535 (".//h1//span[@class='section-number']", '2.1.1. ', True), 536 ], 537})) 538@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 539 reason='docutils-0.13 or above is required') 540@pytest.mark.sphinx('html', testroot='tocdepth') 541@pytest.mark.test_params(shared_result='test_build_html_tocdepth') 542def test_tocdepth(app, cached_etree_parse, fname, expect): 543 app.build() 544 # issue #1251 545 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 546 547 548@pytest.mark.parametrize("fname,expect", flat_dict({ 549 'index.html': [ 550 (".//li[@class='toctree-l3']/a", '1.1.1. Foo A1', True), 551 (".//li[@class='toctree-l3']/a", '1.2.1. Foo B1', True), 552 (".//li[@class='toctree-l3']/a", '2.1.1. Bar A1', False), 553 (".//li[@class='toctree-l3']/a", '2.2.1. Bar B1', False), 554 555 # index.rst 556 (".//h1", 'test-tocdepth', True), 557 558 # foo.rst 559 (".//h2", 'Foo', True), 560 (".//h3", 'Foo A', True), 561 (".//h4", 'Foo A1', True), 562 (".//h3", 'Foo B', True), 563 (".//h4", 'Foo B1', True), 564 (".//h2//span[@class='section-number']", '1. ', True), 565 (".//h3//span[@class='section-number']", '1.1. ', True), 566 (".//h4//span[@class='section-number']", '1.1.1. ', True), 567 (".//h3//span[@class='section-number']", '1.2. ', True), 568 (".//h4//span[@class='section-number']", '1.2.1. ', True), 569 570 # bar.rst 571 (".//h2", 'Bar', True), 572 (".//h3", 'Bar A', True), 573 (".//h3", 'Bar B', True), 574 (".//h4", 'Bar B1', True), 575 (".//h2//span[@class='section-number']", '2. ', True), 576 (".//h3//span[@class='section-number']", '2.1. ', True), 577 (".//h3//span[@class='section-number']", '2.2. ', True), 578 (".//h4//span[@class='section-number']", '2.2.1. ', True), 579 580 # baz.rst 581 (".//h4", 'Baz A', True), 582 (".//h4//span[@class='section-number']", '2.1.1. ', True), 583 ], 584})) 585@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 586 reason='docutils-0.13 or above is required') 587@pytest.mark.sphinx('singlehtml', testroot='tocdepth') 588@pytest.mark.test_params(shared_result='test_build_html_tocdepth') 589def test_tocdepth_singlehtml(app, cached_etree_parse, fname, expect): 590 app.build() 591 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 592 593 594@pytest.mark.sphinx('html', testroot='numfig') 595@pytest.mark.test_params(shared_result='test_build_html_numfig') 596def test_numfig_disabled_warn(app, warning): 597 app.build() 598 warnings = warning.getvalue() 599 assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' in warnings 600 assert 'index.rst:56: WARNING: invalid numfig_format: invalid' not in warnings 601 assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' not in warnings 602 603 604@pytest.mark.parametrize("fname,expect", flat_dict({ 605 'index.html': [ 606 (".//div[@class='figure align-default']/p[@class='caption']/" 607 "span[@class='caption-number']", None, True), 608 (".//table/caption/span[@class='caption-number']", None, True), 609 (".//div[@class='code-block-caption']/" 610 "span[@class='caption-number']", None, True), 611 (".//li/p/code/span", '^fig1$', True), 612 (".//li/p/code/span", '^Figure%s$', True), 613 (".//li/p/code/span", '^table-1$', True), 614 (".//li/p/code/span", '^Table:%s$', True), 615 (".//li/p/code/span", '^CODE_1$', True), 616 (".//li/p/code/span", '^Code-%s$', True), 617 (".//li/p/a/span", '^Section 1$', True), 618 (".//li/p/a/span", '^Section 2.1$', True), 619 (".//li/p/code/span", '^Fig.{number}$', True), 620 (".//li/p/a/span", '^Sect.1 Foo$', True), 621 ], 622 'foo.html': [ 623 (".//div[@class='figure align-default']/p[@class='caption']/" 624 "span[@class='caption-number']", None, True), 625 (".//table/caption/span[@class='caption-number']", None, True), 626 (".//div[@class='code-block-caption']/" 627 "span[@class='caption-number']", None, True), 628 ], 629 'bar.html': [ 630 (".//div[@class='figure align-default']/p[@class='caption']/" 631 "span[@class='caption-number']", None, True), 632 (".//table/caption/span[@class='caption-number']", None, True), 633 (".//div[@class='code-block-caption']/" 634 "span[@class='caption-number']", None, True), 635 ], 636 'baz.html': [ 637 (".//div[@class='figure align-default']/p[@class='caption']/" 638 "span[@class='caption-number']", None, True), 639 (".//table/caption/span[@class='caption-number']", None, True), 640 (".//div[@class='code-block-caption']/" 641 "span[@class='caption-number']", None, True), 642 ], 643})) 644@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 645 reason='docutils-0.13 or above is required') 646@pytest.mark.sphinx('html', testroot='numfig') 647@pytest.mark.test_params(shared_result='test_build_html_numfig') 648def test_numfig_disabled(app, cached_etree_parse, fname, expect): 649 app.build() 650 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 651 652 653@pytest.mark.sphinx( 654 'html', testroot='numfig', 655 srcdir='test_numfig_without_numbered_toctree_warn', 656 confoverrides={'numfig': True}) 657def test_numfig_without_numbered_toctree_warn(app, warning): 658 app.build() 659 # remove :numbered: option 660 index = (app.srcdir / 'index.rst').read_text() 661 index = re.sub(':numbered:.*', '', index) 662 (app.srcdir / 'index.rst').write_text(index) 663 app.builder.build_all() 664 665 warnings = warning.getvalue() 666 assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings 667 assert 'index.rst:55: WARNING: Failed to create a cross reference. Any number is not assigned: index' in warnings 668 assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings 669 assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings 670 671 672@pytest.mark.parametrize("fname,expect", flat_dict({ 673 'index.html': [ 674 (".//div[@class='figure align-default']/p[@class='caption']/" 675 "span[@class='caption-number']", '^Fig. 9 $', True), 676 (".//div[@class='figure align-default']/p[@class='caption']/" 677 "span[@class='caption-number']", '^Fig. 10 $', True), 678 (".//table/caption/span[@class='caption-number']", 679 '^Table 9 $', True), 680 (".//table/caption/span[@class='caption-number']", 681 '^Table 10 $', True), 682 (".//div[@class='code-block-caption']/" 683 "span[@class='caption-number']", '^Listing 9 $', True), 684 (".//div[@class='code-block-caption']/" 685 "span[@class='caption-number']", '^Listing 10 $', True), 686 (".//li/p/a/span", '^Fig. 9$', True), 687 (".//li/p/a/span", '^Figure6$', True), 688 (".//li/p/a/span", '^Table 9$', True), 689 (".//li/p/a/span", '^Table:6$', True), 690 (".//li/p/a/span", '^Listing 9$', True), 691 (".//li/p/a/span", '^Code-6$', True), 692 (".//li/p/code/span", '^foo$', True), 693 (".//li/p/code/span", '^bar_a$', True), 694 (".//li/p/a/span", '^Fig.9 should be Fig.1$', True), 695 (".//li/p/code/span", '^Sect.{number}$', True), 696 ], 697 'foo.html': [ 698 (".//div[@class='figure align-default']/p[@class='caption']/" 699 "span[@class='caption-number']", '^Fig. 1 $', True), 700 (".//div[@class='figure align-default']/p[@class='caption']/" 701 "span[@class='caption-number']", '^Fig. 2 $', True), 702 (".//div[@class='figure align-default']/p[@class='caption']/" 703 "span[@class='caption-number']", '^Fig. 3 $', True), 704 (".//div[@class='figure align-default']/p[@class='caption']/" 705 "span[@class='caption-number']", '^Fig. 4 $', True), 706 (".//table/caption/span[@class='caption-number']", 707 '^Table 1 $', True), 708 (".//table/caption/span[@class='caption-number']", 709 '^Table 2 $', True), 710 (".//table/caption/span[@class='caption-number']", 711 '^Table 3 $', True), 712 (".//table/caption/span[@class='caption-number']", 713 '^Table 4 $', True), 714 (".//div[@class='code-block-caption']/" 715 "span[@class='caption-number']", '^Listing 1 $', True), 716 (".//div[@class='code-block-caption']/" 717 "span[@class='caption-number']", '^Listing 2 $', True), 718 (".//div[@class='code-block-caption']/" 719 "span[@class='caption-number']", '^Listing 3 $', True), 720 (".//div[@class='code-block-caption']/" 721 "span[@class='caption-number']", '^Listing 4 $', True), 722 ], 723 'bar.html': [ 724 (".//div[@class='figure align-default']/p[@class='caption']/" 725 "span[@class='caption-number']", '^Fig. 5 $', True), 726 (".//div[@class='figure align-default']/p[@class='caption']/" 727 "span[@class='caption-number']", '^Fig. 7 $', True), 728 (".//div[@class='figure align-default']/p[@class='caption']/" 729 "span[@class='caption-number']", '^Fig. 8 $', True), 730 (".//table/caption/span[@class='caption-number']", 731 '^Table 5 $', True), 732 (".//table/caption/span[@class='caption-number']", 733 '^Table 7 $', True), 734 (".//table/caption/span[@class='caption-number']", 735 '^Table 8 $', True), 736 (".//div[@class='code-block-caption']/" 737 "span[@class='caption-number']", '^Listing 5 $', True), 738 (".//div[@class='code-block-caption']/" 739 "span[@class='caption-number']", '^Listing 7 $', True), 740 (".//div[@class='code-block-caption']/" 741 "span[@class='caption-number']", '^Listing 8 $', True), 742 ], 743 'baz.html': [ 744 (".//div[@class='figure align-default']/p[@class='caption']/" 745 "span[@class='caption-number']", '^Fig. 6 $', True), 746 (".//table/caption/span[@class='caption-number']", 747 '^Table 6 $', True), 748 (".//div[@class='code-block-caption']/" 749 "span[@class='caption-number']", '^Listing 6 $', True), 750 ], 751})) 752@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 753 reason='docutils-0.13 or above is required') 754@pytest.mark.sphinx( 755 'html', testroot='numfig', 756 srcdir='test_numfig_without_numbered_toctree', 757 confoverrides={'numfig': True}) 758def test_numfig_without_numbered_toctree(app, cached_etree_parse, fname, expect): 759 # remove :numbered: option 760 index = (app.srcdir / 'index.rst').read_text() 761 index = re.sub(':numbered:.*', '', index) 762 (app.srcdir / 'index.rst').write_text(index) 763 764 if not app.outdir.listdir(): 765 app.build() 766 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 767 768 769@pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) 770@pytest.mark.test_params(shared_result='test_build_html_numfig_on') 771def test_numfig_with_numbered_toctree_warn(app, warning): 772 app.build() 773 warnings = warning.getvalue() 774 assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings 775 assert 'index.rst:55: WARNING: Failed to create a cross reference. Any number is not assigned: index' in warnings 776 assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings 777 assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings 778 779 780@pytest.mark.parametrize("fname,expect", flat_dict({ 781 'index.html': [ 782 (".//div[@class='figure align-default']/p[@class='caption']/" 783 "span[@class='caption-number']", '^Fig. 1 $', True), 784 (".//div[@class='figure align-default']/p[@class='caption']/" 785 "span[@class='caption-number']", '^Fig. 2 $', True), 786 (".//table/caption/span[@class='caption-number']", 787 '^Table 1 $', True), 788 (".//table/caption/span[@class='caption-number']", 789 '^Table 2 $', True), 790 (".//div[@class='code-block-caption']/" 791 "span[@class='caption-number']", '^Listing 1 $', True), 792 (".//div[@class='code-block-caption']/" 793 "span[@class='caption-number']", '^Listing 2 $', True), 794 (".//li/p/a/span", '^Fig. 1$', True), 795 (".//li/p/a/span", '^Figure2.2$', True), 796 (".//li/p/a/span", '^Table 1$', True), 797 (".//li/p/a/span", '^Table:2.2$', True), 798 (".//li/p/a/span", '^Listing 1$', True), 799 (".//li/p/a/span", '^Code-2.2$', True), 800 (".//li/p/a/span", '^Section.1$', True), 801 (".//li/p/a/span", '^Section.2.1$', True), 802 (".//li/p/a/span", '^Fig.1 should be Fig.1$', True), 803 (".//li/p/a/span", '^Sect.1 Foo$', True), 804 ], 805 'foo.html': [ 806 (".//div[@class='figure align-default']/p[@class='caption']/" 807 "span[@class='caption-number']", '^Fig. 1.1 $', True), 808 (".//div[@class='figure align-default']/p[@class='caption']/" 809 "span[@class='caption-number']", '^Fig. 1.2 $', True), 810 (".//div[@class='figure align-default']/p[@class='caption']/" 811 "span[@class='caption-number']", '^Fig. 1.3 $', True), 812 (".//div[@class='figure align-default']/p[@class='caption']/" 813 "span[@class='caption-number']", '^Fig. 1.4 $', True), 814 (".//table/caption/span[@class='caption-number']", 815 '^Table 1.1 $', True), 816 (".//table/caption/span[@class='caption-number']", 817 '^Table 1.2 $', True), 818 (".//table/caption/span[@class='caption-number']", 819 '^Table 1.3 $', True), 820 (".//table/caption/span[@class='caption-number']", 821 '^Table 1.4 $', True), 822 (".//div[@class='code-block-caption']/" 823 "span[@class='caption-number']", '^Listing 1.1 $', True), 824 (".//div[@class='code-block-caption']/" 825 "span[@class='caption-number']", '^Listing 1.2 $', True), 826 (".//div[@class='code-block-caption']/" 827 "span[@class='caption-number']", '^Listing 1.3 $', True), 828 (".//div[@class='code-block-caption']/" 829 "span[@class='caption-number']", '^Listing 1.4 $', True), 830 ], 831 'bar.html': [ 832 (".//div[@class='figure align-default']/p[@class='caption']/" 833 "span[@class='caption-number']", '^Fig. 2.1 $', True), 834 (".//div[@class='figure align-default']/p[@class='caption']/" 835 "span[@class='caption-number']", '^Fig. 2.3 $', True), 836 (".//div[@class='figure align-default']/p[@class='caption']/" 837 "span[@class='caption-number']", '^Fig. 2.4 $', True), 838 (".//table/caption/span[@class='caption-number']", 839 '^Table 2.1 $', True), 840 (".//table/caption/span[@class='caption-number']", 841 '^Table 2.3 $', True), 842 (".//table/caption/span[@class='caption-number']", 843 '^Table 2.4 $', True), 844 (".//div[@class='code-block-caption']/" 845 "span[@class='caption-number']", '^Listing 2.1 $', True), 846 (".//div[@class='code-block-caption']/" 847 "span[@class='caption-number']", '^Listing 2.3 $', True), 848 (".//div[@class='code-block-caption']/" 849 "span[@class='caption-number']", '^Listing 2.4 $', True), 850 ], 851 'baz.html': [ 852 (".//div[@class='figure align-default']/p[@class='caption']/" 853 "span[@class='caption-number']", '^Fig. 2.2 $', True), 854 (".//table/caption/span[@class='caption-number']", 855 '^Table 2.2 $', True), 856 (".//div[@class='code-block-caption']/" 857 "span[@class='caption-number']", '^Listing 2.2 $', True), 858 ], 859})) 860@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 861 reason='docutils-0.13 or above is required') 862@pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) 863@pytest.mark.test_params(shared_result='test_build_html_numfig_on') 864def test_numfig_with_numbered_toctree(app, cached_etree_parse, fname, expect): 865 app.build() 866 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 867 868 869@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ 870 'numfig': True, 871 'numfig_format': {'figure': 'Figure:%s', 872 'table': 'Tab_%s', 873 'code-block': 'Code-%s', 874 'section': 'SECTION-%s'}}) 875@pytest.mark.test_params(shared_result='test_build_html_numfig_format_warn') 876def test_numfig_with_prefix_warn(app, warning): 877 app.build() 878 warnings = warning.getvalue() 879 assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings 880 assert 'index.rst:55: WARNING: Failed to create a cross reference. Any number is not assigned: index' in warnings 881 assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings 882 assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings 883 884 885@pytest.mark.parametrize("fname,expect", flat_dict({ 886 'index.html': [ 887 (".//div[@class='figure align-default']/p[@class='caption']/" 888 "span[@class='caption-number']", '^Figure:1 $', True), 889 (".//div[@class='figure align-default']/p[@class='caption']/" 890 "span[@class='caption-number']", '^Figure:2 $', True), 891 (".//table/caption/span[@class='caption-number']", 892 '^Tab_1 $', True), 893 (".//table/caption/span[@class='caption-number']", 894 '^Tab_2 $', True), 895 (".//div[@class='code-block-caption']/" 896 "span[@class='caption-number']", '^Code-1 $', True), 897 (".//div[@class='code-block-caption']/" 898 "span[@class='caption-number']", '^Code-2 $', True), 899 (".//li/p/a/span", '^Figure:1$', True), 900 (".//li/p/a/span", '^Figure2.2$', True), 901 (".//li/p/a/span", '^Tab_1$', True), 902 (".//li/p/a/span", '^Table:2.2$', True), 903 (".//li/p/a/span", '^Code-1$', True), 904 (".//li/p/a/span", '^Code-2.2$', True), 905 (".//li/p/a/span", '^SECTION-1$', True), 906 (".//li/p/a/span", '^SECTION-2.1$', True), 907 (".//li/p/a/span", '^Fig.1 should be Fig.1$', True), 908 (".//li/p/a/span", '^Sect.1 Foo$', True), 909 ], 910 'foo.html': [ 911 (".//div[@class='figure align-default']/p[@class='caption']/" 912 "span[@class='caption-number']", '^Figure:1.1 $', True), 913 (".//div[@class='figure align-default']/p[@class='caption']/" 914 "span[@class='caption-number']", '^Figure:1.2 $', True), 915 (".//div[@class='figure align-default']/p[@class='caption']/" 916 "span[@class='caption-number']", '^Figure:1.3 $', True), 917 (".//div[@class='figure align-default']/p[@class='caption']/" 918 "span[@class='caption-number']", '^Figure:1.4 $', True), 919 (".//table/caption/span[@class='caption-number']", 920 '^Tab_1.1 $', True), 921 (".//table/caption/span[@class='caption-number']", 922 '^Tab_1.2 $', True), 923 (".//table/caption/span[@class='caption-number']", 924 '^Tab_1.3 $', True), 925 (".//table/caption/span[@class='caption-number']", 926 '^Tab_1.4 $', True), 927 (".//div[@class='code-block-caption']/" 928 "span[@class='caption-number']", '^Code-1.1 $', True), 929 (".//div[@class='code-block-caption']/" 930 "span[@class='caption-number']", '^Code-1.2 $', True), 931 (".//div[@class='code-block-caption']/" 932 "span[@class='caption-number']", '^Code-1.3 $', True), 933 (".//div[@class='code-block-caption']/" 934 "span[@class='caption-number']", '^Code-1.4 $', True), 935 ], 936 'bar.html': [ 937 (".//div[@class='figure align-default']/p[@class='caption']/" 938 "span[@class='caption-number']", '^Figure:2.1 $', True), 939 (".//div[@class='figure align-default']/p[@class='caption']/" 940 "span[@class='caption-number']", '^Figure:2.3 $', True), 941 (".//div[@class='figure align-default']/p[@class='caption']/" 942 "span[@class='caption-number']", '^Figure:2.4 $', True), 943 (".//table/caption/span[@class='caption-number']", 944 '^Tab_2.1 $', True), 945 (".//table/caption/span[@class='caption-number']", 946 '^Tab_2.3 $', True), 947 (".//table/caption/span[@class='caption-number']", 948 '^Tab_2.4 $', True), 949 (".//div[@class='code-block-caption']/" 950 "span[@class='caption-number']", '^Code-2.1 $', True), 951 (".//div[@class='code-block-caption']/" 952 "span[@class='caption-number']", '^Code-2.3 $', True), 953 (".//div[@class='code-block-caption']/" 954 "span[@class='caption-number']", '^Code-2.4 $', True), 955 ], 956 'baz.html': [ 957 (".//div[@class='figure align-default']/p[@class='caption']/" 958 "span[@class='caption-number']", '^Figure:2.2 $', True), 959 (".//table/caption/span[@class='caption-number']", 960 '^Tab_2.2 $', True), 961 (".//div[@class='code-block-caption']/" 962 "span[@class='caption-number']", '^Code-2.2 $', True), 963 ], 964})) 965@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 966 reason='docutils-0.13 or above is required') 967@pytest.mark.sphinx('html', testroot='numfig', 968 confoverrides={'numfig': True, 969 'numfig_format': {'figure': 'Figure:%s', 970 'table': 'Tab_%s', 971 'code-block': 'Code-%s', 972 'section': 'SECTION-%s'}}) 973@pytest.mark.test_params(shared_result='test_build_html_numfig_format_warn') 974def test_numfig_with_prefix(app, cached_etree_parse, fname, expect): 975 app.build() 976 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 977 978 979@pytest.mark.sphinx('html', testroot='numfig', 980 confoverrides={'numfig': True, 'numfig_secnum_depth': 2}) 981@pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2') 982def test_numfig_with_secnum_depth_warn(app, warning): 983 app.build() 984 warnings = warning.getvalue() 985 assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings 986 assert 'index.rst:55: WARNING: Failed to create a cross reference. Any number is not assigned: index' in warnings 987 assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings 988 assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings 989 990 991@pytest.mark.parametrize("fname,expect", flat_dict({ 992 'index.html': [ 993 (".//div[@class='figure align-default']/p[@class='caption']/" 994 "span[@class='caption-number']", '^Fig. 1 $', True), 995 (".//div[@class='figure align-default']/p[@class='caption']/" 996 "span[@class='caption-number']", '^Fig. 2 $', True), 997 (".//table/caption/span[@class='caption-number']", 998 '^Table 1 $', True), 999 (".//table/caption/span[@class='caption-number']", 1000 '^Table 2 $', True), 1001 (".//div[@class='code-block-caption']/" 1002 "span[@class='caption-number']", '^Listing 1 $', True), 1003 (".//div[@class='code-block-caption']/" 1004 "span[@class='caption-number']", '^Listing 2 $', True), 1005 (".//li/p/a/span", '^Fig. 1$', True), 1006 (".//li/p/a/span", '^Figure2.1.2$', True), 1007 (".//li/p/a/span", '^Table 1$', True), 1008 (".//li/p/a/span", '^Table:2.1.2$', True), 1009 (".//li/p/a/span", '^Listing 1$', True), 1010 (".//li/p/a/span", '^Code-2.1.2$', True), 1011 (".//li/p/a/span", '^Section.1$', True), 1012 (".//li/p/a/span", '^Section.2.1$', True), 1013 (".//li/p/a/span", '^Fig.1 should be Fig.1$', True), 1014 (".//li/p/a/span", '^Sect.1 Foo$', True), 1015 ], 1016 'foo.html': [ 1017 (".//div[@class='figure align-default']/p[@class='caption']/" 1018 "span[@class='caption-number']", '^Fig. 1.1 $', True), 1019 (".//div[@class='figure align-default']/p[@class='caption']/" 1020 "span[@class='caption-number']", '^Fig. 1.1.1 $', True), 1021 (".//div[@class='figure align-default']/p[@class='caption']/" 1022 "span[@class='caption-number']", '^Fig. 1.1.2 $', True), 1023 (".//div[@class='figure align-default']/p[@class='caption']/" 1024 "span[@class='caption-number']", '^Fig. 1.2.1 $', True), 1025 (".//table/caption/span[@class='caption-number']", 1026 '^Table 1.1 $', True), 1027 (".//table/caption/span[@class='caption-number']", 1028 '^Table 1.1.1 $', True), 1029 (".//table/caption/span[@class='caption-number']", 1030 '^Table 1.1.2 $', True), 1031 (".//table/caption/span[@class='caption-number']", 1032 '^Table 1.2.1 $', True), 1033 (".//div[@class='code-block-caption']/" 1034 "span[@class='caption-number']", '^Listing 1.1 $', True), 1035 (".//div[@class='code-block-caption']/" 1036 "span[@class='caption-number']", '^Listing 1.1.1 $', True), 1037 (".//div[@class='code-block-caption']/" 1038 "span[@class='caption-number']", '^Listing 1.1.2 $', True), 1039 (".//div[@class='code-block-caption']/" 1040 "span[@class='caption-number']", '^Listing 1.2.1 $', True), 1041 ], 1042 'bar.html': [ 1043 (".//div[@class='figure align-default']/p[@class='caption']/" 1044 "span[@class='caption-number']", '^Fig. 2.1.1 $', True), 1045 (".//div[@class='figure align-default']/p[@class='caption']/" 1046 "span[@class='caption-number']", '^Fig. 2.1.3 $', True), 1047 (".//div[@class='figure align-default']/p[@class='caption']/" 1048 "span[@class='caption-number']", '^Fig. 2.2.1 $', True), 1049 (".//table/caption/span[@class='caption-number']", 1050 '^Table 2.1.1 $', True), 1051 (".//table/caption/span[@class='caption-number']", 1052 '^Table 2.1.3 $', True), 1053 (".//table/caption/span[@class='caption-number']", 1054 '^Table 2.2.1 $', True), 1055 (".//div[@class='code-block-caption']/" 1056 "span[@class='caption-number']", '^Listing 2.1.1 $', True), 1057 (".//div[@class='code-block-caption']/" 1058 "span[@class='caption-number']", '^Listing 2.1.3 $', True), 1059 (".//div[@class='code-block-caption']/" 1060 "span[@class='caption-number']", '^Listing 2.2.1 $', True), 1061 ], 1062 'baz.html': [ 1063 (".//div[@class='figure align-default']/p[@class='caption']/" 1064 "span[@class='caption-number']", '^Fig. 2.1.2 $', True), 1065 (".//table/caption/span[@class='caption-number']", 1066 '^Table 2.1.2 $', True), 1067 (".//div[@class='code-block-caption']/" 1068 "span[@class='caption-number']", '^Listing 2.1.2 $', True), 1069 ], 1070})) 1071@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 1072 reason='docutils-0.13 or above is required') 1073@pytest.mark.sphinx('html', testroot='numfig', 1074 confoverrides={'numfig': True, 1075 'numfig_secnum_depth': 2}) 1076@pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2') 1077def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect): 1078 app.build() 1079 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 1080 1081 1082@pytest.mark.parametrize("fname,expect", flat_dict({ 1083 'index.html': [ 1084 (".//div[@class='figure align-default']/p[@class='caption']/" 1085 "span[@class='caption-number']", '^Fig. 1 $', True), 1086 (".//div[@class='figure align-default']/p[@class='caption']/" 1087 "span[@class='caption-number']", '^Fig. 2 $', True), 1088 (".//table/caption/span[@class='caption-number']", 1089 '^Table 1 $', True), 1090 (".//table/caption/span[@class='caption-number']", 1091 '^Table 2 $', True), 1092 (".//div[@class='code-block-caption']/" 1093 "span[@class='caption-number']", '^Listing 1 $', True), 1094 (".//div[@class='code-block-caption']/" 1095 "span[@class='caption-number']", '^Listing 2 $', True), 1096 (".//li/p/a/span", '^Fig. 1$', True), 1097 (".//li/p/a/span", '^Figure2.2$', True), 1098 (".//li/p/a/span", '^Table 1$', True), 1099 (".//li/p/a/span", '^Table:2.2$', True), 1100 (".//li/p/a/span", '^Listing 1$', True), 1101 (".//li/p/a/span", '^Code-2.2$', True), 1102 (".//li/p/a/span", '^Section.1$', True), 1103 (".//li/p/a/span", '^Section.2.1$', True), 1104 (".//li/p/a/span", '^Fig.1 should be Fig.1$', True), 1105 (".//li/p/a/span", '^Sect.1 Foo$', True), 1106 (".//div[@class='figure align-default']/p[@class='caption']/" 1107 "span[@class='caption-number']", '^Fig. 1.1 $', True), 1108 (".//div[@class='figure align-default']/p[@class='caption']/" 1109 "span[@class='caption-number']", '^Fig. 1.2 $', True), 1110 (".//div[@class='figure align-default']/p[@class='caption']/" 1111 "span[@class='caption-number']", '^Fig. 1.3 $', True), 1112 (".//div[@class='figure align-default']/p[@class='caption']/" 1113 "span[@class='caption-number']", '^Fig. 1.4 $', True), 1114 (".//table/caption/span[@class='caption-number']", 1115 '^Table 1.1 $', True), 1116 (".//table/caption/span[@class='caption-number']", 1117 '^Table 1.2 $', True), 1118 (".//table/caption/span[@class='caption-number']", 1119 '^Table 1.3 $', True), 1120 (".//table/caption/span[@class='caption-number']", 1121 '^Table 1.4 $', True), 1122 (".//div[@class='code-block-caption']/" 1123 "span[@class='caption-number']", '^Listing 1.1 $', True), 1124 (".//div[@class='code-block-caption']/" 1125 "span[@class='caption-number']", '^Listing 1.2 $', True), 1126 (".//div[@class='code-block-caption']/" 1127 "span[@class='caption-number']", '^Listing 1.3 $', True), 1128 (".//div[@class='code-block-caption']/" 1129 "span[@class='caption-number']", '^Listing 1.4 $', True), 1130 (".//div[@class='figure align-default']/p[@class='caption']/" 1131 "span[@class='caption-number']", '^Fig. 2.1 $', True), 1132 (".//div[@class='figure align-default']/p[@class='caption']/" 1133 "span[@class='caption-number']", '^Fig. 2.3 $', True), 1134 (".//div[@class='figure align-default']/p[@class='caption']/" 1135 "span[@class='caption-number']", '^Fig. 2.4 $', True), 1136 (".//table/caption/span[@class='caption-number']", 1137 '^Table 2.1 $', True), 1138 (".//table/caption/span[@class='caption-number']", 1139 '^Table 2.3 $', True), 1140 (".//table/caption/span[@class='caption-number']", 1141 '^Table 2.4 $', True), 1142 (".//div[@class='code-block-caption']/" 1143 "span[@class='caption-number']", '^Listing 2.1 $', True), 1144 (".//div[@class='code-block-caption']/" 1145 "span[@class='caption-number']", '^Listing 2.3 $', True), 1146 (".//div[@class='code-block-caption']/" 1147 "span[@class='caption-number']", '^Listing 2.4 $', True), 1148 (".//div[@class='figure align-default']/p[@class='caption']/" 1149 "span[@class='caption-number']", '^Fig. 2.2 $', True), 1150 (".//table/caption/span[@class='caption-number']", 1151 '^Table 2.2 $', True), 1152 (".//div[@class='code-block-caption']/" 1153 "span[@class='caption-number']", '^Listing 2.2 $', True), 1154 ], 1155})) 1156@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 1157 reason='docutils-0.13 or above is required') 1158@pytest.mark.sphinx('singlehtml', testroot='numfig', confoverrides={'numfig': True}) 1159@pytest.mark.test_params(shared_result='test_build_html_numfig_on') 1160def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect): 1161 app.build() 1162 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 1163 1164 1165@pytest.mark.parametrize("fname,expect", flat_dict({ 1166 'index.html': [ 1167 (".//div[@class='figure align-default']/p[@class='caption']" 1168 "/span[@class='caption-number']", "Fig. 1", True), 1169 (".//div[@class='figure align-default']/p[@class='caption']" 1170 "/span[@class='caption-number']", "Fig. 2", True), 1171 (".//div[@class='figure align-default']/p[@class='caption']" 1172 "/span[@class='caption-number']", "Fig. 3", True), 1173 (".//div//span[@class='caption-number']", "No.1 ", True), 1174 (".//div//span[@class='caption-number']", "No.2 ", True), 1175 (".//li/p/a/span", 'Fig. 1', True), 1176 (".//li/p/a/span", 'Fig. 2', True), 1177 (".//li/p/a/span", 'Fig. 3', True), 1178 (".//li/p/a/span", 'No.1', True), 1179 (".//li/p/a/span", 'No.2', True), 1180 ], 1181})) 1182@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 1183 reason='docutils-0.13 or above is required') 1184@pytest.mark.sphinx('html', testroot='add_enumerable_node', 1185 srcdir='test_enumerable_node') 1186def test_enumerable_node(app, cached_etree_parse, fname, expect): 1187 app.build() 1188 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 1189 1190 1191@pytest.mark.sphinx('html', testroot='html_assets') 1192def test_html_assets(app): 1193 app.builder.build_all() 1194 1195 # exclude_path and its family 1196 assert not (app.outdir / 'static' / 'index.html').exists() 1197 assert not (app.outdir / 'extra' / 'index.html').exists() 1198 1199 # html_static_path 1200 assert not (app.outdir / '_static' / '.htaccess').exists() 1201 assert not (app.outdir / '_static' / '.htpasswd').exists() 1202 assert (app.outdir / '_static' / 'API.html').exists() 1203 assert (app.outdir / '_static' / 'API.html').read_text() == 'Sphinx-1.4.4' 1204 assert (app.outdir / '_static' / 'css' / 'style.css').exists() 1205 assert (app.outdir / '_static' / 'js' / 'custom.js').exists() 1206 assert (app.outdir / '_static' / 'rimg.png').exists() 1207 assert not (app.outdir / '_static' / '_build' / 'index.html').exists() 1208 assert (app.outdir / '_static' / 'background.png').exists() 1209 assert not (app.outdir / '_static' / 'subdir' / '.htaccess').exists() 1210 assert not (app.outdir / '_static' / 'subdir' / '.htpasswd').exists() 1211 1212 # html_extra_path 1213 assert (app.outdir / '.htaccess').exists() 1214 assert not (app.outdir / '.htpasswd').exists() 1215 assert (app.outdir / 'API.html_t').exists() 1216 assert (app.outdir / 'css/style.css').exists() 1217 assert (app.outdir / 'rimg.png').exists() 1218 assert not (app.outdir / '_build' / 'index.html').exists() 1219 assert (app.outdir / 'background.png').exists() 1220 assert (app.outdir / 'subdir' / '.htaccess').exists() 1221 assert not (app.outdir / 'subdir' / '.htpasswd').exists() 1222 1223 # html_css_files 1224 content = (app.outdir / 'index.html').read_text() 1225 assert '<link rel="stylesheet" type="text/css" href="_static/css/style.css" />' in content 1226 assert ('<link media="print" rel="stylesheet" title="title" type="text/css" ' 1227 'href="https://example.com/custom.css" />' in content) 1228 1229 # html_js_files 1230 assert '<script src="_static/js/custom.js"></script>' in content 1231 assert ('<script async="async" src="https://example.com/script.js">' 1232 '</script>' in content) 1233 1234 1235@pytest.mark.sphinx('html', testroot='html_assets') 1236def test_assets_order(app): 1237 app.add_css_file('normal.css') 1238 app.add_css_file('early.css', priority=100) 1239 app.add_css_file('late.css', priority=750) 1240 app.add_css_file('lazy.css', priority=900) 1241 app.add_js_file('normal.js') 1242 app.add_js_file('early.js', priority=100) 1243 app.add_js_file('late.js', priority=750) 1244 app.add_js_file('lazy.js', priority=900) 1245 1246 app.builder.build_all() 1247 content = (app.outdir / 'index.html').read_text() 1248 1249 # css_files 1250 expected = ['_static/pygments.css', '_static/alabaster.css', '_static/early.css', 1251 'https://example.com/custom.css', '_static/normal.css', '_static/late.css', 1252 '_static/css/style.css', '_static/lazy.css'] 1253 pattern = '.*'.join('href="%s"' % f for f in expected) 1254 assert re.search(pattern, content, re.S) 1255 1256 # js_files 1257 expected = ['_static/early.js', '_static/jquery.js', '_static/underscore.js', 1258 '_static/doctools.js', 'https://example.com/script.js', '_static/normal.js', 1259 '_static/late.js', '_static/js/custom.js', '_static/lazy.js'] 1260 pattern = '.*'.join('src="%s"' % f for f in expected) 1261 assert re.search(pattern, content, re.S) 1262 1263 1264@pytest.mark.sphinx('html', testroot='basic', confoverrides={'html_copy_source': False}) 1265def test_html_copy_source(app): 1266 app.builder.build_all() 1267 assert not (app.outdir / '_sources' / 'index.rst.txt').exists() 1268 1269 1270@pytest.mark.sphinx('html', testroot='basic', confoverrides={'html_sourcelink_suffix': '.txt'}) 1271def test_html_sourcelink_suffix(app): 1272 app.builder.build_all() 1273 assert (app.outdir / '_sources' / 'index.rst.txt').exists() 1274 1275 1276@pytest.mark.sphinx('html', testroot='basic', confoverrides={'html_sourcelink_suffix': '.rst'}) 1277def test_html_sourcelink_suffix_same(app): 1278 app.builder.build_all() 1279 assert (app.outdir / '_sources' / 'index.rst').exists() 1280 1281 1282@pytest.mark.sphinx('html', testroot='basic', confoverrides={'html_sourcelink_suffix': ''}) 1283def test_html_sourcelink_suffix_empty(app): 1284 app.builder.build_all() 1285 assert (app.outdir / '_sources' / 'index.rst').exists() 1286 1287 1288@pytest.mark.sphinx('html', testroot='html_entity') 1289def test_html_entity(app): 1290 app.builder.build_all() 1291 valid_entities = {'amp', 'lt', 'gt', 'quot', 'apos'} 1292 content = (app.outdir / 'index.html').read_text() 1293 for entity in re.findall(r'&([a-z]+);', content, re.M): 1294 assert entity not in valid_entities 1295 1296 1297@pytest.mark.sphinx('html', testroot='basic') 1298@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") 1299def test_html_inventory(app): 1300 app.builder.build_all() 1301 with open(app.outdir / 'objects.inv', 'rb') as f: 1302 invdata = InventoryFile.load(f, 'https://www.google.com', os.path.join) 1303 assert set(invdata.keys()) == {'std:label', 'std:doc'} 1304 assert set(invdata['std:label'].keys()) == {'modindex', 1305 'py-modindex', 1306 'genindex', 1307 'search'} 1308 assert invdata['std:label']['modindex'] == ('Python', 1309 '', 1310 'https://www.google.com/py-modindex.html', 1311 'Module Index') 1312 assert invdata['std:label']['py-modindex'] == ('Python', 1313 '', 1314 'https://www.google.com/py-modindex.html', 1315 'Python Module Index') 1316 assert invdata['std:label']['genindex'] == ('Python', 1317 '', 1318 'https://www.google.com/genindex.html', 1319 'Index') 1320 assert invdata['std:label']['search'] == ('Python', 1321 '', 1322 'https://www.google.com/search.html', 1323 'Search Page') 1324 assert set(invdata['std:doc'].keys()) == {'index'} 1325 assert invdata['std:doc']['index'] == ('Python', 1326 '', 1327 'https://www.google.com/index.html', 1328 'The basic Sphinx documentation for testing') 1329 1330 1331@pytest.mark.sphinx('html', testroot='images', confoverrides={'html_sourcelink_suffix': ''}) 1332def test_html_anchor_for_figure(app): 1333 app.builder.build_all() 1334 content = (app.outdir / 'index.html').read_text() 1335 assert ('<p class="caption"><span class="caption-text">The caption of pic</span>' 1336 '<a class="headerlink" href="#id1" title="Permalink to this image">¶</a></p>' 1337 in content) 1338 1339 1340@pytest.mark.sphinx('html', testroot='directives-raw') 1341def test_html_raw_directive(app, status, warning): 1342 app.builder.build_all() 1343 result = (app.outdir / 'index.html').read_text() 1344 1345 # standard case 1346 assert 'standalone raw directive (HTML)' in result 1347 assert 'standalone raw directive (LaTeX)' not in result 1348 1349 # with substitution 1350 assert '<p>HTML: abc def ghi</p>' in result 1351 assert '<p>LaTeX: abc ghi</p>' in result 1352 1353 1354@pytest.mark.parametrize("fname,expect", flat_dict({ 1355 'index.html': [ 1356 (".//link[@href='_static/persistent.css']" 1357 "[@rel='stylesheet']", '', True), 1358 (".//link[@href='_static/default.css']" 1359 "[@rel='stylesheet']" 1360 "[@title='Default']", '', True), 1361 (".//link[@href='_static/alternate1.css']" 1362 "[@rel='alternate stylesheet']" 1363 "[@title='Alternate']", '', True), 1364 (".//link[@href='_static/alternate2.css']" 1365 "[@rel='alternate stylesheet']", '', True), 1366 (".//link[@href='_static/more_persistent.css']" 1367 "[@rel='stylesheet']", '', True), 1368 (".//link[@href='_static/more_default.css']" 1369 "[@rel='stylesheet']" 1370 "[@title='Default']", '', True), 1371 (".//link[@href='_static/more_alternate1.css']" 1372 "[@rel='alternate stylesheet']" 1373 "[@title='Alternate']", '', True), 1374 (".//link[@href='_static/more_alternate2.css']" 1375 "[@rel='alternate stylesheet']", '', True), 1376 ], 1377})) 1378@pytest.mark.sphinx('html', testroot='stylesheets') 1379def test_alternate_stylesheets(app, cached_etree_parse, fname, expect): 1380 app.build() 1381 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 1382 1383 1384@pytest.mark.sphinx('html', testroot='html_style') 1385def test_html_style(app, status, warning): 1386 app.build() 1387 result = (app.outdir / 'index.html').read_text() 1388 assert '<link rel="stylesheet" href="_static/default.css" type="text/css" />' in result 1389 assert ('<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />' 1390 not in result) 1391 1392 1393@pytest.mark.sphinx('html', testroot='images') 1394def test_html_remote_images(app, status, warning): 1395 app.builder.build_all() 1396 1397 result = (app.outdir / 'index.html').read_text() 1398 assert ('<img alt="https://www.python.org/static/img/python-logo.png" ' 1399 'src="https://www.python.org/static/img/python-logo.png" />' in result) 1400 assert not (app.outdir / 'python-logo.png').exists() 1401 1402 1403@pytest.mark.sphinx('html', testroot='basic') 1404def test_html_sidebar(app, status, warning): 1405 ctx = {} 1406 1407 # default for alabaster 1408 app.builder.build_all() 1409 result = (app.outdir / 'index.html').read_text() 1410 assert ('<div class="sphinxsidebar" role="navigation" ' 1411 'aria-label="main navigation">' in result) 1412 assert '<h1 class="logo"><a href="#">Python</a></h1>' in result 1413 assert '<h3>Navigation</h3>' in result 1414 assert '<h3>Related Topics</h3>' in result 1415 assert '<h3 id="searchlabel">Quick search</h3>' in result 1416 1417 app.builder.add_sidebars('index', ctx) 1418 assert ctx['sidebars'] == ['about.html', 'navigation.html', 'relations.html', 1419 'searchbox.html', 'donate.html'] 1420 1421 # only relations.html 1422 app.config.html_sidebars = {'**': ['relations.html']} 1423 app.builder.build_all() 1424 result = (app.outdir / 'index.html').read_text() 1425 assert ('<div class="sphinxsidebar" role="navigation" ' 1426 'aria-label="main navigation">' in result) 1427 assert '<h1 class="logo"><a href="#">Python</a></h1>' not in result 1428 assert '<h3>Navigation</h3>' not in result 1429 assert '<h3>Related Topics</h3>' in result 1430 assert '<h3 id="searchlabel">Quick search</h3>' not in result 1431 1432 app.builder.add_sidebars('index', ctx) 1433 assert ctx['sidebars'] == ['relations.html'] 1434 1435 # no sidebars 1436 app.config.html_sidebars = {'**': []} 1437 app.builder.build_all() 1438 result = (app.outdir / 'index.html').read_text() 1439 assert ('<div class="sphinxsidebar" role="navigation" ' 1440 'aria-label="main navigation">' not in result) 1441 assert '<h1 class="logo"><a href="#">Python</a></h1>' not in result 1442 assert '<h3>Navigation</h3>' not in result 1443 assert '<h3>Related Topics</h3>' not in result 1444 assert '<h3 id="searchlabel">Quick search</h3>' not in result 1445 1446 app.builder.add_sidebars('index', ctx) 1447 assert ctx['sidebars'] == [] 1448 1449 1450@pytest.mark.parametrize('fname,expect', flat_dict({ 1451 'index.html': [(".//em/a[@href='https://example.com/man.1']", "", True), 1452 (".//em/a[@href='https://example.com/ls.1']", "", True), 1453 (".//em/a[@href='https://example.com/sphinx.']", "", True)] 1454 1455})) 1456@pytest.mark.sphinx('html', testroot='manpage_url', confoverrides={ 1457 'manpages_url': 'https://example.com/{page}.{section}'}) 1458@pytest.mark.test_params(shared_result='test_build_html_manpage_url') 1459def test_html_manpage(app, cached_etree_parse, fname, expect): 1460 app.build() 1461 check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) 1462 1463 1464@pytest.mark.sphinx('html', testroot='toctree-glob', 1465 confoverrides={'html_baseurl': 'https://example.com/'}) 1466def test_html_baseurl(app, status, warning): 1467 app.build() 1468 1469 result = (app.outdir / 'index.html').read_text() 1470 assert '<link rel="canonical" href="https://example.com/index.html" />' in result 1471 1472 result = (app.outdir / 'qux' / 'index.html').read_text() 1473 assert '<link rel="canonical" href="https://example.com/qux/index.html" />' in result 1474 1475 1476@pytest.mark.sphinx('html', testroot='toctree-glob', 1477 confoverrides={'html_baseurl': 'https://example.com/subdir', 1478 'html_file_suffix': '.htm'}) 1479def test_html_baseurl_and_html_file_suffix(app, status, warning): 1480 app.build() 1481 1482 result = (app.outdir / 'index.htm').read_text() 1483 assert '<link rel="canonical" href="https://example.com/subdir/index.htm" />' in result 1484 1485 result = (app.outdir / 'qux' / 'index.htm').read_text() 1486 assert '<link rel="canonical" href="https://example.com/subdir/qux/index.htm" />' in result 1487 1488 1489@pytest.mark.sphinx('html', testroot='basic') 1490def test_default_html_math_renderer(app, status, warning): 1491 assert app.builder.math_renderer_name == 'mathjax' 1492 1493 1494@pytest.mark.sphinx('html', testroot='basic', 1495 confoverrides={'extensions': ['sphinx.ext.mathjax']}) 1496def test_html_math_renderer_is_mathjax(app, status, warning): 1497 assert app.builder.math_renderer_name == 'mathjax' 1498 1499 1500@pytest.mark.sphinx('html', testroot='basic', 1501 confoverrides={'extensions': ['sphinx.ext.imgmath']}) 1502def test_html_math_renderer_is_imgmath(app, status, warning): 1503 assert app.builder.math_renderer_name == 'imgmath' 1504 1505 1506@pytest.mark.sphinx('html', testroot='basic', 1507 confoverrides={'extensions': ['sphinxcontrib.jsmath', 1508 'sphinx.ext.imgmath']}) 1509def test_html_math_renderer_is_duplicated(make_app, app_params): 1510 try: 1511 args, kwargs = app_params 1512 make_app(*args, **kwargs) 1513 assert False 1514 except ConfigError as exc: 1515 assert str(exc) == ('Many math_renderers are registered. ' 1516 'But no math_renderer is selected.') 1517 1518 1519@pytest.mark.sphinx('html', testroot='basic', 1520 confoverrides={'extensions': ['sphinx.ext.imgmath', 1521 'sphinx.ext.mathjax']}) 1522def test_html_math_renderer_is_duplicated2(app, status, warning): 1523 # case of both mathjax and another math_renderer is loaded 1524 assert app.builder.math_renderer_name == 'imgmath' # The another one is chosen 1525 1526 1527@pytest.mark.sphinx('html', testroot='basic', 1528 confoverrides={'extensions': ['sphinxcontrib.jsmath', 1529 'sphinx.ext.imgmath'], 1530 'html_math_renderer': 'imgmath'}) 1531def test_html_math_renderer_is_chosen(app, status, warning): 1532 assert app.builder.math_renderer_name == 'imgmath' 1533 1534 1535@pytest.mark.sphinx('html', testroot='basic', 1536 confoverrides={'extensions': ['sphinxcontrib.jsmath', 1537 'sphinx.ext.mathjax'], 1538 'html_math_renderer': 'imgmath'}) 1539def test_html_math_renderer_is_mismatched(make_app, app_params): 1540 try: 1541 args, kwargs = app_params 1542 make_app(*args, **kwargs) 1543 assert False 1544 except ConfigError as exc: 1545 assert str(exc) == "Unknown math_renderer 'imgmath' is given." 1546 1547 1548@pytest.mark.sphinx('html', testroot='basic') 1549def test_html_pygments_style_default(app): 1550 style = app.builder.highlighter.formatter_args.get('style') 1551 assert style.__name__ == 'Alabaster' 1552 1553 1554@pytest.mark.sphinx('html', testroot='basic', 1555 confoverrides={'pygments_style': 'sphinx'}) 1556def test_html_pygments_style_manually(app): 1557 style = app.builder.highlighter.formatter_args.get('style') 1558 assert style.__name__ == 'SphinxStyle' 1559 1560 1561@pytest.mark.sphinx('html', testroot='basic', 1562 confoverrides={'html_theme': 'classic'}) 1563def test_html_pygments_for_classic_theme(app): 1564 style = app.builder.highlighter.formatter_args.get('style') 1565 assert style.__name__ == 'SphinxStyle' 1566 1567 1568@pytest.mark.sphinx('html', testroot='basic') 1569def test_html_dark_pygments_style_default(app): 1570 assert app.builder.dark_highlighter is None 1571 1572 1573@pytest.mark.sphinx(testroot='basic', srcdir='validate_html_extra_path') 1574def test_validate_html_extra_path(app): 1575 (app.confdir / '_static').makedirs() 1576 app.config.html_extra_path = [ 1577 '/path/to/not_found', # not found 1578 '_static', 1579 app.outdir, # outdir 1580 app.outdir / '_static', # inside outdir 1581 ] 1582 validate_html_extra_path(app, app.config) 1583 assert app.config.html_extra_path == ['_static'] 1584 1585 1586@pytest.mark.sphinx(testroot='basic', srcdir='validate_html_static_path') 1587def test_validate_html_static_path(app): 1588 (app.confdir / '_static').makedirs() 1589 app.config.html_static_path = [ 1590 '/path/to/not_found', # not found 1591 '_static', 1592 app.outdir, # outdir 1593 app.outdir / '_static', # inside outdir 1594 ] 1595 validate_html_static_path(app, app.config) 1596 assert app.config.html_static_path == ['_static'] 1597 1598 1599@pytest.mark.sphinx(testroot='html_scaled_image_link') 1600def test_html_scaled_image_link(app): 1601 app.build() 1602 context = (app.outdir / 'index.html').read_text() 1603 1604 # no scaled parameters 1605 assert re.search('\n<img alt="_images/img.png" src="_images/img.png" />', context) 1606 1607 # scaled_image_link 1608 assert re.search('\n<a class="reference internal image-reference" href="_images/img.png">' 1609 '<img alt="_images/img.png" src="_images/img.png" style="[^"]+" /></a>', 1610 context) 1611 1612 # no-scaled-link class disables the feature 1613 assert re.search('\n<img alt="_images/img.png" class="no-scaled-link"' 1614 ' src="_images/img.png" style="[^"]+" />', 1615 context) 1616 1617 1618@pytest.mark.sphinx('html', testroot='reST-code-block', 1619 confoverrides={'html_codeblock_linenos_style': 'table'}) 1620def test_html_codeblock_linenos_style_table(app): 1621 app.build() 1622 content = (app.outdir / 'index.html').read_text() 1623 1624 pygments_version = tuple(LooseVersion(pygments.__version__).version) 1625 if pygments_version >= (2, 8): 1626 assert ('<div class="linenodiv"><pre><span class="normal">1</span>\n' 1627 '<span class="normal">2</span>\n' 1628 '<span class="normal">3</span>\n' 1629 '<span class="normal">4</span></pre></div>') in content 1630 else: 1631 assert '<div class="linenodiv"><pre>1\n2\n3\n4</pre></div>' in content 1632 1633 1634@pytest.mark.sphinx('html', testroot='reST-code-block', 1635 confoverrides={'html_codeblock_linenos_style': 'inline'}) 1636def test_html_codeblock_linenos_style_inline(app): 1637 app.build() 1638 content = (app.outdir / 'index.html').read_text() 1639 1640 pygments_version = tuple(LooseVersion(pygments.__version__).version) 1641 if pygments_version > (2, 7): 1642 assert '<span class="linenos">1</span>' in content 1643 else: 1644 assert '<span class="lineno">1 </span>' in content 1645 1646 1647@pytest.mark.sphinx('html', testroot='highlight_options') 1648def test_highlight_options(app): 1649 subject = app.builder.highlighter 1650 with patch.object(subject, 'highlight_block', wraps=subject.highlight_block) as highlight: 1651 app.build() 1652 1653 call_args = highlight.call_args_list 1654 assert len(call_args) == 3 1655 assert call_args[0] == call(ANY, 'default', force=False, linenos=False, 1656 location=ANY, opts={'default_option': True}) 1657 assert call_args[1] == call(ANY, 'python', force=False, linenos=False, 1658 location=ANY, opts={'python_option': True}) 1659 assert call_args[2] == call(ANY, 'java', force=False, linenos=False, 1660 location=ANY, opts={}) 1661 1662 1663@pytest.mark.sphinx('html', testroot='highlight_options', 1664 confoverrides={'highlight_options': {'default_option': True}}) 1665def test_highlight_options_old(app): 1666 subject = app.builder.highlighter 1667 with patch.object(subject, 'highlight_block', wraps=subject.highlight_block) as highlight: 1668 app.build() 1669 1670 call_args = highlight.call_args_list 1671 assert len(call_args) == 3 1672 assert call_args[0] == call(ANY, 'default', force=False, linenos=False, 1673 location=ANY, opts={'default_option': True}) 1674 assert call_args[1] == call(ANY, 'python', force=False, linenos=False, 1675 location=ANY, opts={}) 1676 assert call_args[2] == call(ANY, 'java', force=False, linenos=False, 1677 location=ANY, opts={}) 1678 1679 1680@pytest.mark.sphinx('html', testroot='basic', 1681 confoverrides={'html_permalinks': False}) 1682def test_html_permalink_disable(app): 1683 app.build() 1684 content = (app.outdir / 'index.html').read_text() 1685 1686 assert '<h1>The basic Sphinx documentation for testing</h1>' in content 1687 1688 1689@pytest.mark.sphinx('html', testroot='basic', 1690 confoverrides={'html_permalinks_icon': '<span>[PERMALINK]</span>'}) 1691def test_html_permalink_icon(app): 1692 app.build() 1693 content = (app.outdir / 'index.html').read_text() 1694 1695 assert ('<h1>The basic Sphinx documentation for testing<a class="headerlink" ' 1696 'href="#the-basic-sphinx-documentation-for-testing" ' 1697 'title="Permalink to this headline"><span>[PERMALINK]</span></a></h1>' in content) 1698