refactor: deprecate template caching, get_template_name, get_template, assoc template with Comp cls (#1222)

* refactor: deprecate template caching, get_template_name, get_template, assoc template with Comp cls

* refactor: change implementation

* refactor: handle cached template loader

* refactor: fix tests

* refactor: fix test on windows

* refactor: try to  fix type errors

* refactor: Re-cast `context` to fix type errors

* refactor: fix linter error

* refactor: fix typing

* refactor: more linter fixes

* refactor: more linter errors

* refactor: revert extra node metadata
This commit is contained in:
Juro Oravec 2025-06-01 19:20:22 +02:00 committed by GitHub
parent fa9ae9892f
commit 8677ee7941
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 1548 additions and 652 deletions

View file

@ -66,7 +66,6 @@ if not settings.configured:
}
],
COMPONENTS={
"template_cache_size": 128,
"autodiscover": False,
"context_behavior": CONTEXT_MODE,
},

View file

@ -37,7 +37,6 @@ if not settings.configured:
}
],
COMPONENTS={
"template_cache_size": 128,
"autodiscover": False,
"context_behavior": CONTEXT_MODE,
},

View file

@ -66,7 +66,6 @@ if not settings.configured:
}
],
COMPONENTS={
"template_cache_size": 128,
"autodiscover": False,
"context_behavior": CONTEXT_MODE,
},

View file

@ -37,7 +37,6 @@ if not settings.configured:
}
],
COMPONENTS={
"template_cache_size": 128,
"autodiscover": False,
"context_behavior": CONTEXT_MODE,
},

View file

