You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
614 lines
18 KiB
614 lines
18 KiB
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
|
|
|
|
|
|
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_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_contextfunction_callable_classes(self, env):
|
|
from jinja2.utils import contextfunction
|
|
|
|
class CallableClass:
|
|
@contextfunction
|
|
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, extensions=["jinja2.ext.autoescape"]
|
|
)
|
|
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_legacy_custom_context(self, env):
|
|
from jinja2.runtime import Context, missing
|
|
|
|
class MyContext(Context):
|
|
def resolve(self, name):
|
|
if name == "foo":
|
|
return 42
|
|
return super().resolve(name)
|
|
|
|
x = MyContext(env, parent={"bar": 23}, name="foo", blocks={})
|
|
assert x._legacy_resolve_mode
|
|
assert x.resolve_or_missing("foo") == 42
|
|
assert x.resolve_or_missing("bar") == 23
|
|
assert x.resolve_or_missing("baz") is missing
|
|
|
|
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 jinja2 import Markup
|
|
from jinja2.runtime import ChainableUndefined
|
|
|
|
assert str(Markup(ChainableUndefined())) == ""
|