Merge pull request #5927 from tk0miya/refactor_html5

HTML builder outputs HTML5 by default
This commit is contained in:
Takeshi KOMIYA 2019-02-11 18:41:53 +09:00 committed by GitHub
commit 6bdac80b5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 327 additions and 571 deletions

View File

@ -66,6 +66,7 @@ Incompatible changes
the text width and height, even if width and/or height option were used. the text width and height, even if width and/or height option were used.
(refs: #5956) (refs: #5956)
* #4550: All tables and figures without ``align`` option are displayed to center * #4550: All tables and figures without ``align`` option are displayed to center
* #4587: html: Output HTML5 by default
Deprecated Deprecated
---------- ----------
@ -76,6 +77,10 @@ Deprecated
``EpubBuilder.build_container()``, ``EpubBuilder.bulid_content()``, ``EpubBuilder.build_container()``, ``EpubBuilder.bulid_content()``,
``EpubBuilder.build_toc()`` and ``EpubBuilder.build_epub()`` ``EpubBuilder.build_toc()`` and ``EpubBuilder.build_epub()``
* The arguments of ``Epub3Builder.build_navigation_doc()`` * The arguments of ``Epub3Builder.build_navigation_doc()``
* The config variables
- :confval:`html_experimental_html5_writer`
* The ``encoding`` argument of ``autodoc.Documenter.get_doc()``, * The ``encoding`` argument of ``autodoc.Documenter.get_doc()``,
``autodoc.DocstringSignatureMixin.get_doc()``, ``autodoc.DocstringSignatureMixin.get_doc()``,
``autodoc.DocstringSignatureMixin._find_signature()``, and ``autodoc.DocstringSignatureMixin._find_signature()``, and
@ -179,6 +184,7 @@ Features added
* #4611: epub: Show warning for duplicated ToC entries * #4611: epub: Show warning for duplicated ToC entries
* #1851: Allow to omit an argument for :rst:dir:`code-block` directive. If * #1851: Allow to omit an argument for :rst:dir:`code-block` directive. If
omitted, it follows :rst:dir:`highlight` or :confval:`highlight_language` omitted, it follows :rst:dir:`highlight` or :confval:`highlight_language`
* #4587: html: Add :confval:`html4_writer` to use old HTML4 writer
* #6016: HTML search: A placeholder for the search summary prevents search * #6016: HTML search: A placeholder for the search summary prevents search
result links from changing their position when the search terminates. This result links from changing their position when the search terminates. This
makes navigating search results easier. makes navigating search results easier.

View File

@ -388,32 +388,29 @@ div.admonition, div.warning {
padding: 0; padding: 0;
} }
div.admonition p, div.warning p { div.admonition > p, div.warning > p {
margin: 0.5em 1em 0.5em 1em; margin: 0.5em 1em 0.5em 1em;
padding: 0; padding: 0;
} }
div.admonition pre, div.warning pre { div.admonition > pre, div.warning > pre {
margin: 0.4em 1em 0.4em 1em; margin: 0.4em 1em 0.4em 1em;
} }
div.admonition p.admonition-title, div.admonition > p.admonition-title,
div.warning p.admonition-title { div.warning > p.admonition-title {
margin-top: 1em; margin-top: 0.5em;
padding-top: 0.5em;
font-weight: bold; font-weight: bold;
} }
div.warning { div.warning {
border: 1px solid #940000; border: 1px solid #940000;
/* background-color: #FFCCCF;*/
} }
div.warning p.admonition-title { div.admonition > ul,
} div.admonition > ol,
div.warning > ul,
div.admonition ul, div.admonition ol, div.warning > ol {
div.warning ul, div.warning ol {
margin: 0.1em 0.5em 0.5em 3em; margin: 0.1em 0.5em 0.5em 3em;
padding: 0; padding: 0;
} }

View File

@ -1331,6 +1331,12 @@ that use Sphinx's HTMLWriter class.
.. versionadded:: 1.6 .. versionadded:: 1.6
.. deprecated:: 2.0
.. confval:: html4_writer
Output is processed with HTML4 writer. Default is ``False``.
Options for Single HTML output Options for Single HTML output
------------------------------- -------------------------------

View File

@ -134,8 +134,6 @@ class EpubBuilder(StandaloneHTMLBuilder):
html_scaled_image_link = False html_scaled_image_link = False
# don't generate search index or include search page # don't generate search index or include search page
search = False search = False
# use html5 translator by default
default_html5_translator = True
coverpage_name = COVERPAGE_NAME coverpage_name = COVERPAGE_NAME
toctree_template = TOCTREE_TEMPLATE toctree_template = TOCTREE_TEMPLATE

View File