@ -8,7 +8,6 @@ from typing import Any, NamedTuple
import pytest
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpRequest, HttpResponse
from django.template import Context, RequestContext, Template
from django.template.base import TextNode
@ -26,6 +25,7 @@ from django_components import (
register,
types,
)
from django_components.template import _get_component_template
from django_components.urls import urlpatterns as dc_urlpatterns
from django_components.testing import djc_test
@ -152,23 +152,43 @@ class TestComponentLegacyApi:
""",
)
@djc_test
class TestComponent:
# TODO_v1 - Remove
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_empty_component(self, components_settings):
class EmptyComponent(Component):
pass
def test_get_template_name(self, components_settings):
class SvgComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"name": kwargs.pop("name", None),
"css_class": kwargs.pop("css_class", None),
"title": kwargs.pop("title", None),
**kwargs,
}
with pytest.raises(ImproperlyConfigured):
EmptyComponent.render(args=["123"])
def get_template_name(self, context):
return f"dynamic_{context['name']}.svg"
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_template_string_static_inlined(self, components_settings):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
assertHTMLEqual(
SvgComponent.render(kwargs={"name": "svg1"}),
"""
<svg data-djc-id-ca1bc3e>Dynamic1</svg>
""",
)
assertHTMLEqual(
SvgComponent.render(kwargs={"name": "svg2"}),
"""
<svg data-djc-id-ca1bc3f>Dynamic2</svg>
""",
)
# TODO_v1 - Remove
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_get_template__string(self, components_settings):
class SimpleComponent(Component):
def get_template(self, context):
content: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
return content
def get_template_data(self, args, kwargs, slots, context):
return {
@ -187,8 +207,29 @@ class TestComponent:
""",
)
# TODO_v1 - Remove
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_template_string_dynamic(self, components_settings):
def test_get_template__template(self, components_settings):
class TestComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"variable": kwargs.pop("variable", None),
}
def get_template(self, context):
template_str = "Variable: <strong>{{ variable }}</strong>"
return Template(template_str)
rendered = TestComponent.render(kwargs={"variable": "test"})
assertHTMLEqual(
rendered,
"""
Variable: <strong data-djc-id-ca1bc3e>test</strong>
""",
)
# TODO_v1 - Remove
def test_get_template_is_cached(self):
class SimpleComponent(Component):
def get_template(self, context):
content: types.django_html = """
@ -201,6 +242,35 @@ class TestComponent:
"variable": kwargs.get("variable", None),
}
comp = SimpleComponent()
template_1 = _get_component_template(comp)
template_1._test_id = "123" # type: ignore[union-attr]
template_2 = _get_component_template(comp)
assert template_2._test_id == "123" # type: ignore[union-attr]
@djc_test
class TestComponent:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_empty_component(self, components_settings):
class EmptyComponent(Component):
pass
EmptyComponent.render(args=["123"])
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_template_string_static_inlined(self, components_settings):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
def get_template_data(self, args, kwargs, slots, context):
return {
"variable": kwargs.get("variable", None),
}
class Media:
css = "style.css"
js = "script.js"
@ -235,6 +305,90 @@ class TestComponent:
""",
)
# Test that even with cached template loaders, each Component has its own `Template`
# even when multiple components point to the same template file.
@djc_test(
parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR,
django_settings={
"TEMPLATES": [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
"tests/templates/",
"tests/components/",
],
"OPTIONS": {
"builtins": [
"django_components.templatetags.component_tags",
],
'loaders': [
('django.template.loaders.cached.Loader', [
# Default Django loader
'django.template.loaders.filesystem.Loader',
# Including this is the same as APP_DIRS=True
'django.template.loaders.app_directories.Loader',
# Components loader
'django_components.template_loader.Loader',
]),
],
},
}
],
},
)
def test_template_file_static__cached(self, components_settings):
class SimpleComponent1(Component):
template_file = "simple_template.html"
def get_template_data(self, args, kwargs, slots, context):
return {
"variable": kwargs.get("variable", None),
}
class SimpleComponent2(Component):
template_file = "simple_template.html"
def get_template_data(self, args, kwargs, slots, context):
return {
"variable": kwargs.get("variable", None),
}
SimpleComponent1.template # Triggers template loading
SimpleComponent2.template # Triggers template loading
# Both components have their own Template instance, but they point to the same template file.
assert isinstance(SimpleComponent1._template, Template)
assert isinstance(SimpleComponent2._template, Template)
assert SimpleComponent1._template is not SimpleComponent2._template
assert SimpleComponent1._template.source == SimpleComponent2._template.source
# The Template instances have different origins, but they point to the same template file.
assert SimpleComponent1._template.origin is not SimpleComponent2._template.origin
assert SimpleComponent1._template.origin.template_name == SimpleComponent2._template.origin.template_name
assert SimpleComponent1._template.origin.name == SimpleComponent2._template.origin.name
assert SimpleComponent1._template.origin.loader == SimpleComponent2._template.origin.loader
# The origins point to their respective Component classes.
assert SimpleComponent1._template.origin.component_cls == SimpleComponent1
assert SimpleComponent2._template.origin.component_cls == SimpleComponent2
rendered = SimpleComponent1.render(kwargs={"variable": "test"})
assertHTMLEqual(
rendered,
"""
Variable: <strong data-djc-id-ca1bc3e>test</strong>
""",
)
rendered = SimpleComponent2.render(kwargs={"variable": "test"})
assertHTMLEqual(
rendered,
"""
Variable: <strong data-djc-id-ca1bc3f>test</strong>
""",
)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_template_file_static__compat(self, components_settings):
class SimpleComponent(Component):
@ -288,53 +442,6 @@ class TestComponent:
""",
)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_template_file_dynamic(self, components_settings):
class SvgComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"name": kwargs.pop("name", None),
"css_class": kwargs.pop("css_class", None),
"title": kwargs.pop("title", None),
**kwargs,
}
def get_template_name(self, context):
return f"dynamic_{context['name']}.svg"
assertHTMLEqual(
SvgComponent.render(kwargs={"name": "svg1"}),
"""
<svg data-djc-id-ca1bc3e>Dynamic1</svg>
""",
)
assertHTMLEqual(
SvgComponent.render(kwargs={"name": "svg2"}),
"""
<svg data-djc-id-ca1bc3f>Dynamic2</svg>
""",
)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_allows_to_return_template(self, components_settings):
class TestComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
return {
"variable": kwargs.pop("variable", None),
}
def get_template(self, context):
template_str = "Variable: <strong>{{ variable }}</strong>"
return Template(template_str)
rendered = TestComponent.render(kwargs={"variable": "test"})
assertHTMLEqual(
rendered,
"""
Variable: <strong data-djc-id-ca1bc3e>test</strong>
""",
)
def test_get_component_by_id(self):
class SimpleComponent(Component):
pass
@ -369,6 +476,12 @@ class TestComponentRenderAPI:
def test_input(self):
class TestComponent(Component):
template: types.django_html = """
{% load component_tags %}
Variable: <strong>{{ variable }}</strong>
{% slot 'my_slot' / %}
"""
def get_template_data(self, args, kwargs, slots, context):
assert self.input.args == [123, "str"]
assert self.input.kwargs == {"variable": "test", "another": 1}
@ -381,7 +494,7 @@ class TestComponentRenderAPI:
"variable": kwargs["variable"],
}
def get_template(self, context):
def on_render_before(self, context, template):
assert self.input.args == [123, "str"]
assert self.input.kwargs == {"variable": "test", "another": 1}
assert isinstance(self.input.context, Context)
@ -389,13 +502,6 @@ class TestComponentRenderAPI:
my_slot = self.input.slots["my_slot"]
assert my_slot() == "MY_SLOT"
template_str: types.django_html = """
{% load component_tags %}
Variable: <strong>{{ variable }}</strong>
{% slot 'my_slot' / %}
"""
return Template(template_str)
rendered = TestComponent.render(
kwargs={"variable": "test", "another": 1},
args=(123, "str"),

View file

@ -210,17 +210,6 @@ class TestMainMedia:
assert AppLvlCompComponent._component_media.js == 'console.log("JS file");\n' # type: ignore[attr-defined]
assert AppLvlCompComponent._component_media.js_file == "app_lvl_comp/app_lvl_comp.js" # type: ignore[attr-defined]
def test_html_variable(self):
class VariableHTMLComponent(Component):
def get_template(self, context):
return Template("<div class='variable-html'>{{ variable }}</div>")
rendered = VariableHTMLComponent.render(context=Context({"variable": "Dynamic Content"}))
assertHTMLEqual(
rendered,
'<div class="variable-html" data-djc-id-ca1bc3e>Dynamic Content</div>',
)
def test_html_variable_filtered(self):
class FilteredComponent(Component):
template: types.django_html = """
@ -851,40 +840,46 @@ class TestMediaStaticfiles:
@djc_test
class TestMediaRelativePath:
class ParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
<h1>Parent content</h1>
{% component "variable_display" shadowing_variable='override' new_variable='unique_val' %}
{% endcomponent %}
</div>
<div>
{% slot 'content' %}
<h2>Slot content</h2>
{% component "variable_display" shadowing_variable='slot_default_override' new_variable='slot_default_unique' %}
def _gen_parent_component(self):
class ParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
<h1>Parent content</h1>
{% component "variable_display" shadowing_variable='override' new_variable='unique_val' %}
{% endcomponent %}
{% endslot %}
</div>
""" # noqa
</div>
<div>
{% slot 'content' %}
<h2>Slot content</h2>
{% component "variable_display" shadowing_variable='slot_default_override' new_variable='slot_default_unique' %}
{% endcomponent %}
{% endslot %}
</div>
""" # noqa
def get_template_data(self, args, kwargs, slots, context):
return {"shadowing_variable": "NOT SHADOWED"}
def get_template_data(self, args, kwargs, slots, context):
return {"shadowing_variable": "NOT SHADOWED"}
class VariableDisplay(Component):
template: types.django_html = """
{% load component_tags %}
<h1>Shadowing variable = {{ shadowing_variable }}</h1>
<h1>Uniquely named variable = {{ unique_variable }}</h1>
"""
return ParentComponent
def get_template_data(self, args, kwargs, slots, context):
context = {}
if kwargs["shadowing_variable"] is not None:
context["shadowing_variable"] = kwargs["shadowing_variable"]
if kwargs["new_variable"] is not None:
context["unique_variable"] = kwargs["new_variable"]
return context
def _gen_variable_display_component(self):
class VariableDisplay(Component):
template: types.django_html = """
{% load component_tags %}
<h1>Shadowing variable = {{ shadowing_variable }}</h1>
<h1>Uniquely named variable = {{ unique_variable }}</h1>
"""
def get_template_data(self, args, kwargs, slots, context):
context = {}
if kwargs["shadowing_variable"] is not None:
context["shadowing_variable"] = kwargs["shadowing_variable"]
if kwargs["new_variable"] is not None:
context["unique_variable"] = kwargs["new_variable"]
return context
return VariableDisplay
# Settings required for autodiscover to work
@djc_test(
@ -896,8 +891,8 @@ class TestMediaRelativePath:
}
)
def test_component_with_relative_media_paths(self):
registry.register(name="parent_component", component=self.ParentComponent)
registry.register(name="variable_display", component=self.VariableDisplay)
registry.register(name="parent_component", component=self._gen_parent_component())
registry.register(name="variable_display", component=self._gen_variable_display_component())
# Ensure that the module is executed again after import in autodiscovery
if "tests.components.relative_file.relative_file" in sys.modules:
@ -948,8 +943,8 @@ class TestMediaRelativePath:
}
)
def test_component_with_relative_media_paths_as_subcomponent(self):
registry.register(name="parent_component", component=self.ParentComponent)
registry.register(name="variable_display", component=self.VariableDisplay)
registry.register(name="parent_component", component=self._gen_parent_component())
registry.register(name="variable_display", component=self._gen_variable_display_component())
# Ensure that the module is executed again after import in autodiscovery
if "tests.components.relative_file.relative_file" in sys.modules:
@ -995,8 +990,8 @@ class TestMediaRelativePath:
https://github.com/django-components/django-components/issues/522#issuecomment-2173577094
"""
registry.register(name="parent_component", component=self.ParentComponent)
registry.register(name="variable_display", component=self.VariableDisplay)
registry.register(name="parent_component", component=self._gen_parent_component())
registry.register(name="variable_display", component=self._gen_variable_display_component())
# Ensure that the module is executed again after import in autodiscovery
if "tests.components.relative_file_pathobj.relative_file_pathobj" in sys.modules:
@ -1066,7 +1061,7 @@ class TestSubclassingMedia:
js = "grandparent.js"
class ParentComponent(GrandParentComponent):
Media = None
Media = None # type: ignore[assignment]
class ChildComponent(ParentComponent):
class Media:
@ -1149,7 +1144,7 @@ class TestSubclassingMedia:
js = "parent1.js"
class Parent2Component(GrandParent3Component, GrandParent4Component):
Media = None
Media = None # type: ignore[assignment]
class ChildComponent(Parent1Component, Parent2Component):
template: types.django_html = """

