fix: TemplateDoesNotExist when using {% extends %} on main template and two components with same parent template (#862)

This commit is contained in:
Juro Oravec 2024-12-23 12:58:03 +01:00 committed by GitHub
parent 6bb73bd8af
commit 85fc6e3497
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 876 additions and 394 deletions

2
.gitignore vendored
View file

@ -74,6 +74,8 @@ poetry.lock
.DS_Store
.python-version
site
.direnv/
.envrc
# JS, NPM Dependency directories
node_modules/

View file

@ -692,6 +692,10 @@ class Component(
if not isinstance(context, Context):
context = RequestContext(request, context) if request else Context(context)
# Required for compatibility with Django's {% extends %} tag
# See https://github.com/EmilStenstrom/django-components/pull/859
context.render_context.push({BLOCK_CONTEXT_KEY: context.render_context.get(BLOCK_CONTEXT_KEY, {})})
# By adding the current input to the stack, we temporarily allow users
# to access the provided context, slots, etc. Also required so users can
# call `self.inject()` from within `get_context_data()`.
@ -768,6 +772,7 @@ class Component(
# After rendering is done, remove the current state from the stack, which means
# properties like `self.context` will no longer return the current state.
self._render_stack.pop()
context.render_context.pop()
return output

View file

@ -22,6 +22,10 @@ def make_isolated_context_copy(context: Context) -> Context:
context_copy = context.new()
copy_forloop_context(context, context_copy)
# Required for compatibility with Django's {% extends %} tag
# See https://github.com/EmilStenstrom/django-components/pull/859
context_copy.render_context = context.render_context
# Pass through our internal keys
context_copy[_REGISTRY_CONTEXT_KEY] = context.get(_REGISTRY_CONTEXT_KEY, None)
if _ROOT_CTX_CONTEXT_KEY in context:

View file

@ -338,10 +338,16 @@ class SlotNode(BaseNode):
# came from (or current context if configured so)
used_ctx = self._resolve_slot_context(context, slot_fill)
with used_ctx.update(extra_context):
# Render slot as a function
# NOTE: While `{% fill %}` tag has to opt in for the `default` and `data` variables,
# the render function ALWAYS receives them.
output = slot_fill.slot(used_ctx, kwargs, slot_ref)
# Required for compatibility with Django's {% extends %} tag
# This makes sure that the render context used outside of a component
# is the same as the one used inside the slot.
# See https://github.com/EmilStenstrom/django-components/pull/859
render_ctx_layer = used_ctx.render_context.dicts[-2] if len(used_ctx.render_context.dicts) > 1 else {}
with used_ctx.render_context.push(render_ctx_layer):
# Render slot as a function
# NOTE: While `{% fill %}` tag has to opt in for the `default` and `data` variables,
# the render function ALWAYS receives them.
output = slot_fill.slot(used_ctx, kwargs, slot_ref)
trace_msg("RENDR", "SLOT", self.trace_id, self.node_id, msg="...Done!")
return output

View file

@ -0,0 +1,7 @@
{% load component_tags %}
{% block before_custom %}{% endblock %}
<custom-template>
<header>{% slot "header" %}Default header{% endslot %}</header>
<main>{% slot "main" %}Default main{% endslot %}</main>
<footer>{% slot "footer" %}Default footer{% endslot %}</footer>
</custom-template>

View file

@ -0,0 +1,7 @@
{% load component_tags %}
{% block before_custom %}{% endblock %}
<custom-template>
<header>{% slot "header" %}Default header{% endslot %}</header>
<main>{% slot "main" %}Default main{% endslot %}</main>
<footer>{% slot "footer" %}Default footer{% endslot %}</footer>
</custom-template>

View file

@ -0,0 +1,4 @@
{% extends "simple_template.html" %}
{% block before_custom %}
<div>BLOCK OVERRIDEN</div>
{% endblock %}

View file

@ -94,396 +94,6 @@ class TemplateInstrumentationTest(BaseTestCase):
self.assertIn("simple_template.html", templates_used)
class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slots_inside_extends(self):
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_extends")
class SlotInsideExtendsComponent(Component):
template: types.django_html = """
{% extends "block_in_slot_in_component.html" %}
"""
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_extends" %}
{% fill "body" %}
BODY_FROM_FILL
{% endfill %}
{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>BODY_FROM_FILL</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_slots_inside_include(self):
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_include")
class SlotInsideIncludeComponent(Component):
template: types.django_html = """
{% include "block_in_slot_in_component.html" %}
"""
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_include" %}
{% fill "body" %}
BODY_FROM_FILL
{% endfill %}
{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>BODY_FROM_FILL</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_component_inside_block(self):
registry.register("slotted_component", SlottedComponent)
template: types.django_html = """
{% extends "block.html" %}
{% load component_tags %}
{% block body %}
{% component "slotted_component" %}
{% fill "header" %}{% endfill %}
{% fill "main" %}
TEST
{% endfill %}
{% fill "footer" %}{% endfill %}
{% endcomponent %}
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<main role="main">
<div class='container main-container'>
<custom-template>
<header></header>
<main>TEST</main>
<footer></footer>
</custom-template>
</div>
</main>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_block_inside_component(self):
registry.register("slotted_component", SlottedComponent)
template: types.django_html = """
{% extends "block_in_component.html" %}
{% load component_tags %}
{% block body %}
<div>
58 giraffes and 2 pantaloons
</div>
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
<div> 58 giraffes and 2 pantaloons </div>
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_block_inside_component_parent(self):
registry.register("slotted_component", SlottedComponent)
@register("block_in_component_parent")
class BlockInCompParent(Component):
template_name = "block_in_component_parent.html"
template: types.django_html = """
{% load component_tags %}
{% component "block_in_component_parent" %}{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
<div> 58 giraffes and 2 pantaloons </div>
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_block_does_not_affect_inside_component(self):
"""
Assert that when we call a component with `{% component %}`, that
the `{% block %}` will NOT affect the inner component.
"""
registry.register("slotted_component", SlottedComponent)
@register("block_inside_slot_v1")
class BlockInSlotInComponent(Component):
template_name = "block_in_slot_in_component.html"
template: types.django_html = """
{% load component_tags %}
{% component "block_inside_slot_v1" %}
{% fill "body" %}
BODY_FROM_FILL
{% endfill %}
{% endcomponent %}
{% block inner %}
wow
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>BODY_FROM_FILL</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
wow
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_default_block_default(self):
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
"""
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_block" %}{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
Helloodiddoo
Default inner
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_default_block_override(self):
registry.clear()
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
{% block inner %}
INNER BLOCK OVERRIDEN
{% endblock %}
"""
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_block" %}{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
Helloodiddoo
INNER BLOCK OVERRIDEN
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["isolated", "django"])
def test_slot_inside_block__slot_overriden_block_default(self):
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
"""
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_block" %}
{% fill "body" %}
SLOT OVERRIDEN
{% endfill %}
{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
Helloodiddoo
SLOT OVERRIDEN
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_overriden_block_overriden(self):
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
{% block inner %}
{% load component_tags %}
{% slot "new_slot" %}{% endslot %}
{% endblock %}
whut
"""
# NOTE: The "body" fill will NOT show up, because we override the `inner` block
# with a different slot. But the "new_slot" WILL show up.
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_block" %}
{% fill "body" %}
SLOT_BODY__OVERRIDEN
{% endfill %}
{% fill "new_slot" %}
SLOT_NEW__OVERRIDEN
{% endfill %}
{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
Helloodiddoo
SLOT_NEW__OVERRIDEN
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_inject_inside_block(self):
registry.register("slotted_component", SlottedComponent)
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
def get_context_data(self):
var = self.inject("block_provide")
return {"var": var}
template: types.django_html = """
{% extends "block_in_component_provide.html" %}
{% load component_tags %}
{% block body %}
{% component "injectee" %}
{% endcomponent %}
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
<div> injected: DepInject(hello='from_block') </div>
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
class MultilineTagsTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_multiline_tags(self):

View file

@ -0,0 +1,837 @@
"""Catch-all for tests that use template tags and don't fit other files"""
from django.template import Context, Template
from django_components import Component, register, registry, types
from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior
setup_test_config({"autodiscover": False})
class SlottedComponent(Component):
template_name = "slotted_template.html"
class BlockedAndSlottedComponent(Component):
template_name = "blocked_and_slotted_template.html"
#######################
# TESTS
#######################
class ExtendsCompatTests(BaseTestCase):
@parametrize_context_behavior(["isolated", "django"])
def test_double_extends_on_main_template_and_component_one_component(self):
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
@register("extended_component")
class _ExtendedComponent(Component):
template: types.django_html = """
{% extends "blocked_and_slotted_template.html" %}
{% block before_custom %}
<div>BLOCK OVERRIDEN</div>
{% endblock %}
"""
template: types.django_html = """
{% extends 'block.html' %}
{% load component_tags %}
{% block body %}
{% component "extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN
{% endfill %}
{% endcomponent %}
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<main role="main">
<div class='container main-container'>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
</div>
</main>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["isolated", "django"])
def test_double_extends_on_main_template_and_component_two_identical_components(self):
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
@register("extended_component")
class _ExtendedComponent(Component):
template: types.django_html = """
{% extends "blocked_and_slotted_template.html" %}
{% block before_custom %}
<div>BLOCK OVERRIDEN</div>
{% endblock %}
"""
template: types.django_html = """
{% extends 'block.html' %}
{% load component_tags %}
{% block body %}
{% component "extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN
{% endfill %}
{% endcomponent %}
{% component "extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN 2
{% endfill %}
{% endcomponent %}
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<main role="main">
<div class='container main-container'>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN 2</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
</div>
</main>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["isolated", "django"])
def test_double_extends_on_main_template_and_component_two_different_components_same_parent(self):
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
@register("extended_component")
class _ExtendedComponent(Component):
template: types.django_html = """
{% extends "blocked_and_slotted_template.html" %}
{% block before_custom %}
<div>BLOCK OVERRIDEN</div>
{% endblock %}
"""
@register("second_extended_component")
class _SecondExtendedComponent(Component):
template: types.django_html = """
{% extends "blocked_and_slotted_template.html" %}
{% block before_custom %}
<div>BLOCK OVERRIDEN</div>
{% endblock %}
"""
template_str: types.django_html = """
{% extends 'block.html' %}
{% load component_tags %}
{% block body %}
{% component "extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN
{% endfill %}
{% endcomponent %}
{% component "second_extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN 2
{% endfill %}
{% endcomponent %}
{% endblock %}
"""
template = Template(template_str)
rendered = template.render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<main role="main">
<div class='container main-container'>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN 2</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
</div>
</main>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["isolated", "django"])
def test_double_extends_on_main_template_and_component_two_different_components_different_parent(self):
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
@register("extended_component")
class _ExtendedComponent(Component):
template: types.django_html = """
{% extends "blocked_and_slotted_template.html" %}
{% block before_custom %}
<div>BLOCK OVERRIDEN</div>
{% endblock %}
"""
@register("second_extended_component")
class _SecondExtendedComponent(Component):
template: types.django_html = """
{% extends "blocked_and_slotted_template_2.html" %}
{% block before_custom %}
<div>BLOCK OVERRIDEN</div>
{% endblock %}
"""
template: types.django_html = """
{% extends 'block.html' %}
{% load component_tags %}
{% block body %}
{% component "extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN
{% endfill %}
{% endcomponent %}
{% component "second_extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN 2
{% endfill %}
{% endcomponent %}
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<main role="main">
<div class='container main-container'>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN 2</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
</div>
</main>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["isolated", "django"])
def test_extends_on_component_one_component(self):
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
@register("extended_component")
class _ExtendedComponent(Component):
template: types.django_html = """
{% extends "blocked_and_slotted_template.html" %}
{% block before_custom %}
<div>BLOCK OVERRIDEN</div>
{% endblock %}
"""
template: types.django_html = """
{% load component_tags %}
<!DOCTYPE html>
<html lang="en">
<body>
{% component "extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN
{% endfill %}
{% endcomponent %}
</body>
</html>
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["isolated", "django"])
def test_extends_on_component_two_component(self):
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
@register("extended_component")
class _ExtendedComponent(Component):
template: types.django_html = """
{% extends "blocked_and_slotted_template.html" %}
{% block before_custom %}
<div>BLOCK OVERRIDEN</div>
{% endblock %}
"""
template: types.django_html = """
{% load component_tags %}
<!DOCTYPE html>
<html lang="en">
<body>
{% component "extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN
{% endfill %}
{% endcomponent %}
{% component "extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN 2
{% endfill %}
{% endcomponent %}
</body>
</html>
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN 2</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["isolated", "django"])
def test_double_extends_on_main_template_and_nested_component(self):
registry.register("slotted_component", SlottedComponent)
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
@register("extended_component")
class _ExtendedComponent(Component):
template: types.django_html = """
{% extends "blocked_and_slotted_template.html" %}
{% block before_custom %}
<div>BLOCK OVERRIDEN</div>
{% endblock %}
"""
template: types.django_html = """
{% extends 'block.html' %}
{% load component_tags %}
{% block body %}
{% component "slotted_component" %}
{% fill "main" %}
{% component "extended_component" %}
{% fill "header" %}
SLOT OVERRIDEN
{% endfill %}
{% endcomponent %}
{% endfill %}
{% endcomponent %}
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<main role="main">
<div class='container main-container'>
<custom-template>
<header>Default header</header>
<main>
<div>BLOCK OVERRIDEN</div>
<custom-template>
<header>SLOT OVERRIDEN</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
</main>
<footer>Default footer</footer>
</custom-template>
</div>
</main>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["isolated", "django"])
def test_double_extends_on_main_template_and_nested_component_and_include(self):
registry.register("slotted_component", SlottedComponent)
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
@register("extended_component")
class _ExtendedComponent(Component):
template_name = "included.html"
template: types.django_html = """
{% extends 'block.html' %}
{% load component_tags %}
{% block body %}
{% include 'included.html' %}
{% component "extended_component" / %}
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<main role="main">
<div class='container main-container'>
Variable: <strong></strong>
Variable: <strong></strong>
</div>
</main>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
# second rendering after cache built
rendered_2 = Template(template).render(Context())
expected_2 = expected.replace("data-djc-id-a1bc3f", "data-djc-id-a1bc41")
self.assertHTMLEqual(rendered_2, expected_2)
@parametrize_context_behavior(["django", "isolated"])
def test_slots_inside_extends(self):
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_extends")
class SlotInsideExtendsComponent(Component):
template: types.django_html = """
{% extends "block_in_slot_in_component.html" %}
"""
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_extends" %}
{% fill "body" %}
BODY_FROM_FILL
{% endfill %}
{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>BODY_FROM_FILL</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_slots_inside_include(self):
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_include")
class SlotInsideIncludeComponent(Component):
template: types.django_html = """
{% include "block_in_slot_in_component.html" %}
"""
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_include" %}
{% fill "body" %}
BODY_FROM_FILL
{% endfill %}
{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>BODY_FROM_FILL</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_component_inside_block(self):
registry.register("slotted_component", SlottedComponent)
template: types.django_html = """
{% extends "block.html" %}
{% load component_tags %}
{% block body %}
{% component "slotted_component" %}
{% fill "header" %}{% endfill %}
{% fill "main" %}
TEST
{% endfill %}
{% fill "footer" %}{% endfill %}
{% endcomponent %}
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<main role="main">
<div class='container main-container'>
<custom-template>
<header></header>
<main>TEST</main>
<footer></footer>
</custom-template>
</div>
</main>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_block_inside_component(self):
registry.register("slotted_component", SlottedComponent)
template: types.django_html = """
{% extends "block_in_component.html" %}
{% load component_tags %}
{% block body %}
<div>
58 giraffes and 2 pantaloons
</div>
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
<div> 58 giraffes and 2 pantaloons </div>
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_block_inside_component_parent(self):
registry.register("slotted_component", SlottedComponent)
@register("block_in_component_parent")
class BlockInCompParent(Component):
template_name = "block_in_component_parent.html"
template: types.django_html = """
{% load component_tags %}
{% component "block_in_component_parent" %}{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
<div> 58 giraffes and 2 pantaloons </div>
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_block_does_not_affect_inside_component(self):
"""
Assert that when we call a component with `{% component %}`, that
the `{% block %}` will NOT affect the inner component.
"""
registry.register("slotted_component", SlottedComponent)
@register("block_inside_slot_v1")
class BlockInSlotInComponent(Component):
template_name = "block_in_slot_in_component.html"
template: types.django_html = """
{% load component_tags %}
{% component "block_inside_slot_v1" %}
{% fill "body" %}
BODY_FROM_FILL
{% endfill %}
{% endcomponent %}
{% block inner %}
wow
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>BODY_FROM_FILL</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
wow
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_default_block_default(self):
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
"""
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_block" %}{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
Helloodiddoo
Default inner
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_default_block_override(self):
registry.clear()
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
{% block inner %}
INNER BLOCK OVERRIDEN
{% endblock %}
"""
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_block" %}{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
Helloodiddoo
INNER BLOCK OVERRIDEN
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["isolated", "django"])
def test_slot_inside_block__slot_overriden_block_default(self):
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
"""
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_block" %}
{% fill "body" %}
SLOT OVERRIDEN
{% endfill %}
{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
Helloodiddoo
SLOT OVERRIDEN
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_overriden_block_overriden(self):
registry.register("slotted_component", SlottedComponent)
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
{% block inner %}
{% load component_tags %}
{% slot "new_slot" %}{% endslot %}
{% endblock %}
whut
"""
# NOTE: The "body" fill will NOT show up, because we override the `inner` block
# with a different slot. But the "new_slot" WILL show up.
template: types.django_html = """
{% load component_tags %}
{% component "slot_inside_block" %}
{% fill "body" %}
SLOT_BODY__OVERRIDEN
{% endfill %}
{% fill "new_slot" %}
SLOT_NEW__OVERRIDEN
{% endfill %}
{% endcomponent %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
Helloodiddoo
SLOT_NEW__OVERRIDEN
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)
@parametrize_context_behavior(["django", "isolated"])
def test_inject_inside_block(self):
registry.register("slotted_component", SlottedComponent)
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
def get_context_data(self):
var = self.inject("block_provide")
return {"var": var}
template: types.django_html = """
{% extends "block_in_component_provide.html" %}
{% load component_tags %}
{% block body %}
{% component "injectee" %}
{% endcomponent %}
{% endblock %}
"""
rendered = Template(template).render(Context())
expected = """
<!DOCTYPE html>
<html lang="en">
<body>
<custom-template>
<header></header>
<main>
<div> injected: DepInject(hello='from_block') </div>
</main>
<footer>Default footer</footer>
</custom-template>
</body>
</html>
"""
self.assertHTMLEqual(rendered, expected)