1import pytest 2 3from jinja2 import Environment 4from jinja2 import nodes 5from jinja2 import Template 6from jinja2 import TemplateSyntaxError 7from jinja2 import UndefinedError 8from jinja2.lexer import Token 9from jinja2.lexer import TOKEN_BLOCK_BEGIN 10from jinja2.lexer import TOKEN_BLOCK_END 11from jinja2.lexer import TOKEN_EOF 12from jinja2.lexer import TokenStream 13 14 15class TestTokenStream: 16 test_tokens = [ 17 Token(1, TOKEN_BLOCK_BEGIN, ""), 18 Token(2, TOKEN_BLOCK_END, ""), 19 ] 20 21 def test_simple(self, env): 22 ts = TokenStream(self.test_tokens, "foo", "bar") 23 assert ts.current.type is TOKEN_BLOCK_BEGIN 24 assert bool(ts) 25 assert not bool(ts.eos) 26 next(ts) 27 assert ts.current.type is TOKEN_BLOCK_END 28 assert bool(ts) 29 assert not bool(ts.eos) 30 next(ts) 31 assert ts.current.type is TOKEN_EOF 32 assert not bool(ts) 33 assert bool(ts.eos) 34 35 def test_iter(self, env): 36 token_types = [t.type for t in TokenStream(self.test_tokens, "foo", "bar")] 37 assert token_types == [ 38 "block_begin", 39 "block_end", 40 ] 41 42 43class TestLexer: 44 def test_raw1(self, env): 45 tmpl = env.from_string( 46 "{% raw %}foo{% endraw %}|" 47 "{%raw%}{{ bar }}|{% baz %}{% endraw %}" 48 ) 49 assert tmpl.render() == "foo|{{ bar }}|{% baz %}" 50 51 def test_raw2(self, env): 52 tmpl = env.from_string("1 {%- raw -%} 2 {%- endraw -%} 3") 53 assert tmpl.render() == "123" 54 55 def test_raw3(self, env): 56 # The second newline after baz exists because it is AFTER the 57 # {% raw %} and is ignored. 58 env = Environment(lstrip_blocks=True, trim_blocks=True) 59 tmpl = env.from_string("bar\n{% raw %}\n {{baz}}2 spaces\n{% endraw %}\nfoo") 60 assert tmpl.render(baz="test") == "bar\n\n {{baz}}2 spaces\nfoo" 61 62 def test_raw4(self, env): 63 # The trailing dash of the {% raw -%} cleans both the spaces and 64 # newlines up to the first character of data. 65 env = Environment(lstrip_blocks=True, trim_blocks=False) 66 tmpl = env.from_string( 67 "bar\n{%- raw -%}\n\n \n 2 spaces\n space{%- endraw -%}\nfoo" 68 ) 69 assert tmpl.render() == "bar2 spaces\n spacefoo" 70 71 def test_balancing(self, env): 72 env = Environment("{%", "%}", "${", "}") 73 tmpl = env.from_string( 74 """{% for item in seq 75 %}${{'foo': item}|upper}{% endfor %}""" 76 ) 77 assert tmpl.render(seq=list(range(3))) == "{'FOO': 0}{'FOO': 1}{'FOO': 2}" 78 79 def test_comments(self, env): 80 env = Environment("<!--", "-->", "{", "}") 81 tmpl = env.from_string( 82 """\ 83<ul> 84<!--- for item in seq --> 85 <li>{item}</li> 86<!--- endfor --> 87</ul>""" 88 ) 89 assert tmpl.render(seq=list(range(3))) == ( 90 "<ul>\n <li>0</li>\n <li>1</li>\n <li>2</li>\n</ul>" 91 ) 92 93 def test_string_escapes(self, env): 94 for char in "\0", "\u2668", "\xe4", "\t", "\r", "\n": 95 tmpl = env.from_string(f"{{{{ {char!r} }}}}") 96 assert tmpl.render() == char 97 assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == "\u2668" 98 99 def test_bytefallback(self, env): 100 from pprint import pformat 101 102 tmpl = env.from_string("""{{ 'foo'|pprint }}|{{ 'bär'|pprint }}""") 103 assert tmpl.render() == pformat("foo") + "|" + pformat("bär") 104 105 def test_operators(self, env): 106 from jinja2.lexer import operators 107 108 for test, expect in operators.items(): 109 if test in "([{}])": 110 continue 111 stream = env.lexer.tokenize(f"{{{{ {test} }}}}") 112 next(stream) 113 assert stream.current.type == expect 114 115 def test_normalizing(self, env): 116 for seq in "\r", "\r\n", "\n": 117 env = Environment(newline_sequence=seq) 118 tmpl = env.from_string("1\n2\r\n3\n4\n") 119 result = tmpl.render() 120 assert result.replace(seq, "X") == "1X2X3X4" 121 122 def test_trailing_newline(self, env): 123 for keep in [True, False]: 124 env = Environment(keep_trailing_newline=keep) 125 for template, expected in [ 126 ("", {}), 127 ("no\nnewline", {}), 128 ("with\nnewline\n", {False: "with\nnewline"}), 129 ("with\nseveral\n\n\n", {False: "with\nseveral\n\n"}), 130 ]: 131 tmpl = env.from_string(template) 132 expect = expected.get(keep, template) 133 result = tmpl.render() 134 assert result == expect, (keep, template, result, expect) 135 136 @pytest.mark.parametrize( 137 ("name", "valid"), 138 [ 139 ("foo", True), 140 ("föö", True), 141 ("き", True), 142 ("_", True), 143 ("1a", False), # invalid ascii start 144 ("a-", False), # invalid ascii continue 145 ("\U0001f40da", False), # invalid unicode start 146 ("a\U0001f40d", False), # invalid unicode continue 147 # start characters not matched by \w 148 ("\u1885", True), 149 ("\u1886", True), 150 ("\u2118", True), 151 ("\u212e", True), 152 # continue character not matched by \w 153 ("\xb7", False), 154 ("a\xb7", True), 155 ], 156 ) 157 def test_name(self, env, name, valid): 158 t = "{{ " + name + " }}" 159 160 if valid: 161 # valid for version being tested, shouldn't raise 162 env.from_string(t) 163 else: 164 pytest.raises(TemplateSyntaxError, env.from_string, t) 165 166 def test_lineno_with_strip(self, env): 167 tokens = env.lex( 168 """\ 169<html> 170 <body> 171 {%- block content -%} 172 <hr> 173 {{ item }} 174 {% endblock %} 175 </body> 176</html>""" 177 ) 178 for tok in tokens: 179 lineno, token_type, value = tok 180 if token_type == "name" and value == "item": 181 assert lineno == 5 182 break 183 184 185class TestParser: 186 def test_php_syntax(self, env): 187 env = Environment("<?", "?>", "<?=", "?>", "<!--", "-->") 188 tmpl = env.from_string( 189 """\ 190<!-- I'm a comment, I'm not interesting -->\ 191<? for item in seq -?> 192 <?= item ?> 193<?- endfor ?>""" 194 ) 195 assert tmpl.render(seq=list(range(5))) == "01234" 196 197 def test_erb_syntax(self, env): 198 env = Environment("<%", "%>", "<%=", "%>", "<%#", "%>") 199 tmpl = env.from_string( 200 """\ 201<%# I'm a comment, I'm not interesting %>\ 202<% for item in seq -%> 203 <%= item %> 204<%- endfor %>""" 205 ) 206 assert tmpl.render(seq=list(range(5))) == "01234" 207 208 def test_comment_syntax(self, env): 209 env = Environment("<!--", "-->", "${", "}", "<!--#", "-->") 210 tmpl = env.from_string( 211 """\ 212<!--# I'm a comment, I'm not interesting -->\ 213<!-- for item in seq ---> 214 ${item} 215<!--- endfor -->""" 216 ) 217 assert tmpl.render(seq=list(range(5))) == "01234" 218 219 def test_balancing(self, env): 220 tmpl = env.from_string("""{{{'foo':'bar'}.foo}}""") 221 assert tmpl.render() == "bar" 222 223 def test_start_comment(self, env): 224 tmpl = env.from_string( 225 """{# foo comment 226and bar comment #} 227{% macro blub() %}foo{% endmacro %} 228{{ blub() }}""" 229 ) 230 assert tmpl.render().strip() == "foo" 231 232 def test_line_syntax(self, env): 233 env = Environment("<%", "%>", "${", "}", "<%#", "%>", "%") 234 tmpl = env.from_string( 235 """\ 236<%# regular comment %> 237% for item in seq: 238 ${item} 239% endfor""" 240 ) 241 assert [ 242 int(x.strip()) for x in tmpl.render(seq=list(range(5))).split() 243 ] == list(range(5)) 244 245 env = Environment("<%", "%>", "${", "}", "<%#", "%>", "%", "##") 246 tmpl = env.from_string( 247 """\ 248<%# regular comment %> 249% for item in seq: 250 ${item} ## the rest of the stuff 251% endfor""" 252 ) 253 assert [ 254 int(x.strip()) for x in tmpl.render(seq=list(range(5))).split() 255 ] == list(range(5)) 256 257 def test_line_syntax_priority(self, env): 258 # XXX: why is the whitespace there in front of the newline? 259 env = Environment("{%", "%}", "${", "}", "/*", "*/", "##", "#") 260 tmpl = env.from_string( 261 """\ 262/* ignore me. 263 I'm a multiline comment */ 264## for item in seq: 265* ${item} # this is just extra stuff 266## endfor""" 267 ) 268 assert tmpl.render(seq=[1, 2]).strip() == "* 1\n* 2" 269 env = Environment("{%", "%}", "${", "}", "/*", "*/", "#", "##") 270 tmpl = env.from_string( 271 """\ 272/* ignore me. 273 I'm a multiline comment */ 274# for item in seq: 275* ${item} ## this is just extra stuff 276 ## extra stuff i just want to ignore 277# endfor""" 278 ) 279 assert tmpl.render(seq=[1, 2]).strip() == "* 1\n\n* 2" 280 281 def test_error_messages(self, env): 282 def assert_error(code, expected): 283 with pytest.raises(TemplateSyntaxError, match=expected): 284 Template(code) 285 286 assert_error( 287 "{% for item in seq %}...{% endif %}", 288 "Encountered unknown tag 'endif'. Jinja was looking " 289 "for the following tags: 'endfor' or 'else'. The " 290 "innermost block that needs to be closed is 'for'.", 291 ) 292 assert_error( 293 "{% if foo %}{% for item in seq %}...{% endfor %}{% endfor %}", 294 "Encountered unknown tag 'endfor'. Jinja was looking for " 295 "the following tags: 'elif' or 'else' or 'endif'. The " 296 "innermost block that needs to be closed is 'if'.", 297 ) 298 assert_error( 299 "{% if foo %}", 300 "Unexpected end of template. Jinja was looking for the " 301 "following tags: 'elif' or 'else' or 'endif'. The " 302 "innermost block that needs to be closed is 'if'.", 303 ) 304 assert_error( 305 "{% for item in seq %}", 306 "Unexpected end of template. Jinja was looking for the " 307 "following tags: 'endfor' or 'else'. The innermost block " 308 "that needs to be closed is 'for'.", 309 ) 310 assert_error( 311 "{% block foo-bar-baz %}", 312 "Block names in Jinja have to be valid Python identifiers " 313 "and may not contain hyphens, use an underscore instead.", 314 ) 315 assert_error("{% unknown_tag %}", "Encountered unknown tag 'unknown_tag'.") 316 317 318class TestSyntax: 319 def test_call(self, env): 320 env = Environment() 321 env.globals["foo"] = lambda a, b, c, e, g: a + b + c + e + g 322 tmpl = env.from_string("{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}") 323 assert tmpl.render() == "abdfh" 324 325 def test_slicing(self, env): 326 tmpl = env.from_string("{{ [1, 2, 3][:] }}|{{ [1, 2, 3][::-1] }}") 327 assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]" 328 329 def test_attr(self, env): 330 tmpl = env.from_string("{{ foo.bar }}|{{ foo['bar'] }}") 331 assert tmpl.render(foo={"bar": 42}) == "42|42" 332 333 def test_subscript(self, env): 334 tmpl = env.from_string("{{ foo[0] }}|{{ foo[-1] }}") 335 assert tmpl.render(foo=[0, 1, 2]) == "0|2" 336 337 def test_tuple(self, env): 338 tmpl = env.from_string("{{ () }}|{{ (1,) }}|{{ (1, 2) }}") 339 assert tmpl.render() == "()|(1,)|(1, 2)" 340 341 def test_math(self, env): 342 tmpl = env.from_string("{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}") 343 assert tmpl.render() == "1.5|8" 344 345 def test_div(self, env): 346 tmpl = env.from_string("{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}") 347 assert tmpl.render() == "1|1.5|1" 348 349 def test_unary(self, env): 350 tmpl = env.from_string("{{ +3 }}|{{ -3 }}") 351 assert tmpl.render() == "3|-3" 352 353 def test_concat(self, env): 354 tmpl = env.from_string("{{ [1, 2] ~ 'foo' }}") 355 assert tmpl.render() == "[1, 2]foo" 356 357 @pytest.mark.parametrize( 358 ("a", "op", "b"), 359 [ 360 (1, ">", 0), 361 (1, ">=", 1), 362 (2, "<", 3), 363 (3, "<=", 4), 364 (4, "==", 4), 365 (4, "!=", 5), 366 ], 367 ) 368 def test_compare(self, env, a, op, b): 369 t = env.from_string(f"{{{{ {a} {op} {b} }}}}") 370 assert t.render() == "True" 371 372 def test_compare_parens(self, env): 373 t = env.from_string("{{ i * (j < 5) }}") 374 assert t.render(i=2, j=3) == "2" 375 376 @pytest.mark.parametrize( 377 ("src", "expect"), 378 [ 379 ("{{ 4 < 2 < 3 }}", "False"), 380 ("{{ a < b < c }}", "False"), 381 ("{{ 4 > 2 > 3 }}", "False"), 382 ("{{ a > b > c }}", "False"), 383 ("{{ 4 > 2 < 3 }}", "True"), 384 ("{{ a > b < c }}", "True"), 385 ], 386 ) 387 def test_compare_compound(self, env, src, expect): 388 t = env.from_string(src) 389 assert t.render(a=4, b=2, c=3) == expect 390 391 def test_inop(self, env): 392 tmpl = env.from_string("{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}") 393 assert tmpl.render() == "True|False" 394 395 @pytest.mark.parametrize("value", ("[]", "{}", "()")) 396 def test_collection_literal(self, env, value): 397 t = env.from_string(f"{{{{ {value} }}}}") 398 assert t.render() == value 399 400 @pytest.mark.parametrize( 401 ("value", "expect"), 402 ( 403 ("1", "1"), 404 ("123", "123"), 405 ("12_34_56", "123456"), 406 ("1.2", "1.2"), 407 ("34.56", "34.56"), 408 ("3_4.5_6", "34.56"), 409 ("1e0", "1.0"), 410 ("10e1", "100.0"), 411 ("2.5e100", "2.5e+100"), 412 ("2.5e+100", "2.5e+100"), 413 ("25.6e-10", "2.56e-09"), 414 ("1_2.3_4e5_6", "1.234e+57"), 415 ), 416 ) 417 def test_numeric_literal(self, env, value, expect): 418 t = env.from_string(f"{{{{ {value} }}}}") 419 assert t.render() == expect 420 421 def test_bool(self, env): 422 tmpl = env.from_string( 423 "{{ true and false }}|{{ false or true }}|{{ not false }}" 424 ) 425 assert tmpl.render() == "False|True|True" 426 427 def test_grouping(self, env): 428 tmpl = env.from_string( 429 "{{ (true and false) or (false and true) and not false }}" 430 ) 431 assert tmpl.render() == "False" 432 433 def test_django_attr(self, env): 434 tmpl = env.from_string("{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}") 435 assert tmpl.render() == "1|1" 436 437 def test_conditional_expression(self, env): 438 tmpl = env.from_string("""{{ 0 if true else 1 }}""") 439 assert tmpl.render() == "0" 440 441 def test_short_conditional_expression(self, env): 442 tmpl = env.from_string("<{{ 1 if false }}>") 443 assert tmpl.render() == "<>" 444 445 tmpl = env.from_string("<{{ (1 if false).bar }}>") 446 pytest.raises(UndefinedError, tmpl.render) 447 448 def test_filter_priority(self, env): 449 tmpl = env.from_string('{{ "foo"|upper + "bar"|upper }}') 450 assert tmpl.render() == "FOOBAR" 451 452 def test_function_calls(self, env): 453 tests = [ 454 (True, "*foo, bar"), 455 (True, "*foo, *bar"), 456 (True, "**foo, *bar"), 457 (True, "**foo, bar"), 458 (True, "**foo, **bar"), 459 (True, "**foo, bar=42"), 460 (False, "foo, bar"), 461 (False, "foo, bar=42"), 462 (False, "foo, bar=23, *args"), 463 (False, "foo, *args, bar=23"), 464 (False, "a, b=c, *d, **e"), 465 (False, "*foo, bar=42"), 466 (False, "*foo, **bar"), 467 (False, "*foo, bar=42, **baz"), 468 (False, "foo, *args, bar=23, **baz"), 469 ] 470 for should_fail, sig in tests: 471 if should_fail: 472 with pytest.raises(TemplateSyntaxError): 473 env.from_string(f"{{{{ foo({sig}) }}}}") 474 else: 475 env.from_string(f"foo({sig})") 476 477 def test_tuple_expr(self, env): 478 for tmpl in [ 479 "{{ () }}", 480 "{{ (1, 2) }}", 481 "{{ (1, 2,) }}", 482 "{{ 1, }}", 483 "{{ 1, 2 }}", 484 "{% for foo, bar in seq %}...{% endfor %}", 485 "{% for x in foo, bar %}...{% endfor %}", 486 "{% for x in foo, %}...{% endfor %}", 487 ]: 488 assert env.from_string(tmpl) 489 490 def test_trailing_comma(self, env): 491 tmpl = env.from_string("{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}") 492 assert tmpl.render().lower() == "(1, 2)|[1, 2]|{1: 2}" 493 494 def test_block_end_name(self, env): 495 env.from_string("{% block foo %}...{% endblock foo %}") 496 pytest.raises( 497 TemplateSyntaxError, env.from_string, "{% block x %}{% endblock y %}" 498 ) 499 500 def test_constant_casing(self, env): 501 for const in True, False, None: 502 const = str(const) 503 tmpl = env.from_string( 504 f"{{{{ {const} }}}}|{{{{ {const.lower()} }}}}|{{{{ {const.upper()} }}}}" 505 ) 506 assert tmpl.render() == f"{const}|{const}|" 507 508 def test_test_chaining(self, env): 509 pytest.raises( 510 TemplateSyntaxError, env.from_string, "{{ foo is string is sequence }}" 511 ) 512 assert env.from_string("{{ 42 is string or 42 is number }}").render() == "True" 513 514 def test_string_concatenation(self, env): 515 tmpl = env.from_string('{{ "foo" "bar" "baz" }}') 516 assert tmpl.render() == "foobarbaz" 517 518 def test_notin(self, env): 519 bar = range(100) 520 tmpl = env.from_string("""{{ not 42 in bar }}""") 521 assert tmpl.render(bar=bar) == "False" 522 523 def test_operator_precedence(self, env): 524 tmpl = env.from_string("""{{ 2 * 3 + 4 % 2 + 1 - 2 }}""") 525 assert tmpl.render() == "5" 526 527 def test_implicit_subscribed_tuple(self, env): 528 class Foo: 529 def __getitem__(self, x): 530 return x 531 532 t = env.from_string("{{ foo[1, 2] }}") 533 assert t.render(foo=Foo()) == "(1, 2)" 534 535 def test_raw2(self, env): 536 tmpl = env.from_string("{% raw %}{{ FOO }} and {% BAR %}{% endraw %}") 537 assert tmpl.render() == "{{ FOO }} and {% BAR %}" 538 539 def test_const(self, env): 540 tmpl = env.from_string( 541 "{{ true }}|{{ false }}|{{ none }}|" 542 "{{ none is defined }}|{{ missing is defined }}" 543 ) 544 assert tmpl.render() == "True|False|None|True|False" 545 546 def test_neg_filter_priority(self, env): 547 node = env.parse("{{ -1|foo }}") 548 assert isinstance(node.body[0].nodes[0], nodes.Filter) 549 assert isinstance(node.body[0].nodes[0].node, nodes.Neg) 550 551 def test_const_assign(self, env): 552 constass1 = """{% set true = 42 %}""" 553 constass2 = """{% for none in seq %}{% endfor %}""" 554 for tmpl in constass1, constass2: 555 pytest.raises(TemplateSyntaxError, env.from_string, tmpl) 556 557 def test_localset(self, env): 558 tmpl = env.from_string( 559 """{% set foo = 0 %}\ 560{% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\ 561{{ foo }}""" 562 ) 563 assert tmpl.render() == "0" 564 565 def test_parse_unary(self, env): 566 tmpl = env.from_string('{{ -foo["bar"] }}') 567 assert tmpl.render(foo={"bar": 42}) == "-42" 568 tmpl = env.from_string('{{ -foo["bar"]|abs }}') 569 assert tmpl.render(foo={"bar": 42}) == "42" 570 571 572class TestLstripBlocks: 573 def test_lstrip(self, env): 574 env = Environment(lstrip_blocks=True, trim_blocks=False) 575 tmpl = env.from_string(""" {% if True %}\n {% endif %}""") 576 assert tmpl.render() == "\n" 577 578 def test_lstrip_trim(self, env): 579 env = Environment(lstrip_blocks=True, trim_blocks=True) 580 tmpl = env.from_string(""" {% if True %}\n {% endif %}""") 581 assert tmpl.render() == "" 582 583 def test_no_lstrip(self, env): 584 env = Environment(lstrip_blocks=True, trim_blocks=False) 585 tmpl = env.from_string(""" {%+ if True %}\n {%+ endif %}""") 586 assert tmpl.render() == " \n " 587 588 def test_lstrip_blocks_false_with_no_lstrip(self, env): 589 # Test that + is a NOP (but does not cause an error) if lstrip_blocks=False 590 env = Environment(lstrip_blocks=False, trim_blocks=False) 591 tmpl = env.from_string(""" {% if True %}\n {% endif %}""") 592 assert tmpl.render() == " \n " 593 tmpl = env.from_string(""" {%+ if True %}\n {%+ endif %}""") 594 assert tmpl.render() == " \n " 595 596 def test_lstrip_endline(self, env): 597 env = Environment(lstrip_blocks=True, trim_blocks=False) 598 tmpl = env.from_string(""" hello{% if True %}\n goodbye{% endif %}""") 599 assert tmpl.render() == " hello\n goodbye" 600 601 def test_lstrip_inline(self, env): 602 env = Environment(lstrip_blocks=True, trim_blocks=False) 603 tmpl = env.from_string(""" {% if True %}hello {% endif %}""") 604 assert tmpl.render() == "hello " 605 606 def test_lstrip_nested(self, env): 607 env = Environment(lstrip_blocks=True, trim_blocks=False) 608 tmpl = env.from_string( 609 """ {% if True %}a {% if True %}b {% endif %}c {% endif %}""" 610 ) 611 assert tmpl.render() == "a b c " 612 613 def test_lstrip_left_chars(self, env): 614 env = Environment(lstrip_blocks=True, trim_blocks=False) 615 tmpl = env.from_string( 616 """ abc {% if True %} 617 hello{% endif %}""" 618 ) 619 assert tmpl.render() == " abc \n hello" 620 621 def test_lstrip_embeded_strings(self, env): 622 env = Environment(lstrip_blocks=True, trim_blocks=False) 623 tmpl = env.from_string(""" {% set x = " {% str %} " %}{{ x }}""") 624 assert tmpl.render() == " {% str %} " 625 626 def test_lstrip_preserve_leading_newlines(self, env): 627 env = Environment(lstrip_blocks=True, trim_blocks=False) 628 tmpl = env.from_string("""\n\n\n{% set hello = 1 %}""") 629 assert tmpl.render() == "\n\n\n" 630 631 def test_lstrip_comment(self, env): 632 env = Environment(lstrip_blocks=True, trim_blocks=False) 633 tmpl = env.from_string( 634 """ {# if True #} 635hello 636 {#endif#}""" 637 ) 638 assert tmpl.render() == "\nhello\n" 639 640 def test_lstrip_angle_bracket_simple(self, env): 641 env = Environment( 642 "<%", 643 "%>", 644 "${", 645 "}", 646 "<%#", 647 "%>", 648 "%", 649 "##", 650 lstrip_blocks=True, 651 trim_blocks=True, 652 ) 653 tmpl = env.from_string(""" <% if True %>hello <% endif %>""") 654 assert tmpl.render() == "hello " 655 656 def test_lstrip_angle_bracket_comment(self, env): 657 env = Environment( 658 "<%", 659 "%>", 660 "${", 661 "}", 662 "<%#", 663 "%>", 664 "%", 665 "##", 666 lstrip_blocks=True, 667 trim_blocks=True, 668 ) 669 tmpl = env.from_string(""" <%# if True %>hello <%# endif %>""") 670 assert tmpl.render() == "hello " 671 672 def test_lstrip_angle_bracket(self, env): 673 env = Environment( 674 "<%", 675 "%>", 676 "${", 677 "}", 678 "<%#", 679 "%>", 680 "%", 681 "##", 682 lstrip_blocks=True, 683 trim_blocks=True, 684 ) 685 tmpl = env.from_string( 686 """\ 687 <%# regular comment %> 688 <% for item in seq %> 689${item} ## the rest of the stuff 690 <% endfor %>""" 691 ) 692 assert tmpl.render(seq=range(5)) == "".join(f"{x}\n" for x in range(5)) 693 694 def test_lstrip_angle_bracket_compact(self, env): 695 env = Environment( 696 "<%", 697 "%>", 698 "${", 699 "}", 700 "<%#", 701 "%>", 702 "%", 703 "##", 704 lstrip_blocks=True, 705 trim_blocks=True, 706 ) 707 tmpl = env.from_string( 708 """\ 709 <%#regular comment%> 710 <%for item in seq%> 711${item} ## the rest of the stuff 712 <%endfor%>""" 713 ) 714 assert tmpl.render(seq=range(5)) == "".join(f"{x}\n" for x in range(5)) 715 716 def test_lstrip_blocks_outside_with_new_line(self): 717 env = Environment(lstrip_blocks=True, trim_blocks=False) 718 tmpl = env.from_string( 719 " {% if kvs %}(\n" 720 " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" 721 " ){% endif %}" 722 ) 723 out = tmpl.render(kvs=[("a", 1), ("b", 2)]) 724 assert out == "(\na=1 b=2 \n )" 725 726 def test_lstrip_trim_blocks_outside_with_new_line(self): 727 env = Environment(lstrip_blocks=True, trim_blocks=True) 728 tmpl = env.from_string( 729 " {% if kvs %}(\n" 730 " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" 731 " ){% endif %}" 732 ) 733 out = tmpl.render(kvs=[("a", 1), ("b", 2)]) 734 assert out == "(\na=1 b=2 )" 735 736 def test_lstrip_blocks_inside_with_new_line(self): 737 env = Environment(lstrip_blocks=True, trim_blocks=False) 738 tmpl = env.from_string( 739 " ({% if kvs %}\n" 740 " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" 741 " {% endif %})" 742 ) 743 out = tmpl.render(kvs=[("a", 1), ("b", 2)]) 744 assert out == " (\na=1 b=2 \n)" 745 746 def test_lstrip_trim_blocks_inside_with_new_line(self): 747 env = Environment(lstrip_blocks=True, trim_blocks=True) 748 tmpl = env.from_string( 749 " ({% if kvs %}\n" 750 " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n" 751 " {% endif %})" 752 ) 753 out = tmpl.render(kvs=[("a", 1), ("b", 2)]) 754 assert out == " (a=1 b=2 )" 755 756 def test_lstrip_blocks_without_new_line(self): 757 env = Environment(lstrip_blocks=True, trim_blocks=False) 758 tmpl = env.from_string( 759 " {% if kvs %}" 760 " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}" 761 " {% endif %}" 762 ) 763 out = tmpl.render(kvs=[("a", 1), ("b", 2)]) 764 assert out == " a=1 b=2 " 765 766 def test_lstrip_trim_blocks_without_new_line(self): 767 env = Environment(lstrip_blocks=True, trim_blocks=True) 768 tmpl = env.from_string( 769 " {% if kvs %}" 770 " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}" 771 " {% endif %}" 772 ) 773 out = tmpl.render(kvs=[("a", 1), ("b", 2)]) 774 assert out == " a=1 b=2 " 775 776 def test_lstrip_blocks_consume_after_without_new_line(self): 777 env = Environment(lstrip_blocks=True, trim_blocks=False) 778 tmpl = env.from_string( 779 " {% if kvs -%}" 780 " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor -%}" 781 " {% endif -%}" 782 ) 783 out = tmpl.render(kvs=[("a", 1), ("b", 2)]) 784 assert out == "a=1 b=2 " 785 786 def test_lstrip_trim_blocks_consume_before_without_new_line(self): 787 env = Environment(lstrip_blocks=False, trim_blocks=False) 788 tmpl = env.from_string( 789 " {%- if kvs %}" 790 " {%- for k, v in kvs %}{{ k }}={{ v }} {% endfor -%}" 791 " {%- endif %}" 792 ) 793 out = tmpl.render(kvs=[("a", 1), ("b", 2)]) 794 assert out == "a=1 b=2 " 795 796 def test_lstrip_trim_blocks_comment(self): 797 env = Environment(lstrip_blocks=True, trim_blocks=True) 798 tmpl = env.from_string(" {# 1 space #}\n {# 2 spaces #} {# 4 spaces #}") 799 out = tmpl.render() 800 assert out == " " * 4 801 802 def test_lstrip_trim_blocks_raw(self): 803 env = Environment(lstrip_blocks=True, trim_blocks=True) 804 tmpl = env.from_string("{{x}}\n{%- raw %} {% endraw -%}\n{{ y }}") 805 out = tmpl.render(x=1, y=2) 806 assert out == "1 2" 807 808 def test_php_syntax_with_manual(self, env): 809 env = Environment( 810 "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True 811 ) 812 tmpl = env.from_string( 813 """\ 814 <!-- I'm a comment, I'm not interesting --> 815 <? for item in seq -?> 816 <?= item ?> 817 <?- endfor ?>""" 818 ) 819 assert tmpl.render(seq=range(5)) == "01234" 820 821 def test_php_syntax(self, env): 822 env = Environment( 823 "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True 824 ) 825 tmpl = env.from_string( 826 """\ 827 <!-- I'm a comment, I'm not interesting --> 828 <? for item in seq ?> 829 <?= item ?> 830 <? endfor ?>""" 831 ) 832 assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5)) 833 834 def test_php_syntax_compact(self, env): 835 env = Environment( 836 "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True 837 ) 838 tmpl = env.from_string( 839 """\ 840 <!-- I'm a comment, I'm not interesting --> 841 <?for item in seq?> 842 <?=item?> 843 <?endfor?>""" 844 ) 845 assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5)) 846 847 def test_erb_syntax(self, env): 848 env = Environment( 849 "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True 850 ) 851 tmpl = env.from_string( 852 """\ 853<%# I'm a comment, I'm not interesting %> 854 <% for item in seq %> 855 <%= item %> 856 <% endfor %> 857""" 858 ) 859 assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5)) 860 861 def test_erb_syntax_with_manual(self, env): 862 env = Environment( 863 "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True 864 ) 865 tmpl = env.from_string( 866 """\ 867<%# I'm a comment, I'm not interesting %> 868 <% for item in seq -%> 869 <%= item %> 870 <%- endfor %>""" 871 ) 872 assert tmpl.render(seq=range(5)) == "01234" 873 874 def test_erb_syntax_no_lstrip(self, env): 875 env = Environment( 876 "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True 877 ) 878 tmpl = env.from_string( 879 """\ 880<%# I'm a comment, I'm not interesting %> 881 <%+ for item in seq -%> 882 <%= item %> 883 <%- endfor %>""" 884 ) 885 assert tmpl.render(seq=range(5)) == " 01234" 886 887 def test_comment_syntax(self, env): 888 env = Environment( 889 "<!--", 890 "-->", 891 "${", 892 "}", 893 "<!--#", 894 "-->", 895 lstrip_blocks=True, 896 trim_blocks=True, 897 ) 898 tmpl = env.from_string( 899 """\ 900<!--# I'm a comment, I'm not interesting -->\ 901<!-- for item in seq ---> 902 ${item} 903<!--- endfor -->""" 904 ) 905 assert tmpl.render(seq=range(5)) == "01234" 906 907 908class TestTrimBlocks: 909 def test_trim(self, env): 910 env = Environment(trim_blocks=True, lstrip_blocks=False) 911 tmpl = env.from_string(" {% if True %}\n {% endif %}") 912 assert tmpl.render() == " " 913 914 def test_no_trim(self, env): 915 env = Environment(trim_blocks=True, lstrip_blocks=False) 916 tmpl = env.from_string(" {% if True +%}\n {% endif %}") 917 assert tmpl.render() == " \n " 918 919 def test_no_trim_outer(self, env): 920 env = Environment(trim_blocks=True, lstrip_blocks=False) 921 tmpl = env.from_string("{% if True %}X{% endif +%}\nmore things") 922 assert tmpl.render() == "X\nmore things" 923 924 def test_lstrip_no_trim(self, env): 925 env = Environment(trim_blocks=True, lstrip_blocks=True) 926 tmpl = env.from_string(" {% if True +%}\n {% endif %}") 927 assert tmpl.render() == "\n" 928 929 def test_trim_blocks_false_with_no_trim(self, env): 930 # Test that + is a NOP (but does not cause an error) if trim_blocks=False 931 env = Environment(trim_blocks=False, lstrip_blocks=False) 932 tmpl = env.from_string(" {% if True %}\n {% endif %}") 933 assert tmpl.render() == " \n " 934 tmpl = env.from_string(" {% if True +%}\n {% endif %}") 935 assert tmpl.render() == " \n " 936 937 tmpl = env.from_string(" {# comment #}\n ") 938 assert tmpl.render() == " \n " 939 tmpl = env.from_string(" {# comment +#}\n ") 940 assert tmpl.render() == " \n " 941 942 tmpl = env.from_string(" {% raw %}{% endraw %}\n ") 943 assert tmpl.render() == " \n " 944 tmpl = env.from_string(" {% raw %}{% endraw +%}\n ") 945 assert tmpl.render() == " \n " 946 947 def test_trim_nested(self, env): 948 env = Environment(trim_blocks=True, lstrip_blocks=True) 949 tmpl = env.from_string( 950 " {% if True %}\na {% if True %}\nb {% endif %}\nc {% endif %}" 951 ) 952 assert tmpl.render() == "a b c " 953 954 def test_no_trim_nested(self, env): 955 env = Environment(trim_blocks=True, lstrip_blocks=True) 956 tmpl = env.from_string( 957 " {% if True +%}\na {% if True +%}\nb {% endif +%}\nc {% endif %}" 958 ) 959 assert tmpl.render() == "\na \nb \nc " 960 961 def test_comment_trim(self, env): 962 env = Environment(trim_blocks=True, lstrip_blocks=True) 963 tmpl = env.from_string(""" {# comment #}\n\n """) 964 assert tmpl.render() == "\n " 965 966 def test_comment_no_trim(self, env): 967 env = Environment(trim_blocks=True, lstrip_blocks=True) 968 tmpl = env.from_string(""" {# comment +#}\n\n """) 969 assert tmpl.render() == "\n\n " 970 971 def test_multiple_comment_trim_lstrip(self, env): 972 env = Environment(trim_blocks=True, lstrip_blocks=True) 973 tmpl = env.from_string( 974 " {# comment #}\n\n{# comment2 #}\n \n{# comment3 #}\n\n " 975 ) 976 assert tmpl.render() == "\n \n\n " 977 978 def test_multiple_comment_no_trim_lstrip(self, env): 979 env = Environment(trim_blocks=True, lstrip_blocks=True) 980 tmpl = env.from_string( 981 " {# comment +#}\n\n{# comment2 +#}\n \n{# comment3 +#}\n\n " 982 ) 983 assert tmpl.render() == "\n\n\n \n\n\n " 984 985 def test_raw_trim_lstrip(self, env): 986 env = Environment(trim_blocks=True, lstrip_blocks=True) 987 tmpl = env.from_string("{{x}}{% raw %}\n\n {% endraw %}\n\n{{ y }}") 988 assert tmpl.render(x=1, y=2) == "1\n\n\n2" 989 990 def test_raw_no_trim_lstrip(self, env): 991 env = Environment(trim_blocks=False, lstrip_blocks=True) 992 tmpl = env.from_string("{{x}}{% raw %}\n\n {% endraw +%}\n\n{{ y }}") 993 assert tmpl.render(x=1, y=2) == "1\n\n\n\n2" 994 995 # raw blocks do not process inner text, so start tag cannot ignore trim 996 with pytest.raises(TemplateSyntaxError): 997 tmpl = env.from_string("{{x}}{% raw +%}\n\n {% endraw +%}\n\n{{ y }}") 998 999 def test_no_trim_angle_bracket(self, env): 1000 env = Environment( 1001 "<%", "%>", "${", "}", "<%#", "%>", lstrip_blocks=True, trim_blocks=True, 1002 ) 1003 tmpl = env.from_string(" <% if True +%>\n\n <% endif %>") 1004 assert tmpl.render() == "\n\n" 1005 1006 tmpl = env.from_string(" <%# comment +%>\n\n ") 1007 assert tmpl.render() == "\n\n " 1008 1009 def test_no_trim_php_syntax(self, env): 1010 env = Environment( 1011 "<?", 1012 "?>", 1013 "<?=", 1014 "?>", 1015 "<!--", 1016 "-->", 1017 lstrip_blocks=False, 1018 trim_blocks=True, 1019 ) 1020 tmpl = env.from_string(" <? if True +?>\n\n <? endif ?>") 1021 assert tmpl.render() == " \n\n " 1022 tmpl = env.from_string(" <!-- comment +-->\n\n ") 1023 assert tmpl.render() == " \n\n " 1024