Coverage for tdom / parser_test.py: 96%

143 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-17 23:32 +0000

1import pytest 

2 

3from .parser import TemplateParser 

4from .placeholders import TemplateRef 

5from .tnodes import ( 

6 TComment, 

7 TComponent, 

8 TDocumentType, 

9 TElement, 

10 TFragment, 

11 TInterpolatedAttribute, 

12 TLiteralAttribute, 

13 TSpreadAttribute, 

14 TTemplatedAttribute, 

15 TText, 

16) 

17 

18 

19def test_parse_empty(): 

20 node = TemplateParser.parse(t"") 

21 assert node == TFragment() 

22 

23 

24def test_parse_text(): 

25 node = TemplateParser.parse(t"Hello, world!") 

26 assert node == TText.literal("Hello, world!") 

27 

28 

29def test_parse_text_with_entities(): 

30 node = TemplateParser.parse(t"Panini's") 

31 assert node == TText.literal("Panini's") 

32 

33 

34def test_parse_void_element(): 

35 node = TemplateParser.parse(t"<br>") 

36 assert node == TElement("br") 

37 

38 

39def test_parse_void_element_self_closed(): 

40 node = TemplateParser.parse(t"<br />") 

41 assert node == TElement("br") 

42 

43 

44def test_parse_uppercase_void_element(): 

45 node = TemplateParser.parse(t"<BR>") 

46 assert node == TElement("br") 

47 

48 

49def test_parse_standard_element_with_text(): 

50 node = TemplateParser.parse(t"<div>Hello, world!</div>") 

51 assert node == TElement("div", children=(TText.literal("Hello, world!"),)) 

52 

53 

54def test_parse_nested_elements(): 

55 node = TemplateParser.parse(t"<div><span>Nested</span> content</div>") 

56 assert node == TElement( 

57 "div", 

58 children=( 

59 TElement("span", children=(TText.literal("Nested"),)), 

60 TText.literal(" content"), 

61 ), 

62 ) 

63 

64 

65def test_parse_element_with_attributes(): 

66 node = TemplateParser.parse( 

67 t'<a href="https://example.com" target="_blank">Link</a>' 

68 ) 

69 assert node == TElement( 

70 "a", 

71 attrs=( 

72 TLiteralAttribute("href", "https://example.com"), 

73 TLiteralAttribute("target", "_blank"), 

74 ), 

75 children=(TText.literal("Link"),), 

76 ) 

77 

78 

79def test_parse_element_attribute_order(): 

80 node = TemplateParser.parse(t'<a title="a" href="b" title="c"></a>') 

81 assert isinstance(node, TElement) 

82 assert node.attrs == ( 

83 TLiteralAttribute("title", "a"), 

84 TLiteralAttribute("href", "b"), 

85 TLiteralAttribute("title", "c"), 

86 ) 

87 

88 

89def test_parse_comment(): 

90 node = TemplateParser.parse(t"<!-- This is a comment -->") 

91 assert node == TComment.literal(" This is a comment ") 

92 

93 

94def test_parse_doctype(): 

95 node = TemplateParser.parse(t"<!DOCTYPE html>") 

96 assert node == TDocumentType("html") 

97 

98 

99def test_parse_multiple_voids(): 

100 node = TemplateParser.parse(t"<br><hr><hr /><hr /><br /><br><br>") 

101 assert node == TFragment( 

102 children=( 

103 TElement("br"), 

104 TElement("hr"), 

105 TElement("hr"), 

106 TElement("hr"), 

107 TElement("br"), 

108 TElement("br"), 

109 TElement("br"), 

110 ) 

111 ) 

112 

113 

114def test_parse_mixed_content(): 

115 node = TemplateParser.parse( 

116 t'<!DOCTYPE html><!-- Comment --><div class="container">' 

117 t"Hello, <br class='funky' />world <!-- neato -->!</div>" 

118 ) 

119 assert node == TFragment( 

120 children=( 

121 TDocumentType("html"), 

122 TComment.literal(" Comment "), 

123 TElement( 

124 "div", 

125 attrs=(TLiteralAttribute("class", "container"),), 

126 children=( 

127 TText.literal("Hello, "), 

128 TElement("br", attrs=(TLiteralAttribute("class", "funky"),)), 

129 TText.literal("world "), 

130 TComment.literal(" neato "), 

131 TText.literal("!"), 

132 ), 

133 ), 

134 ) 

135 ) 

136 

137 

138def test_parse_entities_are_escaped(): 

139 node = TemplateParser.parse(t"<p>&lt;/p&gt;</p>") 

140 assert node == TElement( 

141 "p", 

142 children=(TText.literal("</p>"),), 

143 ) 

144 

145 

146def test_parse_script_tag_content(): 

147 node = TemplateParser.parse( 

148 t"<script>if (a < b && c > d) { alert('wow'); } </script>" 

149 ) 

150 assert node == TElement( 

151 "script", 

152 children=(TText.literal("if (a < b && c > d) { alert('wow'); }"),), 

153 ) 

154 

155 

156def test_parse_script_with_entities(): 

157 # The <script> tag (and <style>) tag uses the CDATA content model. 

158 node = TemplateParser.parse(t"<script>var x = 'a &amp; b';</script>") 

159 assert node == TElement( 

160 "script", 

161 children=(TText.literal("var x = 'a &amp; b';"),), 

162 ) 

163 

164 

165def test_parse_textarea_tag_content(): 

166 node = TemplateParser.parse( 

167 t"<textarea>if (a < b && c > d) { alert('wow'); } </textarea>" 

168 ) 

169 assert node == TElement( 

170 "textarea", 

171 children=(TText.literal("if (a < b && c > d) { alert('wow'); }"),), 

172 ) 

