1import pytest 2 3from jinja2 import DictLoader 4from jinja2 import Environment 5from jinja2 import PrefixLoader 6from jinja2 import Template 7from jinja2 import TemplateAssertionError 8from jinja2 import TemplateNotFound 9from jinja2 import TemplateSyntaxError 10 11 12class TestCorner: 13 def test_assigned_scoping(self, env): 14 t = env.from_string( 15 """ 16 {%- for item in (1, 2, 3, 4) -%} 17 [{{ item }}] 18 {%- endfor %} 19 {{- item -}} 20 """ 21 ) 22 assert t.render(item=42) == "[1][2][3][4]42" 23 24 t = env.from_string( 25 """ 26 {%- for item in (1, 2, 3, 4) -%} 27 [{{ item }}] 28 {%- endfor %} 29 {%- set item = 42 %} 30 {{- item -}} 31 """ 32 ) 33 assert t.render() == "[1][2][3][4]42" 34 35 t = env.from_string( 36 """ 37 {%- set item = 42 %} 38 {%- for item in (1, 2, 3, 4) -%} 39 [{{ item }}] 40 {%- endfor %} 41 {{- item -}} 42 """ 43 ) 44 assert t.render() == "[1][2][3][4]42" 45 46 def test_closure_scoping(self, env): 47 t = env.from_string( 48 """ 49 {%- set wrapper = "<FOO>" %} 50 {%- for item in (1, 2, 3, 4) %} 51 {%- macro wrapper() %}[{{ item }}]{% endmacro %} 52 {{- wrapper() }} 53 {%- endfor %} 54 {{- wrapper -}} 55 """ 56 ) 57 assert t.render() == "[1][2][3][4]<FOO>" 58 59 t = env.from_string( 60 """ 61 {%- for item in (1, 2, 3, 4) %} 62 {%- macro wrapper() %}[{{ item }}]{% endmacro %} 63 {{- wrapper() }} 64 {%- endfor %} 65 {%- set wrapper = "<FOO>" %} 66 {{- wrapper -}} 67 """ 68 ) 69 assert t.render() == "[1][2][3][4]<FOO>" 70 71 t = env.from_string( 72 """ 73 {%- for item in (1, 2, 3, 4) %} 74 {%- macro wrapper() %}[{{ item }}]{% endmacro %} 75 {{- wrapper() }} 76 {%- endfor %} 77 {{- wrapper -}} 78 """ 79 ) 80 assert t.render(wrapper=23) == "[1][2][3][4]23" 81 82 83class TestBug: 84 def test_keyword_folding(self, env): 85 env = Environment() 86 env.filters["testing"] = lambda value, some: value + some 87 assert ( 88 env.from_string("{{ 'test'|testing(some='stuff') }}").render() 89 == "teststuff" 90 ) 91 92 def test_extends_output_bugs(self, env): 93 env = Environment( 94 loader=DictLoader({"parent.html": "(({% block title %}{% endblock %}))"}) 95 ) 96 97 t = env.from_string( 98 '{% if expr %}{% extends "parent.html" %}{% endif %}' 99 "[[{% block title %}title{% endblock %}]]" 100 "{% for item in [1, 2, 3] %}({{ item }}){% endfor %}" 101 ) 102 assert t.render(expr=False) == "[[title]](1)(2)(3)" 103 assert t.render(expr=True) == "((title))" 104 105 def test_urlize_filter_escaping(self, env): 106 tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}') 107 assert ( 108 tmpl.render() == '<a href="http://www.example.org/<foo" rel="noopener">' 109 "http://www.example.org/<foo</a>" 110 ) 111 112 def test_loop_call_loop(self, env): 113 tmpl = env.from_string( 114 """ 115 116 {% macro test() %} 117 {{ caller() }} 118 {% endmacro %} 119 120 {% for num1 in range(5) %} 121 {% call test() %} 122 {% for num2 in range(10) %} 123 {{ loop.index }} 124 {% endfor %} 125 {% endcall %} 126 {% endfor %} 127 128 """ 129 ) 130 131 assert tmpl.render().split() == [str(x) for x in range(1, 11)] * 5 132 133 def test_weird_inline_comment(self, env): 134 env = Environment(line_statement_prefix="%") 135 pytest.raises( 136 TemplateSyntaxError, 137 env.from_string, 138 "% for item in seq {# missing #}\n...% endfor", 139 ) 140 141 def test_old_macro_loop_scoping_bug(self, env): 142 tmpl = env.from_string( 143 "{% for i in (1, 2) %}{{ i }}{% endfor %}" 144 "{% macro i() %}3{% endmacro %}{{ i() }}" 145 ) 146 assert tmpl.render() == "123" 147 148 def test_partial_conditional_assignments(self, env): 149 tmpl = env.from_string("{% if b %}{% set a = 42 %}{% endif %}{{ a }}") 150 assert tmpl.render(a=23) == "23" 151 assert tmpl.render(b=True) == "42" 152 153 def test_stacked_locals_scoping_bug(self, env): 154 env = Environment(line_statement_prefix="#") 155 t = env.from_string( 156 """\ 157# for j in [1, 2]: 158# set x = 1 159# for i in [1, 2]: 160# print x 161# if i % 2 == 0: 162# set x = x + 1 163# endif 164# endfor 165# endfor 166# if a 167# print 'A' 168# elif b 169# print 'B' 170# elif c == d 171# print 'C' 172# else 173# print 'D' 174# endif 175 """ 176 ) 177 assert t.render(a=0, b=False, c=42, d=42.0) == "1111C" 178 179 def test_stacked_locals_scoping_bug_twoframe(self, env): 180 t = Template( 181 """ 182 {% set x = 1 %} 183 {% for item in foo %} 184 {% if item == 1 %} 185 {% set x = 2 %} 186 {% endif %} 187 {% endfor %} 188 {{ x }} 189 """ 190 ) 191 rv = t.render(foo=[1]).strip() 192 assert rv == "1" 193 194 def test_call_with_args(self, env): 195 t = Template( 196 """{% macro dump_users(users) -%} 197 <ul> 198 {%- for user in users -%} 199 <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li> 200 {%- endfor -%} 201 </ul> 202 {%- endmacro -%} 203 204 {% call(user) dump_users(list_of_user) -%} 205 <dl> 206 <dl>Realname</dl> 207 <dd>{{ user.realname|e }}</dd> 208 <dl>Description</dl> 209 <dd>{{ user.description }}</dd> 210 </dl> 211 {% endcall %}""" 212 ) 213 214 assert [ 215 x.strip() 216 for x in t.render( 217 list_of_user=[ 218 { 219 "username": "apo", 220 "realname": "something else", 221 "description": "test", 222 } 223 ] 224 ).splitlines() 225 ] == [ 226 "<ul><li><p>apo</p><dl>", 227 "<dl>Realname</dl>", 228 "<dd>something else</dd>", 229 "<dl>Description</dl>", 230 "<dd>test</dd>", 231 "</dl>", 232 "</li></ul>", 233 ] 234 235 def test_empty_if_condition_fails(self, env): 236 pytest.raises(TemplateSyntaxError, Template, "{% if %}....{% endif %}") 237 pytest.raises( 238 TemplateSyntaxError, Template, "{% if foo %}...{% elif %}...{% endif %}" 239 ) 240 pytest.raises(TemplateSyntaxError, Template, "{% for x in %}..{% endfor %}") 241 242 def test_recursive_loop_compile(self, env): 243 Template( 244 """ 245 {% for p in foo recursive%} 246 {{p.bar}} 247 {% for f in p.fields recursive%} 248 {{f.baz}} 249 {{p.bar}} 250 {% if f.rec %} 251 {{ loop(f.sub) }} 252 {% endif %} 253 {% endfor %} 254 {% endfor %} 255 """ 256 ) 257 Template( 258 """ 259 {% for p in foo%} 260 {{p.bar}} 261 {% for f in p.fields recursive%} 262 {{f.baz}} 263 {{p.bar}} 264 {% if f.rec %} 265 {{ loop(f.sub) }} 266 {% endif %} 267 {% endfor %} 268 {% endfor %} 269 """ 270 ) 271 272 def test_else_loop_bug(self, env): 273 t = Template( 274 """ 275 {% for x in y %} 276 {{ loop.index0 }} 277 {% else %} 278 {% for i in range(3) %}{{ i }}{% endfor %} 279 {% endfor %} 280 """ 281 ) 282 assert t.render(y=[]).strip() == "012" 283 284 def test_correct_prefix_loader_name(self, env): 285 env = Environment(loader=PrefixLoader({"foo": DictLoader({})})) 286 with pytest.raises(TemplateNotFound) as e: 287 env.get_template("foo/bar.html") 288 289 assert e.value.name == "foo/bar.html" 290 291 def test_contextfunction_callable_classes(self, env): 292 from jinja2.utils import contextfunction 293 294 class CallableClass: 295 @contextfunction 296 def __call__(self, ctx): 297 return ctx.resolve("hello") 298 299 tpl = Template("""{{ callableclass() }}""") 300 output = tpl.render(callableclass=CallableClass(), hello="TEST") 301 expected = "TEST" 302 303 assert output == expected 304 305 def test_block_set_with_extends(self): 306 env = Environment( 307 loader=DictLoader({"main": "{% block body %}[{{ x }}]{% endblock %}"}) 308 ) 309 t = env.from_string('{% extends "main" %}{% set x %}42{% endset %}') 310 assert t.render() == "[42]" 311 312 def test_nested_for_else(self, env): 313 tmpl = env.from_string( 314 "{% for x in y %}{{ loop.index0 }}{% else %}" 315 "{% for i in range(3) %}{{ i }}{% endfor %}" 316 "{% endfor %}" 317 ) 318 assert tmpl.render() == "012" 319 320 def test_macro_var_bug(self, env): 321 tmpl = env.from_string( 322 """ 323 {% set i = 1 %} 324 {% macro test() %} 325 {% for i in range(0, 10) %}{{ i }}{% endfor %} 326 {% endmacro %}{{ test() }} 327 """ 328 ) 329 assert tmpl.render().strip() == "0123456789" 330 331 def test_macro_var_bug_advanced(self, env): 332 tmpl = env.from_string( 333 """ 334 {% macro outer() %} 335 {% set i = 1 %} 336 {% macro test() %} 337 {% for i in range(0, 10) %}{{ i }}{% endfor %} 338 {% endmacro %}{{ test() }} 339 {% endmacro %}{{ outer() }} 340 """ 341 ) 342 assert tmpl.render().strip() == "0123456789" 343 344 def test_callable_defaults(self): 345 env = Environment() 346 env.globals["get_int"] = lambda: 42 347 t = env.from_string( 348 """ 349 {% macro test(a, b, c=get_int()) -%} 350 {{ a + b + c }} 351 {%- endmacro %} 352 {{ test(1, 2) }}|{{ test(1, 2, 3) }} 353 """ 354 ) 355 assert t.render().strip() == "45|6" 356 357 def test_macro_escaping(self): 358 env = Environment( 359 autoescape=lambda x: False, extensions=["jinja2.ext.autoescape"] 360 ) 361 template = "{% macro m() %}<html>{% endmacro %}" 362 template += "{% autoescape true %}{{ m() }}{% endautoescape %}" 363 assert env.from_string(template).render() 364 365 def test_macro_scoping(self, env): 366 tmpl = env.from_string( 367 """ 368 {% set n=[1,2,3,4,5] %} 369 {% for n in [[1,2,3], [3,4,5], [5,6,7]] %} 370 371 {% macro x(l) %} 372 {{ l.pop() }} 373 {% if l %}{{ x(l) }}{% endif %} 374 {% endmacro %} 375 376 {{ x(n) }} 377 378 {% endfor %} 379 """ 380 ) 381 assert list(map(int, tmpl.render().split())) == [3, 2, 1, 5, 4, 3, 7, 6, 5] 382 383 def test_scopes_and_blocks(self): 384 env = Environment( 385 loader=DictLoader( 386 { 387 "a.html": """ 388 {%- set foo = 'bar' -%} 389 {% include 'x.html' -%} 390 """, 391 "b.html": """ 392 {%- set foo = 'bar' -%} 393 {% block test %}{% include 'x.html' %}{% endblock -%} 394 """, 395 "c.html": """ 396 {%- set foo = 'bar' -%} 397 {% block test %}{% set foo = foo 398 %}{% include 'x.html' %}{% endblock -%} 399 """, 400 "x.html": """{{ foo }}|{{ test }}""", 401 } 402 ) 403 ) 404 405 a = env.get_template("a.html") 406 b = env.get_template("b.html") 407 c = env.get_template("c.html") 408 409 assert a.render(test="x").strip() == "bar|x" 410 assert b.render(test="x").strip() == "bar|x" 411 assert c.render(test="x").strip() == "bar|x" 412 413 def test_scopes_and_include(self): 414 env = Environment( 415 loader=DictLoader( 416 { 417 "include.html": "{{ var }}", 418 "base.html": '{% include "include.html" %}', 419 "child.html": '{% extends "base.html" %}{% set var = 42 %}', 420 } 421 ) 422 ) 423 t = env.get_template("child.html") 424 assert t.render() == "42" 425 426 def test_caller_scoping(self, env): 427 t = env.from_string( 428 """ 429 {% macro detail(icon, value) -%} 430 {% if value -%} 431 <p><span class="fa fa-fw fa-{{ icon }}"></span> 432 {%- if caller is undefined -%} 433 {{ value }} 434 {%- else -%} 435 {{ caller(value, *varargs) }} 436 {%- endif -%}</p> 437 {%- endif %} 438 {%- endmacro %} 439 440 441 {% macro link_detail(icon, value, href) -%} 442 {% call(value, href) detail(icon, value, href) -%} 443 <a href="{{ href }}">{{ value }}</a> 444 {%- endcall %} 445 {%- endmacro %} 446 """ 447 ) 448 449 assert t.module.link_detail("circle", "Index", "/") == ( 450 '<p><span class="fa fa-fw fa-circle"></span><a href="/">Index</a></p>' 451 ) 452 453 def test_variable_reuse(self, env): 454 t = env.from_string("{% for x in x.y %}{{ x }}{% endfor %}") 455 assert t.render(x={"y": [0, 1, 2]}) == "012" 456 457 t = env.from_string("{% for x in x.y %}{{ loop.index0 }}|{{ x }}{% endfor %}") 458 assert t.render(x={"y": [0, 1, 2]}) == "0|01|12|2" 459 460 t = env.from_string("{% for x in x.y recursive %}{{ x }}{% endfor %}") 461 assert t.render(x={"y": [0, 1, 2]}) == "012" 462 463 def test_double_caller(self, env): 464 t = env.from_string( 465 "{% macro x(caller=none) %}[{% if caller %}" 466 "{{ caller() }}{% endif %}]{% endmacro %}" 467 "{{ x() }}{% call x() %}aha!{% endcall %}" 468 ) 469 assert t.render() == "[][aha!]" 470 471 def test_double_caller_no_default(self, env): 472 with pytest.raises(TemplateAssertionError) as exc_info: 473 env.from_string( 474 "{% macro x(caller) %}[{% if caller %}" 475 "{{ caller() }}{% endif %}]{% endmacro %}" 476 ) 477 assert exc_info.match( 478 r'"caller" argument must be omitted or ' r"be given a default" 479 ) 480 481 t = env.from_string( 482 "{% macro x(caller=none) %}[{% if caller %}" 483 "{{ caller() }}{% endif %}]{% endmacro %}" 484 ) 485 with pytest.raises(TypeError) as exc_info: 486 t.module.x(None, caller=lambda: 42) 487 assert exc_info.match( 488 r"\'x\' was invoked with two values for the " r"special caller argument" 489 ) 490 491 def test_macro_blocks(self, env): 492 t = env.from_string( 493 "{% macro x() %}{% block foo %}x{% endblock %}{% endmacro %}{{ x() }}" 494 ) 495 assert t.render() == "x" 496 497 def test_scoped_block(self, env): 498 t = env.from_string( 499 "{% set x = 1 %}{% with x = 2 %}{% block y scoped %}" 500 "{{ x }}{% endblock %}{% endwith %}" 501 ) 502 assert t.render() == "2" 503 504 def test_recursive_loop_filter(self, env): 505 t = env.from_string( 506 """ 507 <?xml version="1.0" encoding="UTF-8"?> 508 <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> 509 {%- for page in [site.root] if page.url != this recursive %} 510 <url><loc>{{ page.url }}</loc></url> 511 {{- loop(page.children) }} 512 {%- endfor %} 513 </urlset> 514 """ 515 ) 516 sm = t.render( 517 this="/foo", 518 site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}}, 519 ) 520 lines = [x.strip() for x in sm.splitlines() if x.strip()] 521 assert lines == [ 522 '<?xml version="1.0" encoding="UTF-8"?>', 523 '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">', 524 "<url><loc>/</loc></url>", 525 "<url><loc>/bar</loc></url>", 526 "</urlset>", 527 ] 528 529 def test_empty_if(self, env): 530 t = env.from_string("{% if foo %}{% else %}42{% endif %}") 531 assert t.render(foo=False) == "42" 532 533 def test_subproperty_if(self, env): 534 t = env.from_string( 535 "{% if object1.subproperty1 is eq object2.subproperty2 %}42{% endif %}" 536 ) 537 assert ( 538 t.render( 539 object1={"subproperty1": "value"}, object2={"subproperty2": "value"} 540 ) 541 == "42" 542 ) 543 544 def test_set_and_include(self): 545 env = Environment( 546 loader=DictLoader( 547 { 548 "inc": "bar", 549 "main": '{% set foo = "foo" %}{{ foo }}{% include "inc" %}', 550 } 551 ) 552 ) 553 assert env.get_template("main").render() == "foobar" 554 555 def test_loop_include(self): 556 env = Environment( 557 loader=DictLoader( 558 { 559 "inc": "{{ i }}", 560 "main": '{% for i in [1, 2, 3] %}{% include "inc" %}{% endfor %}', 561 } 562 ) 563 ) 564 assert env.get_template("main").render() == "123" 565 566 def test_grouper_repr(self): 567 from jinja2.filters import _GroupTuple 568 569 t = _GroupTuple("foo", [1, 2]) 570 assert t.grouper == "foo" 571 assert t.list == [1, 2] 572 assert repr(t) == "('foo', [1, 2])" 573 assert str(t) == "('foo', [1, 2])" 574 575 def test_custom_context(self, env): 576 from jinja2.runtime import Context 577 578 class MyContext(Context): 579 pass 580 581 class MyEnvironment(Environment): 582 context_class = MyContext 583 584 loader = DictLoader({"base": "{{ foobar }}", "test": '{% extends "base" %}'}) 585 env = MyEnvironment(loader=loader) 586 assert env.get_template("test").render(foobar="test") == "test" 587 588 def test_legacy_custom_context(self, env): 589 from jinja2.runtime import Context, missing 590 591 class MyContext(Context): 592 def resolve(self, name): 593 if name == "foo": 594 return 42 595 return super().resolve(name) 596 597 x = MyContext(env, parent={"bar": 23}, name="foo", blocks={}) 598 assert x._legacy_resolve_mode 599 assert x.resolve_or_missing("foo") == 42 600 assert x.resolve_or_missing("bar") == 23 601 assert x.resolve_or_missing("baz") is missing 602 603 def test_recursive_loop_bug(self, env): 604 tmpl = env.from_string( 605 "{%- for value in values recursive %}1{% else %}0{% endfor -%}" 606 ) 607 assert tmpl.render(values=[]) == "0" 608 609 def test_markup_and_chainable_undefined(self): 610 from jinja2 import Markup 611 from jinja2.runtime import ChainableUndefined 612 613 assert str(Markup(ChainableUndefined())) == "" 614