View file

@ -24,58 +24,61 @@ def dummy_context_processor(request):
#########################
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
def gen_simple_component():
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
def get_template_data(self, args, kwargs, slots, context):
return {"variable": kwargs.get("variable", None)} if "variable" in kwargs else {}
def get_template_data(self, args, kwargs, slots, context):
return {"variable": kwargs.get("variable", None)} if "variable" in kwargs else {}
return SimpleComponent
class VariableDisplay(Component):
template: types.django_html = """
{% load component_tags %}
<h1>Shadowing variable = {{ shadowing_variable }}</h1>
<h1>Uniquely named variable = {{ unique_variable }}</h1>
"""
def gen_variable_display_component():
class VariableDisplay(Component):
template: types.django_html = """
{% load component_tags %}
<h1>Shadowing variable = {{ shadowing_variable }}</h1>
<h1>Uniquely named variable = {{ unique_variable }}</h1>
"""
def get_template_data(self, args, kwargs, slots, context):
context = {}
if kwargs["shadowing_variable"] is not None:
context["shadowing_variable"] = kwargs["shadowing_variable"]
if kwargs["new_variable"] is not None:
context["unique_variable"] = kwargs["new_variable"]
return context
def get_template_data(self, args, kwargs, slots, context):
context = {}
if kwargs["shadowing_variable"] is not None:
context["shadowing_variable"] = kwargs["shadowing_variable"]
if kwargs["new_variable"] is not None:
context["unique_variable"] = kwargs["new_variable"]
return context
return VariableDisplay
class IncrementerComponent(Component):
template: types.django_html = """
{% load component_tags %}
<p class="incrementer">value={{ value }};calls={{ calls }}</p>
{% slot 'content' %}{% endslot %}
"""
def gen_incrementer_component():
class IncrementerComponent(Component):
template: types.django_html = """
{% load component_tags %}
<p class="incrementer">value={{ value }};calls={{ calls }}</p>
{% slot 'content' %}{% endslot %}
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.call_count = 0
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.call_count = 0
def get_template_data(self, args, kwargs, slots, context):
value = int(kwargs.get("value", 0))
if hasattr(self, "call_count"):
self.call_count += 1
else:
self.call_count = 1
return {"value": value + 1, "calls": self.call_count}
def get_template_data(self, args, kwargs, slots, context):
value = int(kwargs.get("value", 0))
if hasattr(self, "call_count"):
self.call_count += 1
else:
self.call_count = 1
return {"value": value + 1, "calls": self.call_count}
return IncrementerComponent
#########################
# TESTS
#########################
@djc_test
class TestContext:
def gen_parent_component():
class ParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
@ -96,129 +99,10 @@ class TestContext:
def get_template_data(self, args, kwargs, slots, context):
return {"shadowing_variable": "NOT SHADOWED"}
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_context_shadows_parent_with_unfilled_slots_and_component_tag(
self, components_settings,
):
registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="parent_component", component=self.ParentComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}{% endcomponent %}
"""
template = Template(template_str)
rendered = template.render(Context())
assertInHTML("<h1 data-djc-id-ca1bc43>Shadowing variable = override</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc44>Shadowing variable = slot_default_override</h1>", rendered)
assert "Shadowing variable = NOT SHADOWED" not in rendered
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_instances_have_unique_context_with_unfilled_slots_and_component_tag(
self, components_settings,
):
registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="parent_component", component=self.ParentComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}{% endcomponent %}
"""
template = Template(template_str)
rendered = template.render(Context())
assertInHTML("<h1 data-djc-id-ca1bc43>Uniquely named variable = unique_val</h1>", rendered)
assertInHTML(
"<h1 data-djc-id-ca1bc44>Uniquely named variable = slot_default_unique</h1>",
rendered,
)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_context_shadows_parent_with_filled_slots(self, components_settings):
registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="parent_component", component=self.ParentComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}
{% fill 'content' %}
{% component 'variable_display' shadowing_variable='shadow_from_slot' new_variable='unique_from_slot' %}
{% endcomponent %}
{% endfill %}
{% endcomponent %}
""" # NOQA
template = Template(template_str)
rendered = template.render(Context())
assertInHTML("<h1 data-djc-id-ca1bc45>Shadowing variable = override</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc46>Shadowing variable = shadow_from_slot</h1>", rendered)
assert "Shadowing variable = NOT SHADOWED" not in rendered
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_instances_have_unique_context_with_filled_slots(self, components_settings):
registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="parent_component", component=self.ParentComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}
{% fill 'content' %}
{% component 'variable_display' shadowing_variable='shadow_from_slot' new_variable='unique_from_slot' %}
{% endcomponent %}
{% endfill %}
{% endcomponent %}
""" # NOQA
template = Template(template_str)
rendered = template.render(Context())
assertInHTML("<h1 data-djc-id-ca1bc45>Uniquely named variable = unique_val</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc46>Uniquely named variable = unique_from_slot</h1>", rendered)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_context_shadows_outer_context_with_unfilled_slots_and_component_tag(
self, components_settings,
):
registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="parent_component", component=self.ParentComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}{% endcomponent %}
"""
template = Template(template_str)
rendered = template.render(Context({"shadowing_variable": "NOT SHADOWED"}))
assertInHTML("<h1 data-djc-id-ca1bc43>Shadowing variable = override</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc44>Shadowing variable = slot_default_override</h1>", rendered)
assert "Shadowing variable = NOT SHADOWED" not in rendered
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_context_shadows_outer_context_with_filled_slots(
self, components_settings,
):
registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="parent_component", component=self.ParentComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}
{% fill 'content' %}
{% component 'variable_display' shadowing_variable='shadow_from_slot' new_variable='unique_from_slot' %}
{% endcomponent %}
{% endfill %}
{% endcomponent %}
""" # NOQA
template = Template(template_str)
rendered = template.render(Context({"shadowing_variable": "NOT SHADOWED"}))
assertInHTML("<h1 data-djc-id-ca1bc45>Shadowing variable = override</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc46>Shadowing variable = shadow_from_slot</h1>", rendered)
assert "Shadowing variable = NOT SHADOWED" not in rendered
return ParentComponent
@djc_test
class TestParentArgs:
def gen_parent_component_with_args():
class ParentComponentWithArgs(Component):
template: types.django_html = """
{% load component_tags %}
@ -239,11 +123,144 @@ class TestParentArgs:
def get_template_data(self, args, kwargs, slots, context):
return {"inner_parent_value": kwargs["parent_value"]}
return ParentComponentWithArgs
#########################
# TESTS
#########################
@djc_test
class TestContext:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_context_shadows_parent_with_unfilled_slots_and_component_tag(
self, components_settings,
):
registry.register(name="variable_display", component=gen_variable_display_component())
registry.register(name="parent_component", component=gen_parent_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}{% endcomponent %}
"""
template = Template(template_str)
rendered = template.render(Context())
assertInHTML("<h1 data-djc-id-ca1bc43>Shadowing variable = override</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc44>Shadowing variable = slot_default_override</h1>", rendered)
assert "Shadowing variable = NOT SHADOWED" not in rendered
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_instances_have_unique_context_with_unfilled_slots_and_component_tag(
self, components_settings,
):
registry.register(name="variable_display", component=gen_variable_display_component())
registry.register(name="parent_component", component=gen_parent_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}{% endcomponent %}
"""
template = Template(template_str)
rendered = template.render(Context())
assertInHTML("<h1 data-djc-id-ca1bc43>Uniquely named variable = unique_val</h1>", rendered)
assertInHTML(
"<h1 data-djc-id-ca1bc44>Uniquely named variable = slot_default_unique</h1>",
rendered,
)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_context_shadows_parent_with_filled_slots(self, components_settings):
registry.register(name="variable_display", component=gen_variable_display_component())
registry.register(name="parent_component", component=gen_parent_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}
{% fill 'content' %}
{% component 'variable_display' shadowing_variable='shadow_from_slot' new_variable='unique_from_slot' %}
{% endcomponent %}
{% endfill %}
{% endcomponent %}
""" # NOQA
template = Template(template_str)
rendered = template.render(Context())
assertInHTML("<h1 data-djc-id-ca1bc45>Shadowing variable = override</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc46>Shadowing variable = shadow_from_slot</h1>", rendered)
assert "Shadowing variable = NOT SHADOWED" not in rendered
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_instances_have_unique_context_with_filled_slots(self, components_settings):
registry.register(name="variable_display", component=gen_variable_display_component())
registry.register(name="parent_component", component=gen_parent_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}
{% fill 'content' %}
{% component 'variable_display' shadowing_variable='shadow_from_slot' new_variable='unique_from_slot' %}
{% endcomponent %}
{% endfill %}
{% endcomponent %}
""" # NOQA
template = Template(template_str)
rendered = template.render(Context())
assertInHTML("<h1 data-djc-id-ca1bc45>Uniquely named variable = unique_val</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc46>Uniquely named variable = unique_from_slot</h1>", rendered)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_context_shadows_outer_context_with_unfilled_slots_and_component_tag(
self, components_settings,
):
registry.register(name="variable_display", component=gen_variable_display_component())
registry.register(name="parent_component", component=gen_parent_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}{% endcomponent %}
"""
template = Template(template_str)
rendered = template.render(Context({"shadowing_variable": "NOT SHADOWED"}))
assertInHTML("<h1 data-djc-id-ca1bc43>Shadowing variable = override</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc44>Shadowing variable = slot_default_override</h1>", rendered)
assert "Shadowing variable = NOT SHADOWED" not in rendered
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_nested_component_context_shadows_outer_context_with_filled_slots(
self, components_settings,
):
registry.register(name="variable_display", component=gen_variable_display_component())
registry.register(name="parent_component", component=gen_parent_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'parent_component' %}
{% fill 'content' %}
{% component 'variable_display' shadowing_variable='shadow_from_slot' new_variable='unique_from_slot' %}
{% endcomponent %}
{% endfill %}
{% endcomponent %}
""" # NOQA
template = Template(template_str)
rendered = template.render(Context({"shadowing_variable": "NOT SHADOWED"}))
assertInHTML("<h1 data-djc-id-ca1bc45>Shadowing variable = override</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc46>Shadowing variable = shadow_from_slot</h1>", rendered)
assert "Shadowing variable = NOT SHADOWED" not in rendered
@djc_test
class TestParentArgs:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_parent_args_can_be_drawn_from_context(self, components_settings):
registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="parent_with_args", component=self.ParentComponentWithArgs)
registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="incrementer", component=gen_incrementer_component())
registry.register(name="parent_with_args", component=gen_parent_component_with_args())
registry.register(name="variable_display", component=gen_variable_display_component())
template_str: types.django_html = """
{% load component_tags %}
@ -271,9 +288,9 @@ class TestParentArgs:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_parent_args_available_outside_slots(self, components_settings):
registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="parent_with_args", component=self.ParentComponentWithArgs)
registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="incrementer", component=gen_incrementer_component())
registry.register(name="parent_with_args", component=gen_parent_component_with_args())
registry.register(name="variable_display", component=gen_variable_display_component())
template_str: types.django_html = """
{% load component_tags %}
@ -282,8 +299,21 @@ class TestParentArgs:
template = Template(template_str)
rendered = template.render(Context())
assertInHTML("<h1 data-djc-id-ca1bc43>Shadowing variable = passed_in</h1>", rendered)
assertInHTML("<h1 data-djc-id-ca1bc44>Uniquely named variable = passed_in</h1>", rendered)
assertHTMLEqual(
rendered,
"""
<div data-djc-id-ca1bc3f>
<h1>Parent content</h1>
<h1 data-djc-id-ca1bc43>Shadowing variable = passed_in</h1>
<h1 data-djc-id-ca1bc43>Uniquely named variable = unique_val</h1>
</div>
<div data-djc-id-ca1bc3f>
<h2>Slot content</h2>
<h1 data-djc-id-ca1bc44>Shadowing variable = slot_default_override</h1>
<h1 data-djc-id-ca1bc44>Uniquely named variable = passed_in</h1>
</div>
""",
)
assert "Shadowing variable = NOT SHADOWED" not in rendered
@djc_test(
@ -297,9 +327,9 @@ class TestParentArgs:
)
)
def test_parent_args_available_in_slots(self, components_settings, first_val, second_val):
registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="parent_with_args", component=self.ParentComponentWithArgs)
registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="incrementer", component=gen_incrementer_component())
registry.register(name="parent_with_args", component=gen_parent_component_with_args())
registry.register(name="variable_display", component=gen_variable_display_component())
template_str: types.django_html = """
{% load component_tags %}
@ -333,7 +363,7 @@ class TestParentArgs:
class TestContextCalledOnce:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_one_context_call_with_simple_component(self, components_settings):
registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="incrementer", component=gen_incrementer_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'incrementer' %}{% endcomponent %}
@ -347,7 +377,7 @@ class TestContextCalledOnce:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_one_context_call_with_simple_component_and_arg(self, components_settings):
registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="incrementer", component=gen_incrementer_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'incrementer' value='2' %}{% endcomponent %}
@ -364,7 +394,7 @@ class TestContextCalledOnce:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_one_context_call_with_component(self, components_settings):
registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="incrementer", component=gen_incrementer_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'incrementer' %}{% endcomponent %}
@ -376,7 +406,7 @@ class TestContextCalledOnce:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_one_context_call_with_component_and_arg(self, components_settings):
registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="incrementer", component=gen_incrementer_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'incrementer' value='3' %}{% endcomponent %}
@ -388,7 +418,7 @@ class TestContextCalledOnce:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_one_context_call_with_slot(self, components_settings):
registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="incrementer", component=gen_incrementer_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'incrementer' %}
@ -411,7 +441,7 @@ class TestContextCalledOnce:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_one_context_call_with_slot_and_arg(self, components_settings):
registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="incrementer", component=gen_incrementer_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'incrementer' value='3' %}
@ -446,7 +476,7 @@ class TestComponentsCanAccessOuterContext:
)
)
def test_simple_component_can_use_outer_context(self, components_settings, expected_value):
registry.register(name="simple_component", component=SimpleComponent)
registry.register(name="simple_component", component=gen_simple_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'simple_component' %}{% endcomponent %}
@ -465,7 +495,7 @@ class TestComponentsCanAccessOuterContext:
class TestIsolatedContext:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_simple_component_can_pass_outer_context_in_args(self, components_settings):
registry.register(name="simple_component", component=SimpleComponent)
registry.register(name="simple_component", component=gen_simple_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'simple_component' variable=variable only %}{% endcomponent %}
@ -476,7 +506,7 @@ class TestIsolatedContext:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_simple_component_cannot_use_outer_context(self, components_settings):
registry.register(name="simple_component", component=SimpleComponent)
registry.register(name="simple_component", component=gen_simple_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'simple_component' only %}{% endcomponent %}
@ -492,7 +522,7 @@ class TestIsolatedContextSetting:
def test_component_tag_includes_variable_with_isolated_context_from_settings(
self,
):
registry.register(name="simple_component", component=SimpleComponent)
registry.register(name="simple_component", component=gen_simple_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'simple_component' variable=variable %}{% endcomponent %}
@ -505,7 +535,7 @@ class TestIsolatedContextSetting:
def test_component_tag_excludes_variable_with_isolated_context_from_settings(
self,
):
registry.register(name="simple_component", component=SimpleComponent)
registry.register(name="simple_component", component=gen_simple_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'simple_component' %}{% endcomponent %}
@ -518,7 +548,7 @@ class TestIsolatedContextSetting:
def test_component_includes_variable_with_isolated_context_from_settings(
self,
):
registry.register(name="simple_component", component=SimpleComponent)
registry.register(name="simple_component", component=gen_simple_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'simple_component' variable=variable %}
@ -532,7 +562,7 @@ class TestIsolatedContextSetting:
def test_component_excludes_variable_with_isolated_context_from_settings(
self,
):
registry.register(name="simple_component", component=SimpleComponent)
registry.register(name="simple_component", component=gen_simple_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'simple_component' %}

View file

@ -919,11 +919,11 @@ class TestSignatureBasedValidation:
template3.render(Context({}))
params3, nodelist3, node_id3, contents3 = captured # type: ignore
assert len(params3) == 1
assert isinstance(params3[0], TagAttr)
assert len(nodelist3) == 0
assert contents3 is None
assert node_id3 == "a1bc40"
assert len(params3) == 1 # type: ignore
assert isinstance(params3[0], TagAttr) # type: ignore
assert len(nodelist3) == 0 # type: ignore
assert contents3 is None # type: ignore
assert node_id3 == "a1bc40" # type: ignore
# Cleanup
TestNodeWithEndTag.unregister(component_tags.register)

View file

@ -10,10 +10,6 @@ from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
setup_test_config({"autodiscover": False})
class SlottedComponent(Component):
template_file = "slotted_template.html"
def _get_templates_used_to_render(subject_template, render_context=None):
"""Emulate django.test.client.Client (see request method)."""
from django.test.signals import template_rendered
@ -48,24 +44,33 @@ def with_template_signal(func):
@djc_test
class TestTemplateSignal:
class InnerComponent(Component):
template_file = "simple_template.html"
def gen_slotted_component(self):
class SlottedComponent(Component):
template_file = "slotted_template.html"
def get_template_data(self, args, kwargs, slots, context):
return {
"variable": kwargs["variable"],
"variable2": kwargs.get("variable2", "default"),
}
return SlottedComponent
class Media:
css = "style.css"
js = "script.js"
def gen_inner_component(self):
class InnerComponent(Component):
template_file = "simple_template.html"
def get_template_data(self, args, kwargs, slots, context):
return {
"variable": kwargs["variable"],
"variable2": kwargs.get("variable2", "default"),
}
class Media:
css = "style.css"
js = "script.js"
return InnerComponent
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
@with_template_signal
def test_template_rendered(self, components_settings):
registry.register("test_component", SlottedComponent)
registry.register("inner_component", self.InnerComponent)
registry.register("test_component", self.gen_slotted_component())
registry.register("inner_component", self.gen_inner_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'test_component' %}{% endcomponent %}
@ -77,8 +82,8 @@ class TestTemplateSignal:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
@with_template_signal
def test_template_rendered_nested_components(self, components_settings):
registry.register("test_component", SlottedComponent)
registry.register("inner_component", self.InnerComponent)
registry.register("test_component", self.gen_slotted_component())
registry.register("inner_component", self.gen_inner_component())
template_str: types.django_html = """
{% load component_tags %}
{% component 'test_component' %}

