• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/&lt;foo" rel="noopener">'
109            "http://www.example.org/&lt;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