411 lines
13 KiB
Python
411 lines
13 KiB
Python
import pytest
|
|
|
|
from jinja2 import DictLoader
|
|
from jinja2 import Environment
|
|
from jinja2 import TemplateRuntimeError
|
|
from jinja2 import TemplateSyntaxError
|
|
|
|
LAYOUTTEMPLATE = """\
|
|
|{% block block1 %}block 1 from layout{% endblock %}
|
|
|{% block block2 %}block 2 from layout{% endblock %}
|
|
|{% block block3 %}
|
|
{% block block4 %}nested block 4 from layout{% endblock %}
|
|
{% endblock %}|"""
|
|
|
|
LEVEL1TEMPLATE = """\
|
|
{% extends "layout" %}
|
|
{% block block1 %}block 1 from level1{% endblock %}"""
|
|
|
|
LEVEL2TEMPLATE = """\
|
|
{% extends "level1" %}
|
|
{% block block2 %}{% block block5 %}nested block 5 from level2{%
|
|
endblock %}{% endblock %}"""
|
|
|
|
LEVEL3TEMPLATE = """\
|
|
{% extends "level2" %}
|
|
{% block block5 %}block 5 from level3{% endblock %}
|
|
{% block block4 %}block 4 from level3{% endblock %}
|
|
"""
|
|
|
|
LEVEL4TEMPLATE = """\
|
|
{% extends "level3" %}
|
|
{% block block3 %}block 3 from level4{% endblock %}
|
|
"""
|
|
|
|
WORKINGTEMPLATE = """\
|
|
{% extends "layout" %}
|
|
{% block block1 %}
|
|
{% if false %}
|
|
{% block block2 %}
|
|
this should work
|
|
{% endblock %}
|
|
{% endif %}
|
|
{% endblock %}
|
|
"""
|
|
|
|
DOUBLEEXTENDS = """\
|
|
{% extends "layout" %}
|
|
{% extends "layout" %}
|
|
{% block block1 %}
|
|
{% if false %}
|
|
{% block block2 %}
|
|
this should work
|
|
{% endblock %}
|
|
{% endif %}
|
|
{% endblock %}
|
|
"""
|
|
|
|
|
|
@pytest.fixture
|
|
def env():
|
|
return Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"layout": LAYOUTTEMPLATE,
|
|
"level1": LEVEL1TEMPLATE,
|
|
"level2": LEVEL2TEMPLATE,
|
|
"level3": LEVEL3TEMPLATE,
|
|
"level4": LEVEL4TEMPLATE,
|
|
"working": WORKINGTEMPLATE,
|
|
"doublee": DOUBLEEXTENDS,
|
|
}
|
|
),
|
|
trim_blocks=True,
|
|
)
|
|
|
|
|
|
class TestInheritance:
|
|
def test_layout(self, env):
|
|
tmpl = env.get_template("layout")
|
|
assert tmpl.render() == (
|
|
"|block 1 from layout|block 2 from layout|nested block 4 from layout|"
|
|
)
|
|
|
|
def test_level1(self, env):
|
|
tmpl = env.get_template("level1")
|
|
assert tmpl.render() == (
|
|
"|block 1 from level1|block 2 from layout|nested block 4 from layout|"
|
|
)
|
|
|
|
def test_level2(self, env):
|
|
tmpl = env.get_template("level2")
|
|
assert tmpl.render() == (
|
|
"|block 1 from level1|nested block 5 from "
|
|
"level2|nested block 4 from layout|"
|
|
)
|
|
|
|
def test_level3(self, env):
|
|
tmpl = env.get_template("level3")
|
|
assert tmpl.render() == (
|
|
"|block 1 from level1|block 5 from level3|block 4 from level3|"
|
|
)
|
|
|
|
def test_level4(self, env):
|
|
tmpl = env.get_template("level4")
|
|
assert tmpl.render() == (
|
|
"|block 1 from level1|block 5 from level3|block 3 from level4|"
|
|
)
|
|
|
|
def test_super(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"a": "{% block intro %}INTRO{% endblock %}|"
|
|
"BEFORE|{% block data %}INNER{% endblock %}|AFTER",
|
|
"b": '{% extends "a" %}{% block data %}({{ '
|
|
"super() }}){% endblock %}",
|
|
"c": '{% extends "b" %}{% block intro %}--{{ '
|
|
"super() }}--{% endblock %}\n{% block data "
|
|
"%}[{{ super() }}]{% endblock %}",
|
|
}
|
|
)
|
|
)
|
|
tmpl = env.get_template("c")
|
|
assert tmpl.render() == "--INTRO--|BEFORE|[(INNER)]|AFTER"
|
|
|
|
def test_working(self, env):
|
|
env.get_template("working")
|
|
|
|
def test_reuse_blocks(self, env):
|
|
tmpl = env.from_string(
|
|
"{{ self.foo() }}|{% block foo %}42{% endblock %}|{{ self.foo() }}"
|
|
)
|
|
assert tmpl.render() == "42|42|42"
|
|
|
|
def test_preserve_blocks(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"a": "{% if false %}{% block x %}A{% endblock %}"
|
|
"{% endif %}{{ self.x() }}",
|
|
"b": '{% extends "a" %}{% block x %}B{{ super() }}{% endblock %}',
|
|
}
|
|
)
|
|
)
|
|
tmpl = env.get_template("b")
|
|
assert tmpl.render() == "BA"
|
|
|
|
def test_dynamic_inheritance(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"default1": "DEFAULT1{% block x %}{% endblock %}",
|
|
"default2": "DEFAULT2{% block x %}{% endblock %}",
|
|
"child": "{% extends default %}{% block x %}CHILD{% endblock %}",
|
|
}
|
|
)
|
|
)
|
|
tmpl = env.get_template("child")
|
|
for m in range(1, 3):
|
|
assert tmpl.render(default=f"default{m}") == f"DEFAULT{m}CHILD"
|
|
|
|
def test_multi_inheritance(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"default1": "DEFAULT1{% block x %}{% endblock %}",
|
|
"default2": "DEFAULT2{% block x %}{% endblock %}",
|
|
"child": (
|
|
"{% if default %}{% extends default %}{% else %}"
|
|
"{% extends 'default1' %}{% endif %}"
|
|
"{% block x %}CHILD{% endblock %}"
|
|
),
|
|
}
|
|
)
|
|
)
|
|
tmpl = env.get_template("child")
|
|
assert tmpl.render(default="default2") == "DEFAULT2CHILD"
|
|
assert tmpl.render(default="default1") == "DEFAULT1CHILD"
|
|
assert tmpl.render() == "DEFAULT1CHILD"
|
|
|
|
def test_scoped_block(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"default.html": "{% for item in seq %}[{% block item scoped %}"
|
|
"{% endblock %}]{% endfor %}"
|
|
}
|
|
)
|
|
)
|
|
t = env.from_string(
|
|
"{% extends 'default.html' %}{% block item %}{{ item }}{% endblock %}"
|
|
)
|
|
assert t.render(seq=list(range(5))) == "[0][1][2][3][4]"
|
|
|
|
def test_super_in_scoped_block(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"default.html": "{% for item in seq %}[{% block item scoped %}"
|
|
"{{ item }}{% endblock %}]{% endfor %}"
|
|
}
|
|
)
|
|
)
|
|
t = env.from_string(
|
|
'{% extends "default.html" %}{% block item %}'
|
|
"{{ super() }}|{{ item * 2 }}{% endblock %}"
|
|
)
|
|
assert t.render(seq=list(range(5))) == "[0|0][1|2][2|4][3|6][4|8]"
|
|
|
|
def test_scoped_block_after_inheritance(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"layout.html": """
|
|
{% block useless %}{% endblock %}
|
|
""",
|
|
"index.html": """
|
|
{%- extends 'layout.html' %}
|
|
{% from 'helpers.html' import foo with context %}
|
|
{% block useless %}
|
|
{% for x in [1, 2, 3] %}
|
|
{% block testing scoped %}
|
|
{{ foo(x) }}
|
|
{% endblock %}
|
|
{% endfor %}
|
|
{% endblock %}
|
|
""",
|
|
"helpers.html": """
|
|
{% macro foo(x) %}{{ the_foo + x }}{% endmacro %}
|
|
""",
|
|
}
|
|
)
|
|
)
|
|
rv = env.get_template("index.html").render(the_foo=42).split()
|
|
assert rv == ["43", "44", "45"]
|
|
|
|
def test_level1_required(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"default": "{% block x required %}{# comment #}\n {% endblock %}",
|
|
"level1": "{% extends 'default' %}{% block x %}[1]{% endblock %}",
|
|
}
|
|
)
|
|
)
|
|
rv = env.get_template("level1").render()
|
|
assert rv == "[1]"
|
|
|
|
def test_level2_required(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"default": "{% block x required %}{% endblock %}",
|
|
"level1": "{% extends 'default' %}{% block x %}[1]{% endblock %}",
|
|
"level2": "{% extends 'default' %}{% block x %}[2]{% endblock %}",
|
|
}
|
|
)
|
|
)
|
|
rv1 = env.get_template("level1").render()
|
|
rv2 = env.get_template("level2").render()
|
|
|
|
assert rv1 == "[1]"
|
|
assert rv2 == "[2]"
|
|
|
|
def test_level3_required(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"default": "{% block x required %}{% endblock %}",
|
|
"level1": "{% extends 'default' %}",
|
|
"level2": "{% extends 'level1' %}{% block x %}[2]{% endblock %}",
|
|
"level3": "{% extends 'level2' %}",
|
|
}
|
|
)
|
|
)
|
|
t1 = env.get_template("level1")
|
|
t2 = env.get_template("level2")
|
|
t3 = env.get_template("level3")
|
|
|
|
with pytest.raises(TemplateRuntimeError, match="Required block 'x' not found"):
|
|
assert t1.render()
|
|
|
|
assert t2.render() == "[2]"
|
|
assert t3.render() == "[2]"
|
|
|
|
def test_invalid_required(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"empty": "{% block x required %}{% endblock %}",
|
|
"blank": "{% block x required %} {# c #}{% endblock %}",
|
|
"text": "{% block x required %}data {# c #}{% endblock %}",
|
|
"block": "{% block x required %}{% block y %}"
|
|
"{% endblock %}{% endblock %}",
|
|
"if": "{% block x required %}{% if true %}"
|
|
"{% endif %}{% endblock %}",
|
|
"top": "{% extends t %}{% block x %}CHILD{% endblock %}",
|
|
}
|
|
)
|
|
)
|
|
t = env.get_template("top")
|
|
assert t.render(t="empty") == "CHILD"
|
|
assert t.render(t="blank") == "CHILD"
|
|
|
|
required_block_check = pytest.raises(
|
|
TemplateSyntaxError,
|
|
match="Required blocks can only contain comments or whitespace",
|
|
)
|
|
|
|
with required_block_check:
|
|
t.render(t="text")
|
|
|
|
with required_block_check:
|
|
t.render(t="block")
|
|
|
|
with required_block_check:
|
|
t.render(t="if")
|
|
|
|
def test_required_with_scope(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"default1": "{% for item in seq %}[{% block item scoped required %}"
|
|
"{% endblock %}]{% endfor %}",
|
|
"child1": "{% extends 'default1' %}{% block item %}"
|
|
"{{ item }}{% endblock %}",
|
|
"default2": "{% for item in seq %}[{% block item required scoped %}"
|
|
"{% endblock %}]{% endfor %}",
|
|
"child2": "{% extends 'default2' %}{% block item %}"
|
|
"{{ item }}{% endblock %}",
|
|
}
|
|
)
|
|
)
|
|
t1 = env.get_template("child1")
|
|
t2 = env.get_template("child2")
|
|
|
|
assert t1.render(seq=list(range(3))) == "[0][1][2]"
|
|
|
|
# scoped must come before required
|
|
with pytest.raises(TemplateSyntaxError):
|
|
t2.render(seq=list(range(3)))
|
|
|
|
def test_duplicate_required_or_scoped(self, env):
|
|
env = Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"default1": "{% for item in seq %}[{% block item "
|
|
"scoped scoped %}}{{% endblock %}}]{{% endfor %}}",
|
|
"default2": "{% for item in seq %}[{% block item "
|
|
"required required %}}{{% endblock %}}]{{% endfor %}}",
|
|
"child": "{% if default %}{% extends default %}{% else %}"
|
|
"{% extends 'default1' %}{% endif %}{%- block x %}"
|
|
"CHILD{% endblock %}",
|
|
}
|
|
)
|
|
)
|
|
tmpl = env.get_template("child")
|
|
|
|
with pytest.raises(TemplateSyntaxError):
|
|
tmpl.render(default="default1", seq=list(range(3)))
|
|
|
|
with pytest.raises(TemplateSyntaxError):
|
|
tmpl.render(default="default2", seq=list(range(3)))
|
|
|
|
|
|
class TestBugFix:
|
|
def test_fixed_macro_scoping_bug(self, env):
|
|
assert Environment(
|
|
loader=DictLoader(
|
|
{
|
|
"test.html": """\
|
|
{% extends 'details.html' %}
|
|
|
|
{% macro my_macro() %}
|
|
my_macro
|
|
{% endmacro %}
|
|
|
|
{% block inner_box %}
|
|
{{ my_macro() }}
|
|
{% endblock %}
|
|
""",
|
|
"details.html": """\
|
|
{% extends 'standard.html' %}
|
|
|
|
{% macro my_macro() %}
|
|
my_macro
|
|
{% endmacro %}
|
|
|
|
{% block content %}
|
|
{% block outer_box %}
|
|
outer_box
|
|
{% block inner_box %}
|
|
inner_box
|
|
{% endblock %}
|
|
{% endblock %}
|
|
{% endblock %}
|
|
""",
|
|
"standard.html": """
|
|
{% block content %} {% endblock %}
|
|
""",
|
|
}
|
|
)
|
|
).get_template("test.html").render().split() == ["outer_box", "my_macro"]
|
|
|
|
def test_double_extends(self, env):
|
|
"""Ensures that a template with more than 1 {% extends ... %} usage
|
|
raises a ``TemplateError``.
|
|
"""
|
|
with pytest.raises(TemplateRuntimeError, match="extended multiple times"):
|
|
env.get_template("doublee").render()
|