"""Test the HTML builder and check output against XPath."""
from __future__ import annotations
import os
import posixpath
import re
from typing import TYPE_CHECKING
import pytest
from sphinx._cli.util.errors import strip_escape_sequences
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
from sphinx.errors import ConfigError
from sphinx.util.inventory import InventoryFile, _InventoryItem
from tests.test_builders.xpath_data import FIGURE_CAPTION
from tests.test_builders.xpath_util import check_xpath
if TYPE_CHECKING:
from typing import Any
def test_html_sidebars_error(make_app, tmp_path):
(tmp_path / 'conf.py').touch()
with pytest.raises(
ConfigError,
match="Values in 'html_sidebars' must be a list of strings. "
"At least one pattern has a string value: 'index'. "
r"Change to `html_sidebars = \{'index': \['searchbox.html'\]\}`.",
):
make_app(
'html',
srcdir=tmp_path,
confoverrides={'html_sidebars': {'index': 'searchbox.html'}},
)
def test_html4_error(make_app, tmp_path):
(tmp_path / 'conf.py').touch()
with pytest.raises(
ConfigError,
match='HTML 4 is no longer supported by Sphinx',
):
make_app(
'html',
srcdir=tmp_path,
confoverrides={'html4_writer': True},
)
@pytest.mark.parametrize(
('fname', 'path', 'check'),
[
('index.html', ".//div[@class='citation']/span", r'Ref1'),
('index.html', ".//div[@class='citation']/span", r'Ref_1'),
(
'footnote.html',
".//a[@class='footnote-reference brackets'][@href='#id9'][@id='id1']",
r'1',
),
(
'footnote.html',
".//a[@class='footnote-reference brackets'][@href='#id10'][@id='id2']",
r'2',
),
(
'footnote.html',
".//a[@class='footnote-reference brackets'][@href='#foo'][@id='id3']",
r'3',
),
(
'footnote.html',
".//a[@class='reference internal'][@href='#bar'][@id='id4']/span",
r'\[bar\]',
),
(
'footnote.html',
".//a[@class='reference internal'][@href='#baz-qux'][@id='id5']/span",
r'\[baz_qux\]',
),
(
'footnote.html',
".//a[@class='footnote-reference brackets'][@href='#id11'][@id='id6']",
r'4',
),
(
'footnote.html',
".//a[@class='footnote-reference brackets'][@href='#id12'][@id='id7']",
r'5',
),
(
'footnote.html',
".//aside[@class='footnote brackets']/span/a[@href='#id1']",
r'1',
),
(
'footnote.html',
".//aside[@class='footnote brackets']/span/a[@href='#id2']",
r'2',
),
(
'footnote.html',
".//aside[@class='footnote brackets']/span/a[@href='#id3']",
r'3',
),
('footnote.html', ".//div[@class='citation']/span/a[@href='#id4']", r'bar'),
('footnote.html', ".//div[@class='citation']/span/a[@href='#id5']", r'baz_qux'),
(
'footnote.html',
".//aside[@class='footnote brackets']/span/a[@href='#id6']",
r'4',
),
(
'footnote.html',
".//aside[@class='footnote brackets']/span/a[@href='#id7']",
r'5',
),
(
'footnote.html',
".//aside[@class='footnote brackets']/span/a[@href='#id8']",
r'6',
),
],
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.test_params(shared_result='test_build_html_output_docutils18')
def test_docutils_output(app, cached_etree_parse, fname, path, check):
app.build()
check_xpath(cached_etree_parse(app.outdir / fname), fname, path, check)
@pytest.mark.sphinx(
'html',
testroot='root',
parallel=2,
)
def test_html_parallel(app):
app.build()
@pytest.mark.sphinx('html', testroot='build-html-translator')
def test_html_translator(app):
app.build()
assert app.builder.docwriter.visitor.depart_with_node == 10
@pytest.mark.parametrize(
'expect',
[
(FIGURE_CAPTION + "//span[@class='caption-number']", 'Fig. 1', True),
(FIGURE_CAPTION + "//span[@class='caption-number']", 'Fig. 2', True),
(FIGURE_CAPTION + "//span[@class='caption-number']", 'Fig. 3', True),
(".//div//span[@class='caption-number']", 'No.1 ', True),
(".//div//span[@class='caption-number']", 'No.2 ', True),
('.//li/p/a/span', 'Fig. 1', True),
('.//li/p/a/span', 'Fig. 2', True),
('.//li/p/a/span', 'Fig. 3', True),
('.//li/p/a/span', 'No.1', True),
('.//li/p/a/span', 'No.2', True),
],
)
@pytest.mark.sphinx(
'html',
testroot='add_enumerable_node',
srcdir='test_enumerable_node',
)
def test_enumerable_node(app, cached_etree_parse, expect):
app.build()
check_xpath(cached_etree_parse(app.outdir / 'index.html'), 'index.html', *expect)
@pytest.mark.sphinx(
'html',
testroot='basic',
confoverrides={'html_copy_source': False},
)
def test_html_copy_source(app):
app.build(force_all=True)
assert not (app.outdir / '_sources' / 'index.rst.txt').exists()
@pytest.mark.sphinx(
'html',
testroot='basic',
confoverrides={'html_sourcelink_suffix': '.txt'},
)
def test_html_sourcelink_suffix(app):
app.build(force_all=True)
assert (app.outdir / '_sources' / 'index.rst.txt').exists()
@pytest.mark.sphinx(
'html',
testroot='basic',
confoverrides={'html_sourcelink_suffix': '.rst'},
)
def test_html_sourcelink_suffix_same(app):
app.build(force_all=True)
assert (app.outdir / '_sources' / 'index.rst').exists()
@pytest.mark.sphinx(
'html',
testroot='basic',
confoverrides={'html_sourcelink_suffix': ''},
)
def test_html_sourcelink_suffix_empty(app):
app.build(force_all=True)
assert (app.outdir / '_sources' / 'index.rst').exists()
@pytest.mark.sphinx('html', testroot='html_entity')
def test_html_entity(app):
app.build(force_all=True)
valid_entities = {'amp', 'lt', 'gt', 'quot', 'apos'}
content = (app.outdir / 'index.html').read_text(encoding='utf8')
for entity in re.findall(r'&([a-z]+);', content, re.MULTILINE):
assert entity not in valid_entities
@pytest.mark.sphinx('html', testroot='basic')
def test_html_inventory(app):
app.build(force_all=True)
with app.outdir.joinpath('objects.inv').open('rb') as f:
invdata = InventoryFile.load(f, 'https://www.google.com', posixpath.join)
assert set(invdata.keys()) == {'std:label', 'std:doc'}
assert set(invdata['std:label'].keys()) == {
'modindex',
'py-modindex',
'genindex',
'search',
}
assert invdata['std:label']['modindex'] == _InventoryItem(
project_name='Project name not set',
project_version='',
uri='https://www.google.com/py-modindex.html',
display_name='Module Index',
)
assert invdata['std:label']['py-modindex'] == _InventoryItem(
project_name='Project name not set',
project_version='',
uri='https://www.google.com/py-modindex.html',
display_name='Python Module Index',
)
assert invdata['std:label']['genindex'] == _InventoryItem(
project_name='Project name not set',
project_version='',
uri='https://www.google.com/genindex.html',
display_name='Index',
)
assert invdata['std:label']['search'] == _InventoryItem(
project_name='Project name not set',
project_version='',
uri='https://www.google.com/search.html',
display_name='Search Page',
)
assert set(invdata['std:doc'].keys()) == {'index'}
assert invdata['std:doc']['index'] == _InventoryItem(
project_name='Project name not set',
project_version='',
uri='https://www.google.com/index.html',
display_name='The basic Sphinx documentation for testing',
)
@pytest.mark.usefixtures('_http_teapot')
@pytest.mark.sphinx(
'html',
testroot='images',
confoverrides={'html_sourcelink_suffix': ''},
)
def test_html_anchor_for_figure(app):
app.build(force_all=True)
content = (app.outdir / 'index.html').read_text(encoding='utf8')
assert (
' The caption of pic'
'¶
HTML: abc def ghi
' in result assert 'LaTeX: abc ghi
' in result @pytest.mark.parametrize( 'expect', [ (".//link[@href='_static/persistent.css'][@rel='stylesheet']", '', True), ( ".//link[@href='_static/default.css'][@rel='stylesheet'][@title='Default']", '', True, ), ( ".//link[@href='_static/alternate1.css']" "[@rel='alternate stylesheet']" "[@title='Alternate']", '', True, ), ( ".//link[@href='_static/alternate2.css'][@rel='alternate stylesheet']", '', True, ), ( ".//link[@href='_static/more_persistent.css'][@rel='stylesheet']", '', True, ), ( ".//link[@href='_static/more_default.css']" "[@rel='stylesheet']" "[@title='Default']", '', True, ), ( ".//link[@href='_static/more_alternate1.css']" "[@rel='alternate stylesheet']" "[@title='Alternate']", '', True, ), ( ".//link[@href='_static/more_alternate2.css'][@rel='alternate stylesheet']", '', True, ), ], ) @pytest.mark.sphinx('html', testroot='stylesheets') def test_alternate_stylesheets(app, cached_etree_parse, expect): app.build() check_xpath(cached_etree_parse(app.outdir / 'index.html'), 'index.html', *expect) @pytest.mark.sphinx('html', testroot='html_style') def test_html_style(app): app.build() result = (app.outdir / 'index.html').read_text(encoding='utf8') assert ( '' ) in result assert ( '' ) not in result @pytest.mark.sphinx( 'html', testroot='basic', confoverrides={ 'html_sidebars': { '**': [ 'localtoc.html', 'searchfield.html', 'sourcelink.html', ] } }, ) def test_html_sidebar(app): ctx: dict[str, Any] = {} # default for alabaster app.build(force_all=True) result = (app.outdir / 'index.html').read_text(encoding='utf8') # layout.html assert '