View file

@ -1,7 +1,8 @@
from django.template import Context, Template
from django.template import Template
from django_components import Component, cached_template, types
from django_components.template import _get_component_template
from django_components.testing import djc_test
from .testutils import setup_test_config
@ -10,6 +11,7 @@ setup_test_config({"autodiscover": False})
@djc_test
class TestTemplateCache:
# TODO_v1 - Remove
def test_cached_template(self):
template_1 = cached_template("Variable: <strong>{{ variable }}</strong>")
template_1._test_id = "123"
@ -18,6 +20,7 @@ class TestTemplateCache:
assert template_2._test_id == "123"
# TODO_v1 - Remove
def test_cached_template_accepts_class(self):
class MyTemplate(Template):
pass
@ -25,6 +28,8 @@ class TestTemplateCache:
template = cached_template("Variable: <strong>{{ variable }}</strong>", MyTemplate)
assert isinstance(template, MyTemplate)
# TODO_v1 - Move to `test_component.py`. While `cached_template()` will be removed,
# we will internally still cache templates by class, and we will want to test for that.
def test_component_template_is_cached(self):
class SimpleComponent(Component):
def get_template(self, context):
@ -38,9 +43,11 @@ class TestTemplateCache:
"variable": kwargs.get("variable", None),
}
comp = SimpleComponent()
template_1 = comp._get_template(Context({}), component_id="123")
template_1._test_id = "123"
comp = SimpleComponent(kwargs={"variable": "test"})
template_2 = comp._get_template(Context({}), component_id="123")
assert template_2._test_id == "123"
# Check that we get the same template instance
template_1 = _get_component_template(comp)
template_1._test_id = "123" # type: ignore[union-attr]
template_2 = _get_component_template(comp)
assert template_2._test_id == "123" # type: ignore[union-attr]

