1""" 2 test_domain_std 3 ~~~~~~~~~~~~~~~ 4 5 Tests the std domain 6 7 :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9""" 10 11from unittest import mock 12 13import pytest 14from docutils import nodes 15from docutils.nodes import definition, definition_list, definition_list_item, term 16from html5lib import HTMLParser 17 18from sphinx import addnodes 19from sphinx.addnodes import (desc, desc_addname, desc_content, desc_name, desc_signature, 20 glossary, index, pending_xref) 21from sphinx.domains.std import StandardDomain 22from sphinx.testing import restructuredtext 23from sphinx.testing.util import assert_node 24from sphinx.util import docutils 25 26 27def test_process_doc_handle_figure_caption(): 28 env = mock.Mock(domaindata={}) 29 env.app.registry.enumerable_nodes = {} 30 figure_node = nodes.figure( 31 '', 32 nodes.caption('caption text', 'caption text'), 33 ) 34 document = mock.Mock( 35 nametypes={'testname': True}, 36 nameids={'testname': 'testid'}, 37 ids={'testid': figure_node}, 38 citation_refs={}, 39 ) 40 document.traverse.return_value = [] 41 42 domain = StandardDomain(env) 43 if 'testname' in domain.data['labels']: 44 del domain.data['labels']['testname'] 45 domain.process_doc(env, 'testdoc', document) 46 assert 'testname' in domain.data['labels'] 47 assert domain.data['labels']['testname'] == ( 48 'testdoc', 'testid', 'caption text') 49 50 51def test_process_doc_handle_table_title(): 52 env = mock.Mock(domaindata={}) 53 env.app.registry.enumerable_nodes = {} 54 table_node = nodes.table( 55 '', 56 nodes.title('title text', 'title text'), 57 ) 58 document = mock.Mock( 59 nametypes={'testname': True}, 60 nameids={'testname': 'testid'}, 61 ids={'testid': table_node}, 62 citation_refs={}, 63 ) 64 document.traverse.return_value = [] 65 66 domain = StandardDomain(env) 67 if 'testname' in domain.data['labels']: 68 del domain.data['labels']['testname'] 69 domain.process_doc(env, 'testdoc', document) 70 assert 'testname' in domain.data['labels'] 71 assert domain.data['labels']['testname'] == ( 72 'testdoc', 'testid', 'title text') 73 74 75def test_get_full_qualified_name(): 76 env = mock.Mock(domaindata={}) 77 env.app.registry.enumerable_nodes = {} 78 domain = StandardDomain(env) 79 80 # normal references 81 node = nodes.reference() 82 assert domain.get_full_qualified_name(node) is None 83 84 # simple reference to options 85 node = nodes.reference(reftype='option', reftarget='-l') 86 assert domain.get_full_qualified_name(node) is None 87 88 # options with std:program context 89 kwargs = {'std:program': 'ls'} 90 node = nodes.reference(reftype='option', reftarget='-l', **kwargs) 91 assert domain.get_full_qualified_name(node) == 'ls.-l' 92 93 94def test_cmd_option_with_optional_value(app): 95 text = ".. option:: -j[=N]" 96 doctree = restructuredtext.parse(app, text) 97 assert_node(doctree, (index, 98 [desc, ([desc_signature, ([desc_name, '-j'], 99 [desc_addname, '[=N]'])], 100 [desc_content, ()])])) 101 objects = list(app.env.get_domain("std").get_objects()) 102 assert ('-j', '-j', 'cmdoption', 'index', 'cmdoption-j', 1) in objects 103 104 105def test_cmd_option_starting_with_bracket(app): 106 text = ".. option:: [enable=]PATTERN" 107 doctree = restructuredtext.parse(app, text) 108 assert_node(doctree, (index, 109 [desc, ([desc_signature, ([desc_name, '[enable'], 110 [desc_addname, '=]PATTERN'])], 111 [desc_content, ()])])) 112 objects = list(app.env.get_domain("std").get_objects()) 113 assert ('[enable', '[enable', 'cmdoption', 'index', 'cmdoption-arg-enable', 1) in objects 114 115 116def test_glossary(app): 117 text = (".. glossary::\n" 118 "\n" 119 " term1\n" 120 " TERM2\n" 121 " description\n" 122 "\n" 123 " term3 : classifier\n" 124 " description\n" 125 " description\n" 126 "\n" 127 " term4 : class1 : class2\n" 128 " description\n") 129 130 # doctree 131 doctree = restructuredtext.parse(app, text) 132 assert_node(doctree, ( 133 [glossary, definition_list, ([definition_list_item, ([term, ("term1", 134 index)], 135 [term, ("TERM2", 136 index)], 137 definition)], 138 [definition_list_item, ([term, ("term3", 139 index)], 140 definition)], 141 [definition_list_item, ([term, ("term4", 142 index)], 143 definition)])], 144 )) 145 assert_node(doctree[0][0][0][0][1], 146 entries=[("single", "term1", "term-term1", "main", None)]) 147 assert_node(doctree[0][0][0][1][1], 148 entries=[("single", "TERM2", "term-TERM2", "main", None)]) 149 assert_node(doctree[0][0][0][2], 150 [definition, nodes.paragraph, "description"]) 151 assert_node(doctree[0][0][1][0][1], 152 entries=[("single", "term3", "term-term3", "main", "classifier")]) 153 assert_node(doctree[0][0][1][1], 154 [definition, nodes.paragraph, ("description\n" 155 "description")]) 156 assert_node(doctree[0][0][2][0][1], 157 entries=[("single", "term4", "term-term4", "main", "class1")]) 158 assert_node(doctree[0][0][2][1], 159 [nodes.definition, nodes.paragraph, "description"]) 160 161 # index 162 domain = app.env.get_domain("std") 163 objects = list(domain.get_objects()) 164 assert ("term1", "term1", "term", "index", "term-term1", -1) in objects 165 assert ("TERM2", "TERM2", "term", "index", "term-TERM2", -1) in objects 166 assert ("term3", "term3", "term", "index", "term-term3", -1) in objects 167 assert ("term4", "term4", "term", "index", "term-term4", -1) in objects 168 169 # term reference (case sensitive) 170 refnode = domain.resolve_xref(app.env, 'index', app.builder, 'term', 'term1', 171 pending_xref(), nodes.paragraph()) 172 assert_node(refnode, nodes.reference, refid="term-term1") 173 174 # term reference (case insensitive) 175 refnode = domain.resolve_xref(app.env, 'index', app.builder, 'term', 'term2', 176 pending_xref(), nodes.paragraph()) 177 assert_node(refnode, nodes.reference, refid="term-TERM2") 178 179 180def test_glossary_warning(app, status, warning): 181 # empty line between terms 182 text = (".. glossary::\n" 183 "\n" 184 " term1\n" 185 "\n" 186 " term2\n") 187 restructuredtext.parse(app, text, "case1") 188 assert ("case1.rst:4: WARNING: glossary terms must not be separated by empty lines" 189 in warning.getvalue()) 190 191 # glossary starts with indented item 192 text = (".. glossary::\n" 193 "\n" 194 " description\n" 195 " term\n") 196 restructuredtext.parse(app, text, "case2") 197 assert ("case2.rst:3: WARNING: glossary term must be preceded by empty line" 198 in warning.getvalue()) 199 200 # empty line between terms 201 text = (".. glossary::\n" 202 "\n" 203 " term1\n" 204 " description\n" 205 " term2\n") 206 restructuredtext.parse(app, text, "case3") 207 assert ("case3.rst:4: WARNING: glossary term must be preceded by empty line" 208 in warning.getvalue()) 209 210 # duplicated terms 211 text = (".. glossary::\n" 212 "\n" 213 " term-case4\n" 214 " term-case4\n") 215 restructuredtext.parse(app, text, "case4") 216 assert ("case4.rst:3: WARNING: duplicate term description of term-case4, " 217 "other instance in case4" in warning.getvalue()) 218 219 220def test_glossary_comment(app): 221 text = (".. glossary::\n" 222 "\n" 223 " term1\n" 224 " description\n" 225 " .. term2\n" 226 " description\n" 227 " description\n") 228 doctree = restructuredtext.parse(app, text) 229 assert_node(doctree, ( 230 [glossary, definition_list, definition_list_item, ([term, ("term1", 231 index)], 232 definition)], 233 )) 234 assert_node(doctree[0][0][0][1], 235 [nodes.definition, nodes.paragraph, "description"]) 236 237 238def test_glossary_comment2(app): 239 text = (".. glossary::\n" 240 "\n" 241 " term1\n" 242 " description\n" 243 "\n" 244 " .. term2\n" 245 " term3\n" 246 " description\n" 247 " description\n") 248 doctree = restructuredtext.parse(app, text) 249 assert_node(doctree, ( 250 [glossary, definition_list, ([definition_list_item, ([term, ("term1", 251 index)], 252 definition)], 253 [definition_list_item, ([term, ("term3", 254 index)], 255 definition)])], 256 )) 257 assert_node(doctree[0][0][0][1], 258 [nodes.definition, nodes.paragraph, "description"]) 259 assert_node(doctree[0][0][1][1], 260 [nodes.definition, nodes.paragraph, ("description\n" 261 "description")]) 262 263 264def test_glossary_sorted(app): 265 text = (".. glossary::\n" 266 " :sorted:\n" 267 "\n" 268 " term3\n" 269 " description\n" 270 "\n" 271 " term2\n" 272 " term1\n" 273 " description\n") 274 doctree = restructuredtext.parse(app, text) 275 assert_node(doctree, ( 276 [glossary, definition_list, ([definition_list_item, ([term, ("term2", 277 index)], 278 [term, ("term1", 279 index)], 280 definition)], 281 [definition_list_item, ([term, ("term3", 282 index)], 283 definition)])], 284 )) 285 assert_node(doctree[0][0][0][2], 286 [nodes.definition, nodes.paragraph, "description"]) 287 assert_node(doctree[0][0][1][1], 288 [nodes.definition, nodes.paragraph, "description"]) 289 290 291def test_glossary_alphanumeric(app): 292 text = (".. glossary::\n" 293 "\n" 294 " 1\n" 295 " /\n") 296 restructuredtext.parse(app, text) 297 objects = list(app.env.get_domain("std").get_objects()) 298 assert ("1", "1", "term", "index", "term-1", -1) in objects 299 assert ("/", "/", "term", "index", "term-0", -1) in objects 300 301 302def test_glossary_conflicted_labels(app): 303 text = (".. _term-foo:\n" 304 ".. glossary::\n" 305 "\n" 306 " foo\n") 307 restructuredtext.parse(app, text) 308 objects = list(app.env.get_domain("std").get_objects()) 309 assert ("foo", "foo", "term", "index", "term-0", -1) in objects 310 311 312def test_cmdoption(app): 313 text = (".. program:: ls\n" 314 "\n" 315 ".. option:: -l\n") 316 domain = app.env.get_domain('std') 317 doctree = restructuredtext.parse(app, text) 318 assert_node(doctree, (addnodes.index, 319 [desc, ([desc_signature, ([desc_name, "-l"], 320 [desc_addname, ()])], 321 [desc_content, ()])])) 322 assert_node(doctree[0], addnodes.index, 323 entries=[('pair', 'ls command line option; -l', 'cmdoption-ls-l', '', None)]) 324 assert ('ls', '-l') in domain.progoptions 325 assert domain.progoptions[('ls', '-l')] == ('index', 'cmdoption-ls-l') 326 327 328def test_multiple_cmdoptions(app): 329 text = (".. program:: cmd\n" 330 "\n" 331 ".. option:: -o directory, --output directory\n") 332 domain = app.env.get_domain('std') 333 doctree = restructuredtext.parse(app, text) 334 assert_node(doctree, (addnodes.index, 335 [desc, ([desc_signature, ([desc_name, "-o"], 336 [desc_addname, " directory"], 337 [desc_addname, ", "], 338 [desc_name, "--output"], 339 [desc_addname, " directory"])], 340 [desc_content, ()])])) 341 assert_node(doctree[0], addnodes.index, 342 entries=[('pair', 'cmd command line option; -o directory', 343 'cmdoption-cmd-o', '', None), 344 ('pair', 'cmd command line option; --output directory', 345 'cmdoption-cmd-o', '', None)]) 346 assert ('cmd', '-o') in domain.progoptions 347 assert ('cmd', '--output') in domain.progoptions 348 assert domain.progoptions[('cmd', '-o')] == ('index', 'cmdoption-cmd-o') 349 assert domain.progoptions[('cmd', '--output')] == ('index', 'cmdoption-cmd-o') 350 351 352@pytest.mark.skipif(docutils.__version_info__ < (0, 13), 353 reason='docutils-0.13 or above is required') 354@pytest.mark.sphinx(testroot='productionlist') 355def test_productionlist(app, status, warning): 356 app.builder.build_all() 357 358 warnings = warning.getvalue().split("\n") 359 assert len(warnings) == 2 360 assert warnings[-1] == '' 361 assert "Dup2.rst:4: WARNING: duplicate token description of Dup, other instance in Dup1" in warnings[0] 362 363 with (app.outdir / 'index.html').open('rb') as f: 364 etree = HTMLParser(namespaceHTMLElements=False).parse(f) 365 ul = list(etree.iter('ul'))[1] 366 cases = [] 367 for li in list(ul): 368 assert len(list(li)) == 1 369 p = list(li)[0] 370 assert p.tag == 'p' 371 text = str(p.text).strip(' :') 372 assert len(list(p)) == 1 373 a = list(p)[0] 374 assert a.tag == 'a' 375 link = a.get('href') 376 assert len(list(a)) == 1 377 code = list(a)[0] 378 assert code.tag == 'code' 379 assert len(list(code)) == 1 380 span = list(code)[0] 381 assert span.tag == 'span' 382 linkText = span.text.strip() 383 cases.append((text, link, linkText)) 384 assert cases == [ 385 ('A', 'Bare.html#grammar-token-A', 'A'), 386 ('B', 'Bare.html#grammar-token-B', 'B'), 387 ('P1:A', 'P1.html#grammar-token-P1-A', 'P1:A'), 388 ('P1:B', 'P1.html#grammar-token-P1-B', 'P1:B'), 389 ('P2:A', 'P1.html#grammar-token-P1-A', 'P1:A'), 390 ('P2:B', 'P2.html#grammar-token-P2-B', 'P2:B'), 391 ('Explicit title A, plain', 'Bare.html#grammar-token-A', 'MyTitle'), 392 ('Explicit title A, colon', 'Bare.html#grammar-token-A', 'My:Title'), 393 ('Explicit title P1:A, plain', 'P1.html#grammar-token-P1-A', 'MyTitle'), 394 ('Explicit title P1:A, colon', 'P1.html#grammar-token-P1-A', 'My:Title'), 395 ('Tilde A', 'Bare.html#grammar-token-A', 'A'), 396 ('Tilde P1:A', 'P1.html#grammar-token-P1-A', 'A'), 397 ('Tilde explicit title P1:A', 'P1.html#grammar-token-P1-A', '~MyTitle'), 398 ('Tilde, explicit title P1:A', 'P1.html#grammar-token-P1-A', 'MyTitle'), 399 ('Dup', 'Dup2.html#grammar-token-Dup', 'Dup'), 400 ('FirstLine', 'firstLineRule.html#grammar-token-FirstLine', 'FirstLine'), 401 ('SecondLine', 'firstLineRule.html#grammar-token-SecondLine', 'SecondLine'), 402 ] 403 404 text = (app.outdir / 'LineContinuation.html').read_text() 405 assert "A</strong> ::= B C D E F G" in text 406 407 408def test_productionlist2(app): 409 text = (".. productionlist:: P2\n" 410 " A: `:A` `A`\n" 411 " B: `P1:B` `~P1:B`\n") 412 doctree = restructuredtext.parse(app, text) 413 refnodes = list(doctree.traverse(pending_xref)) 414 assert_node(refnodes[0], pending_xref, reftarget="A") 415 assert_node(refnodes[1], pending_xref, reftarget="P2:A") 416 assert_node(refnodes[2], pending_xref, reftarget="P1:B") 417 assert_node(refnodes[3], pending_xref, reftarget="P1:B") 418 assert_node(refnodes[0], [pending_xref, nodes.literal, "A"]) 419 assert_node(refnodes[1], [pending_xref, nodes.literal, "A"]) 420 assert_node(refnodes[2], [pending_xref, nodes.literal, "P1:B"]) 421 assert_node(refnodes[3], [pending_xref, nodes.literal, "B"]) 422 423 424def test_disabled_docref(app): 425 text = (":doc:`index`\n" 426 ":doc:`!index`\n") 427 doctree = restructuredtext.parse(app, text) 428 assert_node(doctree, ([nodes.paragraph, ([pending_xref, nodes.inline, "index"], 429 "\n", 430 [nodes.inline, "index"])],)) 431 432 433def test_labeled_rubric(app): 434 text = (".. _label:\n" 435 ".. rubric:: blah *blah* blah\n") 436 restructuredtext.parse(app, text) 437 438 domain = app.env.get_domain("std") 439 assert 'label' in domain.labels 440 assert domain.labels['label'] == ('index', 'label', 'blah blah blah') 441