Coverage for tdom/parser_utils_test.py: 100%
109 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-19 21:37 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-19 21:37 +0000
1from string.templatelib import Template
3import pytest
5from .parser_utils import (
6 ParserPositionTranslator,
7 make_parser_pos_translator,
8)
9from .placeholders import PlaceholderConfig, make_placeholder_config
10from .source import LinePosition
11from .template_utils import PartPosition, TemplateSpan
14@pytest.fixture(scope="module")
15def ph_config():
16 return make_placeholder_config()
19def make_ppt(template: Template, config: PlaceholderConfig) -> ParserPositionTranslator:
20 """Just a shorthand function."""
21 return make_parser_pos_translator(template=template, config=config)
24class TestParserPositionTranslator:
25 def test_line_pos_to_abs_pos(self, ph_config):
26 ppt = make_ppt(t"ab\ncd\n", ph_config)
28 assert ppt.line_pos_to_abs_pos(LinePosition(1, 0)) == 0
29 assert ppt.line_pos_to_abs_pos(LinePosition(1, 2)) == 2
30 assert ppt.line_pos_to_abs_pos(LinePosition(2, 0)) == 3
31 assert ppt.line_pos_to_abs_pos(LinePosition(2, 2)) == 5
32 assert ppt.line_pos_to_abs_pos(LinePosition(3, 0)) == 6
34 def test_translate_abs_pos_to_part_pos(self, ph_config):
35 ppt = make_ppt(t"a{0}b", ph_config)
36 placeholder_length = len(ph_config.make_placeholder(0))
38 assert ppt.abs_pos_to_part_pos(0) == PartPosition(0, 0)
39 assert ppt.abs_pos_to_part_pos(1) == PartPosition(0, 1)
40 assert ppt.abs_pos_to_part_pos(1 + placeholder_length) == PartPosition(1, 0)
41 assert ppt.abs_pos_to_part_pos(2 + placeholder_length) == PartPosition(1, 1)
43 with pytest.raises(ValueError, match="Absolute position falls outside"):
44 ppt.abs_pos_to_part_pos(-1)
45 with pytest.raises(ValueError, match="Absolute position falls outside"):
46 ppt.abs_pos_to_part_pos(3 + placeholder_length)
48 def test_translate_span(self, ph_config):
49 ppt = make_ppt(t"before\n <{0}\n x>", ph_config)
50 placeholder_length = len(ph_config.make_placeholder(0))
52 assert ppt.translate_span(
53 LinePosition(line=2, offset=2),
54 len("<") + placeholder_length + len("\n x>"),
55 ) == TemplateSpan(
56 start=PartPosition(0, len("before\n ")),
57 stop=PartPosition(1, len("\n x>")),
58 )
60 with pytest.raises(ValueError, match="Parser span length must be positive"):
61 ppt.translate_span(LinePosition(), -1)
63 def test_case_nontailing_string_ends_with_newline(self, ph_config):
64 ppt = make_ppt(t"a\n{0}b", ph_config)
65 assert ppt.translate(LinePosition(line=2, offset=0)) == PartPosition(
66 s_index=0, offset=len("a\n")
67 ), "The placeholder starts at the end of its preceding string."
68 assert ppt.translate(
69 LinePosition(line=2, offset=len(ph_config.make_placeholder(0)))
70 ) == PartPosition(
71 s_index=1, offset=0
72 ), """This must be the start of the following string because we can't
73 know the offset of the actual interpolation content."""
75 def test_case_tailing_string_starts_with_newline(self, ph_config):
76 ppt = make_ppt(t"a{0}\nb", ph_config)
77 assert ppt.translate(LinePosition(line=2, offset=0)) == PartPosition(
78 s_index=1, offset=1
79 ), "line 2 should be inside the tailing string"
80 assert ppt.translate(
81 LinePosition(line=1, offset=1 + len(ph_config.make_placeholder(0)))
82 ) == PartPosition(s_index=1, offset=0), (
83 "end of line 1 should be inside the tailing string?"
84 )
86 def test_case_interpolation_without_lines(self, ph_config):
87 ppt = make_ppt(t"a{0}b", ph_config)
88 assert ppt.translate(LinePosition(line=1, offset=1)) == PartPosition(
89 s_index=0, offset=1
90 ), "the interpolation starts at the end of the preceding string"
91 assert ppt.translate(
92 LinePosition(line=1, offset=1 + len(ph_config.make_placeholder(0)))
93 ) == PartPosition(s_index=1, offset=0), (
94 "the end of the interpolation is the start of the tailing string"
95 )
96 assert ppt.translate(
97 LinePosition(line=1, offset=1 + len(ph_config.make_placeholder(0)) + 1)
98 ) == PartPosition(s_index=1, offset=1), (
99 "the end of the tailing string remains the end."
100 )
102 def test_case_consecutive_interpolations(self, ph_config):
103 ppt = make_ppt(t"{0}{1}", ph_config)
104 placeholder_length = len(ph_config.make_placeholder(0))
106 assert ppt.translate(LinePosition(1, 0)) == PartPosition(0, 0)
107 assert ppt.translate(LinePosition(1, placeholder_length)) == PartPosition(1, 0)
108 assert ppt.translate(LinePosition(1, 2 * placeholder_length)) == PartPosition(
109 2, 0
110 )
112 def test_offset_without_line(self, ph_config):
113 ppt = make_ppt(t"a*", ph_config)
114 assert ppt.translate(LinePosition(line=1, offset=1)) == PartPosition(
115 s_index=0, offset=1
116 ), "the offset matches up without lines"
117 assert ppt.translate(LinePosition(line=1, offset=2)) == PartPosition(
118 s_index=0, offset=2
119 ), "end of line is end of string"
120 with pytest.raises(ValueError, match="Offset exceeds reachable"):
121 # only 0, 1 and 2 are valid offsets for line 1
122 _ = ppt.translate(LinePosition(line=1, offset=3)) == PartPosition(
123 s_index=0, offset=3
124 )
126 def test_offset_with_line(self, ph_config):
127 ppt = make_ppt(t"ab\n*", ph_config)
128 assert ppt.translate(LinePosition(line=2, offset=0)) == PartPosition(
129 s_index=0, offset=3
130 ), "2nd line starts at offset in the head string"
131 assert ppt.translate(LinePosition(line=1, offset=2)) == PartPosition(
132 s_index=0, offset=2
133 ), "end of 1st line is offset to NL"
134 with pytest.raises(ValueError, match="Offset exceeds reachable"):
135 # only 0, 1 and 2 are valid offsets for line 1
136 _ = ppt.translate(LinePosition(line=1, offset=3))
138 def test_offset_with_line_in_middle_part(self, ph_config):
139 ppt = make_ppt(t"a\nb{0}cd\ne{1}\nfe", ph_config)
140 assert ppt.translate(
141 LinePosition(line=2, offset=1 + len(ph_config.make_placeholder(0)) + 2)
142 ) == PartPosition(s_index=1, offset=2)
144 def test_empty_strings(self, ph_config):
145 ppt = make_ppt(t"{0}", ph_config)
146 assert ppt.translate(LinePosition(line=1, offset=0)) == PartPosition(
147 s_index=0, offset=0
148 ), "the interpolation starts at the end of the empty preceding string"
149 assert ppt.translate(
150 LinePosition(line=1, offset=len(ph_config.make_placeholder(0)))
151 ) == PartPosition(s_index=1, offset=0), (
152 "end of interpolation is start of tail string"
153 )
154 with pytest.raises(ValueError, match="Offset exceeds reachable"):
155 # Cannot go past end of the template.
156 _ = ppt.translate(
157 LinePosition(line=1, offset=len(ph_config.make_placeholder(0)) + 1)
158 )
160 def test_empty_string(self, ph_config):
161 ppt = make_ppt(t"", ph_config)
162 assert ppt.translate(LinePosition(line=1, offset=0)) == PartPosition(
163 s_index=0, offset=0
164 )
165 with pytest.raises(ValueError, match="Offset exceeds reachable"):
166 # Cannot go past end of the template.
167 _ = ppt.translate(LinePosition(line=1, offset=1))
169 def test_empty_line(self, ph_config):
170 ppt = make_ppt(t"\n", ph_config)
171 assert ppt.translate(LinePosition(line=1, offset=0)) == PartPosition(
172 s_index=0, offset=0
173 )
174 with pytest.raises(ValueError, match="Offset exceeds reachable"):
175 # line 1 is empty, cannot offset anything
176 _ = ppt.translate(LinePosition(line=1, offset=1))
177 assert ppt.translate(LinePosition(line=2, offset=0)) == PartPosition(
178 s_index=0, offset=1
179 ), "To skip over empty line just skip over newline"
180 with pytest.raises(ValueError, match="Offset exceeds reachable"):
181 # line 2 is empty, cannot offset anything
182 _ = ppt.translate(LinePosition(line=2, offset=1))
184 def test_bad_parser_pos_check_bounds(self, ph_config):
185 ppt = make_ppt(t"abc\ndef", ph_config)
187 with pytest.raises(ValueError, match="Line does not exist"):
188 _ = ppt.translate(LinePosition(line=3, offset=0))
189 with pytest.raises(ValueError, match="Unreachable line number"):
190 _ = ppt.translate(LinePosition(line=0, offset=0))
191 with pytest.raises(ValueError, match="Unreachable offset"):
192 _ = ppt.translate(LinePosition(line=1, offset=-1))
193 with pytest.raises(ValueError, match="Unreachable offset"):
194 _ = ppt.translate(LinePosition(line=2, offset=-1))
195 with pytest.raises(ValueError, match="Offset exceeds reachable"):
196 _ = ppt.translate(LinePosition(line=1, offset=100))
197 with pytest.raises(ValueError, match="Offset exceeds reachable"):
198 _ = ppt.translate(LinePosition(line=2, offset=100))
200 def test_bad_parser_pos_cannot_offset_interpolation(self, ph_config):
201 ppt = make_ppt(t"abc\n{0}def", ph_config)
203 with pytest.raises(
204 ValueError,
205 match=r"Positions inside interpolation placeholders are undefined.",
206 ):
207 _ = ppt.translate(LinePosition(line=2, offset=1))
208 with pytest.raises(
209 ValueError,
210 match=r"Positions inside interpolation placeholders are undefined.",
211 ):
212 _ = ppt.translate(
213 LinePosition(line=2, offset=len(ph_config.make_placeholder(0)) - 1)
214 )