123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745 |
- 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 = "<FOO>" %}
- {%- for item in (1, 2, 3, 4) %}
- {%- macro wrapper() %}[{{ item }}]{% endmacro %}
- {{- wrapper() }}
- {%- endfor %}
- {{- wrapper -}}
- """
- )
- assert t.render() == "[1][2][3][4]<FOO>"
- t = env.from_string(
- """
- {%- for item in (1, 2, 3, 4) %}
- {%- macro wrapper() %}[{{ item }}]{% endmacro %}
- {{- wrapper() }}
- {%- endfor %}
- {%- set wrapper = "<FOO>" %}
- {{- wrapper -}}
- """
- )
- assert t.render() == "[1][2][3][4]<FOO>"
- 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/<foo"|urlize }}')
- assert (
- tmpl.render() == '<a href="http://www.example.org/<foo" rel="noopener">'
- "http://www.example.org/<foo</a>"
- )
- def test_urlize_filter_closing_punctuation(self, env):
- tmpl = env.from_string(
- '{{ "(see http://www.example.org/?page=subj_<desc.h>)"|urlize }}'
- )
- assert tmpl.render() == (
- '(see <a href="http://www.example.org/?page=subj_<desc.h>" '
- 'rel="noopener">http://www.example.org/?page=subj_<desc.h></a>)'
- )
- 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) -%}
- <ul>
- {%- for user in users -%}
- <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
- {%- endfor -%}
- </ul>
- {%- endmacro -%}
- {% call(user) dump_users(list_of_user) -%}
- <dl>
- <dl>Realname</dl>
- <dd>{{ user.realname|e }}</dd>
- <dl>Description</dl>
- <dd>{{ user.description }}</dd>
- </dl>
- {% endcall %}"""
- )
- assert [
- x.strip()
- for x in t.render(
- list_of_user=[
- {
- "username": "apo",
- "realname": "something else",
- "description": "test",
- }
- ]
- ).splitlines()
- ] == [
- "<ul><li><p>apo</p><dl>",
- "<dl>Realname</dl>",
- "<dd>something else</dd>",
- "<dl>Description</dl>",
- "<dd>test</dd>",
- "</dl>",
- "</li></ul>",
- ]
- 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() %}<html>{% 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 -%}
- <p><span class="fa fa-fw fa-{{ icon }}"></span>
- {%- if caller is undefined -%}
- {{ value }}
- {%- else -%}
- {{ caller(value, *varargs) }}
- {%- endif -%}</p>
- {%- endif %}
- {%- endmacro %}
- {% macro link_detail(icon, value, href) -%}
- {% call(value, href) detail(icon, value, href) -%}
- <a href="{{ href }}">{{ value }}</a>
- {%- endcall %}
- {%- endmacro %}
- """
- )
- assert t.module.link_detail("circle", "Index", "/") == (
- '<p><span class="fa fa-fw fa-circle"></span><a href="/">Index</a></p>'
- )
- 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(
- """
- <?xml version="1.0" encoding="UTF-8"?>
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
- {%- for page in [site.root] if page.url != this recursive %}
- <url><loc>{{ page.url }}</loc></url>
- {{- loop(page.children) }}
- {%- endfor %}
- </urlset>
- """
- )
- 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 == [
- '<?xml version="1.0" encoding="UTF-8"?>',
- '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
- "<url><loc>/</loc></url>",
- "<url><loc>/bar</loc></url>",
- "</urlset>",
- ]
- 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
|