1""" 2 test_util_nodes 3 ~~~~~~~~~~~~~~~ 4 5 Tests uti.nodes functions. 6 7 :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9""" 10from textwrap import dedent 11from typing import Any 12 13import pytest 14from docutils import frontend, nodes 15from docutils.parsers import rst 16from docutils.utils import new_document 17 18from sphinx.transforms import ApplySourceWorkaround 19from sphinx.util.nodes import (NodeMatcher, clean_astext, extract_messages, make_id, 20 split_explicit_title) 21 22 23def _transform(doctree): 24 ApplySourceWorkaround(doctree).apply() 25 26 27def create_new_document(): 28 settings = frontend.OptionParser( 29 components=(rst.Parser,)).get_default_values() 30 settings.id_prefix = 'id' 31 document = new_document('dummy.txt', settings) 32 return document 33 34 35def _get_doctree(text): 36 document = create_new_document() 37 rst.Parser().parse(text, document) 38 _transform(document) 39 return document 40 41 42def assert_node_count(messages, node_type, expect_count): 43 count = 0 44 node_list = [node for node, msg in messages] 45 for node in node_list: 46 if isinstance(node, node_type): 47 count += 1 48 49 assert count == expect_count, ( 50 "Count of %r in the %r is %d instead of %d" 51 % (node_type, node_list, count, expect_count)) 52 53 54def test_NodeMatcher(): 55 doctree = nodes.document(None, None) 56 doctree += nodes.paragraph('', 'Hello') 57 doctree += nodes.paragraph('', 'Sphinx', block=1) 58 doctree += nodes.paragraph('', 'World', block=2) 59 doctree += nodes.literal_block('', 'blah blah blah', block=3) 60 61 # search by node class 62 matcher = NodeMatcher(nodes.paragraph) 63 assert len(doctree.traverse(matcher)) == 3 64 65 # search by multiple node classes 66 matcher = NodeMatcher(nodes.paragraph, nodes.literal_block) 67 assert len(doctree.traverse(matcher)) == 4 68 69 # search by node attribute 70 matcher = NodeMatcher(block=1) 71 assert len(doctree.traverse(matcher)) == 1 72 73 # search by node attribute (Any) 74 matcher = NodeMatcher(block=Any) 75 assert len(doctree.traverse(matcher)) == 3 76 77 # search by both class and attribute 78 matcher = NodeMatcher(nodes.paragraph, block=Any) 79 assert len(doctree.traverse(matcher)) == 2 80 81 # mismatched 82 matcher = NodeMatcher(nodes.title) 83 assert len(doctree.traverse(matcher)) == 0 84 85 # search with Any does not match to Text node 86 matcher = NodeMatcher(blah=Any) 87 assert len(doctree.traverse(matcher)) == 0 88 89 90@pytest.mark.parametrize( 91 'rst,node_cls,count', 92 [ 93 ( 94 """ 95 .. admonition:: admonition title 96 97 admonition body 98 """, 99 nodes.title, 1 100 ), 101 ( 102 """ 103 .. figure:: foo.jpg 104 105 this is title 106 """, 107 nodes.caption, 1, 108 ), 109 ( 110 """ 111 .. rubric:: spam 112 """, 113 nodes.rubric, 1, 114 ), 115 ( 116 """ 117 | spam 118 | egg 119 """, 120 nodes.line, 2, 121 ), 122 ( 123 """ 124 section 125 ======= 126 127 +----------------+ 128 | | **Title 1** | 129 | | Message 1 | 130 +----------------+ 131 """, 132 nodes.line, 2, 133 ), 134 ( 135 """ 136 * | **Title 1** 137 | Message 1 138 """, 139 nodes.line, 2, 140 141 ), 142 ] 143) 144def test_extract_messages(rst, node_cls, count): 145 msg = extract_messages(_get_doctree(dedent(rst))) 146 assert_node_count(msg, node_cls, count) 147 148 149def test_extract_messages_without_rawsource(): 150 """ 151 Check node.rawsource is fall-backed by using node.astext() value. 152 153 `extract_message` which is used from Sphinx i18n feature drop ``not node.rawsource`` 154 nodes. So, all nodes which want to translate must have ``rawsource`` value. 155 However, sometimes node.rawsource is not set. 156 157 For example: recommonmark-0.2.0 doesn't set rawsource to `paragraph` node. 158 159 refs #1994: Fall back to node's astext() during i18n message extraction. 160 """ 161 p = nodes.paragraph() 162 p.append(nodes.Text('test')) 163 p.append(nodes.Text('sentence')) 164 assert not p.rawsource # target node must not have rawsource value 165 document = create_new_document() 166 document.append(p) 167 _transform(document) 168 assert_node_count(extract_messages(document), nodes.TextElement, 1) 169 assert [m for n, m in extract_messages(document)][0], 'text sentence' 170 171 172def test_clean_astext(): 173 node = nodes.paragraph(text='hello world') 174 assert 'hello world' == clean_astext(node) 175 176 node = nodes.image(alt='hello world') 177 assert '' == clean_astext(node) 178 179 node = nodes.paragraph(text='hello world') 180 node += nodes.raw('', 'raw text', format='html') 181 assert 'hello world' == clean_astext(node) 182 183 184@pytest.mark.parametrize( 185 'prefix, term, expected', 186 [ 187 ('', '', 'id0'), 188 ('term', '', 'term-0'), 189 ('term', 'Sphinx', 'term-Sphinx'), 190 ('', 'io.StringIO', 'io.StringIO'), # contains a dot 191 ('', 'sphinx.setup_command', 'sphinx.setup_command'), # contains a dot & underscore 192 ('', '_io.StringIO', 'io.StringIO'), # starts with underscore 193 ('', 'sphinx', 'sphinx'), # alphabets in unicode fullwidth characters 194 ('', '悠好', 'id0'), # multibytes text (in Chinese) 195 ('', 'Hello=悠好=こんにちは', 'Hello'), # alphabets and multibytes text 196 ('', 'fünf', 'funf'), # latin1 (umlaut) 197 ('', '0sphinx', 'sphinx'), # starts with number 198 ('', 'sphinx-', 'sphinx'), # ends with hyphen 199 ]) 200def test_make_id(app, prefix, term, expected): 201 document = create_new_document() 202 assert make_id(app.env, document, prefix, term) == expected 203 204 205def test_make_id_already_registered(app): 206 document = create_new_document() 207 document.ids['term-Sphinx'] = True # register "term-Sphinx" manually 208 assert make_id(app.env, document, 'term', 'Sphinx') == 'term-0' 209 210 211def test_make_id_sequential(app): 212 document = create_new_document() 213 document.ids['term-0'] = True 214 assert make_id(app.env, document, 'term') == 'term-1' 215 216 217@pytest.mark.parametrize( 218 'title, expected', 219 [ 220 # implicit 221 ('hello', (False, 'hello', 'hello')), 222 # explicit 223 ('hello <world>', (True, 'hello', 'world')), 224 # explicit (title having angle brackets) 225 ('hello <world> <sphinx>', (True, 'hello <world>', 'sphinx')), 226 ] 227) 228def test_split_explicit_target(title, expected): 229 assert expected == split_explicit_title(title) 230