diff options
Diffstat (limited to 'tests/test_markup')
-rw-r--r-- | tests/test_markup/__init__.py | 0 | ||||
-rw-r--r-- | tests/test_markup/test_markup.py | 619 | ||||
-rw-r--r-- | tests/test_markup/test_metadata.py | 43 | ||||
-rw-r--r-- | tests/test_markup/test_parser.py | 57 | ||||
-rw-r--r-- | tests/test_markup/test_smartquotes.py | 98 |
5 files changed, 817 insertions, 0 deletions
diff --git a/tests/test_markup/__init__.py b/tests/test_markup/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/test_markup/__init__.py diff --git a/tests/test_markup/test_markup.py b/tests/test_markup/test_markup.py new file mode 100644 index 0000000..c933481 --- /dev/null +++ b/tests/test_markup/test_markup.py @@ -0,0 +1,619 @@ +"""Test various Sphinx-specific markup extensions.""" + +import re +import warnings +from types import SimpleNamespace + +import pytest +from docutils import frontend, nodes, utils +from docutils.parsers.rst import Parser as RstParser + +from sphinx import addnodes +from sphinx.builders.html.transforms import KeyboardTransform +from sphinx.builders.latex import LaTeXBuilder +from sphinx.environment import default_settings +from sphinx.roles import XRefRole +from sphinx.testing.util import assert_node +from sphinx.transforms import SphinxSmartQuotes +from sphinx.util import texescape +from sphinx.util.docutils import sphinx_domains +from sphinx.writers.html import HTML5Translator, HTMLWriter +from sphinx.writers.latex import LaTeXTranslator, LaTeXWriter + + +@pytest.fixture() +def settings(app): + texescape.init() # otherwise done by the latex builder + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + # DeprecationWarning: The frontend.OptionParser class will be replaced + # by a subclass of argparse.ArgumentParser in Docutils 0.21 or later. + optparser = frontend.OptionParser( + components=(RstParser, HTMLWriter, LaTeXWriter), + defaults=default_settings) + settings = optparser.get_default_values() + settings.smart_quotes = True + settings.env = app.builder.env + settings.env.temp_data['docname'] = 'dummy' + settings.contentsname = 'dummy' + domain_context = sphinx_domains(settings.env) + domain_context.enable() + yield settings + domain_context.disable() + + +@pytest.fixture() +def new_document(settings): + def create(): + document = utils.new_document('test data', settings) + document['file'] = 'dummy' + return document + + return create + + +@pytest.fixture() +def inliner(new_document): + document = new_document() + document.reporter.get_source_and_line = lambda line=1: ('dummy.rst', line) + return SimpleNamespace(document=document, reporter=document.reporter) + + +@pytest.fixture() +def parse(new_document): + def parse_(rst): + document = new_document() + parser = RstParser() + parser.parse(rst, document) + SphinxSmartQuotes(document, startnode=None).apply() + for msg in list(document.findall(nodes.system_message)): + if msg['level'] == 1: + msg.replace_self([]) + return document + return parse_ + + +# since we're not resolving the markup afterwards, these nodes may remain +class ForgivingTranslator: + def visit_pending_xref(self, node): + pass + + def depart_pending_xref(self, node): + pass + + +class ForgivingHTMLTranslator(HTML5Translator, ForgivingTranslator): + pass + + +class ForgivingLaTeXTranslator(LaTeXTranslator, ForgivingTranslator): + pass + + +@pytest.fixture() +def verify_re_html(app, parse): + def verify(rst, html_expected): + document = parse(rst) + KeyboardTransform(document).apply() + html_translator = ForgivingHTMLTranslator(document, app.builder) + document.walkabout(html_translator) + html_translated = ''.join(html_translator.fragment).strip() + assert re.match(html_expected, html_translated), 'from ' + rst + return verify + + +@pytest.fixture() +def verify_re_latex(app, parse): + def verify(rst, latex_expected): + document = parse(rst) + app.builder = LaTeXBuilder(app, app.env) + app.builder.init() + theme = app.builder.themes.get('manual') + latex_translator = ForgivingLaTeXTranslator(document, app.builder, theme) + latex_translator.first_document = -1 # don't write \begin{document} + document.walkabout(latex_translator) + latex_translated = ''.join(latex_translator.body).strip() + assert re.match(latex_expected, latex_translated), 'from ' + repr(rst) + return verify + + +@pytest.fixture() +def verify_re(verify_re_html, verify_re_latex): + def verify_re_(rst, html_expected, latex_expected): + if html_expected: + verify_re_html(rst, html_expected) + if latex_expected: + verify_re_latex(rst, latex_expected) + return verify_re_ + + +@pytest.fixture() +def verify(verify_re_html, verify_re_latex): + def verify_(rst, html_expected, latex_expected): + if html_expected: + verify_re_html(rst, re.escape(html_expected) + '$') + if latex_expected: + verify_re_latex(rst, re.escape(latex_expected) + '$') + return verify_ + + +@pytest.fixture() +def get_verifier(verify, verify_re): + v = { + 'verify': verify, + 'verify_re': verify_re, + } + + def get(name): + return v[name] + return get + + +@pytest.mark.parametrize(('type', 'rst', 'html_expected', 'latex_expected'), [ + ( + # pep role + 'verify', + ':pep:`8`', + ('<p><span class="target" id="index-0"></span><a class="pep reference external" ' + 'href="https://peps.python.org/pep-0008/"><strong>PEP 8</strong></a></p>'), + ('\\sphinxAtStartPar\n' + '\\index{Python Enhancement Proposals@\\spxentry{Python Enhancement Proposals}' + '!PEP 8@\\spxentry{PEP 8}}\\sphinxhref{https://peps.python.org/pep-0008/}' + '{\\sphinxstylestrong{PEP 8}}'), + ), + ( + # pep role with anchor + 'verify', + ':pep:`8#id1`', + ('<p><span class="target" id="index-0"></span><a class="pep reference external" ' + 'href="https://peps.python.org/pep-0008/#id1">' + '<strong>PEP 8#id1</strong></a></p>'), + ('\\sphinxAtStartPar\n' + '\\index{Python Enhancement Proposals@\\spxentry{Python Enhancement Proposals}' + '!PEP 8\\#id1@\\spxentry{PEP 8\\#id1}}\\sphinxhref' + '{https://peps.python.org/pep-0008/\\#id1}' + '{\\sphinxstylestrong{PEP 8\\#id1}}'), + ), + ( + # rfc role + 'verify', + ':rfc:`2324`', + ('<p><span class="target" id="index-0"></span><a class="rfc reference external" ' + 'href="https://datatracker.ietf.org/doc/html/rfc2324.html"><strong>RFC 2324</strong></a></p>'), + ('\\sphinxAtStartPar\n' + '\\index{RFC@\\spxentry{RFC}!RFC 2324@\\spxentry{RFC 2324}}' + '\\sphinxhref{https://datatracker.ietf.org/doc/html/rfc2324.html}' + '{\\sphinxstylestrong{RFC 2324}}'), + ), + ( + # rfc role with anchor + 'verify', + ':rfc:`2324#id1`', + ('<p><span class="target" id="index-0"></span><a class="rfc reference external" ' + 'href="https://datatracker.ietf.org/doc/html/rfc2324.html#id1">' + '<strong>RFC 2324#id1</strong></a></p>'), + ('\\sphinxAtStartPar\n' + '\\index{RFC@\\spxentry{RFC}!RFC 2324\\#id1@\\spxentry{RFC 2324\\#id1}}' + '\\sphinxhref{https://datatracker.ietf.org/doc/html/rfc2324.html\\#id1}' + '{\\sphinxstylestrong{RFC 2324\\#id1}}'), + ), + ( + # correct interpretation of code with whitespace + 'verify_re', + '``code sample``', + ('<p><code class="(samp )?docutils literal notranslate"><span class="pre">' + 'code</span>   <span class="pre">sample</span></code></p>'), + r'\\sphinxAtStartPar\n\\sphinxcode{\\sphinxupquote{code sample}}', + ), + ( + # interpolation of arrows in menuselection + 'verify', + ':menuselection:`a --> b`', + ('<p><span class="menuselection">a \N{TRIANGULAR BULLET} b</span></p>'), + '\\sphinxAtStartPar\n\\sphinxmenuselection{a \\(\\rightarrow\\) b}', + ), + ( + # interpolation of ampersands in menuselection + 'verify', + ':menuselection:`&Foo -&&- &Bar`', + ('<p><span class="menuselection"><span class="accelerator">F</span>oo ' + '-&- <span class="accelerator">B</span>ar</span></p>'), + ('\\sphinxAtStartPar\n' + r'\sphinxmenuselection{\sphinxaccelerator{F}oo \sphinxhyphen{}' + r'\&\sphinxhyphen{} \sphinxaccelerator{B}ar}'), + ), + ( + # interpolation of ampersands in guilabel + 'verify', + ':guilabel:`&Foo -&&- &Bar`', + ('<p><span class="guilabel"><span class="accelerator">F</span>oo ' + '-&- <span class="accelerator">B</span>ar</span></p>'), + ('\\sphinxAtStartPar\n' + r'\sphinxguilabel{\sphinxaccelerator{F}oo \sphinxhyphen{}\&\sphinxhyphen{} \sphinxaccelerator{B}ar}'), + ), + ( + # no ampersands in guilabel + 'verify', + ':guilabel:`Foo`', + '<p><span class="guilabel">Foo</span></p>', + '\\sphinxAtStartPar\n\\sphinxguilabel{Foo}', + ), + ( + # kbd role + 'verify', + ':kbd:`space`', + '<p><kbd class="kbd docutils literal notranslate">space</kbd></p>', + '\\sphinxAtStartPar\n\\sphinxkeyboard{\\sphinxupquote{space}}', + ), + ( + # kbd role + 'verify', + ':kbd:`Control+X`', + ('<p><kbd class="kbd compound docutils literal notranslate">' + '<kbd class="kbd docutils literal notranslate">Control</kbd>' + '+' + '<kbd class="kbd docutils literal notranslate">X</kbd>' + '</kbd></p>'), + '\\sphinxAtStartPar\n\\sphinxkeyboard{\\sphinxupquote{Control+X}}', + ), + ( + # kbd role + 'verify', + ':kbd:`Alt+^`', + ('<p><kbd class="kbd compound docutils literal notranslate">' + '<kbd class="kbd docutils literal notranslate">Alt</kbd>' + '+' + '<kbd class="kbd docutils literal notranslate">^</kbd>' + '</kbd></p>'), + ('\\sphinxAtStartPar\n' + '\\sphinxkeyboard{\\sphinxupquote{Alt+\\textasciicircum{}}}'), + ), + ( + # kbd role + 'verify', + ':kbd:`M-x M-s`', + ('<p><kbd class="kbd compound docutils literal notranslate">' + '<kbd class="kbd docutils literal notranslate">M</kbd>' + '-' + '<kbd class="kbd docutils literal notranslate">x</kbd>' + ' ' + '<kbd class="kbd docutils literal notranslate">M</kbd>' + '-' + '<kbd class="kbd docutils literal notranslate">s</kbd>' + '</kbd></p>'), + ('\\sphinxAtStartPar\n' + '\\sphinxkeyboard{\\sphinxupquote{M\\sphinxhyphen{}x M\\sphinxhyphen{}s}}'), + ), + ( + # kbd role + 'verify', + ':kbd:`-`', + '<p><kbd class="kbd docutils literal notranslate">-</kbd></p>', + ('\\sphinxAtStartPar\n' + '\\sphinxkeyboard{\\sphinxupquote{\\sphinxhyphen{}}}'), + ), + ( + # kbd role + 'verify', + ':kbd:`Caps Lock`', + '<p><kbd class="kbd docutils literal notranslate">Caps Lock</kbd></p>', + ('\\sphinxAtStartPar\n' + '\\sphinxkeyboard{\\sphinxupquote{Caps Lock}}'), + ), + ( + # kbd role + 'verify', + ':kbd:`sys rq`', + '<p><kbd class="kbd docutils literal notranslate">sys rq</kbd></p>', + ('\\sphinxAtStartPar\n' + '\\sphinxkeyboard{\\sphinxupquote{sys rq}}'), + ), + ( + # non-interpolation of dashes in option role + 'verify_re', + ':option:`--with-option`', + ('<p><code( class="xref std std-option docutils literal notranslate")?>' + '<span class="pre">--with-option</span></code></p>$'), + (r'\\sphinxAtStartPar\n' + r'\\sphinxcode{\\sphinxupquote{\\sphinxhyphen{}\\sphinxhyphen{}with\\sphinxhyphen{}option}}$'), + ), + ( + # verify smarty-pants quotes + 'verify', + '"John"', + '<p>“John”</p>', + "\\sphinxAtStartPar\n“John”", + ), + ( + # ... but not in literal text + 'verify', + '``"John"``', + ('<p><code class="docutils literal notranslate"><span class="pre">' + '"John"</span></code></p>'), + '\\sphinxAtStartPar\n\\sphinxcode{\\sphinxupquote{"John"}}', + ), + ( + # verify classes for inline roles + 'verify', + ':manpage:`mp(1)`', + '<p><em class="manpage">mp(1)</em></p>', + '\\sphinxAtStartPar\n\\sphinxstyleliteralemphasis{\\sphinxupquote{mp(1)}}', + ), + ( + # correct escaping in normal mode + 'verify', + 'Γ\\\\∞$', + None, + '\\sphinxAtStartPar\nΓ\\textbackslash{}\\(\\infty\\)\\$', + ), + ( + # in verbatim code fragments + 'verify', + '::\n\n @Γ\\∞${}', + None, + ('\\begin{sphinxVerbatim}[commandchars=\\\\\\{\\}]\n' + '@Γ\\PYGZbs{}\\(\\infty\\)\\PYGZdl{}\\PYGZob{}\\PYGZcb{}\n' + '\\end{sphinxVerbatim}'), + ), + ( + # in URIs + 'verify_re', + '`test <https://www.google.com/~me/>`_', + None, + r'\\sphinxAtStartPar\n\\sphinxhref{https://www.google.com/~me/}{test}.*', + ), + ( + # description list: simple + 'verify', + 'term\n description', + '<dl class="simple">\n<dt>term</dt><dd><p>description</p>\n</dd>\n</dl>', + None, + ), + ( + # description list: with classifiers + 'verify', + 'term : class1 : class2\n description', + ('<dl class="simple">\n<dt>term<span class="classifier">class1</span>' + '<span class="classifier">class2</span></dt><dd><p>description</p>\n</dd>\n</dl>'), + None, + ), + ( + # glossary (description list): multiple terms + 'verify', + '.. glossary::\n\n term1\n term2\n description', + ('<dl class="simple glossary">\n' + '<dt id="term-term1">term1<a class="headerlink" href="#term-term1"' + ' title="Link to this term">¶</a></dt>' + '<dt id="term-term2">term2<a class="headerlink" href="#term-term2"' + ' title="Link to this term">¶</a></dt>' + '<dd><p>description</p>\n</dd>\n</dl>'), + None, + ), +]) +def test_inline(get_verifier, type, rst, html_expected, latex_expected): + verifier = get_verifier(type) + verifier(rst, html_expected, latex_expected) + + +@pytest.mark.parametrize(('type', 'rst', 'html_expected', 'latex_expected'), [ + ( + 'verify', + r'4 backslashes \\\\', + r'<p>4 backslashes \\</p>', + None, + ), +]) +def test_inline_docutils16(get_verifier, type, rst, html_expected, latex_expected): + verifier = get_verifier(type) + verifier(rst, html_expected, latex_expected) + + +@pytest.mark.sphinx(confoverrides={'latex_engine': 'xelatex'}) +@pytest.mark.parametrize(('type', 'rst', 'html_expected', 'latex_expected'), [ + ( + # in verbatim code fragments + 'verify', + '::\n\n @Γ\\∞${}', + None, + ('\\begin{sphinxVerbatim}[commandchars=\\\\\\{\\}]\n' + '@Γ\\PYGZbs{}∞\\PYGZdl{}\\PYGZob{}\\PYGZcb{}\n' + '\\end{sphinxVerbatim}'), + ), +]) +def test_inline_for_unicode_latex_engine(get_verifier, type, rst, + html_expected, latex_expected): + verifier = get_verifier(type) + verifier(rst, html_expected, latex_expected) + + +def test_samp_role(parse): + # no braces + text = ':samp:`a{b}c`' + doctree = parse(text) + assert_node(doctree[0], [nodes.paragraph, nodes.literal, ("a", + [nodes.emphasis, "b"], + "c")]) + # nested braces + text = ':samp:`a{{b}}c`' + doctree = parse(text) + assert_node(doctree[0], [nodes.paragraph, nodes.literal, ("a", + [nodes.emphasis, "{b"], + "}c")]) + + # half-opened braces + text = ':samp:`a{bc`' + doctree = parse(text) + assert_node(doctree[0], [nodes.paragraph, nodes.literal, "a{bc"]) + + # escaped braces + text = ':samp:`a\\\\{b}c`' + doctree = parse(text) + assert_node(doctree[0], [nodes.paragraph, nodes.literal, "a{b}c"]) + + # no braces (whitespaces are keeped as is) + text = ':samp:`code sample`' + doctree = parse(text) + assert_node(doctree[0], [nodes.paragraph, nodes.literal, "code sample"]) + + +def test_download_role(parse): + # implicit + text = ':download:`sphinx.rst`' + doctree = parse(text) + assert_node(doctree[0], [nodes.paragraph, addnodes.download_reference, + nodes.literal, "sphinx.rst"]) + assert_node(doctree[0][0], refdoc='dummy', refdomain='', reftype='download', + refexplicit=False, reftarget='sphinx.rst', refwarn=False) + assert_node(doctree[0][0][0], classes=['xref', 'download']) + + # explicit + text = ':download:`reftitle <sphinx.rst>`' + doctree = parse(text) + assert_node(doctree[0], [nodes.paragraph, addnodes.download_reference, + nodes.literal, "reftitle"]) + assert_node(doctree[0][0], refdoc='dummy', refdomain='', reftype='download', + refexplicit=True, reftarget='sphinx.rst', refwarn=False) + assert_node(doctree[0][0][0], classes=['xref', 'download']) + + +def test_XRefRole(inliner): + role = XRefRole() + + # implicit + doctrees, errors = role('ref', 'rawtext', 'text', 5, inliner, {}, []) + assert len(doctrees) == 1 + assert_node(doctrees[0], [addnodes.pending_xref, nodes.literal, 'text']) + assert_node(doctrees[0], refdoc='dummy', refdomain='', reftype='ref', reftarget='text', + refexplicit=False, refwarn=False) + assert errors == [] + + # explicit + doctrees, errors = role('ref', 'rawtext', 'title <target>', 5, inliner, {}, []) + assert_node(doctrees[0], [addnodes.pending_xref, nodes.literal, 'title']) + assert_node(doctrees[0], refdoc='dummy', refdomain='', reftype='ref', reftarget='target', + refexplicit=True, refwarn=False) + + # bang + doctrees, errors = role('ref', 'rawtext', '!title <target>', 5, inliner, {}, []) + assert_node(doctrees[0], [nodes.literal, 'title <target>']) + + # refdomain + doctrees, errors = role('test:doc', 'rawtext', 'text', 5, inliner, {}, []) + assert_node(doctrees[0], [addnodes.pending_xref, nodes.literal, 'text']) + assert_node(doctrees[0], refdoc='dummy', refdomain='test', reftype='doc', reftarget='text', + refexplicit=False, refwarn=False) + + # fix_parens + role = XRefRole(fix_parens=True) + doctrees, errors = role('ref', 'rawtext', 'text()', 5, inliner, {}, []) + assert_node(doctrees[0], [addnodes.pending_xref, nodes.literal, 'text()']) + assert_node(doctrees[0], refdoc='dummy', refdomain='', reftype='ref', reftarget='text', + refexplicit=False, refwarn=False) + + # lowercase + role = XRefRole(lowercase=True) + doctrees, errors = role('ref', 'rawtext', 'TEXT', 5, inliner, {}, []) + assert_node(doctrees[0], [addnodes.pending_xref, nodes.literal, 'TEXT']) + assert_node(doctrees[0], refdoc='dummy', refdomain='', reftype='ref', reftarget='text', + refexplicit=False, refwarn=False) + + +@pytest.mark.sphinx('dummy', testroot='prolog') +def test_rst_prolog(app, status, warning): + app.build(force_all=True) + rst = app.env.get_doctree('restructuredtext') + md = app.env.get_doctree('markdown') + + # rst_prolog + assert_node(rst[0], nodes.paragraph) + assert_node(rst[0][0], nodes.emphasis) + assert_node(rst[0][0][0], nodes.Text) + assert rst[0][0][0] == 'Hello world' + + # rst_epilog + assert_node(rst[-1], nodes.section) + assert_node(rst[-1][-1], nodes.paragraph) + assert_node(rst[-1][-1][0], nodes.emphasis) + assert_node(rst[-1][-1][0][0], nodes.Text) + assert rst[-1][-1][0][0] == 'Good-bye world' + + # rst_prolog & rst_epilog on exlucding reST parser + assert not md.rawsource.startswith('*Hello world*.') + assert not md.rawsource.endswith('*Good-bye world*.\n') + + +@pytest.mark.sphinx('dummy', testroot='keep_warnings') +def test_keep_warnings_is_True(app, status, warning): + app.build(force_all=True) + doctree = app.env.get_doctree('index') + assert_node(doctree[0], nodes.section) + assert len(doctree[0]) == 2 + assert_node(doctree[0][1], nodes.system_message) + + +@pytest.mark.sphinx('dummy', testroot='keep_warnings', + confoverrides={'keep_warnings': False}) +def test_keep_warnings_is_False(app, status, warning): + app.build(force_all=True) + doctree = app.env.get_doctree('index') + assert_node(doctree[0], nodes.section) + assert len(doctree[0]) == 1 + + +@pytest.mark.sphinx('dummy', testroot='refonly_bullet_list') +def test_compact_refonly_bullet_list(app, status, warning): + app.build(force_all=True) + doctree = app.env.get_doctree('index') + assert_node(doctree[0], nodes.section) + assert len(doctree[0]) == 5 + + assert doctree[0][1].astext() == 'List A:' + assert_node(doctree[0][2], nodes.bullet_list) + assert_node(doctree[0][2][0][0], addnodes.compact_paragraph) + assert doctree[0][2][0][0].astext() == 'genindex' + + assert doctree[0][3].astext() == 'List B:' + assert_node(doctree[0][4], nodes.bullet_list) + assert_node(doctree[0][4][0][0], nodes.paragraph) + assert doctree[0][4][0][0].astext() == 'Hello' + + +@pytest.mark.sphinx('dummy', testroot='default_role') +def test_default_role1(app, status, warning): + app.build(force_all=True) + + # default-role: pep + doctree = app.env.get_doctree('index') + assert_node(doctree[0], nodes.section) + assert_node(doctree[0][1], nodes.paragraph) + assert_node(doctree[0][1][0], addnodes.index) + assert_node(doctree[0][1][1], nodes.target) + assert_node(doctree[0][1][2], nodes.reference, classes=["pep"]) + + # no default-role + doctree = app.env.get_doctree('foo') + assert_node(doctree[0], nodes.section) + assert_node(doctree[0][1], nodes.paragraph) + assert_node(doctree[0][1][0], nodes.title_reference) + assert_node(doctree[0][1][1], nodes.Text) + + +@pytest.mark.sphinx('dummy', testroot='default_role', + confoverrides={'default_role': 'guilabel'}) +def test_default_role2(app, status, warning): + app.build(force_all=True) + + # default-role directive is stronger than configratuion + doctree = app.env.get_doctree('index') + assert_node(doctree[0], nodes.section) + assert_node(doctree[0][1], nodes.paragraph) + assert_node(doctree[0][1][0], addnodes.index) + assert_node(doctree[0][1][1], nodes.target) + assert_node(doctree[0][1][2], nodes.reference, classes=["pep"]) + + # default_role changes the default behavior + doctree = app.env.get_doctree('foo') + assert_node(doctree[0], nodes.section) + assert_node(doctree[0][1], nodes.paragraph) + assert_node(doctree[0][1][0], nodes.inline, classes=["guilabel"]) + assert_node(doctree[0][1][1], nodes.Text) diff --git a/tests/test_markup/test_metadata.py b/tests/test_markup/test_metadata.py new file mode 100644 index 0000000..7f31997 --- /dev/null +++ b/tests/test_markup/test_metadata.py @@ -0,0 +1,43 @@ +"""Test our handling of metadata in files with bibliographic metadata.""" + +# adapted from an example of bibliographic metadata at +# https://docutils.sourceforge.io/docs/user/rst/demo.txt + +import pytest + + +@pytest.mark.sphinx('dummy', testroot='metadata') +def test_docinfo(app, status, warning): + """ + Inspect the 'docinfo' metadata stored in the first node of the document. + Note this doesn't give us access to data stored in subsequence blocks + that might be considered document metadata, such as 'abstract' or + 'dedication' blocks, or the 'meta' role. Doing otherwise is probably more + messing with the internals of sphinx than this rare use case merits. + """ + app.build() + expecteddocinfo = { + 'author': 'David Goodger', + 'authors': ['Me', 'Myself', 'I'], + 'address': '123 Example Street\nExample, EX Canada\nA1B 2C3', + 'field name': 'This is a generic bibliographic field.', + 'field name 2': ('Generic bibliographic fields may contain multiple ' + 'body elements.\n\nLike this.'), + 'status': 'This is a “work in progress”', + 'version': '1', + 'copyright': ('This document has been placed in the public domain. ' + 'You\nmay do with it as you wish. You may copy, modify,' + '\nredistribute, reattribute, sell, buy, rent, lease,\n' + 'destroy, or improve it, quote it at length, excerpt,\n' + 'incorporate, collate, fold, staple, or mutilate it, or ' + 'do\nanything else to it that your or anyone else’s ' + 'heart\ndesires.'), + 'contact': 'goodger@python.org', + 'date': '2006-05-21', + 'organization': 'humankind', + 'revision': '4564', + 'tocdepth': 1, + 'orphan': '', + 'nocomments': '', + } + assert app.env.metadata['index'] == expecteddocinfo diff --git a/tests/test_markup/test_parser.py b/tests/test_markup/test_parser.py new file mode 100644 index 0000000..86163c6 --- /dev/null +++ b/tests/test_markup/test_parser.py @@ -0,0 +1,57 @@ +"""Tests parsers module.""" + +from unittest.mock import Mock, patch + +import pytest + +from sphinx.parsers import RSTParser +from sphinx.util.docutils import new_document + + +@pytest.mark.sphinx(testroot='basic') +@patch('docutils.parsers.rst.states.RSTStateMachine') +def test_RSTParser_prolog_epilog(RSTStateMachine, app): + document = new_document('dummy.rst') + document.settings = Mock(tab_width=8, language_code='') + parser = RSTParser() + parser.set_application(app) + + # normal case + text = ('hello Sphinx world\n' + 'Sphinx is a document generator') + parser.parse(text, document) + (content, _), _ = RSTStateMachine().run.call_args + + assert list(content.xitems()) == [('dummy.rst', 0, 'hello Sphinx world'), + ('dummy.rst', 1, 'Sphinx is a document generator')] + + # with rst_prolog + app.env.config.rst_prolog = 'this is rst_prolog\nhello reST!' + parser.parse(text, document) + (content, _), _ = RSTStateMachine().run.call_args + assert list(content.xitems()) == [('<rst_prolog>', 0, 'this is rst_prolog'), + ('<rst_prolog>', 1, 'hello reST!'), + ('<generated>', 0, ''), + ('dummy.rst', 0, 'hello Sphinx world'), + ('dummy.rst', 1, 'Sphinx is a document generator')] + + # with rst_epilog + app.env.config.rst_prolog = None + app.env.config.rst_epilog = 'this is rst_epilog\ngood-bye reST!' + parser.parse(text, document) + (content, _), _ = RSTStateMachine().run.call_args + assert list(content.xitems()) == [('dummy.rst', 0, 'hello Sphinx world'), + ('dummy.rst', 1, 'Sphinx is a document generator'), + ('dummy.rst', 2, ''), + ('<rst_epilog>', 0, 'this is rst_epilog'), + ('<rst_epilog>', 1, 'good-bye reST!')] + + # expandtabs / convert whitespaces + app.env.config.rst_prolog = None + app.env.config.rst_epilog = None + text = ('\thello Sphinx world\n' + '\v\fSphinx is a document generator') + parser.parse(text, document) + (content, _), _ = RSTStateMachine().run.call_args + assert list(content.xitems()) == [('dummy.rst', 0, ' hello Sphinx world'), + ('dummy.rst', 1, ' Sphinx is a document generator')] diff --git a/tests/test_markup/test_smartquotes.py b/tests/test_markup/test_smartquotes.py new file mode 100644 index 0000000..6c84386 --- /dev/null +++ b/tests/test_markup/test_smartquotes.py @@ -0,0 +1,98 @@ +"""Test smart quotes.""" + +import pytest + +from sphinx.testing.util import etree_parse + + +@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True) +def test_basic(app, status, warning): + app.build() + + content = (app.outdir / 'index.html').read_text(encoding='utf8') + assert '<p>– “Sphinx” is a tool that makes it easy …</p>' in content + + +@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True) +def test_literals(app, status, warning): + app.build() + + etree = etree_parse(app.outdir / 'literals.html') + for code_element in etree.iter('code'): + code_text = ''.join(code_element.itertext()) + + if code_text.startswith('code role'): + assert "'quotes'" in code_text + elif code_text.startswith('{'): + assert code_text == "{'code': 'role', 'with': 'quotes'}" + elif code_text.startswith('literal'): + assert code_text == "literal with 'quotes'" + + +@pytest.mark.sphinx(buildername='text', testroot='smartquotes', freshenv=True) +def test_text_builder(app, status, warning): + app.build() + + content = (app.outdir / 'index.txt').read_text(encoding='utf8') + assert '-- "Sphinx" is a tool that makes it easy ...' in content + + +@pytest.mark.sphinx(buildername='man', testroot='smartquotes', freshenv=True) +def test_man_builder(app, status, warning): + app.build() + + content = (app.outdir / 'python.1').read_text(encoding='utf8') + assert r'\-\- \(dqSphinx\(dq is a tool that makes it easy ...' in content + + +@pytest.mark.sphinx(buildername='latex', testroot='smartquotes', freshenv=True) +def test_latex_builder(app, status, warning): + app.build() + + content = (app.outdir / 'python.tex').read_text(encoding='utf8') + assert '\\textendash{} “Sphinx” is a tool that makes it easy …' in content + + +@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True, + confoverrides={'language': 'ja'}) +def test_ja_html_builder(app, status, warning): + app.build() + + content = (app.outdir / 'index.html').read_text(encoding='utf8') + assert '<p>-- "Sphinx" is a tool that makes it easy ...</p>' in content + + +@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True, + confoverrides={'smartquotes': False}) +def test_smartquotes_disabled(app, status, warning): + app.build() + + content = (app.outdir / 'index.html').read_text(encoding='utf8') + assert '<p>-- "Sphinx" is a tool that makes it easy ...</p>' in content + + +@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True, + confoverrides={'smartquotes_action': 'q'}) +def test_smartquotes_action(app, status, warning): + app.build() + + content = (app.outdir / 'index.html').read_text(encoding='utf8') + assert '<p>-- “Sphinx” is a tool that makes it easy ...</p>' in content + + +@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True, + confoverrides={'language': 'ja', 'smartquotes_excludes': {}}) +def test_smartquotes_excludes_language(app, status, warning): + app.build() + + content = (app.outdir / 'index.html').read_text(encoding='utf8') + assert '<p>– 「Sphinx」 is a tool that makes it easy …</p>' in content + + +@pytest.mark.sphinx(buildername='man', testroot='smartquotes', freshenv=True, + confoverrides={'smartquotes_excludes': {}}) +def test_smartquotes_excludes_builders(app, status, warning): + app.build() + + content = (app.outdir / 'python.1').read_text(encoding='utf8') + assert '– “Sphinx” is a tool that makes it easy …' in content |