View file

@ -10,10 +10,6 @@ from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
setup_test_config({"autodiscover": False})
class SlottedComponent(Component):
template_file = "slotted_template.html"
#######################
# TESTS
#######################

View file

@ -12,22 +12,28 @@ from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
setup_test_config({"autodiscover": False})
class SlottedComponent(Component):
template_file = "slotted_template.html"
def gen_slotted_component():
class SlottedComponent(Component):
template_file = "slotted_template.html"
return SlottedComponent
class SlottedComponentWithContext(Component):
template: types.django_html = """
{% load component_tags %}
<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>
"""
def gen_slotted_component_with_context():
class SlottedComponentWithContext(Component):
template: types.django_html = """
{% load component_tags %}
<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>
"""
def get_template_data(self, args, kwargs, slots, context):
return {"variable": kwargs["variable"]}
def get_template_data(self, args, kwargs, slots, context):
return {"variable": kwargs["variable"]}
return SlottedComponentWithContext
#######################
@ -522,8 +528,8 @@ class TestDynamicComponentTemplateTag:
class TestMultiComponent:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_both_components_render_correctly_with_no_slots(self, components_settings):
registry.register("first_component", SlottedComponent)
registry.register("second_component", SlottedComponentWithContext)
registry.register("first_component", gen_slotted_component())
registry.register("second_component", gen_slotted_component_with_context())
template_str: types.django_html = """
{% load component_tags %}
@ -557,8 +563,8 @@ class TestMultiComponent:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_both_components_render_correctly_with_slots(self, components_settings):
registry.register("first_component", SlottedComponent)
registry.register("second_component", SlottedComponentWithContext)
registry.register("first_component", gen_slotted_component())
registry.register("second_component", gen_slotted_component_with_context())
template_str: types.django_html = """
{% load component_tags %}
@ -594,8 +600,8 @@ class TestMultiComponent:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_both_components_render_correctly_when_only_first_has_slots(self, components_settings):
registry.register("first_component", SlottedComponent)
registry.register("second_component", SlottedComponentWithContext)
registry.register("first_component", gen_slotted_component())
registry.register("second_component", gen_slotted_component_with_context())
template_str: types.django_html = """
{% load component_tags %}
@ -630,8 +636,8 @@ class TestMultiComponent:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_both_components_render_correctly_when_only_second_has_slots(self, components_settings):
registry.register("first_component", SlottedComponent)
registry.register("second_component", SlottedComponentWithContext)
registry.register("first_component", gen_slotted_component())
registry.register("second_component", gen_slotted_component_with_context())
template_str: types.django_html = """
{% load component_tags %}
@ -667,19 +673,19 @@ class TestMultiComponent:
@djc_test
class TestComponentIsolation:
class SlottedComponent(Component):
template: types.django_html = """
{% load component_tags %}
<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>
"""
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_instances_of_component_do_not_share_slots(self, components_settings):
registry.register("test", self.SlottedComponent)
@register("test")
class SlottedComponent(Component):
template: types.django_html = """
{% load component_tags %}
<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>
"""
template_str: types.django_html = """
{% load component_tags %}
{% component "test" %}
@ -791,7 +797,7 @@ class TestRecursiveComponent:
class TestComponentTemplateSyntaxError:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_variable_outside_fill_tag_compiles_w_out_error(self, components_settings):
registry.register("test", SlottedComponent)
registry.register("test", gen_slotted_component())
# As of v0.28 this is valid, provided the component registered under "test"
# contains a slot tag marked as 'default'. This is verified outside
# template compilation time.
@ -805,7 +811,7 @@ class TestComponentTemplateSyntaxError:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_text_outside_fill_tag_is_not_error_when_no_fill_tags(self, components_settings):
registry.register("test", SlottedComponent)
registry.register("test", gen_slotted_component())
# As of v0.28 this is valid, provided the component registered under "test"
# contains a slot tag marked as 'default'. This is verified outside
# template compilation time.
@ -819,7 +825,7 @@ class TestComponentTemplateSyntaxError:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_text_outside_fill_tag_is_error_when_fill_tags(self, components_settings):
registry.register("test", SlottedComponent)
registry.register("test", gen_slotted_component())
template_str: types.django_html = """
{% load component_tags %}
{% component "test" %}
@ -837,7 +843,7 @@ class TestComponentTemplateSyntaxError:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_unclosed_component_is_error(self, components_settings):
registry.register("test", SlottedComponent)
registry.register("test", gen_slotted_component())
with pytest.raises(
TemplateSyntaxError,
match=re.escape("Unclosed tag on line 3: 'component'"),

View file

@ -11,21 +11,18 @@ from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
setup_test_config({"autodiscover": False})
class SlottedComponent(Component):
template_file = "slotted_template.html"
def gen_slotted_component():
class SlottedComponent(Component):
template_file = "slotted_template.html"
return SlottedComponent
class BlockedAndSlottedComponent(Component):
template_file = "blocked_and_slotted_template.html"
def gen_blocked_and_slotted_component():
class BlockedAndSlottedComponent(Component):
template_file = "blocked_and_slotted_template.html"
class RelativeFileComponentUsingTemplateFile(Component):
template_file = "relative_extends.html"
class RelativeFileComponentUsingGetTemplateName(Component):
def get_template_name(self, context):
return "relative_extends.html"
return BlockedAndSlottedComponent
#######################
@ -37,7 +34,7 @@ class RelativeFileComponentUsingGetTemplateName(Component):
class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_double_extends_on_main_template_and_component_one_component(self, components_settings):
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
registry.register("blocked_and_slotted_component", gen_blocked_and_slotted_component())
@register("extended_component")
class _ExtendedComponent(Component):
@ -82,7 +79,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_double_extends_on_main_template_and_component_two_identical_components(self, components_settings):
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
registry.register("blocked_and_slotted_component", gen_blocked_and_slotted_component())
@register("extended_component")
class _ExtendedComponent(Component):
@ -134,12 +131,11 @@ class TestExtendsCompat:
</body>
</html>
"""
assertHTMLEqual(rendered, expected)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_double_extends_on_main_template_and_component_two_different_components_same_parent(self, components_settings): # noqa: E501
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
registry.register("blocked_and_slotted_component", gen_blocked_and_slotted_component())
@register("extended_component")
class _ExtendedComponent(Component):
@ -201,12 +197,11 @@ class TestExtendsCompat:
</body>
</html>
"""
assertHTMLEqual(rendered, expected)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_double_extends_on_main_template_and_component_two_different_components_different_parent(self, components_settings): # noqa: E501
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
registry.register("blocked_and_slotted_component", gen_blocked_and_slotted_component())
@register("extended_component")
class _ExtendedComponent(Component):
@ -271,7 +266,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_extends_on_component_one_component(self, components_settings):
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
registry.register("blocked_and_slotted_component", gen_blocked_and_slotted_component())
@register("extended_component")
class _ExtendedComponent(Component):
@ -314,7 +309,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_extends_on_component_two_component(self, components_settings):
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
registry.register("blocked_and_slotted_component", gen_blocked_and_slotted_component())
@register("extended_component")
class _ExtendedComponent(Component):
@ -368,8 +363,8 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_double_extends_on_main_template_and_nested_component(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
registry.register("slotted_component", gen_slotted_component())
registry.register("blocked_and_slotted_component", gen_blocked_and_slotted_component())
@register("extended_component")
class _ExtendedComponent(Component):
@ -425,8 +420,8 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_double_extends_on_main_template_and_nested_component_and_include(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("blocked_and_slotted_component", BlockedAndSlottedComponent)
registry.register("slotted_component", gen_slotted_component())
registry.register("blocked_and_slotted_component", gen_blocked_and_slotted_component())
@register("extended_component")
class _ExtendedComponent(Component):
@ -459,12 +454,25 @@ class TestExtendsCompat:
# second rendering after cache built
rendered_2 = Template(template).render(Context({"DJC_DEPS_STRATEGY": "ignore"}))
expected_2 = expected.replace("data-djc-id-ca1bc3f", "data-djc-id-ca1bc41")
expected_2 = """
<!DOCTYPE html>
<html lang="en">
<body>
<main role="main">
<div class='container main-container'>
Variable: <strong></strong>
Variable: <strong data-djc-id-ca1bc41></strong>
</div>
</main>
</body>
</html>
"""
assertHTMLEqual(rendered_2, expected_2)
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_slots_inside_extends(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
@register("slot_inside_extends")
class SlotInsideExtendsComponent(Component):
@ -497,7 +505,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_slots_inside_include(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
@register("slot_inside_include")
class SlotInsideIncludeComponent(Component):
@ -530,7 +538,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_component_inside_block(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
template: types.django_html = """
{% extends "block.html" %}
{% load component_tags %}
@ -565,7 +573,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_block_inside_component(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
template: types.django_html = """
{% extends "block_in_component.html" %}
@ -594,7 +602,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_block_inside_component_parent(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
@register("block_in_component_parent")
class BlockInCompParent(Component):
@ -627,7 +635,7 @@ class TestExtendsCompat:
Assert that when we call a component with `{% component %}`, that
the `{% block %}` will NOT affect the inner component.
"""
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
@register("block_inside_slot_v1")
class BlockInSlotInComponent(Component):
@ -662,7 +670,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_slot_inside_block__slot_default_block_default(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
@ -695,7 +703,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_slot_inside_block__slot_default_block_override(self, components_settings):
registry.clear()
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
@ -730,7 +738,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_slot_inside_block__slot_overriden_block_default(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
@ -766,7 +774,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_slot_inside_block__slot_overriden_block_overriden(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
@ -812,7 +820,7 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_inject_inside_block(self, components_settings):
registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", gen_slotted_component())
@register("injectee")
class InjectComponent(Component):
@ -851,7 +859,9 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_component_using_template_file_extends_relative_file(self, components_settings):
registry.register("relative_file_component_using_template_file", RelativeFileComponentUsingTemplateFile)
@register("relative_file_component_using_template_file")
class RelativeFileComponentUsingTemplateFile(Component):
template_file = "relative_extends.html"
template: types.django_html = """
{% load component_tags %}
@ -874,7 +884,10 @@ class TestExtendsCompat:
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
def test_component_using_get_template_name_extends_relative_file(self, components_settings):
registry.register("relative_file_component_using_get_template_name", RelativeFileComponentUsingGetTemplateName)
@register("relative_file_component_using_get_template_name")
class RelativeFileComponentUsingGetTemplateName(Component):
def get_template_name(self, context):
return "relative_extends.html"
template: types.django_html = """
{% load component_tags %}

View file

@ -37,12 +37,19 @@ def setup_test_config(
"OPTIONS": {
"builtins": [
"django_components.templatetags.component_tags",
]
],
'loaders': [
# Default Django loader
'django.template.loaders.filesystem.Loader',
# Including this is the same as APP_DIRS=True
'django.template.loaders.app_directories.Loader',
# Components loader
'django_components.template_loader.Loader',
],
},
}
],
"COMPONENTS": {
"template_cache_size": 128,
**(components or {}),
},
"MIDDLEWARE": [],