refactor: fix slot fills for components nested in themselves (#456)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Juro Oravec 2024-04-25 20:36:05 +02:00 committed by GitHub
parent 091da26da5
commit 29c931f150
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 5 deletions

View file

@ -263,7 +263,7 @@ class Component(View, metaclass=SimplifiedInterfaceMediaDefiningClass):
# NOTE: This if/else is important to avoid nested Contexts,
# See https://github.com/EmilStenstrom/django-components/issues/414
context = context_data if isinstance(context_data, Context) else Context(context_data)
prepare_context(context, self.outer_context or Context(), component_id=self.component_id)
prepare_context(context, component_id=self.component_id, outer_context=self.outer_context or Context())
template = self.get_template(context)
# Associate the slots with this component for this context
@ -304,8 +304,8 @@ class Component(View, metaclass=SimplifiedInterfaceMediaDefiningClass):
"""Fill component slots outside of template rendering."""
self.fill_content = {
slot_name: FillContent(
TextNode(escape(content) if escape_content else content),
None,
nodes=NodeList([TextNode(escape(content) if escape_content else content)]),
alias=None,
)
for (slot_name, content) in slots_data.items()
}

View file

@ -195,6 +195,13 @@ def set_slot_component_association(
Hence, we use the Context to store the associations of SlotNode <-> Component
for the current context stack.
"""
# Store associations on the latest context layer so that we can nest components
# onto themselves (component A is rendered in slot fill of component A).
# Otherwise, they would overwrite each other as the ComponentNode and SlotNodes
# are re-used, so their IDs don't change across these two occurences.
latest_dict = context.dicts[-1]
if _SLOT_COMPONENT_ASSOC_KEY not in latest_dict:
latest_dict[_SLOT_COMPONENT_ASSOC_KEY] = context[_SLOT_COMPONENT_ASSOC_KEY].copy()
context[_SLOT_COMPONENT_ASSOC_KEY][slot_id] = component_id

View file

@ -232,7 +232,7 @@ class IfSlotFilledConditionBranchNode(_IfSlotFilledBranchNode, ComponentIdMixin)
super().__init__(nodelist)
def evaluate(self, context: Context) -> bool:
slot_fill = get_slot_fill(context, self.component_id, self.slot_name)
slot_fill = get_slot_fill(context, component_id=self.component_id, slot_name=self.slot_name)
is_filled = slot_fill is not None
# Make polarity switchable.
# i.e. if slot name is NOT filled and is_positive=False,

View file

@ -212,7 +212,7 @@ class ComponentTest(BaseTestCase):
Path(__file__).resolve().parent / "components",
],
)
def test_component_media_with_dict_with_relative_paths(self):
def test_component_with_relative_paths_as_subcomponent(self):
# Ensure that the module is executed again after import in autodiscovery
if "tests.components.relative_file.relative_file" in sys.modules:
del sys.modules["tests.components.relative_file.relative_file"]
@ -287,6 +287,60 @@ class ComponentTest(BaseTestCase):
""",
)
def test_fill_inside_fill_with_same_name(self):
class SlottedComponent(component.Component):
template_name = "slotted_template.html"
def get_context_data(self, name: Optional[str] = None) -> Dict[str, Any]:
return {
"name": name,
}
component.registry.register("test", SlottedComponent)
self.template = Template(
"""
{% load component_tags %}
{% component "test" name='Igor' %}
{% fill "header" %}
{% component "test" name='Joe2' %}
{% fill "header" %}
Name2: {{ name }}
{% endfill %}
{% fill "main" %}
Day2: {{ day }}
{% endfill %}
{% fill "footer" %}
XYZ
{% endfill %}
{% endcomponent %}
{% endfill %}
{% fill "footer" %}
WWW
{% endfill %}
{% endcomponent %}
"""
)
# {{ name }} should be "Jannete" everywhere
rendered = self.template.render(Context({"day": "Monday", "name": "Jannete"}))
self.assertHTMLEqual(
rendered,
"""
<custom-template>
<header>
<custom-template>
<header>Name2: Jannete</header>
<main>Day2: Monday</main>
<footer>XYZ</footer>
</custom-template>
</header>
<main>Default main</main>
<footer>WWW</footer>
</custom-template>
""",
)
@override_settings(
COMPONENTS={
"context_behavior": "isolated",