| import pytest | |
| from jinja2 import DictLoader | |
| from jinja2 import Environment | |
| from jinja2 import TemplateRuntimeError | |
| 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 workd | |
| {% endblock %} | |
| {% endif %} | |
| {% endblock %} | |
| """ | |
| DOUBLEEXTENDS = """\ | |
| {% extends "layout" %} | |
| {% extends "layout" %} | |
| {% block block1 %} | |
| {% if false %} | |
| {% block block2 %} | |
| this should workd | |
| {% 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( | |
| { | |
| "master1": "MASTER1{% block x %}{% endblock %}", | |
| "master2": "MASTER2{% block x %}{% endblock %}", | |
| "child": "{% extends master %}{% block x %}CHILD{% endblock %}", | |
| } | |
| ) | |
| ) | |
| tmpl = env.get_template("child") | |
| for m in range(1, 3): | |
| assert tmpl.render(master=f"master{m}") == f"MASTER{m}CHILD" | |
| def test_multi_inheritance(self, env): | |
| env = Environment( | |
| loader=DictLoader( | |
| { | |
| "master1": "MASTER1{% block x %}{% endblock %}", | |
| "master2": "MASTER2{% block x %}{% endblock %}", | |
| "child": """{% if master %}{% extends master %}{% else %}{% extends | |
| 'master1' %}{% endif %}{% block x %}CHILD{% endblock %}""", | |
| } | |
| ) | |
| ) | |
| tmpl = env.get_template("child") | |
| assert tmpl.render(master="master2") == "MASTER2CHILD" | |
| assert tmpl.render(master="master1") == "MASTER1CHILD" | |
| assert tmpl.render() == "MASTER1CHILD" | |
| def test_scoped_block(self, env): | |
| env = Environment( | |
| loader=DictLoader( | |
| { | |
| "master.html": "{% for item in seq %}[{% block item scoped %}" | |
| "{% endblock %}]{% endfor %}" | |
| } | |
| ) | |
| ) | |
| t = env.from_string( | |
| "{% extends 'master.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( | |
| { | |
| "master.html": "{% for item in seq %}[{% block item scoped %}" | |
| "{{ item }}{% endblock %}]{% endfor %}" | |
| } | |
| ) | |
| ) | |
| t = env.from_string( | |
| '{% extends "master.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"] | |
| 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() |