1"""
2    test_ext_math
3    ~~~~~~~~~~~~~
4
5    Test math extensions.
6
7    :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
8    :license: BSD, see LICENSE for details.
9"""
10
11import re
12import subprocess
13import warnings
14
15import pytest
16from docutils import nodes
17
18from sphinx.ext.mathjax import MATHJAX_URL
19from sphinx.testing.util import assert_node
20
21
22def has_binary(binary):
23    try:
24        subprocess.check_output([binary])
25    except FileNotFoundError:
26        return False
27    except OSError:
28        pass
29    return True
30
31
32@pytest.mark.skipif(not has_binary('dvipng'),
33                    reason='Requires dvipng" binary')
34@pytest.mark.sphinx('html', testroot='ext-math-simple',
35                    confoverrides = {'extensions': ['sphinx.ext.imgmath']})
36def test_imgmath_png(app, status, warning):
37    app.builder.build_all()
38    if "LaTeX command 'latex' cannot be run" in warning.getvalue():
39        raise pytest.skip.Exception('LaTeX command "latex" is not available')
40    if "dvipng command 'dvipng' cannot be run" in warning.getvalue():
41        raise pytest.skip.Exception('dvipng command "dvipng" is not available')
42
43    content = (app.outdir / 'index.html').read_text()
44    html = (r'<div class="math">\s*<p>\s*<img src="_images/math/\w+.png"'
45            r'\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>')
46    assert re.search(html, content, re.S)
47
48
49@pytest.mark.skipif(not has_binary('dvisvgm'),
50                    reason='Requires dvisvgm" binary')
51@pytest.mark.sphinx('html', testroot='ext-math-simple',
52                    confoverrides={'extensions': ['sphinx.ext.imgmath'],
53                                   'imgmath_image_format': 'svg'})
54def test_imgmath_svg(app, status, warning):
55    app.builder.build_all()
56    if "LaTeX command 'latex' cannot be run" in warning.getvalue():
57        raise pytest.skip.Exception('LaTeX command "latex" is not available')
58    if "dvisvgm command 'dvisvgm' cannot be run" in warning.getvalue():
59        raise pytest.skip.Exception('dvisvgm command "dvisvgm" is not available')
60
61    content = (app.outdir / 'index.html').read_text()
62    html = (r'<div class="math">\s*<p>\s*<img src="_images/math/\w+.svg"'
63            r'\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>')
64    assert re.search(html, content, re.S)
65
66
67@pytest.mark.sphinx('html', testroot='ext-math',
68                    confoverrides={'extensions': ['sphinx.ext.mathjax'],
69                                   'mathjax_options': {'integrity': 'sha384-0123456789'}})
70def test_mathjax_options(app, status, warning):
71    app.builder.build_all()
72
73    content = (app.outdir / 'index.html').read_text()
74    assert ('<script async="async" integrity="sha384-0123456789" '
75            'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?'
76            'config=TeX-AMS-MML_HTMLorMML"></script>' in content)
77
78
79@pytest.mark.sphinx('html', testroot='ext-math',
80                    confoverrides={'extensions': ['sphinx.ext.mathjax']})
81def test_mathjax_align(app, status, warning):
82    app.builder.build_all()
83
84    content = (app.outdir / 'index.html').read_text()
85    html = (r'<div class="math notranslate nohighlight">\s*'
86            r'\\\[ \\begin\{align\}\\begin\{aligned\}S \&amp;= \\pi r\^2\\\\'
87            r'V \&amp;= \\frac\{4\}\{3\} \\pi r\^3\\end\{aligned\}\\end\{align\} \\\]</div>')
88    assert re.search(html, content, re.S)
89
90
91@pytest.mark.sphinx('html', testroot='ext-math',
92                    confoverrides={'math_number_all': True,
93                                   'extensions': ['sphinx.ext.mathjax']})
94def test_math_number_all_mathjax(app, status, warning):
95    app.builder.build_all()
96
97    content = (app.outdir / 'index.html').read_text()
98    html = (r'<div class="math notranslate nohighlight" id="equation-index-0">\s*'
99            r'<span class="eqno">\(1\)<a .*>\xb6</a></span>\\\[a\^2\+b\^2=c\^2\\\]</div>')
100    assert re.search(html, content, re.S)
101
102
103@pytest.mark.sphinx('latex', testroot='ext-math',
104                    confoverrides={'extensions': ['sphinx.ext.mathjax']})
105def test_math_number_all_latex(app, status, warning):
106    app.builder.build_all()
107
108    content = (app.outdir / 'python.tex').read_text()
109    macro = (r'\\begin{equation\*}\s*'
110             r'\\begin{split}a\^2\+b\^2=c\^2\\end{split}\s*'
111             r'\\end{equation\*}')
112    assert re.search(macro, content, re.S)
113
114    macro = r'Inline \\\(E=mc\^2\\\)'
115    assert re.search(macro, content, re.S)
116
117    macro = (r'\\begin{equation\*}\s*'
118             r'\\begin{split}e\^{i\\pi}\+1=0\\end{split}\s+'
119             r'\\end{equation\*}')
120    assert re.search(macro, content, re.S)
121
122    macro = (r'\\begin{align\*}\\!\\begin{aligned}\s*'
123             r'S &= \\pi r\^2\\\\\s*'
124             r'V &= \\frac\{4}\{3} \\pi r\^3\\\\\s*'
125             r'\\end{aligned}\\end{align\*}')
126    assert re.search(macro, content, re.S)
127
128    macro = r'Referencing equation \\eqref{equation:math:foo}.'
129    assert re.search(macro, content, re.S)
130
131
132@pytest.mark.sphinx('html', testroot='ext-math',
133                    confoverrides={'extensions': ['sphinx.ext.mathjax'],
134                                   'math_eqref_format': 'Eq.{number}'})
135def test_math_eqref_format_html(app, status, warning):
136    app.builder.build_all()
137
138    content = (app.outdir / 'math.html').read_text()
139    html = ('<p>Referencing equation <a class="reference internal" '
140            'href="#equation-foo">Eq.1</a> and <a class="reference internal" '
141            'href="#equation-foo">Eq.1</a>.</p>')
142    assert html in content
143
144
145@pytest.mark.sphinx('latex', testroot='ext-math',
146                    confoverrides={'extensions': ['sphinx.ext.mathjax'],
147                                   'math_eqref_format': 'Eq.{number}'})
148def test_math_eqref_format_latex(app, status, warning):
149    app.builder.build_all()
150
151    content = (app.outdir / 'python.tex').read_text()
152    macro = (r'Referencing equation Eq.\\ref{equation:math:foo} and '
153             r'Eq.\\ref{equation:math:foo}.')
154    assert re.search(macro, content, re.S)
155
156
157@pytest.mark.sphinx('html', testroot='ext-math',
158                    confoverrides={'extensions': ['sphinx.ext.mathjax'],
159                                   'numfig': True,
160                                   'math_numfig': True})
161def test_mathjax_numfig_html(app, status, warning):
162    app.builder.build_all()
163
164    content = (app.outdir / 'math.html').read_text()
165    html = ('<div class="math notranslate nohighlight" id="equation-math-0">\n'
166            '<span class="eqno">(1.2)')
167    assert html in content
168    html = ('<p>Referencing equation <a class="reference internal" '
169            'href="#equation-foo">(1.1)</a> and '
170            '<a class="reference internal" href="#equation-foo">(1.1)</a>.</p>')
171    assert html in content
172
173
174@pytest.mark.sphinx('html', testroot='ext-math',
175                    confoverrides={'extensions': ['sphinx.ext.imgmath'],
176                                   'numfig': True,
177                                   'numfig_secnum_depth': 0,
178                                   'math_numfig': True})
179def test_imgmath_numfig_html(app, status, warning):
180    app.builder.build_all()
181
182    content = (app.outdir / 'page.html').read_text()
183    html = '<span class="eqno">(3)<a class="headerlink" href="#equation-bar"'
184    assert html in content
185    html = ('<p>Referencing equations <a class="reference internal" '
186            'href="math.html#equation-foo">(1)</a> and '
187            '<a class="reference internal" href="#equation-bar">(3)</a>.</p>')
188    assert html in content
189
190
191@pytest.mark.sphinx('dummy', testroot='ext-math-compat')
192def test_math_compat(app, status, warning):
193    with warnings.catch_warnings(record=True):
194        app.builder.build_all()
195        doctree = app.env.get_and_resolve_doctree('index', app.builder)
196
197        assert_node(doctree,
198                    [nodes.document, nodes.section, (nodes.title,
199                                                     [nodes.section, (nodes.title,
200                                                                      nodes.paragraph)],
201                                                     nodes.section)])
202        assert_node(doctree[0][1][1],
203                    ('Inline: ',
204                     [nodes.math, "E=mc^2"],
205                     '\nInline my math: ',
206                     [nodes.math, "E = mc^2"]))
207        assert_node(doctree[0][2],
208                    ([nodes.title, "block"],
209                     [nodes.math_block, "a^2+b^2=c^2\n\n"],
210                     [nodes.paragraph, "Second math"],
211                     [nodes.math_block, "e^{i\\pi}+1=0\n\n"],
212                     [nodes.paragraph, "Multi math equations"],
213                     [nodes.math_block, "E = mc^2"]))
214
215
216@pytest.mark.sphinx('html', testroot='ext-math',
217                    confoverrides={'extensions': ['sphinx.ext.mathjax'],
218                                   'mathjax_config': {'extensions': ['tex2jax.js']}})
219def test_mathjax_config(app, status, warning):
220    app.builder.build_all()
221
222    content = (app.outdir / 'index.html').read_text()
223    assert ('<script type="text/x-mathjax-config">'
224            'MathJax.Hub.Config({"extensions": ["tex2jax.js"]})'
225            '</script>' in content)
226
227
228@pytest.mark.sphinx('html', testroot='ext-math',
229                    confoverrides={'extensions': ['sphinx.ext.mathjax']})
230def test_mathjax_is_installed_only_if_document_having_math(app, status, warning):
231    app.builder.build_all()
232
233    content = (app.outdir / 'index.html').read_text()
234    assert MATHJAX_URL in content
235
236    content = (app.outdir / 'nomath.html').read_text()
237    assert MATHJAX_URL not in content
238
239
240@pytest.mark.sphinx('html', testroot='basic',
241                    confoverrides={'extensions': ['sphinx.ext.mathjax']})
242def test_mathjax_is_not_installed_if_no_equations(app, status, warning):
243    app.builder.build_all()
244
245    content = (app.outdir / 'index.html').read_text()
246    assert 'MathJax.js' not in content
247