173 

174 

175def test_parse_textarea_with_entities(): 

176 # The <textarea> (and <title>) tag uses the RCDATA content model. 

177 node = TemplateParser.parse(t"<textarea>var x = 'a &amp; b';</textarea>") 

178 assert node == TElement( 

179 "textarea", 

180 children=(TText.literal("var x = 'a & b';"),), 

181 ) 

182 

183 

184def test_parse_title_unusual(): 

185 node = TemplateParser.parse(t"<title>My & Awesome <Site></title>") 

186 assert node == TElement( 

187 "title", 

188 children=(TText.literal("My & Awesome <Site>"),), 

189 ) 

190 

191 

192def test_parse_mismatched_tags(): 

193 with pytest.raises(ValueError): 

194 _ = TemplateParser.parse(t"<div><span>Mismatched</div></span>") 

195 

196 

197def test_parse_unclosed_tag(): 

198 with pytest.raises(ValueError): 

199 _ = TemplateParser.parse(t"<div>Unclosed") 

200 

201 

202def test_parse_unexpected_closing_tag(): 

203 with pytest.raises(ValueError): 

204 _ = TemplateParser.parse(t"Unopened</div>") 

205 

206 

207def test_self_closing_tags(): 

208 node = TemplateParser.parse(t"<div/><p></p>") 

209 assert node == TFragment( 

210 children=( 

211 TElement("div"), 

212 TElement("p"), 

213 ) 

214 ) 

215 

216 

217def test_nested_self_closing_tags(): 

218 node = TemplateParser.parse(t"<div><br><div /><br></div>") 

219 assert node == TElement( 

220 "div", children=(TElement("br"), TElement("div"), TElement("br")) 

221 ) 

222 node = TemplateParser.parse(t"<div><div /></div>") 

223 assert node == TElement("div", children=(TElement("div"),)) 

224 

225 

226def test_self_closing_tags_unexpected_closing_tag(): 

227 with pytest.raises(ValueError): 

228 _ = TemplateParser.parse(t"<div /></div>") 

229 

230 

231def test_self_closing_void_tags_unexpected_closing_tag(): 

232 with pytest.raises(ValueError): 

233 _ = TemplateParser.parse(t"<input /></input>") 

234 

235 

236def test_literal_attributes(): 

237 node = TemplateParser.parse(t'<input type="text" disabled />') 

238 assert node == TElement( 

239 "input", 

240 attrs=( 

241 TLiteralAttribute("type", "text"), 

242 TLiteralAttribute("disabled", None), 

243 ), 

244 ) 

245 

246 

247def test_interpolated_attributes(): 

248 value1 = 42 

249 value2 = 99 

250 node = TemplateParser.parse(t'<div value1="{value1}" value2={value2} />') 

251 assert node == TElement( 

252 "div", 

253 attrs=( 

254 TInterpolatedAttribute("value1", 0), 

255 TInterpolatedAttribute("value2", 1), 

256 ), 

257 children=(), 

258 ) 

259 

260 

261def test_templated_attributes(): 

262 value1 = 42 

263 value2 = 99 

264 node = TemplateParser.parse( 

265 t'<div value1="{value1}-burrito" value2="neato-{value2}-wow" />' 

266 ) 

267 value1_ref = TemplateRef(strings=("", "-burrito"), i_indexes=(0,)) 

268 value2_ref = TemplateRef(strings=("neato-", "-wow"), i_indexes=(1,)) 

269 assert node == TElement( 

270 "div", 

271 attrs=( 

272 TTemplatedAttribute("value1", value1_ref), 

273 TTemplatedAttribute("value2", value2_ref), 

274 ), 

275 children=(), 

276 ) 

277 

278 

279def test_templated_attribute_name_error(): 

280 with pytest.raises(ValueError): 

281 attr_name = "some-attr" 

282 _ = TemplateParser.parse(t'<div {attr_name}="value" />') 

283 

284 

285def test_templated_attribute_name_and_value_error(): 

286 with pytest.raises(ValueError): 

287 attr_name = "some-attr" 

288 value = "value" 

289 _ = TemplateParser.parse(t'<div {attr_name}="{value}" />') 

290 

291 

292def test_spread_attribute(): 

293 props = "doesnt-matter-the-type" 

294 node = TemplateParser.parse(t"<div {props} />") 

295 assert node == TElement( 

296 "div", 

297 attrs=(TSpreadAttribute(i_index=0),), 

298 children=(), 

299 ) 

300 

301 

302def test_component_element_self_closing(): 

303 def Component(): 

304 pass 

305 

306 node = TemplateParser.parse(t"<{Component} />") 

307 assert node == TComponent(start_i_index=0) 

308 

309 

310def test_component_element_with_closing_tag(): 

311 def Component(): 

312 pass 

313 

314 node = TemplateParser.parse(t"<{Component}></{Component}>") 

315 assert node == TComponent(start_i_index=0, end_i_index=1) 

316 

317 

318def test_component_element_special_case_mismatched_closing_tag_still_parses(): 

319 def Component1(): 

320 pass 

321 

322 def Component2(): 

323 pass 

324 

325 node = TemplateParser.parse(t"<{Component1}></{Component2}>") 

326 assert node == TComponent(start_i_index=0, end_i_index=1) 

327 

328 

329def test_component_element_invalid_closing_tag(): 

330 def Component(): 

331 pass 

332 

333 with pytest.raises(ValueError): 

334 _ = TemplateParser.parse(t"<{Component}></div>") 

335 

336 

337def test_component_element_invalid_opening_tag(): 

338 def Component(): 

339 pass 

340 

341 with pytest.raises(ValueError): 

342 _ = TemplateParser.parse(t"<div></{Component}>")