123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- 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()
|