import pytest from jinja2 import DictLoader from jinja2 import Environment from jinja2 import PrefixLoader from jinja2 import Template from jinja2 import TemplateAssertionError from jinja2 import TemplateNotFound from jinja2 import TemplateSyntaxError from jinja2.utils import pass_context class TestCorner: def test_assigned_scoping(self, env): t = env.from_string( """ {%- for item in (1, 2, 3, 4) -%} [{{ item }}] {%- endfor %} {{- item -}} """ ) assert t.render(item=42) == "[1][2][3][4]42" t = env.from_string( """ {%- for item in (1, 2, 3, 4) -%} [{{ item }}] {%- endfor %} {%- set item = 42 %} {{- item -}} """ ) assert t.render() == "[1][2][3][4]42" t = env.from_string( """ {%- set item = 42 %} {%- for item in (1, 2, 3, 4) -%} [{{ item }}] {%- endfor %} {{- item -}} """ ) assert t.render() == "[1][2][3][4]42" def test_closure_scoping(self, env): t = env.from_string( """ {%- set wrapper = "" %} {%- for item in (1, 2, 3, 4) %} {%- macro wrapper() %}[{{ item }}]{% endmacro %} {{- wrapper() }} {%- endfor %} {{- wrapper -}} """ ) assert t.render() == "[1][2][3][4]" t = env.from_string( """ {%- for item in (1, 2, 3, 4) %} {%- macro wrapper() %}[{{ item }}]{% endmacro %} {{- wrapper() }} {%- endfor %} {%- set wrapper = "" %} {{- wrapper -}} """ ) assert t.render() == "[1][2][3][4]" t = env.from_string( """ {%- for item in (1, 2, 3, 4) %} {%- macro wrapper() %}[{{ item }}]{% endmacro %} {{- wrapper() }} {%- endfor %} {{- wrapper -}} """ ) assert t.render(wrapper=23) == "[1][2][3][4]23" class TestBug: def test_keyword_folding(self, env): env = Environment() env.filters["testing"] = lambda value, some: value + some assert ( env.from_string("{{ 'test'|testing(some='stuff') }}").render() == "teststuff" ) def test_extends_output_bugs(self, env): env = Environment( loader=DictLoader({"parent.html": "(({% block title %}{% endblock %}))"}) ) t = env.from_string( '{% if expr %}{% extends "parent.html" %}{% endif %}' "[[{% block title %}title{% endblock %}]]" "{% for item in [1, 2, 3] %}({{ item }}){% endfor %}" ) assert t.render(expr=False) == "[[title]](1)(2)(3)" assert t.render(expr=True) == "((title))" def test_urlize_filter_escaping(self, env): tmpl = env.from_string('{{ "http://www.example.org/' "http://www.example.org/<foo" ) def test_urlize_filter_closing_punctuation(self, env): tmpl = env.from_string( '{{ "(see http://www.example.org/?page=subj_)"|urlize }}' ) assert tmpl.render() == ( '(see http://www.example.org/?page=subj_<desc.h>)' ) def test_loop_call_loop(self, env): tmpl = env.from_string( """ {% macro test() %} {{ caller() }} {% endmacro %} {% for num1 in range(5) %} {% call test() %} {% for num2 in range(10) %} {{ loop.index }} {% endfor %} {% endcall %} {% endfor %} """ ) assert tmpl.render().split() == [str(x) for x in range(1, 11)] * 5 def test_weird_inline_comment(self, env): env = Environment(line_statement_prefix="%") pytest.raises( TemplateSyntaxError, env.from_string, "% for item in seq {# missing #}\n...% endfor", ) def test_old_macro_loop_scoping_bug(self, env): tmpl = env.from_string( "{% for i in (1, 2) %}{{ i }}{% endfor %}" "{% macro i() %}3{% endmacro %}{{ i() }}" ) assert tmpl.render() == "123" def test_partial_conditional_assignments(self, env): tmpl = env.from_string("{% if b %}{% set a = 42 %}{% endif %}{{ a }}") assert tmpl.render(a=23) == "23" assert tmpl.render(b=True) == "42" def test_stacked_locals_scoping_bug(self, env): env = Environment(line_statement_prefix="#") t = env.from_string( """\ # for j in [1, 2]: # set x = 1 # for i in [1, 2]: # print x # if i % 2 == 0: # set x = x + 1 # endif # endfor # endfor # if a # print 'A' # elif b # print 'B' # elif c == d # print 'C' # else # print 'D' # endif """ ) assert t.render(a=0, b=False, c=42, d=42.0) == "1111C" def test_stacked_locals_scoping_bug_twoframe(self, env): t = Template( """ {% set x = 1 %} {% for item in foo %} {% if item == 1 %} {% set x = 2 %} {% endif %} {% endfor %} {{ x }} """ ) rv = t.render(foo=[1]).strip() assert rv == "1" def test_call_with_args(self, env): t = Template( """{% macro dump_users(users) -%}
    {%- for user in users -%}
  • {{ user.username|e }}

    {{ caller(user) }}
  • {%- endfor -%}
{%- endmacro -%} {% call(user) dump_users(list_of_user) -%}
Realname
{{ user.realname|e }}
Description
{{ user.description }}
{% endcall %}""" ) assert [ x.strip() for x in t.render( list_of_user=[ { "username": "apo", "realname": "something else", "description": "test", } ] ).splitlines() ] == [ "
  • apo

    ", "
    Realname
    ", "
    something else
    ", "
    Description
    ", "
    test
    ", "
    ", "
", ] def test_empty_if_condition_fails(self, env): pytest.raises(TemplateSyntaxError, Template, "{% if %}....{% endif %}") pytest.raises( TemplateSyntaxError, Template, "{% if foo %}...{% elif %}...{% endif %}" ) pytest.raises(TemplateSyntaxError, Template, "{% for x in %}..{% endfor %}") def test_recursive_loop_compile(self, env): Template( """ {% for p in foo recursive%} {{p.bar}} {% for f in p.fields recursive%} {{f.baz}} {{p.bar}} {% if f.rec %} {{ loop(f.sub) }} {% endif %} {% endfor %} {% endfor %} """ ) Template( """ {% for p in foo%} {{p.bar}} {% for f in p.fields recursive%} {{f.baz}} {{p.bar}} {% if f.rec %} {{ loop(f.sub) }} {% endif %} {% endfor %} {% endfor %} """ ) def test_else_loop_bug(self, env): t = Template( """ {% for x in y %} {{ loop.index0 }} {% else %} {% for i in range(3) %}{{ i }}{% endfor %} {% endfor %} """ ) assert t.render(y=[]).strip() == "012" def test_correct_prefix_loader_name(self, env): env = Environment(loader=PrefixLoader({"foo": DictLoader({})})) with pytest.raises(TemplateNotFound) as e: env.get_template("foo/bar.html") assert e.value.name == "foo/bar.html" def test_pass_context_callable_class(self, env): class CallableClass: @pass_context def __call__(self, ctx): return ctx.resolve("hello") tpl = Template("""{{ callableclass() }}""") output = tpl.render(callableclass=CallableClass(), hello="TEST") expected = "TEST" assert output == expected def test_block_set_with_extends(self): env = Environment( loader=DictLoader({"main": "{% block body %}[{{ x }}]{% endblock %}"}) ) t = env.from_string('{% extends "main" %}{% set x %}42{% endset %}') assert t.render() == "[42]" def test_nested_for_else(self, env): tmpl = env.from_string( "{% for x in y %}{{ loop.index0 }}{% else %}" "{% for i in range(3) %}{{ i }}{% endfor %}" "{% endfor %}" ) assert tmpl.render() == "012" def test_macro_var_bug(self, env): tmpl = env.from_string( """ {% set i = 1 %} {% macro test() %} {% for i in range(0, 10) %}{{ i }}{% endfor %} {% endmacro %}{{ test() }} """ ) assert tmpl.render().strip() == "0123456789" def test_macro_var_bug_advanced(self, env): tmpl = env.from_string( """ {% macro outer() %} {% set i = 1 %} {% macro test() %} {% for i in range(0, 10) %}{{ i }}{% endfor %} {% endmacro %}{{ test() }} {% endmacro %}{{ outer() }} """ ) assert tmpl.render().strip() == "0123456789" def test_callable_defaults(self): env = Environment() env.globals["get_int"] = lambda: 42 t = env.from_string( """ {% macro test(a, b, c=get_int()) -%} {{ a + b + c }} {%- endmacro %} {{ test(1, 2) }}|{{ test(1, 2, 3) }} """ ) assert t.render().strip() == "45|6" def test_macro_escaping(self): env = Environment(autoescape=lambda x: False) template = "{% macro m() %}{% endmacro %}" template += "{% autoescape true %}{{ m() }}{% endautoescape %}" assert env.from_string(template).render() def test_macro_scoping(self, env): tmpl = env.from_string( """ {% set n=[1,2,3,4,5] %} {% for n in [[1,2,3], [3,4,5], [5,6,7]] %} {% macro x(l) %} {{ l.pop() }} {% if l %}{{ x(l) }}{% endif %} {% endmacro %} {{ x(n) }} {% endfor %} """ ) assert list(map(int, tmpl.render().split())) == [3, 2, 1, 5, 4, 3, 7, 6, 5] def test_scopes_and_blocks(self): env = Environment( loader=DictLoader( { "a.html": """ {%- set foo = 'bar' -%} {% include 'x.html' -%} """, "b.html": """ {%- set foo = 'bar' -%} {% block test %}{% include 'x.html' %}{% endblock -%} """, "c.html": """ {%- set foo = 'bar' -%} {% block test %}{% set foo = foo %}{% include 'x.html' %}{% endblock -%} """, "x.html": """{{ foo }}|{{ test }}""", } ) ) a = env.get_template("a.html") b = env.get_template("b.html") c = env.get_template("c.html") assert a.render(test="x").strip() == "bar|x" assert b.render(test="x").strip() == "bar|x" assert c.render(test="x").strip() == "bar|x" def test_scopes_and_include(self): env = Environment( loader=DictLoader( { "include.html": "{{ var }}", "base.html": '{% include "include.html" %}', "child.html": '{% extends "base.html" %}{% set var = 42 %}', } ) ) t = env.get_template("child.html") assert t.render() == "42" def test_caller_scoping(self, env): t = env.from_string( """ {% macro detail(icon, value) -%} {% if value -%}

{%- if caller is undefined -%} {{ value }} {%- else -%} {{ caller(value, *varargs) }} {%- endif -%}

{%- endif %} {%- endmacro %} {% macro link_detail(icon, value, href) -%} {% call(value, href) detail(icon, value, href) -%} {{ value }} {%- endcall %} {%- endmacro %} """ ) assert t.module.link_detail("circle", "Index", "/") == ( '

Index

' ) def test_variable_reuse(self, env): t = env.from_string("{% for x in x.y %}{{ x }}{% endfor %}") assert t.render(x={"y": [0, 1, 2]}) == "012" t = env.from_string("{% for x in x.y %}{{ loop.index0 }}|{{ x }}{% endfor %}") assert t.render(x={"y": [0, 1, 2]}) == "0|01|12|2" t = env.from_string("{% for x in x.y recursive %}{{ x }}{% endfor %}") assert t.render(x={"y": [0, 1, 2]}) == "012" def test_double_caller(self, env): t = env.from_string( "{% macro x(caller=none) %}[{% if caller %}" "{{ caller() }}{% endif %}]{% endmacro %}" "{{ x() }}{% call x() %}aha!{% endcall %}" ) assert t.render() == "[][aha!]" def test_double_caller_no_default(self, env): with pytest.raises(TemplateAssertionError) as exc_info: env.from_string( "{% macro x(caller) %}[{% if caller %}" "{{ caller() }}{% endif %}]{% endmacro %}" ) assert exc_info.match( r'"caller" argument must be omitted or ' r"be given a default" ) t = env.from_string( "{% macro x(caller=none) %}[{% if caller %}" "{{ caller() }}{% endif %}]{% endmacro %}" ) with pytest.raises(TypeError) as exc_info: t.module.x(None, caller=lambda: 42) assert exc_info.match( r"\'x\' was invoked with two values for the " r"special caller argument" ) def test_macro_blocks(self, env): t = env.from_string( "{% macro x() %}{% block foo %}x{% endblock %}{% endmacro %}{{ x() }}" ) assert t.render() == "x" def test_scoped_block(self, env): t = env.from_string( "{% set x = 1 %}{% with x = 2 %}{% block y scoped %}" "{{ x }}{% endblock %}{% endwith %}" ) assert t.render() == "2" def test_recursive_loop_filter(self, env): t = env.from_string( """ {%- for page in [site.root] if page.url != this recursive %} {{ page.url }} {{- loop(page.children) }} {%- endfor %} """ ) sm = t.render( this="/foo", site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}}, ) lines = [x.strip() for x in sm.splitlines() if x.strip()] assert lines == [ '', '', "/", "/bar", "", ] def test_empty_if(self, env): t = env.from_string("{% if foo %}{% else %}42{% endif %}") assert t.render(foo=False) == "42" def test_subproperty_if(self, env): t = env.from_string( "{% if object1.subproperty1 is eq object2.subproperty2 %}42{% endif %}" ) assert ( t.render( object1={"subproperty1": "value"}, object2={"subproperty2": "value"} ) == "42" ) def test_set_and_include(self): env = Environment( loader=DictLoader( { "inc": "bar", "main": '{% set foo = "foo" %}{{ foo }}{% include "inc" %}', } ) ) assert env.get_template("main").render() == "foobar" def test_loop_include(self): env = Environment( loader=DictLoader( { "inc": "{{ i }}", "main": '{% for i in [1, 2, 3] %}{% include "inc" %}{% endfor %}', } ) ) assert env.get_template("main").render() == "123" def test_grouper_repr(self): from jinja2.filters import _GroupTuple t = _GroupTuple("foo", [1, 2]) assert t.grouper == "foo" assert t.list == [1, 2] assert repr(t) == "('foo', [1, 2])" assert str(t) == "('foo', [1, 2])" def test_custom_context(self, env): from jinja2.runtime import Context class MyContext(Context): pass class MyEnvironment(Environment): context_class = MyContext loader = DictLoader({"base": "{{ foobar }}", "test": '{% extends "base" %}'}) env = MyEnvironment(loader=loader) assert env.get_template("test").render(foobar="test") == "test" def test_recursive_loop_bug(self, env): tmpl = env.from_string( "{%- for value in values recursive %}1{% else %}0{% endfor -%}" ) assert tmpl.render(values=[]) == "0" def test_markup_and_chainable_undefined(self): from markupsafe import Markup from jinja2.runtime import ChainableUndefined assert str(Markup(ChainableUndefined())) == "" def test_scoped_block_loop_vars(self, env): tmpl = env.from_string( """\ Start {% for i in ["foo", "bar"] -%} {% block body scoped -%} {{ loop.index }}) {{ i }}{% if loop.last %} last{% endif -%} {%- endblock %} {% endfor -%} End""" ) assert tmpl.render() == "Start\n1) foo\n2) bar last\nEnd" def test_pass_context_loop_vars(self, env): @pass_context def test(ctx): return f"{ctx['i']}{ctx['j']}" tmpl = env.from_string( """\ {% set i = 42 %} {%- for idx in range(2) -%} {{ i }}{{ j }} {% set i = idx -%} {%- set j = loop.index -%} {{ test() }} {{ i }}{{ j }} {% endfor -%} {{ i }}{{ j }}""" ) tmpl.globals["test"] = test assert tmpl.render() == "42\n01\n01\n42\n12\n12\n42" def test_pass_context_scoped_loop_vars(self, env): @pass_context def test(ctx): return f"{ctx['i']}" tmpl = env.from_string( """\ {% set i = 42 %} {%- for idx in range(2) -%} {{ i }} {%- set i = loop.index0 -%} {% block body scoped %} {{ test() }} {% endblock -%} {% endfor -%} {{ i }}""" ) tmpl.globals["test"] = test assert tmpl.render() == "42\n0\n42\n1\n42" def test_pass_context_in_blocks(self, env): @pass_context def test(ctx): return f"{ctx['i']}" tmpl = env.from_string( """\ {%- set i = 42 -%} {{ i }} {% block body -%} {% set i = 24 -%} {{ test() }} {% endblock -%} {{ i }}""" ) tmpl.globals["test"] = test assert tmpl.render() == "42\n24\n42" def test_pass_context_block_and_loop(self, env): @pass_context def test(ctx): return f"{ctx['i']}" tmpl = env.from_string( """\ {%- set i = 42 -%} {% for idx in range(2) -%} {{ test() }} {%- set i = idx -%} {% block body scoped %} {{ test() }} {% set i = 24 -%} {{ test() }} {% endblock -%} {{ test() }} {% endfor -%} {{ test() }}""" ) tmpl.globals["test"] = test # values set within a block or loop should not # show up outside of it assert tmpl.render() == "42\n0\n24\n0\n42\n1\n24\n1\n42" @pytest.mark.parametrize("op", ["extends", "include"]) def test_cached_extends(self, op): env = Environment( loader=DictLoader( {"base": "{{ x }} {{ y }}", "main": f"{{% {op} 'base' %}}"} ) ) env.globals["x"] = "x" env.globals["y"] = "y" # template globals overlay env globals tmpl = env.get_template("main", globals={"x": "bar"}) assert tmpl.render() == "bar y" # base was loaded indirectly, it just has env globals tmpl = env.get_template("base") assert tmpl.render() == "x y" # set template globals for base, no longer uses env globals tmpl = env.get_template("base", globals={"x": 42}) assert tmpl.render() == "42 y" # templates are cached, they keep template globals set earlier tmpl = env.get_template("main") assert tmpl.render() == "bar y" tmpl = env.get_template("base") assert tmpl.render() == "42 y" def test_nested_loop_scoping(self, env): tmpl = env.from_string( "{% set output %}{% for x in [1,2,3] %}hello{% endfor %}" "{% endset %}{{ output }}" ) assert tmpl.render() == "hellohellohello" @pytest.mark.parametrize("unicode_char", ["\N{FORM FEED}", "\x85"]) def test_unicode_whitespace(env, unicode_char): content = "Lorem ipsum\n" + unicode_char + "\nMore text" tmpl = env.from_string(content) assert tmpl.render() == content