@ -18,7 +18,6 @@ import warnings
from hashlib import md5 from hashlib import md5
from os import path from os import path
import docutils
from docutils import nodes from docutils import nodes
from docutils.core import publish_parts from docutils.core import publish_parts
from docutils.frontend import OptionParser from docutils.frontend import OptionParser
@ -58,7 +57,7 @@ if False:
from sphinx.domains import Domain, Index, IndexEntry # NOQA from sphinx.domains import Domain, Index, IndexEntry # NOQA
from sphinx.util.tags import Tags # NOQA from sphinx.util.tags import Tags # NOQA
# Experimental HTML5 Writer # HTML5 Writer is avialable or not
if is_html5_writer_available(): if is_html5_writer_available():
from sphinx.writers.html5 import HTML5Translator from sphinx.writers.html5 import HTML5Translator
html5_ready = True html5_ready = True
@ -242,8 +241,6 @@ class StandaloneHTMLBuilder(Builder):
search = True # for things like HTML help and Apple help: suppress search search = True # for things like HTML help and Apple help: suppress search
use_index = False use_index = False
download_support = True # enable download role download_support = True # enable download role
# use html5 translator by default
default_html5_translator = False
imgpath = None # type: str imgpath = None # type: str
domain_indices = [] # type: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] # NOQA domain_indices = [] # type: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] # NOQA
@ -285,11 +282,6 @@ class StandaloneHTMLBuilder(Builder):
self.use_index = self.get_builder_config('use_index', 'html') self.use_index = self.get_builder_config('use_index', 'html')
if self.config.html_experimental_html5_writer and not html5_ready:
logger.warning(__('html_experimental_html5_writer is set, but current version '
'is old. Docutils\' version should be 0.13 or newer, but %s.'),
docutils.__version__)
def create_build_info(self): def create_build_info(self):
# type: () -> BuildInfo # type: () -> BuildInfo
return BuildInfo(self.config, self.tags, ['html']) return BuildInfo(self.config, self.tags, ['html'])
@ -374,14 +366,10 @@ class StandaloneHTMLBuilder(Builder):
@property @property
def default_translator_class(self): # type: ignore def default_translator_class(self): # type: ignore
# type: () -> Type[nodes.NodeVisitor] # type: () -> Type[nodes.NodeVisitor]
use_html5_writer = self.config.html_experimental_html5_writer if not html5_ready or self.config.html4_writer:
if use_html5_writer is None:
use_html5_writer = self.default_html5_translator
if use_html5_writer and html5_ready:
return HTML5Translator
else:
return HTMLTranslator return HTMLTranslator
else:
return HTML5Translator
@property @property
def math_renderer_name(self): def math_renderer_name(self):
@ -562,7 +550,7 @@ class StandaloneHTMLBuilder(Builder):
'parents': [], 'parents': [],
'logo': logo, 'logo': logo,
'favicon': favicon, 'favicon': favicon,
'html5_doctype': self.config.html_experimental_html5_writer and html5_ready, 'html5_doctype': html5_ready and not self.config.html4_writer
} }
if self.theme: if self.theme:
self.globalcontext.update( self.globalcontext.update(
@ -1440,9 +1428,9 @@ def setup(app):
app.add_config_value('html_search_options', {}, 'html') app.add_config_value('html_search_options', {}, 'html')
app.add_config_value('html_search_scorer', '', None) app.add_config_value('html_search_scorer', '', None)
app.add_config_value('html_scaled_image_link', True, 'html') app.add_config_value('html_scaled_image_link', True, 'html')
app.add_config_value('html_experimental_html5_writer', None, 'html')
app.add_config_value('html_baseurl', '', 'html') app.add_config_value('html_baseurl', '', 'html')
app.add_config_value('html_math_renderer', None, 'env') app.add_config_value('html_math_renderer', None, 'env')
app.add_config_value('html4_writer', False, 'html')
# event handlers # event handlers
app.connect('config-inited', convert_html_css_files) app.connect('config-inited', convert_html_css_files)

View File

@ -231,6 +231,16 @@ a.headerlink {
visibility: hidden; visibility: hidden;
} }
a.brackets:before,
span.brackets > a:before{
content: "[";
}
a.brackets:after,
span.brackets > a:after {
content: "]";
}
h1:hover > a.headerlink, h1:hover > a.headerlink,
h2:hover > a.headerlink, h2:hover > a.headerlink,
h3:hover > a.headerlink, h3:hover > a.headerlink,
@ -391,6 +401,14 @@ table.citation td {
border-bottom: none; border-bottom: none;
} }
td > p:first-child {
margin-top: 0px;
}
td > p:only-child {
margin-bottom: 0px;
}
/* -- figures --------------------------------------------------------------- */ /* -- figures --------------------------------------------------------------- */
div.figure { div.figure {
@ -460,11 +478,57 @@ ol.upperroman {
list-style: upper-roman; list-style: upper-roman;
} }
li > p:first-child {
margin-top: 0px;
}
li > p:only-child {
margin-bottom: 0px;
}
dl.footnote > dt,
dl.citation > dt {
float: left;
}
dl.footnote > dd,
dl.citation > dd {
margin-bottom: 0em;
}
dl.footnote > dd:after,
dl.citation > dd:after {
content: "";
clear: both;
}
dl.field-list {
display: flex;
flex-wrap: wrap;
}
dl.field-list > dt {
flex-basis: 20%;
font-weight: bold;
word-break: break-word;
}
dl.field-list > dt:after {
content: ":";
}
dl.field-list > dd {
flex-basis: 70%;
padding-left: 1em;
margin-left: 0em;
margin-bottom: 0em;
}
dl { dl {
margin-bottom: 15px; margin-bottom: 15px;
} }
dd p { dd > p:first-child {
margin-top: 0px; margin-top: 0px;
} }

View File

@ -12,6 +12,8 @@ import sys
import pytest import pytest
from sphinx.util import docutils
@pytest.fixture(scope='module', autouse=True) @pytest.fixture(scope='module', autouse=True)
def setup_module(rootdir): def setup_module(rootdir):
@ -26,7 +28,10 @@ def test_html_translator(app, status, warning):
# no set_translator() # no set_translator()
translator_class = app.builder.get_translator_class() translator_class = app.builder.get_translator_class()
assert translator_class assert translator_class
assert translator_class.__name__ == 'HTMLTranslator' if docutils.__version_info__ < (0, 13):
assert translator_class.__name__ == 'HTMLTranslator'
else:
assert translator_class.__name__ == 'HTML5Translator'
@pytest.mark.sphinx('html', testroot='api-set-translator') @pytest.mark.sphinx('html', testroot='api-set-translator')

View File

@ -10,6 +10,7 @@
import os import os
import re import re
from hashlib import md5
from itertools import cycle, chain from itertools import cycle, chain
import pytest import pytest
@ -17,6 +18,7 @@ from html5lib import HTMLParser
from sphinx.errors import ConfigError from sphinx.errors import ConfigError
from sphinx.testing.util import strip_escseq from sphinx.testing.util import strip_escseq
from sphinx.util import docutils
from sphinx.util.inventory import InventoryFile from sphinx.util.inventory import InventoryFile
@ -128,6 +130,11 @@ def test_html_warnings(app, warning):
'--- Got:\n' + html_warnings '--- Got:\n' + html_warnings
@pytest.mark.sphinx('html', confoverrides={'html4_writer': True})
def test_html4_output(app, status, warning):
app.build()
@pytest.mark.parametrize("fname,expect", flat_dict({ @pytest.mark.parametrize("fname,expect", flat_dict({
'images.html': [ 'images.html': [
(".//img[@src='_images/img.png']", ''), (".//img[@src='_images/img.png']", ''),
@ -192,18 +199,18 @@ def test_html_warnings(app, warning):
# an option list # an option list
(".//span[@class='option']", '--help'), (".//span[@class='option']", '--help'),
# admonitions # admonitions
(".//p[@class='first admonition-title']", 'My Admonition'), (".//p[@class='admonition-title']", 'My Admonition'),
(".//p[@class='last']", 'Note text.'), (".//div[@class='admonition note']/p", 'Note text.'),
(".//p[@class='last']", 'Warning text.'), (".//div[@class='admonition warning']/p", 'Warning text.'),
# inline markup # inline markup
(".//li/strong", r'^command\\n$'), (".//li/p/strong", r'^command\\n$'),
(".//li/strong", r'^program\\n$'), (".//li/p/strong", r'^program\\n$'),
(".//li/em", r'^dfn\\n$'), (".//li/p/em", r'^dfn\\n$'),
(".//li/kbd", r'^kbd\\n$'), (".//li/p/kbd", r'^kbd\\n$'),
(".//li/span", 'File \N{TRIANGULAR BULLET} Close'), (".//li/p/span", 'File \N{TRIANGULAR BULLET} Close'),
(".//li/code/span[@class='pre']", '^a/$'), (".//li/p/code/span[@class='pre']", '^a/$'),
(".//li/code/em/span[@class='pre']", '^varpart$'), (".//li/p/code/em/span[@class='pre']", '^varpart$'),
(".//li/code/em/span[@class='pre']", '^i$'), (".//li/p/code/em/span[@class='pre']", '^i$'),
(".//a[@href='https://www.python.org/dev/peps/pep-0008']" (".//a[@href='https://www.python.org/dev/peps/pep-0008']"
"[@class='pep reference external']/strong", 'PEP 8'), "[@class='pep reference external']/strong", 'PEP 8'),
(".//a[@href='https://www.python.org/dev/peps/pep-0008']" (".//a[@href='https://www.python.org/dev/peps/pep-0008']"
@ -236,13 +243,13 @@ def test_html_warnings(app, warning):
(".//div[@class='versionchanged']/p", (".//div[@class='versionchanged']/p",
'Second paragraph of versionchanged'), 'Second paragraph of versionchanged'),
# footnote reference # footnote reference
(".//a[@class='footnote-reference']", r'\[1\]'), (".//a[@class='footnote-reference brackets']", r'1'),
# created by reference lookup # created by reference lookup
(".//a[@href='index.html#ref1']", ''), (".//a[@href='index.html#ref1']", ''),
# ``seealso`` directive # ``seealso`` directive
(".//div/p[@class='first admonition-title']", 'See also'), (".//div/p[@class='admonition-title']", 'See also'),
# a ``hlist`` directive # a ``hlist`` directive
(".//table[@class='hlist']/tbody/tr/td/ul/li", '^This$'), (".//table[@class='hlist']/tbody/tr/td/ul/li/p", '^This$'),
# a ``centered`` directive # a ``centered`` directive
(".//p[@class='centered']/strong", 'LICENSE'), (".//p[@class='centered']/strong", 'LICENSE'),
# a glossary # a glossary
@ -261,10 +268,10 @@ def test_html_warnings(app, warning):
# tests for numeric labels # tests for numeric labels
(".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'), (".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'),
# tests for smartypants # tests for smartypants
(".//li", 'Smart “quotes” in English text.'), (".//li/p", 'Smart “quotes” in English text.'),
(".//li", 'Smart — long and short dashes.'), (".//li/p", 'Smart — long and short dashes.'),
(".//li", 'Ellipsis…'), (".//li/p", 'Ellipsis…'),
(".//li//code//span[@class='pre']", 'foo--"bar"...'), (".//li/p/code/span[@class='pre']", 'foo--"bar"...'),
(".//p", 'Этот «абзац» должен использовать „русские“ кавычки.'), (".//p", 'Этот «абзац» должен использовать „русские“ кавычки.'),
(".//p", 'Il dit : « Cest “super” ! »'), (".//p", 'Il dit : « Cest “super” ! »'),
], ],
@ -294,24 +301,24 @@ def test_html_warnings(app, warning):
(".//li[@class='toctree-l1']/a[@href='markup.html']", (".//li[@class='toctree-l1']/a[@href='markup.html']",
'Testing various markup'), 'Testing various markup'),
# test unknown field names # test unknown field names
(".//th[@class='field-name']", 'Field_name:'), (".//dt[@class='field-odd']", 'Field_name'),
(".//th[@class='field-name']", 'Field_name all lower:'), (".//dt[@class='field-even']", 'Field_name all lower'),
(".//th[@class='field-name']", 'FIELD_NAME:'), (".//dt[@class='field-odd']", 'FIELD_NAME'),
(".//th[@class='field-name']", 'FIELD_NAME ALL CAPS:'), (".//dt[@class='field-even']", 'FIELD_NAME ALL CAPS'),
(".//th[@class='field-name']", 'Field_Name:'), (".//dt[@class='field-odd']", 'Field_Name'),
(".//th[@class='field-name']", 'Field_Name All Word Caps:'), (".//dt[@class='field-even']", 'Field_Name All Word Caps'),
(".//th[@class='field-name']", 'Field_name:'), (".//dt[@class='field-odd']", 'Field_name'),
(".//th[@class='field-name']", 'Field_name First word cap:'), (".//dt[@class='field-even']", 'Field_name First word cap'),
(".//th[@class='field-name']", 'FIELd_name:'), (".//dt[@class='field-odd']", 'FIELd_name'),
(".//th[@class='field-name']", 'FIELd_name PARTial caps:'), (".//dt[@class='field-even']", 'FIELd_name PARTial caps'),
# custom sidebar # custom sidebar
(".//h4", 'Custom sidebar'), (".//h4", 'Custom sidebar'),
# docfields # docfields
(".//td[@class='field-body']/strong", '^moo$'), (".//dd[@class='field-odd']/p/strong", '^moo$'),
(".//td[@class='field-body']/strong", tail_check(r'\(Moo\) .* Moo')), (".//dd[@class='field-odd']/p/strong", tail_check(r'\(Moo\) .* Moo')),
(".//td[@class='field-body']/ul/li/strong", '^hour$'), (".//dd[@class='field-odd']/ul/li/p/strong", '^hour$'),
(".//td[@class='field-body']/ul/li/em", '^DuplicateType$'), (".//dd[@class='field-odd']/ul/li/p/em", '^DuplicateType$'),
(".//td[@class='field-body']/ul/li/em", tail_check(r'.* Some parameter')), (".//dd[@class='field-odd']/ul/li/p/em", tail_check(r'.* Some parameter')),
# others # others
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span", (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span",
'perl'), 'perl'),
@ -340,17 +347,17 @@ def test_html_warnings(app, warning):
'index.html': [ 'index.html': [
(".//meta[@name='hc'][@content='hcval']", ''), (".//meta[@name='hc'][@content='hcval']", ''),
(".//meta[@name='hc_co'][@content='hcval_co']", ''), (".//meta[@name='hc_co'][@content='hcval_co']", ''),
(".//td[@class='label']", r'\[Ref1\]'), (".//dt[@class='label']/span[@class='brackets']", r'Ref1'),
(".//td[@class='label']", ''), (".//dt[@class='label']", ''),
(".//li[@class='toctree-l1']/a", 'Testing various markup'), (".//li[@class='toctree-l1']/a", 'Testing various markup'),
(".//li[@class='toctree-l2']/a", 'Inline markup'), (".//li[@class='toctree-l2']/a", 'Inline markup'),
(".//title", 'Sphinx <Tests>'), (".//title", 'Sphinx <Tests>'),
(".//div[@class='footer']", 'Georg Brandl & Team'), (".//div[@class='footer']", 'Georg Brandl & Team'),
(".//a[@href='http://python.org/']" (".//a[@href='http://python.org/']"
"[@class='reference external']", ''), "[@class='reference external']", ''),
(".//li/a[@href='genindex.html']/span", 'Index'), (".//li/p/a[@href='genindex.html']/span", 'Index'),
(".//li/a[@href='py-modindex.html']/span", 'Module Index'), (".//li/p/a[@href='py-modindex.html']/span", 'Module Index'),
(".//li/a[@href='search.html']/span", 'Search Page'), (".//li/p/a[@href='search.html']/span", 'Search Page'),
# custom sidebar only for contents # custom sidebar only for contents
(".//h4", 'Contents sidebar'), (".//h4", 'Contents sidebar'),
# custom JavaScript # custom JavaScript
@ -381,37 +388,41 @@ def test_html_warnings(app, warning):
(".//li/a", "double"), (".//li/a", "double"),
], ],
'footnote.html': [ 'footnote.html': [
(".//a[@class='footnote-reference'][@href='#id9'][@id='id1']", r"\[1\]"), (".//a[@class='footnote-reference brackets'][@href='#id9'][@id='id1']", r"1"),
(".//a[@class='footnote-reference'][@href='#id10'][@id='id2']", r"\[2\]"), (".//a[@class='footnote-reference brackets'][@href='#id10'][@id='id2']", r"2"),
(".//a[@class='footnote-reference'][@href='#foo'][@id='id3']", r"\[3\]"), (".//a[@class='footnote-reference brackets'][@href='#foo'][@id='id3']", r"3"),
(".//a[@class='reference internal'][@href='#bar'][@id='id4']", r"\[bar\]"), (".//a[@class='reference internal'][@href='#bar'][@id='id4']", r"\[bar\]"),
(".//a[@class='reference internal'][@href='#baz-qux'][@id='id5']", r"\[baz_qux\]"), (".//a[@class='reference internal'][@href='#baz-qux'][@id='id5']", r"\[baz_qux\]"),
(".//a[@class='footnote-reference'][@href='#id11'][@id='id6']", r"\[4\]"), (".//a[@class='footnote-reference brackets'][@href='#id11'][@id='id6']", r"4"),
(".//a[@class='footnote-reference'][@href='#id12'][@id='id7']", r"\[5\]"), (".//a[@class='footnote-reference brackets'][@href='#id12'][@id='id7']", r"5"),
(".//a[@class='fn-backref'][@href='#id1']", r"\[1\]"), (".//a[@class='fn-backref'][@href='#id1']", r"1"),
(".//a[@class='fn-backref'][@href='#id2']", r"\[2\]"), (".//a[@class='fn-backref'][@href='#id2']", r"2"),
(".//a[@class='fn-backref'][@href='#id3']", r"\[3\]"), (".//a[@class='fn-backref'][@href='#id3']", r"3"),
(".//a[@class='fn-backref'][@href='#id4']", r"\[bar\]"), (".//a[@class='fn-backref'][@href='#id4']", r"bar"),
(".//a[@class='fn-backref'][@href='#id5']", r"\[baz_qux\]"), (".//a[@class='fn-backref'][@href='#id5']", r"baz_qux"),
(".//a[@class='fn-backref'][@href='#id6']", r"\[4\]"), (".//a[@class='fn-backref'][@href='#id6']", r"4"),
(".//a[@class='fn-backref'][@href='#id7']", r"\[5\]"), (".//a[@class='fn-backref'][@href='#id7']", r"5"),
(".//a[@class='fn-backref'][@href='#id8']", r"\[6\]"), (".//a[@class='fn-backref'][@href='#id8']", r"6"),
], ],
'otherext.html': [ 'otherext.html': [
(".//h1", "Generated section"), (".//h1", "Generated section"),
(".//a[@href='_sources/otherext.foo.txt']", ''), (".//a[@href='_sources/otherext.foo.txt']", ''),
] ]
})) }))
@pytest.mark.sphinx('html', tags=['testtag'], confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'html_context.hckey_co': 'hcval_co'}) reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', tags=['testtag'],
confoverrides={'html_context.hckey_co': 'hcval_co'})
@pytest.mark.test_params(shared_result='test_build_html_output') @pytest.mark.test_params(shared_result='test_build_html_output')
def test_html_output(app, cached_etree_parse, fname, expect): def test_html5_output(app, cached_etree_parse, fname, expect):
app.build() app.build()
print(app.outdir / fname)
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
@pytest.mark.sphinx('html', tags=['testtag'], confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'html_context.hckey_co': 'hcval_co'}) reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html')
@pytest.mark.test_params(shared_result='test_build_html_output') @pytest.mark.test_params(shared_result='test_build_html_output')
def test_html_download(app): def test_html_download(app):
app.build() app.build()
@ -435,6 +446,29 @@ def test_html_download(app):
assert matched.group(1) == filename assert matched.group(1) == filename
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='roles-download')
def test_html_download_role(app, status, warning):
app.build()
digest = md5((app.srcdir / 'dummy.dat').encode()).hexdigest()
assert (app.outdir / '_downloads' / digest / 'dummy.dat').exists()
content = (app.outdir / 'index.html').text()
assert (('<li><p><a class="reference download internal" download="" '
'href="_downloads/%s/dummy.dat">'
'<code class="xref download docutils literal notranslate">'
'<span class="pre">dummy.dat</span></code></a></p></li>' % digest)
in content)
assert ('<li><p><code class="xref download docutils literal notranslate">'
'<span class="pre">not_found.dat</span></code></p></li>' in content)
assert ('<li><p><a class="reference download external" download="" '
'href="http://www.sphinx-doc.org/en/master/_static/sphinxheader.png">'
'<code class="xref download docutils literal notranslate">'
'<span class="pre">Sphinx</span> <span class="pre">logo</span>'
'</code></a></p></li>' in content)
@pytest.mark.sphinx('html', testroot='build-html-translator') @pytest.mark.sphinx('html', testroot='build-html-translator')
def test_html_translator(app): def test_html_translator(app):
app.build() app.build()
@ -473,6 +507,8 @@ def test_html_translator(app):
(".//h1", '2.1.1. Baz A', True), (".//h1", '2.1.1. Baz A', True),
], ],
})) }))
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='tocdepth') @pytest.mark.sphinx('html', testroot='tocdepth')
@pytest.mark.test_params(shared_result='test_build_html_tocdepth') @pytest.mark.test_params(shared_result='test_build_html_tocdepth')
def test_tocdepth(app, cached_etree_parse, fname, expect): def test_tocdepth(app, cached_etree_parse, fname, expect):
@ -508,6 +544,8 @@ def test_tocdepth(app, cached_etree_parse, fname, expect):
(".//h4", '2.1.1. Baz A', True), (".//h4", '2.1.1. Baz A', True),
], ],
})) }))
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('singlehtml', testroot='tocdepth') @pytest.mark.sphinx('singlehtml', testroot='tocdepth')
@pytest.mark.test_params(shared_result='test_build_html_tocdepth') @pytest.mark.test_params(shared_result='test_build_html_tocdepth')
def test_tocdepth_singlehtml(app, cached_etree_parse, fname, expect): def test_tocdepth_singlehtml(app, cached_etree_parse, fname, expect):
@ -532,16 +570,16 @@ def test_numfig_disabled_warn(app, warning):
(".//table/caption/span[@class='caption-number']", None, True), (".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", None, True), "span[@class='caption-number']", None, True),
(".//li/code/span", '^fig1$', True), (".//li/p/code/span", '^fig1$', True),
(".//li/code/span", '^Figure%s$', True), (".//li/p/code/span", '^Figure%s$', True),
(".//li/code/span", '^table-1$', True), (".//li/p/code/span", '^table-1$', True),
(".//li/code/span", '^Table:%s$', True), (".//li/p/code/span", '^Table:%s$', True),
(".//li/code/span", '^CODE_1$', True), (".//li/p/code/span", '^CODE_1$', True),
(".//li/code/span", '^Code-%s$', True), (".//li/p/code/span", '^Code-%s$', True),
(".//li/a/span", '^Section 1$', True), (".//li/p/a/span", '^Section 1$', True),
(".//li/a/span", '^Section 2.1$', True), (".//li/p/a/span", '^Section 2.1$', True),
(".//li/code/span", '^Fig.{number}$', True), (".//li/p/code/span", '^Fig.{number}$', True),
(".//li/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
@ -565,6 +603,8 @@ def test_numfig_disabled_warn(app, warning):
"span[@class='caption-number']", None, True), "span[@class='caption-number']", None, True),
], ],
})) }))
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='numfig') @pytest.mark.sphinx('html', testroot='numfig')
@pytest.mark.test_params(shared_result='test_build_html_numfig') @pytest.mark.test_params(shared_result='test_build_html_numfig')
def test_numfig_disabled(app, cached_etree_parse, fname, expect): def test_numfig_disabled(app, cached_etree_parse, fname, expect):
@ -605,16 +645,16 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 9 $', True), "span[@class='caption-number']", '^Listing 9 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 10 $', True), "span[@class='caption-number']", '^Listing 10 $', True),
(".//li/a/span", '^Fig. 9$', True), (".//li/p/a/span", '^Fig. 9$', True),
(".//li/a/span", '^Figure6$', True), (".//li/p/a/span", '^Figure6$', True),
(".//li/a/span", '^Table 9$', True), (".//li/p/a/span", '^Table 9$', True),
(".//li/a/span", '^Table:6$', True), (".//li/p/a/span", '^Table:6$', True),
(".//li/a/span", '^Listing 9$', True), (".//li/p/a/span", '^Listing 9$', True),
(".//li/a/span", '^Code-6$', True), (".//li/p/a/span", '^Code-6$', True),
(".//li/code/span", '^foo$', True), (".//li/p/code/span", '^foo$', True),
(".//li/code/span", '^bar_a$', True), (".//li/p/code/span", '^bar_a$', True),
(".//li/a/span", '^Fig.9 should be Fig.1$', True), (".//li/p/a/span", '^Fig.9 should be Fig.1$', True),
(".//li/code/span", '^Sect.{number}$', True), (".//li/p/code/span", '^Sect.{number}$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
@ -671,6 +711,8 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 6 $', True), "span[@class='caption-number']", '^Listing 6 $', True),
], ],
})) }))
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx( @pytest.mark.sphinx(
'html', testroot='numfig', 'html', testroot='numfig',
srcdir='test_numfig_without_numbered_toctree', srcdir='test_numfig_without_numbered_toctree',
@ -711,16 +753,16 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 1 $', True), "span[@class='caption-number']", '^Listing 1 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 2 $', True), "span[@class='caption-number']", '^Listing 2 $', True),
(".//li/a/span", '^Fig. 1$', True), (".//li/p/a/span", '^Fig. 1$', True),
(".//li/a/span", '^Figure2.2$', True), (".//li/p/a/span", '^Figure2.2$', True),
(".//li/a/span", '^Table 1$', True), (".//li/p/a/span", '^Table 1$', True),
(".//li/a/span", '^Table:2.2$', True), (".//li/p/a/span", '^Table:2.2$', True),
(".//li/a/span", '^Listing 1$', True), (".//li/p/a/span", '^Listing 1$', True),
(".//li/a/span", '^Code-2.2$', True), (".//li/p/a/span", '^Code-2.2$', True),
(".//li/a/span", '^Section.1$', True), (".//li/p/a/span", '^Section.1$', True),
(".//li/a/span", '^Section.2.1$', True), (".//li/p/a/span", '^Section.2.1$', True),
(".//li/a/span", '^Fig.1 should be Fig.1$', True), (".//li/p/a/span", '^Fig.1 should be Fig.1$', True),
(".//li/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
@ -777,6 +819,8 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 2.2 $', True), "span[@class='caption-number']", '^Listing 2.2 $', True),
], ],
})) }))
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) @pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True})
@pytest.mark.test_params(shared_result='test_build_html_numfig_on') @pytest.mark.test_params(shared_result='test_build_html_numfig_on')
def test_numfig_with_numbered_toctree(app, cached_etree_parse, fname, expect): def test_numfig_with_numbered_toctree(app, cached_etree_parse, fname, expect):
@ -814,16 +858,16 @@ def test_numfig_with_prefix_warn(app, warning):
"span[@class='caption-number']", '^Code-1 $', True), "span[@class='caption-number']", '^Code-1 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Code-2 $', True), "span[@class='caption-number']", '^Code-2 $', True),
(".//li/a/span", '^Figure:1$', True), (".//li/p/a/span", '^Figure:1$', True),
(".//li/a/span", '^Figure2.2$', True), (".//li/p/a/span", '^Figure2.2$', True),
(".//li/a/span", '^Tab_1$', True), (".//li/p/a/span", '^Tab_1$', True),
(".//li/a/span", '^Table:2.2$', True), (".//li/p/a/span", '^Table:2.2$', True),
(".//li/a/span", '^Code-1$', True), (".//li/p/a/span", '^Code-1$', True),
(".//li/a/span", '^Code-2.2$', True), (".//li/p/a/span", '^Code-2.2$', True),
(".//li/a/span", '^SECTION-1$', True), (".//li/p/a/span", '^SECTION-1$', True),
(".//li/a/span", '^SECTION-2.1$', True), (".//li/p/a/span", '^SECTION-2.1$', True),
(".//li/a/span", '^Fig.1 should be Fig.1$', True), (".//li/p/a/span", '^Fig.1 should be Fig.1$', True),
(".//li/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
@ -880,20 +924,22 @@ def test_numfig_with_prefix_warn(app, warning):
"span[@class='caption-number']", '^Code-2.2 $', True), "span[@class='caption-number']", '^Code-2.2 $', True),
], ],
})) }))
@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'numfig': True, reason='docutils-0.13 or above is required')
'numfig_format': {'figure': 'Figure:%s', @pytest.mark.sphinx('html', testroot='numfig',
'table': 'Tab_%s', confoverrides={'numfig': True,
'code-block': 'Code-%s', 'numfig_format': {'figure': 'Figure:%s',
'section': 'SECTION-%s'}}) 'table': 'Tab_%s',
'code-block': 'Code-%s',
'section': 'SECTION-%s'}})
@pytest.mark.test_params(shared_result='test_build_html_numfig_format_warn') @pytest.mark.test_params(shared_result='test_build_html_numfig_format_warn')
def test_numfig_with_prefix(app, cached_etree_parse, fname, expect): def test_numfig_with_prefix(app, cached_etree_parse, fname, expect):
app.build() app.build()
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ @pytest.mark.sphinx('html', testroot='numfig',
'numfig': True, 'numfig_secnum_depth': 2}) confoverrides={'numfig': True, 'numfig_secnum_depth': 2})
@pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2') @pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2')
def test_numfig_with_secnum_depth_warn(app, warning): def test_numfig_with_secnum_depth_warn(app, warning):
app.build() app.build()
@ -918,16 +964,16 @@ def test_numfig_with_secnum_depth_warn(app, warning):
"span[@class='caption-number']", '^Listing 1 $', True), "span[@class='caption-number']", '^Listing 1 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 2 $', True), "span[@class='caption-number']", '^Listing 2 $', True),
(".//li/a/span", '^Fig. 1$', True), (".//li/p/a/span", '^Fig. 1$', True),
(".//li/a/span", '^Figure2.1.2$', True), (".//li/p/a/span", '^Figure2.1.2$', True),
(".//li/a/span", '^Table 1$', True), (".//li/p/a/span", '^Table 1$', True),
(".//li/a/span", '^Table:2.1.2$', True), (".//li/p/a/span", '^Table:2.1.2$', True),
(".//li/a/span", '^Listing 1$', True), (".//li/p/a/span", '^Listing 1$', True),
(".//li/a/span", '^Code-2.1.2$', True), (".//li/p/a/span", '^Code-2.1.2$', True),
(".//li/a/span", '^Section.1$', True), (".//li/p/a/span", '^Section.1$', True),
(".//li/a/span", '^Section.2.1$', True), (".//li/p/a/span", '^Section.2.1$', True),
(".//li/a/span", '^Fig.1 should be Fig.1$', True), (".//li/p/a/span", '^Fig.1 should be Fig.1$', True),
(".//li/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
@ -984,8 +1030,11 @@ def test_numfig_with_secnum_depth_warn(app, warning):
"span[@class='caption-number']", '^Listing 2.1.2 $', True), "span[@class='caption-number']", '^Listing 2.1.2 $', True),
], ],
})) }))
@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'numfig': True, 'numfig_secnum_depth': 2}) reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='numfig',
confoverrides={'numfig': True,
'numfig_secnum_depth': 2})
@pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2') @pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2')
def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect): def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
app.build() app.build()
@ -1006,16 +1055,16 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
"span[@class='caption-number']", '^Listing 1 $', True), "span[@class='caption-number']", '^Listing 1 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 2 $', True), "span[@class='caption-number']", '^Listing 2 $', True),
(".//li/a/span", '^Fig. 1$', True), (".//li/p/a/span", '^Fig. 1$', True),
(".//li/a/span", '^Figure2.2$', True), (".//li/p/a/span", '^Figure2.2$', True),
(".//li/a/span", '^Table 1$', True), (".//li/p/a/span", '^Table 1$', True),
(".//li/a/span", '^Table:2.2$', True), (".//li/p/a/span", '^Table:2.2$', True),
(".//li/a/span", '^Listing 1$', True), (".//li/p/a/span", '^Listing 1$', True),
(".//li/a/span", '^Code-2.2$', True), (".//li/p/a/span", '^Code-2.2$', True),
(".//li/a/span", '^Section.1$', True), (".//li/p/a/span", '^Section.1$', True),
(".//li/a/span", '^Section.2.1$', True), (".//li/p/a/span", '^Section.2.1$', True),
(".//li/a/span", '^Fig.1 should be Fig.1$', True), (".//li/p/a/span", '^Fig.1 should be Fig.1$', True),
(".//li/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1 $', True), "span[@class='caption-number']", '^Fig. 1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
@ -1066,8 +1115,9 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
"span[@class='caption-number']", '^Listing 2.2 $', True), "span[@class='caption-number']", '^Listing 2.2 $', True),
], ],
})) }))
@pytest.mark.sphinx('singlehtml', testroot='numfig', confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'numfig': True}) reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('singlehtml', testroot='numfig', confoverrides={'numfig': True})
@pytest.mark.test_params(shared_result='test_build_html_numfig_on') @pytest.mark.test_params(shared_result='test_build_html_numfig_on')
def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect): def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect):
app.build() app.build()
@ -1084,17 +1134,17 @@ def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect):
"/span[@class='caption-number']", "Fig. 3", True), "/span[@class='caption-number']", "Fig. 3", True),
(".//div//span[@class='caption-number']", "No.1 ", True), (".//div//span[@class='caption-number']", "No.1 ", True),
(".//div//span[@class='caption-number']", "No.2 ", True), (".//div//span[@class='caption-number']", "No.2 ", True),
(".//li/a/span", 'Fig. 1', True), (".//li/p/a/span", 'Fig. 1', True),
(".//li/a/span", 'Fig. 2', True), (".//li/p/a/span", 'Fig. 2', True),
(".//li/a/span", 'Fig. 3', True), (".//li/p/a/span", 'Fig. 3', True),
(".//li/a/span", 'No.1', True), (".//li/p/a/span", 'No.1', True),
(".//li/a/span", 'No.2', True), (".//li/p/a/span", 'No.2', True),
], ],
})) }))
@pytest.mark.sphinx( @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'html', testroot='add_enumerable_node', reason='docutils-0.13 or above is required')
srcdir='test_enumerable_node', @pytest.mark.sphinx('html', testroot='add_enumerable_node',
) srcdir='test_enumerable_node')
def test_enumerable_node(app, cached_etree_parse, fname, expect): def test_enumerable_node(app, cached_etree_parse, fname, expect):
app.build() app.build()
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)

