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