• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import pytest
2
3from jinja2 import DictLoader
4from jinja2 import Environment
5from jinja2 import TemplateRuntimeError
6
7LAYOUTTEMPLATE = """\
8|{% block block1 %}block 1 from layout{% endblock %}
9|{% block block2 %}block 2 from layout{% endblock %}
10|{% block block3 %}
11{% block block4 %}nested block 4 from layout{% endblock %}
12{% endblock %}|"""
13
14LEVEL1TEMPLATE = """\
15{% extends "layout" %}
16{% block block1 %}block 1 from level1{% endblock %}"""
17
18LEVEL2TEMPLATE = """\
19{% extends "level1" %}
20{% block block2 %}{% block block5 %}nested block 5 from level2{%
21endblock %}{% endblock %}"""
22
23LEVEL3TEMPLATE = """\
24{% extends "level2" %}
25{% block block5 %}block 5 from level3{% endblock %}
26{% block block4 %}block 4 from level3{% endblock %}
27"""
28
29LEVEL4TEMPLATE = """\
30{% extends "level3" %}
31{% block block3 %}block 3 from level4{% endblock %}
32"""
33
34WORKINGTEMPLATE = """\
35{% extends "layout" %}
36{% block block1 %}
37  {% if false %}
38    {% block block2 %}
39      this should workd
40    {% endblock %}
41  {% endif %}
42{% endblock %}
43"""
44
45DOUBLEEXTENDS = """\
46{% extends "layout" %}
47{% extends "layout" %}
48{% block block1 %}
49  {% if false %}
50    {% block block2 %}
51      this should workd
52    {% endblock %}
53  {% endif %}
54{% endblock %}
55"""
56
57
58@pytest.fixture
59def env():
60    return Environment(
61        loader=DictLoader(
62            {
63                "layout": LAYOUTTEMPLATE,
64                "level1": LEVEL1TEMPLATE,
65                "level2": LEVEL2TEMPLATE,
66                "level3": LEVEL3TEMPLATE,
67                "level4": LEVEL4TEMPLATE,
68                "working": WORKINGTEMPLATE,
69                "doublee": DOUBLEEXTENDS,
70            }
71        ),
72        trim_blocks=True,
73    )
74
75
76class TestInheritance:
77    def test_layout(self, env):
78        tmpl = env.get_template("layout")
79        assert tmpl.render() == (
80            "|block 1 from layout|block 2 from layout|nested block 4 from layout|"
81        )
82
83    def test_level1(self, env):
84        tmpl = env.get_template("level1")
85        assert tmpl.render() == (
86            "|block 1 from level1|block 2 from layout|nested block 4 from layout|"
87        )
88
89    def test_level2(self, env):
90        tmpl = env.get_template("level2")
91        assert tmpl.render() == (
92            "|block 1 from level1|nested block 5 from "
93            "level2|nested block 4 from layout|"
94        )
95
96    def test_level3(self, env):
97        tmpl = env.get_template("level3")
98        assert tmpl.render() == (
99            "|block 1 from level1|block 5 from level3|block 4 from level3|"
100        )
101
102    def test_level4(self, env):
103        tmpl = env.get_template("level4")
104        assert tmpl.render() == (
105            "|block 1 from level1|block 5 from level3|block 3 from level4|"
106        )
107
108    def test_super(self, env):
109        env = Environment(
110            loader=DictLoader(
111                {
112                    "a": "{% block intro %}INTRO{% endblock %}|"
113                    "BEFORE|{% block data %}INNER{% endblock %}|AFTER",
114                    "b": '{% extends "a" %}{% block data %}({{ '
115                    "super() }}){% endblock %}",
116                    "c": '{% extends "b" %}{% block intro %}--{{ '
117                    "super() }}--{% endblock %}\n{% block data "
118                    "%}[{{ super() }}]{% endblock %}",
119                }
120            )
121        )
122        tmpl = env.get_template("c")
123        assert tmpl.render() == "--INTRO--|BEFORE|[(INNER)]|AFTER"
124
125    def test_working(self, env):
126        env.get_template("working")
127
128    def test_reuse_blocks(self, env):
129        tmpl = env.from_string(
130            "{{ self.foo() }}|{% block foo %}42{% endblock %}|{{ self.foo() }}"
131        )
132        assert tmpl.render() == "42|42|42"
133
134    def test_preserve_blocks(self, env):
135        env = Environment(
136            loader=DictLoader(
137                {
138                    "a": "{% if false %}{% block x %}A{% endblock %}"
139                    "{% endif %}{{ self.x() }}",
140                    "b": '{% extends "a" %}{% block x %}B{{ super() }}{% endblock %}',
141                }
142            )
143        )
144        tmpl = env.get_template("b")
145        assert tmpl.render() == "BA"
146
147    def test_dynamic_inheritance(self, env):
148        env = Environment(
149            loader=DictLoader(
150                {
151                    "master1": "MASTER1{% block x %}{% endblock %}",
152                    "master2": "MASTER2{% block x %}{% endblock %}",
153                    "child": "{% extends master %}{% block x %}CHILD{% endblock %}",
154                }
155            )
156        )
157        tmpl = env.get_template("child")
158        for m in range(1, 3):
159            assert tmpl.render(master=f"master{m}") == f"MASTER{m}CHILD"
160
161    def test_multi_inheritance(self, env):
162        env = Environment(
163            loader=DictLoader(
164                {
165                    "master1": "MASTER1{% block x %}{% endblock %}",
166                    "master2": "MASTER2{% block x %}{% endblock %}",
167                    "child": """{% if master %}{% extends master %}{% else %}{% extends
168                'master1' %}{% endif %}{% block x %}CHILD{% endblock %}""",
169                }
170            )
171        )
172        tmpl = env.get_template("child")
173        assert tmpl.render(master="master2") == "MASTER2CHILD"
174        assert tmpl.render(master="master1") == "MASTER1CHILD"
175        assert tmpl.render() == "MASTER1CHILD"
176
177    def test_scoped_block(self, env):
178        env = Environment(
179            loader=DictLoader(
180                {
181                    "master.html": "{% for item in seq %}[{% block item scoped %}"
182                    "{% endblock %}]{% endfor %}"
183                }
184            )
185        )
186        t = env.from_string(
187            "{% extends 'master.html' %}{% block item %}{{ item }}{% endblock %}"
188        )
189        assert t.render(seq=list(range(5))) == "[0][1][2][3][4]"
190
191    def test_super_in_scoped_block(self, env):
192        env = Environment(
193            loader=DictLoader(
194                {
195                    "master.html": "{% for item in seq %}[{% block item scoped %}"
196                    "{{ item }}{% endblock %}]{% endfor %}"
197                }
198            )
199        )
200        t = env.from_string(
201            '{% extends "master.html" %}{% block item %}'
202            "{{ super() }}|{{ item * 2 }}{% endblock %}"
203        )
204        assert t.render(seq=list(range(5))) == "[0|0][1|2][2|4][3|6][4|8]"
205
206    def test_scoped_block_after_inheritance(self, env):
207        env = Environment(
208            loader=DictLoader(
209                {
210                    "layout.html": """
211            {% block useless %}{% endblock %}
212            """,
213                    "index.html": """
214            {%- extends 'layout.html' %}
215            {% from 'helpers.html' import foo with context %}
216            {% block useless %}
217                {% for x in [1, 2, 3] %}
218                    {% block testing scoped %}
219                        {{ foo(x) }}
220                    {% endblock %}
221                {% endfor %}
222            {% endblock %}
223            """,
224                    "helpers.html": """
225            {% macro foo(x) %}{{ the_foo + x }}{% endmacro %}
226            """,
227                }
228            )
229        )
230        rv = env.get_template("index.html").render(the_foo=42).split()
231        assert rv == ["43", "44", "45"]
232
233
234class TestBugFix:
235    def test_fixed_macro_scoping_bug(self, env):
236        assert (
237            Environment(
238                loader=DictLoader(
239                    {
240                        "test.html": """\
241        {% extends 'details.html' %}
242
243        {% macro my_macro() %}
244        my_macro
245        {% endmacro %}
246
247        {% block inner_box %}
248            {{ my_macro() }}
249        {% endblock %}
250            """,
251                        "details.html": """\
252        {% extends 'standard.html' %}
253
254        {% macro my_macro() %}
255        my_macro
256        {% endmacro %}
257
258        {% block content %}
259            {% block outer_box %}
260                outer_box
261                {% block inner_box %}
262                    inner_box
263                {% endblock %}
264            {% endblock %}
265        {% endblock %}
266        """,
267                        "standard.html": """
268        {% block content %} {% endblock %}
269        """,
270                    }
271                )
272            )
273            .get_template("test.html")
274            .render()
275            .split()
276            == ["outer_box", "my_macro"]
277        )
278
279    def test_double_extends(self, env):
280        """Ensures that a template with more than 1 {% extends ... %} usage
281        raises a ``TemplateError``.
282        """
283        with pytest.raises(TemplateRuntimeError, match="extended multiple times"):
284            env.get_template("doublee").render()
285