mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
[tests] Add basic build test for all builtin themes (#12168)
Add `tests/test_theming/test_theming.py::test_theme_builds`, which is a parametrized test against all builtin sphinx HTML themes, that tests: 1. that the themes builds without warnings for a basic project, and 2. that all `.html` files it produces are valid XML (see https://html.spec.whatwg.org/) https://pypi.org/project/defusedxml/ was added to the test dependencies, in order to safely parse the XML This required one fix for `sphinx/themes/basic/search.html`, and one for `sphinx/themes/bizstyle/layout.html` Also, `tests/test_theming` was removed from the `ruff format` exclude list
This commit is contained in:
parent
982679eeee
commit
66fa790b3a
@ -465,7 +465,6 @@ exclude = [
|
|||||||
"tests/test_quickstart.py",
|
"tests/test_quickstart.py",
|
||||||
"tests/test_roles.py",
|
"tests/test_roles.py",
|
||||||
"tests/test_search.py",
|
"tests/test_search.py",
|
||||||
"tests/test_theming/**/*",
|
|
||||||
"tests/test_toctree.py",
|
"tests/test_toctree.py",
|
||||||
"tests/test_transforms/**/*",
|
"tests/test_transforms/**/*",
|
||||||
"tests/test_util/**/*",
|
"tests/test_util/**/*",
|
||||||
|
@ -92,6 +92,7 @@ lint = [
|
|||||||
test = [
|
test = [
|
||||||
"pytest>=6.0",
|
"pytest>=6.0",
|
||||||
"html5lib",
|
"html5lib",
|
||||||
|
"defusedxml>=0.7.1", # for secure XML/HTML parsing
|
||||||
"cython>=3.0",
|
"cython>=3.0",
|
||||||
"setuptools>=67.0", # for Cython compilation
|
"setuptools>=67.0", # for Cython compilation
|
||||||
"filelock",
|
"filelock",
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script src="{{ pathto('_static/language_data.js', 1) }}"></script>
|
<script src="{{ pathto('_static/language_data.js', 1) }}"></script>
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
{% block extrahead %}
|
{% block extrahead %}
|
||||||
<script src="{{ pathto('searchindex.js', 1) }}" defer></script>
|
<script src="{{ pathto('searchindex.js', 1) }}" defer="defer"></script>
|
||||||
<meta name="robots" content="noindex" />
|
<meta name="robots" content="noindex" />
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -14,11 +14,6 @@
|
|||||||
<script src="{{ pathto('_static/bizstyle.js', 1) }}"></script>
|
<script src="{{ pathto('_static/bizstyle.js', 1) }}"></script>
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{# doctype override #}
|
|
||||||
{%- block doctype %}
|
|
||||||
<!doctype html>
|
|
||||||
{%- endblock %}
|
|
||||||
|
|
||||||
{%- block extrahead %}
|
{%- block extrahead %}
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
|
@ -10,9 +10,14 @@ def test_theme_options(app, status, warning):
|
|||||||
assert 'ENABLE_SEARCH_SHORTCUTS: true' in result
|
assert 'ENABLE_SEARCH_SHORTCUTS: true' in result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx('html', testroot='theming',
|
@pytest.mark.sphinx(
|
||||||
confoverrides={'html_theme_options.navigation_with_keys': True,
|
'html',
|
||||||
'html_theme_options.enable_search_shortcuts': False})
|
testroot='theming',
|
||||||
|
confoverrides={
|
||||||
|
'html_theme_options.navigation_with_keys': True,
|
||||||
|
'html_theme_options.enable_search_shortcuts': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
def test_theme_options_with_override(app, status, warning):
|
def test_theme_options_with_override(app, status, warning):
|
||||||
app.build()
|
app.build()
|
||||||
|
|
||||||
|
@ -23,19 +23,26 @@ def test_autosummary_class_template_overloading(make_app, app_params):
|
|||||||
setup_documenters(app)
|
setup_documenters(app)
|
||||||
app.build()
|
app.build()
|
||||||
|
|
||||||
result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').read_text(encoding='utf8')
|
result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').read_text(
|
||||||
|
encoding='utf8'
|
||||||
|
)
|
||||||
assert 'autosummary/class.rst method block overloading' in result
|
assert 'autosummary/class.rst method block overloading' in result
|
||||||
assert 'foobar' not in result
|
assert 'foobar' not in result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx('html', testroot='templating',
|
@pytest.mark.sphinx(
|
||||||
confoverrides={'autosummary_context': {'sentence': 'foobar'}})
|
'html',
|
||||||
|
testroot='templating',
|
||||||
|
confoverrides={'autosummary_context': {'sentence': 'foobar'}},
|
||||||
|
)
|
||||||
def test_autosummary_context(make_app, app_params):
|
def test_autosummary_context(make_app, app_params):
|
||||||
args, kwargs = app_params
|
args, kwargs = app_params
|
||||||
app = make_app(*args, **kwargs)
|
app = make_app(*args, **kwargs)
|
||||||
setup_documenters(app)
|
setup_documenters(app)
|
||||||
app.build()
|
app.build()
|
||||||
|
|
||||||
result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').read_text(encoding='utf8')
|
result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').read_text(
|
||||||
|
encoding='utf8'
|
||||||
|
)
|
||||||
assert 'autosummary/class.rst method block overloading' in result
|
assert 'autosummary/class.rst method block overloading' in result
|
||||||
assert 'foobar' in result
|
assert 'foobar' in result
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
"""Test the Theme class."""
|
"""Test the Theme class."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
|
from xml.etree.ElementTree import ParseError
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from defusedxml.ElementTree import parse as xml_parse
|
||||||
|
|
||||||
import sphinx.builders.html
|
import sphinx.builders.html
|
||||||
from sphinx.errors import ThemeError
|
from sphinx.errors import ThemeError
|
||||||
@ -11,18 +14,40 @@ from sphinx.theming import _load_theme_conf
|
|||||||
|
|
||||||
@pytest.mark.sphinx(
|
@pytest.mark.sphinx(
|
||||||
testroot='theming',
|
testroot='theming',
|
||||||
confoverrides={'html_theme': 'ziptheme',
|
confoverrides={'html_theme': 'ziptheme', 'html_theme_options.testopt': 'foo'},
|
||||||
'html_theme_options.testopt': 'foo'})
|
)
|
||||||
def test_theme_api(app, status, warning):
|
def test_theme_api(app, status, warning):
|
||||||
themes = ['basic', 'default', 'scrolls', 'agogo', 'sphinxdoc', 'haiku',
|
themes = [
|
||||||
'traditional', 'epub', 'nature', 'pyramid', 'bizstyle', 'classic', 'nonav',
|
'basic',
|
||||||
'test-theme', 'ziptheme', 'staticfiles', 'parent', 'child', 'alabaster']
|
'default',
|
||||||
|
'scrolls',
|
||||||
|
'agogo',
|
||||||
|
'sphinxdoc',
|
||||||
|
'haiku',
|
||||||
|
'traditional',
|
||||||
|
'epub',
|
||||||
|
'nature',
|
||||||
|
'pyramid',
|
||||||
|
'bizstyle',
|
||||||
|
'classic',
|
||||||
|
'nonav',
|
||||||
|
'test-theme',
|
||||||
|
'ziptheme',
|
||||||
|
'staticfiles',
|
||||||
|
'parent',
|
||||||
|
'child',
|
||||||
|
'alabaster',
|
||||||
|
]
|
||||||
|
|
||||||
# test Theme class API
|
# test Theme class API
|
||||||
assert set(app.registry.html_themes.keys()) == set(themes)
|
assert set(app.registry.html_themes.keys()) == set(themes)
|
||||||
assert app.registry.html_themes['test-theme'] == str(app.srcdir / 'test_theme' / 'test-theme')
|
assert app.registry.html_themes['test-theme'] == str(
|
||||||
|
app.srcdir / 'test_theme' / 'test-theme'
|
||||||
|
)
|
||||||
assert app.registry.html_themes['ziptheme'] == str(app.srcdir / 'ziptheme.zip')
|
assert app.registry.html_themes['ziptheme'] == str(app.srcdir / 'ziptheme.zip')
|
||||||
assert app.registry.html_themes['staticfiles'] == str(app.srcdir / 'test_theme' / 'staticfiles')
|
assert app.registry.html_themes['staticfiles'] == str(
|
||||||
|
app.srcdir / 'test_theme' / 'staticfiles'
|
||||||
|
)
|
||||||
|
|
||||||
# test Theme instance API
|
# test Theme instance API
|
||||||
theme = app.builder.theme
|
theme = app.builder.theme
|
||||||
@ -65,30 +90,26 @@ def test_double_inheriting_theme(app, status, warning):
|
|||||||
app.build() # => not raises TemplateNotFound
|
app.build() # => not raises TemplateNotFound
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(testroot='theming',
|
@pytest.mark.sphinx(testroot='theming', confoverrides={'html_theme': 'child'})
|
||||||
confoverrides={'html_theme': 'child'})
|
|
||||||
def test_nested_zipped_theme(app, status, warning):
|
def test_nested_zipped_theme(app, status, warning):
|
||||||
assert app.builder.theme.name == 'child'
|
assert app.builder.theme.name == 'child'
|
||||||
app.build() # => not raises TemplateNotFound
|
app.build() # => not raises TemplateNotFound
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(testroot='theming',
|
@pytest.mark.sphinx(testroot='theming', confoverrides={'html_theme': 'staticfiles'})
|
||||||
confoverrides={'html_theme': 'staticfiles'})
|
|
||||||
def test_staticfiles(app, status, warning):
|
def test_staticfiles(app, status, warning):
|
||||||
app.build()
|
app.build()
|
||||||
assert (app.outdir / '_static' / 'staticimg.png').exists()
|
assert (app.outdir / '_static' / 'staticimg.png').exists()
|
||||||
assert (app.outdir / '_static' / 'statictmpl.html').exists()
|
assert (app.outdir / '_static' / 'statictmpl.html').exists()
|
||||||
assert (app.outdir / '_static' / 'statictmpl.html').read_text(encoding='utf8') == (
|
assert (app.outdir / '_static' / 'statictmpl.html').read_text(encoding='utf8') == (
|
||||||
'<!-- testing static templates -->\n'
|
'<!-- testing static templates -->\n<html><project>Python</project></html>'
|
||||||
'<html><project>Python</project></html>'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
result = (app.outdir / 'index.html').read_text(encoding='utf8')
|
result = (app.outdir / 'index.html').read_text(encoding='utf8')
|
||||||
assert '<meta name="testopt" content="optdefault" />' in result
|
assert '<meta name="testopt" content="optdefault" />' in result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(testroot='theming',
|
@pytest.mark.sphinx(testroot='theming', confoverrides={'html_theme': 'test-theme'})
|
||||||
confoverrides={'html_theme': 'test-theme'})
|
|
||||||
def test_dark_style(app, monkeypatch):
|
def test_dark_style(app, monkeypatch):
|
||||||
monkeypatch.setattr(sphinx.builders.html, '_file_checksum', lambda o, f: '')
|
monkeypatch.setattr(sphinx.builders.html, '_file_checksum', lambda o, f: '')
|
||||||
|
|
||||||
@ -100,8 +121,8 @@ def test_dark_style(app, monkeypatch):
|
|||||||
|
|
||||||
css_file, properties = app.registry.css_files[0]
|
css_file, properties = app.registry.css_files[0]
|
||||||
assert css_file == 'pygments_dark.css'
|
assert css_file == 'pygments_dark.css'
|
||||||
assert "media" in properties
|
assert 'media' in properties
|
||||||
assert properties["media"] == '(prefers-color-scheme: dark)'
|
assert properties['media'] == '(prefers-color-scheme: dark)'
|
||||||
|
|
||||||
assert sorted(f.filename for f in app.builder._css_files) == [
|
assert sorted(f.filename for f in app.builder._css_files) == [
|
||||||
'_static/classic.css',
|
'_static/classic.css',
|
||||||
@ -111,9 +132,11 @@ def test_dark_style(app, monkeypatch):
|
|||||||
|
|
||||||
result = (app.outdir / 'index.html').read_text(encoding='utf8')
|
result = (app.outdir / 'index.html').read_text(encoding='utf8')
|
||||||
assert '<link rel="stylesheet" type="text/css" href="_static/pygments.css" />' in result
|
assert '<link rel="stylesheet" type="text/css" href="_static/pygments.css" />' in result
|
||||||
assert ('<link id="pygments_dark_css" media="(prefers-color-scheme: dark)" '
|
assert (
|
||||||
|
'<link id="pygments_dark_css" media="(prefers-color-scheme: dark)" '
|
||||||
'rel="stylesheet" type="text/css" '
|
'rel="stylesheet" type="text/css" '
|
||||||
'href="_static/pygments_dark.css" />') in result
|
'href="_static/pygments_dark.css" />'
|
||||||
|
) in result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(testroot='theming')
|
@pytest.mark.sphinx(testroot='theming')
|
||||||
@ -126,3 +149,41 @@ def test_theme_sidebars(app, status, warning):
|
|||||||
assert '<h3>Related Topics</h3>' not in result
|
assert '<h3>Related Topics</h3>' not in result
|
||||||
assert '<h3>This Page</h3>' not in result
|
assert '<h3>This Page</h3>' not in result
|
||||||
assert '<h3 id="searchlabel">Quick search</h3>' in result
|
assert '<h3 id="searchlabel">Quick search</h3>' in result
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'theme_name',
|
||||||
|
[
|
||||||
|
'alabaster',
|
||||||
|
'agogo',
|
||||||
|
'basic',
|
||||||
|
'bizstyle',
|
||||||
|
'classic',
|
||||||
|
'default',
|
||||||
|
'epub',
|
||||||
|
'haiku',
|
||||||
|
'nature',
|
||||||
|
'nonav',
|
||||||
|
'pyramid',
|
||||||
|
'scrolls',
|
||||||
|
'sphinxdoc',
|
||||||
|
'traditional',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_theme_builds(make_app, rootdir, sphinx_test_tempdir, theme_name):
|
||||||
|
"""Test all the themes included with Sphinx build a simple project and produce valid XML."""
|
||||||
|
testroot_path = rootdir / 'test-basic'
|
||||||
|
srcdir = sphinx_test_tempdir / f'test-theme-{theme_name}'
|
||||||
|
shutil.copytree(testroot_path, srcdir)
|
||||||
|
|
||||||
|
app = make_app(srcdir=srcdir, confoverrides={'html_theme': theme_name})
|
||||||
|
app.build()
|
||||||
|
assert not app.warning.getvalue().strip()
|
||||||
|
assert app.outdir.joinpath('index.html').exists()
|
||||||
|
|
||||||
|
# check that the generated HTML files are well-formed (as strict XML)
|
||||||
|
for html_file in app.outdir.rglob('*.html'):
|
||||||
|
try:
|
||||||
|
xml_parse(html_file)
|
||||||
|
except ParseError as exc:
|
||||||
|
pytest.fail(f'Failed to parse {html_file.relative_to(app.outdir)}: {exc}')
|
||||||
|
Loading…
Reference in New Issue
Block a user