View File

@ -1,372 +0,0 @@
"""
test_build_html5
~~~~~~~~~~~~~~~~
Test the HTML5 writer and check output against XPath.
This code is digest to reduce test running time.
Complete test code is here:
https://github.com/sphinx-doc/sphinx/pull/2805/files
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
from hashlib import md5
import pytest
from html5lib import HTMLParser
from test_build_html import flat_dict, tail_check, check_xpath
from sphinx.util import docutils
from sphinx.util.docutils import is_html5_writer_available
etree_cache = {}
@pytest.mark.skipif(not is_html5_writer_available(), reason='HTML5 writer is not available')
@pytest.fixture(scope='module')
def cached_etree_parse():
def parse(fname):
if fname in etree_cache:
return etree_cache[fname]
with (fname).open('rb') as fp:
etree = HTMLParser(namespaceHTMLElements=False).parse(fp)
etree_cache.clear()
etree_cache[fname] = etree
return etree
yield parse
etree_cache.clear()
@pytest.mark.skipif(not is_html5_writer_available(), reason='HTML5 writer is not available')
@pytest.mark.parametrize("fname,expect", flat_dict({
'images.html': [
(".//img[@src='_images/img.png']", ''),
(".//img[@src='_images/img1.png']", ''),
(".//img[@src='_images/simg.png']", ''),
(".//img[@src='_images/svgimg.svg']", ''),
(".//a[@href='_sources/images.txt']", ''),
],
'subdir/images.html': [
(".//img[@src='../_images/img1.png']", ''),
(".//img[@src='../_images/rimg.png']", ''),
],
'subdir/includes.html': [
(".//a[@class='reference download internal']", ''),
(".//img[@src='../_images/img.png']", ''),
(".//p", 'This is an include file.'),
(".//pre/span", 'line 1'),
(".//pre/span", 'line 2'),
],
'includes.html': [
(".//pre", 'Max Strauß'),
(".//a[@class='reference download internal']", ''),
(".//pre/span", '"quotes"'),
(".//pre/span", "'included'"),
(".//pre/span[@class='s2']", 'üöä'),
(".//div[@class='inc-pyobj1 highlight-text notranslate']//pre",
r'^class Foo:\n pass\n\s*$'),
(".//div[@class='inc-pyobj2 highlight-text notranslate']//pre",
r'^ def baz\(\):\n pass\n\s*$'),
(".//div[@class='inc-lines highlight-text notranslate']//pre",
r'^class Foo:\n pass\nclass Bar:\n$'),
(".//div[@class='inc-startend highlight-text notranslate']//pre",
'^foo = "Including Unicode characters: üöä"\\n$'),
(".//div[@class='inc-preappend highlight-text notranslate']//pre",
r'(?m)^START CODE$'),
(".//div[@class='inc-pyobj-dedent highlight-python notranslate']//span",
r'def'),
(".//div[@class='inc-tab3 highlight-text notranslate']//pre",
r'-| |-'),
(".//div[@class='inc-tab8 highlight-python notranslate']//pre/span",
r'-| |-'),
],
'autodoc.html': [
(".//dt[@id='autodoc_target.Class']", ''),
(".//dt[@id='autodoc_target.function']/em", r'\*\*kwds'),
(".//dd/p", r'Return spam\.'),
],
'extapi.html': [
(".//strong", 'from class: Bar'),
],
'markup.html': [
(".//title", 'set by title directive'),
(".//p/em", 'Section author: Georg Brandl'),
(".//p/em", 'Module author: Georg Brandl'),
# created by the meta directive
(".//meta[@name='author'][@content='Me']", ''),
(".//meta[@name='keywords'][@content='docs, sphinx']", ''),
# a label created by ``.. _label:``
(".//div[@id='label']", ''),
# code with standard code blocks
(".//pre", '^some code$'),
# an option list
(".//span[@class='option']", '--help'),
# admonitions
(".//p[@class='admonition-title']", 'My Admonition'),
(".//div[@class='admonition note']/p", 'Note text.'),
(".//div[@class='admonition warning']/p", 'Warning text.'),
# inline markup
(".//li/p/strong", r'^command\\n$'),
(".//li/p/strong", r'^program\\n$'),
(".//li/p/em", r'^dfn\\n$'),
(".//li/p/kbd", r'^kbd\\n$'),
(".//li/p/span", 'File \N{TRIANGULAR BULLET} Close'),
(".//li/p/code/span[@class='pre']", '^a/$'),
(".//li/p/code/em/span[@class='pre']", '^varpart$'),
(".//li/p/code/em/span[@class='pre']", '^i$'),
(".//a[@href='https://www.python.org/dev/peps/pep-0008']"
"[@class='pep reference external']/strong", 'PEP 8'),
(".//a[@href='https://www.python.org/dev/peps/pep-0008']"
"[@class='pep reference external']/strong",
'Python Enhancement Proposal #8'),
(".//a[@href='https://tools.ietf.org/html/rfc1.html']"
"[@class='rfc reference external']/strong", 'RFC 1'),
(".//a[@href='https://tools.ietf.org/html/rfc1.html']"
"[@class='rfc reference external']/strong", 'Request for Comments #1'),
(".//a[@href='objects.html#envvar-HOME']"
"[@class='reference internal']/code/span[@class='pre']", 'HOME'),
(".//a[@href='#with']"
"[@class='reference internal']/code/span[@class='pre']", '^with$'),
(".//a[@href='#grammar-token-try-stmt']"
"[@class='reference internal']/code/span", '^statement$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^here$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^there$'),
(".//a[@href='subdir/includes.html']"
"[@class='reference internal']/span", 'Including in subdir'),
(".//a[@href='objects.html#cmdoption-python-c']"
"[@class='reference internal']/code/span[@class='pre']", '-c'),
# abbreviations
(".//abbr[@title='abbreviation']", '^abbr$'),
# version stuff
(".//div[@class='versionadded']/p/span", 'New in version 0.6: '),
(".//div[@class='versionadded']/p/span",
tail_check('First paragraph of versionadded')),
(".//div[@class='versionchanged']/p/span",
tail_check('First paragraph of versionchanged')),
(".//div[@class='versionchanged']/p",
'Second paragraph of versionchanged'),
# footnote reference
(".//a[@class='footnote-reference brackets']", r'1'),
# created by reference lookup
(".//a[@href='index.html#ref1']", ''),
# ``seealso`` directive
(".//div/p[@class='admonition-title']", 'See also'),
# a ``hlist`` directive
(".//table[@class='hlist']/tbody/tr/td/ul/li/p", '^This$'),
# a ``centered`` directive
(".//p[@class='centered']/strong", 'LICENSE'),
# a glossary
(".//dl/dt[@id='term-boson']", 'boson'),
# a production list
(".//pre/strong", 'try_stmt'),
(".//pre/a[@href='#grammar-token-try1-stmt']/code/span", 'try1_stmt'),
# tests for ``only`` directive
(".//p", 'A global substitution.'),
(".//p", 'In HTML.'),
(".//p", 'In both.'),
(".//p", 'Always present'),
# tests for ``any`` role
(".//a[@href='#with']/span", 'headings'),
(".//a[@href='objects.html#func_without_body']/code/span", 'objects'),
# tests for numeric labels
(".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'),
],
'objects.html': [
(".//dt[@id='mod.Cls.meth1']", ''),
(".//dt[@id='errmod.Error']", ''),
(".//dt/code", r'long\(parameter,\s* list\)'),
(".//dt/code", 'another one'),
(".//a[@href='#mod.Cls'][@class='reference internal']", ''),
(".//dl[@class='userdesc']", ''),
(".//dt[@id='userdesc-myobj']", ''),
(".//a[@href='#userdesc-myobj'][@class='reference internal']", ''),
# docfields
(".//a[@class='reference internal'][@href='#TimeInt']/em", 'TimeInt'),
(".//a[@class='reference internal'][@href='#Time']", 'Time'),
(".//a[@class='reference internal'][@href='#errmod.Error']/strong", 'Error'),
# C references
(".//span[@class='pre']", 'CFunction()'),
(".//a[@href='#c.Sphinx_DoSomething']", ''),
(".//a[@href='#c.SphinxStruct.member']", ''),
(".//a[@href='#c.SPHINX_USE_PYTHON']", ''),
(".//a[@href='#c.SphinxType']", ''),
(".//a[@href='#c.sphinx_global']", ''),
# test global TOC created by toctree()
(".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='#']",
'Testing object descriptions'),
(".//li[@class='toctree-l1']/a[@href='markup.html']",
'Testing various markup'),
# test unknown field names
(".//dt[@class='field-odd']", 'Field_name'),
(".//dt[@class='field-even']", 'Field_name all lower'),
(".//dt[@class='field-odd']", 'FIELD_NAME'),
(".//dt[@class='field-even']", 'FIELD_NAME ALL CAPS'),
(".//dt[@class='field-odd']", 'Field_Name'),
(".//dt[@class='field-even']", 'Field_Name All Word Caps'),
(".//dt[@class='field-odd']", 'Field_name'),
(".//dt[@class='field-even']", 'Field_name First word cap'),
(".//dt[@class='field-odd']", 'FIELd_name'),
(".//dt[@class='field-even']", 'FIELd_name PARTial caps'),
# custom sidebar
(".//h4", 'Custom sidebar'),
# docfields
(".//dd[@class='field-odd']/p/strong", '^moo$'),
(".//dd[@class='field-odd']/p/strong", tail_check(r'\(Moo\) .* Moo')),
(".//dd[@class='field-odd']/ul/li/p/strong", '^hour$'),
(".//dd[@class='field-odd']/ul/li/p/em", '^DuplicateType$'),
(".//dd[@class='field-odd']/ul/li/p/em", tail_check(r'.* Some parameter')),
# others
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span",
'perl'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span",
'\\+p'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-objc']/code/span",
'--ObjC\\+\\+'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-plugin-option']/code/span",
'--plugin.option'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-create-auth-token']"
"/code/span",
'create-auth-token'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-arg']/code/span",
'arg'),
(".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span",
'hg'),
(".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span",
'commit'),
(".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span",
'git'),
(".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span",
'commit'),
(".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span",
'-p'),
],
'index.html': [
(".//meta[@name='hc'][@content='hcval']", ''),
(".//meta[@name='hc_co'][@content='hcval_co']", ''),
(".//dt[@class='label']/span[@class='brackets']", r'Ref1'),
(".//dt[@class='label']", ''),
(".//li[@class='toctree-l1']/a", 'Testing various markup'),
(".//li[@class='toctree-l2']/a", 'Inline markup'),
(".//title", 'Sphinx <Tests>'),
(".//div[@class='footer']", 'Georg Brandl & Team'),
(".//a[@href='http://python.org/']"
"[@class='reference external']", ''),
(".//li/p/a[@href='genindex.html']/span", 'Index'),
(".//li/p/a[@href='py-modindex.html']/span", 'Module Index'),
(".//li/p/a[@href='search.html']/span", 'Search Page'),
# custom sidebar only for contents
(".//h4", 'Contents sidebar'),
# custom JavaScript
(".//script[@src='file://moo.js']", ''),
# URL in contents
(".//a[@class='reference external'][@href='http://sphinx-doc.org/']",
'http://sphinx-doc.org/'),
(".//a[@class='reference external'][@href='http://sphinx-doc.org/latest/']",
'Latest reference'),
# Indirect hyperlink targets across files
(".//a[@href='markup.html#some-label'][@class='reference internal']/span",
'^indirect hyperref$'),
],
'bom.html': [
(".//title", " File with UTF-8 BOM"),
],
'extensions.html': [
(".//a[@href='http://python.org/dev/']", "http://python.org/dev/"),
(".//a[@href='http://bugs.python.org/issue1000']", "issue 1000"),
(".//a[@href='http://bugs.python.org/issue1042']", "explicit caption"),
],
'genindex.html': [
# index entries
(".//a/strong", "Main"),
(".//a/strong", "[1]"),
(".//a/strong", "Other"),
(".//a", "entry"),
(".//li/a", "double"),
],
'footnote.html': [
(".//a[@class='footnote-reference brackets'][@href='#id9'][@id='id1']", r"1"),
(".//a[@class='footnote-reference brackets'][@href='#id10'][@id='id2']", r"2"),
(".//a[@class='footnote-reference brackets'][@href='#foo'][@id='id3']", r"3"),
(".//a[@class='reference internal'][@href='#bar'][@id='id4']", r"\[bar\]"),
(".//a[@class='reference internal'][@href='#baz-qux'][@id='id5']", r"\[baz_qux\]"),
(".//a[@class='footnote-reference brackets'][@href='#id11'][@id='id6']", r"4"),
(".//a[@class='footnote-reference brackets'][@href='#id12'][@id='id7']", r"5"),
(".//a[@class='fn-backref'][@href='#id1']", r"1"),
(".//a[@class='fn-backref'][@href='#id2']", r"2"),
(".//a[@class='fn-backref'][@href='#id3']", r"3"),
(".//a[@class='fn-backref'][@href='#id4']", r"bar"),
(".//a[@class='fn-backref'][@href='#id5']", r"baz_qux"),
(".//a[@class='fn-backref'][@href='#id6']", r"4"),
(".//a[@class='fn-backref'][@href='#id7']", r"5"),
(".//a[@class='fn-backref'][@href='#id8']", r"6"),
],
'otherext.html': [
(".//h1", "Generated section"),
(".//a[@href='_sources/otherext.foo.txt']", ''),
]
}))
@pytest.mark.sphinx('html', tags=['testtag'], confoverrides={
'html_context.hckey_co': 'hcval_co',
'html_experimental_html5_writer': True})
@pytest.mark.test_params(shared_result='test_build_html5_output')
def test_html5_output(app, cached_etree_parse, fname, expect):
app.build()
print(app.outdir / fname)
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', tags=['testtag'], confoverrides={
'html_context.hckey_co': 'hcval_co',
'html_experimental_html5_writer': True})
@pytest.mark.test_params(shared_result='test_build_html_output')
def test_html_download(app):
app.build()
# subdir/includes.html
result = (app.outdir / 'subdir' / 'includes.html').text()
pattern = ('<a class="reference download internal" download="" '
'href="../(_downloads/.*/img.png)">')
matched = re.search(pattern, result)
assert matched
assert (app.outdir / matched.group(1)).exists()
filename = matched.group(1)
# includes.html
result = (app.outdir / 'includes.html').text()
pattern = ('<a class="reference download internal" download="" '
'href="(_downloads/.*/img.png)">')
matched = re.search(pattern, result)
assert matched
assert (app.outdir / matched.group(1)).exists()
assert matched.group(1) == filename
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='roles-download',
confoverrides={'html_experimental_html5_writer': True})
def test_html_download_role(app, status, warning):
app.build()
digest = md5((app.srcdir / 'dummy.dat').encode()).hexdigest()
assert (app.outdir / '_downloads' / digest / 'dummy.dat').exists()
content = (app.outdir / 'index.html').text()
assert (('<li><p><a class="reference download internal" download="" '
'href="_downloads/%s/dummy.dat">'
'<code class="xref download docutils literal notranslate">'
'<span class="pre">dummy.dat</span></code></a></p></li>' % digest)
in content)
assert ('<li><p><code class="xref download docutils literal notranslate">'
'<span class="pre">not_found.dat</span></code></p></li>' in content)
assert ('<li><p><a class="reference download external" download="" '
'href="http://www.sphinx-doc.org/en/master/_static/sphinxheader.png">'
'<code class="xref download docutils literal notranslate">'
'<span class="pre">Sphinx</span> <span class="pre">logo</span>'
'</code></a></p></li>' in content)

View File

@ -17,6 +17,7 @@ import sphinx.domains.cpp as cppDomain
from sphinx import addnodes from sphinx import addnodes
from sphinx.domains.cpp import DefinitionParser, DefinitionError, NoOldIdError from sphinx.domains.cpp import DefinitionParser, DefinitionError, NoOldIdError
from sphinx.domains.cpp import Symbol, _max_id, _id_prefix from sphinx.domains.cpp import Symbol, _max_id, _id_prefix
from sphinx.util import docutils
def parse(name, string): def parse(name, string):
@ -734,12 +735,14 @@ def test_build_domain_cpp_misuse_of_roles(app, status, warning):
# TODO: properly check for the warnings we expect # TODO: properly check for the warnings we expect
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': True}) @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': True})
def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, warning): def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, warning):
app.builder.build_all() app.builder.build_all()
def check(spec, text, file): def check(spec, text, file):
pattern = '<li>%s<a .*?><code .*?><span .*?>%s</span></code></a></li>' % spec pattern = '<li><p>%s<a .*?><code .*?><span .*?>%s</span></code></a></p></li>' % spec
res = re.search(pattern, text) res = re.search(pattern, text)
if not res: if not res:
print("Pattern\n\t%s\nnot found in %s" % (pattern, file)) print("Pattern\n\t%s\nnot found in %s" % (pattern, file))
@ -775,13 +778,14 @@ def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, war
check(s, t, f) check(s, t, f)
@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'add_function_parentheses': False}) reason='docutils-0.13 or above is required')
@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': False})
def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, warning): def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, warning):
app.builder.build_all() app.builder.build_all()
def check(spec, text, file): def check(spec, text, file):
pattern = '<li>%s<a .*?><code .*?><span .*?>%s</span></code></a></li>' % spec pattern = '<li><p>%s<a .*?><code .*?><span .*?>%s</span></code></a></p></li>' % spec
res = re.search(pattern, text) res = re.search(pattern, text)
if not res: if not res:
print("Pattern\n\t%s\nnot found in %s" % (pattern, file)) print("Pattern\n\t%s\nnot found in %s" % (pattern, file))

View File

@ -12,37 +12,43 @@ import re
import pytest import pytest
from sphinx.util import docutils
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='ext-autosectionlabel') @pytest.mark.sphinx('html', testroot='ext-autosectionlabel')
def test_autosectionlabel_html(app, status, warning): def test_autosectionlabel_html(app, status, warning):
app.builder.build_all() app.builder.build_all()
content = (app.outdir / 'index.html').text() content = (app.outdir / 'index.html').text()
html = ('<li><a class="reference internal" href="#introduce-of-sphinx">' html = ('<li><p><a class="reference internal" href="#introduce-of-sphinx">'
'<span class=".*?">Introduce of Sphinx</span></a></li>') '<span class=".*?">Introduce of Sphinx</span></a></p></li>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
html = ('<li><a class="reference internal" href="#installation">' html = ('<li><p><a class="reference internal" href="#installation">'
'<span class="std std-ref">Installation</span></a></li>') '<span class="std std-ref">Installation</span></a></p></li>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
html = ('<li><a class="reference internal" href="#for-windows-users">' html = ('<li><p><a class="reference internal" href="#for-windows-users">'
'<span class="std std-ref">For Windows users</span></a></li>') '<span class="std std-ref">For Windows users</span></a></p></li>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
html = ('<li><a class="reference internal" href="#for-unix-users">' html = ('<li><p><a class="reference internal" href="#for-unix-users">'
'<span class="std std-ref">For UNIX users</span></a></li>') '<span class="std std-ref">For UNIX users</span></a></p></li>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
# for smart_quotes (refs: #4027) # for smart_quotes (refs: #4027)
html = ('<li><a class="reference internal" ' html = ('<li><p><a class="reference internal" '
'href="#this-one-s-got-an-apostrophe">' 'href="#this-one-s-got-an-apostrophe">'
'<span class="std std-ref">This ones got an apostrophe' '<span class="std std-ref">This ones got an apostrophe'
'</span></a></li>') '</span></a></p></li>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
# Re-use test definition from above, just change the test root directory # Re-use test definition from above, just change the test root directory
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='ext-autosectionlabel-prefix-document') @pytest.mark.sphinx('html', testroot='ext-autosectionlabel-prefix-document')
def test_autosectionlabel_prefix_document_html(app, status, warning): def test_autosectionlabel_prefix_document_html(app, status, warning):
return test_autosectionlabel_html(app, status, warning) return test_autosectionlabel_html(app, status, warning)

View File

@ -12,7 +12,11 @@ import re
import pytest import pytest
from sphinx.util import docutils
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='ext-todo', freshenv=True, @pytest.mark.sphinx('html', testroot='ext-todo', freshenv=True,
confoverrides={'todo_include_todos': True, 'todo_emit_warnings': True}) confoverrides={'todo_include_todos': True, 'todo_emit_warnings': True})
def test_todo(app, status, warning): def test_todo(app, status, warning):
@ -26,22 +30,22 @@ def test_todo(app, status, warning):
# check todolist # check todolist
content = (app.outdir / 'index.html').text() content = (app.outdir / 'index.html').text()
html = ('<p class="first admonition-title">Todo</p>\n' html = ('<p class="admonition-title">Todo</p>\n'
'<p class="last">todo in foo</p>') '<p>todo in foo</p>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
html = ('<p class="first admonition-title">Todo</p>\n' html = ('<p class="admonition-title">Todo</p>\n'
'<p class="last">todo in bar</p>') '<p>todo in bar</p>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
# check todo # check todo
content = (app.outdir / 'foo.html').text() content = (app.outdir / 'foo.html').text()
html = ('<p class="first admonition-title">Todo</p>\n' html = ('<p class="admonition-title">Todo</p>\n'
'<p class="last">todo in foo</p>') '<p>todo in foo</p>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
html = ('<p class="first admonition-title">Todo</p>\n' html = ('<p class="admonition-title">Todo</p>\n'
'<p class="last">todo in param field</p>') '<p>todo in param field</p>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
# check emitted warnings # check emitted warnings
@ -68,18 +72,18 @@ def test_todo_not_included(app, status, warning):
# check todolist # check todolist
content = (app.outdir / 'index.html').text() content = (app.outdir / 'index.html').text()
html = ('<p class="first admonition-title">Todo</p>\n' html = ('<p class="admonition-title">Todo</p>\n'
'<p class="last">todo in foo</p>') '<p>todo in foo</p>')
assert not re.search(html, content, re.S) assert not re.search(html, content, re.S)
html = ('<p class="first admonition-title">Todo</p>\n' html = ('<p class="admonition-title">Todo</p>\n'
'<p class="last">todo in bar</p>') '<p>todo in bar</p>')
assert not re.search(html, content, re.S) assert not re.search(html, content, re.S)
# check todo # check todo
content = (app.outdir / 'foo.html').text() content = (app.outdir / 'foo.html').text()
html = ('<p class="first admonition-title">Todo</p>\n' html = ('<p class="admonition-title">Todo</p>\n'
'<p class="last">todo in foo</p>') '<p>todo in foo</p>')
assert not re.search(html, content, re.S) assert not re.search(html, content, re.S)
# check emitted warnings # check emitted warnings