)'"
- )
- if not is_wrapped_in_quotes(slot_name):
- raise TemplateSyntaxError(f"First argument of '{bits[0]}' must be a quoted string 'literal'.")
- slot_name = strip_quotes(slot_name)
- return slot_name, is_positive
-
-
def check_for_isolated_context_keyword(bits: List[str]) -> Tuple[List[str], bool]:
"""Return True and strip the last word if token ends with 'only' keyword or if CONTEXT_BEHAVIOR is 'isolated'."""
if bits[-1] == "only":
return bits[:-1], True
- if app_settings.CONTEXT_BEHAVIOR == "isolated":
+ if app_settings.CONTEXT_BEHAVIOR == ContextBehavior.ISOLATED:
return bits, True
return bits, False
@@ -416,13 +312,3 @@ def is_wrapped_in_quotes(s: str) -> bool:
def strip_quotes(s: str) -> str:
return s.strip("\"'")
-
-
-def bool_from_string(s: str) -> bool:
- s = strip_quotes(s.lower())
- if s == "true":
- return True
- elif s == "false":
- return False
- else:
- raise TemplateSyntaxError(f"Expected a bool value. Received: '{s}'")
diff --git a/tests/templates/template_is_filled.html b/tests/templates/template_is_filled.html
new file mode 100644
index 00000000..491508bb
--- /dev/null
+++ b/tests/templates/template_is_filled.html
@@ -0,0 +1,10 @@
+{% load component_tags %}
+
+ {% slot "title" %}{% endslot %}
+ {% slot "my_title" %}{% endslot %}
+ {% slot "my title 1" %}{% endslot %}
+ {% slot "my-title-2" %}{% endslot %}
+ {% slot "escape this: #$%^*()" %}{% endslot %}
+
+ {{ component_vars.is_filled }}
+
diff --git a/tests/templates/template_with_conditional_slots.html b/tests/templates/template_with_conditional_slots.html
index 4028b0f8..76f265e8 100644
--- a/tests/templates/template_with_conditional_slots.html
+++ b/tests/templates/template_with_conditional_slots.html
@@ -2,7 +2,7 @@
{% load component_tags %}
{% slot "title" %}Title{% endslot %}
- {% if_filled "subtitle" %}
+ {% if component_vars.is_filled.subtitle %}
{% slot "subtitle" %}Optional subtitle{% endslot %}
- {% endif_filled %}
+ {% endif %}
diff --git a/tests/templates/template_with_if_elif_else_conditional_slots.html b/tests/templates/template_with_if_elif_else_conditional_slots.html
index 08e5c92e..f5084c79 100644
--- a/tests/templates/template_with_if_elif_else_conditional_slots.html
+++ b/tests/templates/template_with_if_elif_else_conditional_slots.html
@@ -2,11 +2,11 @@
{% load component_tags %}
{% slot "title" %}Title{% endslot %}
- {% if_filled 'subtitle'%}
+ {% if component_vars.is_filled.subtitle %}
{% slot "subtitle" %}Optional subtitle{% endslot %}
- {% elif_filled "alt_subtitle" %}
+ {% elif component_vars.is_filled.alt_subtitle %}
{% slot "alt_subtitle" %}Why would you want this?{% endslot %}
- {% else_filled %}
+ {% else %}
Nothing filled!
- {% endif_filled %}
+ {% endif %}
diff --git a/tests/templates/template_with_negated_conditional_slots.html b/tests/templates/template_with_negated_conditional_slots.html
index af7783c0..db5f3a7f 100644
--- a/tests/templates/template_with_negated_conditional_slots.html
+++ b/tests/templates/template_with_negated_conditional_slots.html
@@ -2,9 +2,9 @@
{% load component_tags %}
{% slot "title" %}Title{% endslot %}
- {% if_filled "subtitle" False %}
+ {% if not component_vars.is_filled.subtitle %}
Subtitle not filled!
- {% else_filled %}
+ {% else %}
{% slot "alt_subtitle" %}Why would you want this?{% endslot %}
- {% endif_filled %}
+ {% endif %}
diff --git a/tests/test_component.py b/tests/test_component.py
index 00b0a927..5d803b9a 100644
--- a/tests/test_component.py
+++ b/tests/test_component.py
@@ -368,7 +368,6 @@ class ComponentTest(BaseTestCase):
@override_settings(
COMPONENTS={
"context_behavior": "isolated",
- "slot_context_behavior": "isolated",
},
)
def test_slots_of_top_level_comps_can_access_full_outer_ctx(self):
@@ -387,15 +386,16 @@ class ComponentTest(BaseTestCase):
{% load component_tags %}
{% component "test" %}
- ABC: {{ name }}
+ ABC: {{ name }} {{ some }}
{% endcomponent %}
"""
)
nested_ctx = Context()
- nested_ctx.push({"some": "var"}) # <-- Nested comp's take data only from this layer
- nested_ctx.push({"name": "carl"}) # <-- But for top-level comp, it should access this layer too
+ # Check that the component can access vars across different context layers
+ nested_ctx.push({"some": "var"})
+ nested_ctx.push({"name": "carl"})
rendered = self.template.render(nested_ctx)
self.assertHTMLEqual(
@@ -403,7 +403,7 @@ class ComponentTest(BaseTestCase):
"""
- ABC: carl
+ ABC: carl var
""",
@@ -805,7 +805,9 @@ class ComponentIsolationTests(BaseTestCase):
class SlotBehaviorTests(BaseTestCase):
- def setUp(self):
+ # NOTE: This is standalone function instead of setUp, so we can configure
+ # Django settings per test with `@override_settings`
+ def make_template(self) -> Template:
class SlottedComponent(component.Component):
template_name = "slotted_template.html"
@@ -816,7 +818,7 @@ class SlotBehaviorTests(BaseTestCase):
component.registry.register("test", SlottedComponent)
- self.template = Template(
+ return Template(
"""
{% load component_tags %}
{% component "test" name='Igor' %}
@@ -841,11 +843,12 @@ class SlotBehaviorTests(BaseTestCase):
)
@override_settings(
- COMPONENTS={"slot_context_behavior": "allow_override"},
+ COMPONENTS={"context_behavior": "django"},
)
- def test_slot_context_allow_override(self):
+ def test_slot_context__django(self):
+ template = self.make_template()
# {{ name }} should be neither Jannete not empty, because overriden everywhere
- rendered = self.template.render(Context({"day": "Monday", "name": "Jannete"}))
+ rendered = template.render(Context({"day": "Monday", "name": "Jannete"}))
self.assertHTMLEqual(
rendered,
"""
@@ -864,15 +867,16 @@ class SlotBehaviorTests(BaseTestCase):
)
# {{ name }} should be effectively the same as before, because overriden everywhere
- rendered2 = self.template.render(Context({"day": "Monday"}))
+ rendered2 = template.render(Context({"day": "Monday"}))
self.assertHTMLEqual(rendered2, rendered)
@override_settings(
- COMPONENTS={"slot_context_behavior": "isolated"},
+ COMPONENTS={"context_behavior": "isolated"},
)
- def test_slot_context_isolated(self):
+ def test_slot_context__isolated(self):
+ template = self.make_template()
# {{ name }} should be "Jannete" everywhere
- rendered = self.template.render(Context({"day": "Monday", "name": "Jannete"}))
+ rendered = template.render(Context({"day": "Monday", "name": "Jannete"}))
self.assertHTMLEqual(
rendered,
"""
@@ -891,7 +895,7 @@ class SlotBehaviorTests(BaseTestCase):
)
# {{ name }} should be empty everywhere
- rendered2 = self.template.render(Context({"day": "Monday"}))
+ rendered2 = template.render(Context({"day": "Monday"}))
self.assertHTMLEqual(
rendered2,
"""
@@ -908,47 +912,3 @@ class SlotBehaviorTests(BaseTestCase):
""",
)
-
- @override_settings(
- COMPONENTS={
- "slot_context_behavior": "prefer_root",
- },
- )
- def test_slot_context_prefer_root(self):
- # {{ name }} should be "Jannete" everywhere
- rendered = self.template.render(Context({"day": "Monday", "name": "Jannete"}))
- self.assertHTMLEqual(
- rendered,
- """
-
-
- Day: Monday
-
-
- """,
- )
-
- # {{ name }} should be neither "Jannete" nor empty anywhere
- rendered = self.template.render(Context({"day": "Monday"}))
- self.assertHTMLEqual(
- rendered,
- """
-
-
- Day: Monday
-
-
- """,
- )
diff --git a/tests/test_context.py b/tests/test_context.py
index a21d90e6..dd237d49 100644
--- a/tests/test_context.py
+++ b/tests/test_context.py
@@ -213,17 +213,67 @@ class ParentArgsTests(BaseTestCase):
component.registry.register(name="parent_with_args", component=ParentComponentWithArgs)
component.registry.register(name="variable_display", component=VariableDisplay)
- def test_parent_args_can_be_drawn_from_context(self):
+ @override_settings(
+ COMPONENTS={
+ "context_behavior": "django",
+ }
+ )
+ def test_parent_args_can_be_drawn_from_context__django(self):
template = Template(
- "{% load component_tags %}{% component_dependencies %}"
- "{% component 'parent_with_args' parent_value=parent_value %}"
- "{% endcomponent %}"
+ """
+ {% load component_tags %}{% component_dependencies %}
+ {% component 'parent_with_args' parent_value=parent_value %}
+ {% endcomponent %}
+ """
)
rendered = template.render(Context({"parent_value": "passed_in"}))
- self.assertIn("Shadowing variable = passed_in
", rendered, rendered)
- self.assertIn("Uniquely named variable = passed_in
", rendered, rendered)
- self.assertNotIn("Shadowing variable = NOT SHADOWED
", rendered, rendered)
+ self.assertHTMLEqual(
+ rendered,
+ """
+
+
Parent content
+ Shadowing variable = passed_in
+ Uniquely named variable = unique_val
+
+
+
Slot content
+ Shadowing variable = slot_default_override
+ Uniquely named variable = passed_in
+
+ """,
+ )
+
+ @override_settings(
+ COMPONENTS={
+ "context_behavior": "isolated",
+ }
+ )
+ def test_parent_args_can_be_drawn_from_context__isolated(self):
+ template = Template(
+ """
+ {% load component_tags %}{% component_dependencies %}
+ {% component 'parent_with_args' parent_value=parent_value %}
+ {% endcomponent %}
+ """
+ )
+ rendered = template.render(Context({"parent_value": "passed_in"}))
+
+ self.assertHTMLEqual(
+ rendered,
+ """
+
+
Parent content
+ Shadowing variable = passed_in
+ Uniquely named variable = unique_val
+
+
+
Slot content
+ Shadowing variable = slot_default_override
+ Uniquely named variable = passed_in
+
+ """,
+ )
def test_parent_args_available_outside_slots(self):
template = Template(
@@ -236,7 +286,12 @@ class ParentArgsTests(BaseTestCase):
self.assertIn("Uniquely named variable = passed_in
", rendered, rendered)
self.assertNotIn("Shadowing variable = NOT SHADOWED
", rendered, rendered)
- def test_parent_args_available_in_slots(self):
+ @override_settings(
+ COMPONENTS={
+ "context_behavior": "django",
+ }
+ )
+ def test_parent_args_available_in_slots__django(self):
template = Template(
"""
{% load component_tags %}{% component_dependencies %}
@@ -246,13 +301,56 @@ class ParentArgsTests(BaseTestCase):
{% endcomponent %}
{% endfill %}
{% endcomponent %}
- """ # NOQA
+ """ # noqa: E501
)
rendered = template.render(Context())
+ self.assertHTMLEqual(
+ rendered,
+ """
+
+
Parent content
+ Shadowing variable = passed_in
+ Uniquely named variable = unique_val
+
+
+
Shadowing variable = value_from_slot
+ Uniquely named variable = passed_in
+
+ """,
+ )
- self.assertIn("Shadowing variable = value_from_slot
", rendered, rendered)
- self.assertIn("Uniquely named variable = passed_in
", rendered, rendered)
- self.assertNotIn("Shadowing variable = NOT SHADOWED
", rendered, rendered)
+ @override_settings(
+ COMPONENTS={
+ "context_behavior": "isolated",
+ }
+ )
+ def test_parent_args_not_available_in_slots__isolated(self):
+ template = Template(
+ """
+ {% load component_tags %}{% component_dependencies %}
+ {% component 'parent_with_args' parent_value='passed_in' %}
+ {% fill 'content' %}
+ {% component name='variable_display' shadowing_variable='value_from_slot' new_variable=inner_parent_value %}
+ {% endcomponent %}
+ {% endfill %}
+ {% endcomponent %}
+ """ # noqa: E501
+ )
+ rendered = template.render(Context())
+ self.assertHTMLEqual(
+ rendered,
+ """
+
+
Parent content
+ Shadowing variable = passed_in
+ Uniquely named variable = unique_val
+
+
+
Shadowing variable = value_from_slot
+ Uniquely named variable =
+
+ """,
+ )
class ContextCalledOnceTests(BaseTestCase):
@@ -325,13 +423,37 @@ class ComponentsCanAccessOuterContext(BaseTestCase):
super().setUpClass()
component.registry.register(name="simple_component", component=SimpleComponent)
- def test_simple_component_can_use_outer_context(self):
+ @override_settings(
+ COMPONENTS={"context_behavior": "django"},
+ )
+ def test_simple_component_can_use_outer_context__django(self):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'simple_component' %}{% endcomponent %}"
)
- rendered = template.render(Context({"variable": "outer_value"})).strip()
- self.assertIn("outer_value", rendered, rendered)
+ rendered = template.render(Context({"variable": "outer_value"}))
+ self.assertHTMLEqual(
+ rendered,
+ """
+ Variable: outer_value
+ """,
+ )
+
+ @override_settings(
+ COMPONENTS={"context_behavior": "isolated"},
+ )
+ def test_simple_component_cannot_use_outer_context__isolated(self):
+ template = Template(
+ "{% load component_tags %}{% component_dependencies %}"
+ "{% component 'simple_component' %}{% endcomponent %}"
+ )
+ rendered = template.render(Context({"variable": "outer_value"}))
+ self.assertHTMLEqual(
+ rendered,
+ """
+ Variable:
+ """,
+ )
class IsolatedContextTests(BaseTestCase):
@@ -424,9 +546,9 @@ class OuterContextPropertyTests(BaseTestCase):
component.registry.register(name="outer_context_component", component=OuterContextComponent)
@override_settings(
- COMPONENTS={"context_behavior": "global"},
+ COMPONENTS={"context_behavior": "django"},
)
- def test_outer_context_property_with_component_global(self):
+ def test_outer_context_property_with_component__django(self):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'outer_context_component' only %}{% endcomponent %}"
@@ -437,7 +559,7 @@ class OuterContextPropertyTests(BaseTestCase):
@override_settings(
COMPONENTS={"context_behavior": "isolated"},
)
- def test_outer_context_property_with_component_isolated(self):
+ def test_outer_context_property_with_component__isolated(self):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'outer_context_component' only %}{% endcomponent %}"
diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py
index 10f63de8..40dffc25 100644
--- a/tests/test_templatetags.py
+++ b/tests/test_templatetags.py
@@ -266,7 +266,44 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
""",
)
- def test_slotted_template_with_context_var(self):
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_slotted_template_with_context_var__isolated(self):
+ component.registry.register(name="test1", component=SlottedComponentWithContext)
+
+ template = Template(
+ """
+ {% load component_tags %}
+ {% with my_first_variable="test123" %}
+ {% component "test1" variable="test456" %}
+ {% fill "main" %}
+ {{ my_first_variable }} - {{ variable }}
+ {% endfill %}
+ {% fill "footer" %}
+ {{ my_second_variable }}
+ {% endfill %}
+ {% endcomponent %}
+ {% endwith %}
+ """
+ )
+ rendered = template.render(Context({"my_second_variable": "test321"}))
+
+ self.assertHTMLEqual(
+ rendered,
+ """
+
+
+ test123 -
+
+
+ """,
+ )
+
+ @override_settings(
+ COMPONENTS={
+ "context_behavior": "django",
+ }
+ )
+ def test_slotted_template_with_context_var__django(self):
component.registry.register(name="test1", component=SlottedComponentWithContext)
template = Template(
@@ -743,7 +780,9 @@ class NestedSlotTests(BaseTestCase):
template = Template(
"""
{% load component_tags %}
- {% component 'test' %}{% fill 'inner' %}Override{% endfill %}{% endcomponent %}
+ {% component 'test' %}
+ {% fill 'inner' %}Override{% endfill %}
+ {% endcomponent %}
"""
)
rendered = template.render(Context({}))
@@ -1045,7 +1084,8 @@ class ComponentNestingTests(BaseTestCase):
super().tearDownClass()
component.registry.clear()
- def test_component_nesting_component_without_fill(self):
+ @override_settings(COMPONENTS={"context_behavior": "django"})
+ def test_component_nesting_component_without_fill__django(self):
template = Template(
"""
{% load component_tags %}
@@ -1072,13 +1112,8 @@ class ComponentNestingTests(BaseTestCase):
"""
self.assertHTMLEqual(rendered, expected)
- @override_settings(
- COMPONENTS={
- "context_behavior": "isolated",
- "slot_context_behavior": "isolated",
- }
- )
- def test_component_nesting_slot_inside_component_fill_isolated(self):
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_component_nesting_component_without_fill__isolated(self):
template = Template(
"""
{% load component_tags %}
@@ -1102,13 +1137,33 @@ class ComponentNestingTests(BaseTestCase):
"""
self.assertHTMLEqual(rendered, expected)
- @override_settings(
- COMPONENTS={
- "context_behavior": "isolated",
- "slot_context_behavior": "isolated",
- }
- )
- def test_component_nesting_slot_inside_component_fill_isolated_2(self):
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_component_nesting_slot_inside_component_fill__isolated(self):
+ template = Template(
+ """
+ {% load component_tags %}
+ {% component "dashboard" %}{% endcomponent %}
+ """
+ )
+ rendered = template.render(Context({"items": [1, 2, 3]}))
+ expected = """
+
+
+
+ Welcome to your dashboard!
+
+
+ Here are your to-do items for today:
+
+
+
+
+
+ """
+ self.assertHTMLEqual(rendered, expected)
+
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_component_nesting_slot_inside_component_fill__isolated_2(self):
template = Template(
"""
{% load component_tags %}
@@ -1136,13 +1191,8 @@ class ComponentNestingTests(BaseTestCase):
"""
self.assertHTMLEqual(rendered, expected)
- @override_settings(
- COMPONENTS={
- "context_behavior": "isolated",
- "slot_context_behavior": "isolated",
- }
- )
- def test_component_nesting_deep_slot_inside_component_fill_isolated(self):
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_component_nesting_deep_slot_inside_component_fill__isolated(self):
template = Template(
"""
@@ -1166,7 +1216,8 @@ class ComponentNestingTests(BaseTestCase):
"""
self.assertHTMLEqual(rendered, expected)
- def test_component_nesting_component_with_fill_and_super(self):
+ @override_settings(COMPONENTS={"context_behavior": "django"})
+ def test_component_nesting_component_with_fill_and_super__django(self):
template = Template(
"""
{% load component_tags %}
@@ -1194,6 +1245,33 @@ class ComponentNestingTests(BaseTestCase):
"""
self.assertHTMLEqual(rendered, expected)
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_component_nesting_component_with_fill_and_super__isolated(self):
+ template = Template(
+ """
+ {% load component_tags %}
+ {% component "dashboard" %}
+ {% fill "header" as "h" %} Hello! {{ h.default }} {% endfill %}
+ {% endcomponent %}
+ """
+ )
+ rendered = template.render(Context({"items": [1, 2]}))
+ expected = """
+
+
+
+ Hello! Welcome to your dashboard!
+
+
+ Here are your to-do items for today:
+
+
+
+
+
+ """
+ self.assertHTMLEqual(rendered, expected)
+
class ConditionalIfFilledSlotsTests(BaseTestCase):
class ComponentWithConditionalSlots(component.Component):
@@ -1235,7 +1313,8 @@ class ConditionalIfFilledSlotsTests(BaseTestCase):
rendered = Template(template).render(Context({}))
self.assertHTMLEqual(rendered, expected)
- def test_component_with_filled_conditional_slot(self):
+ @override_settings(COMPONENTS={"context_behavior": "django"})
+ def test_component_with_filled_conditional_slot__django(self):
template = """
{% load component_tags %}
{% component "conditional_slots" %}
@@ -1311,6 +1390,38 @@ class ConditionalIfFilledSlotsTests(BaseTestCase):
self.assertHTMLEqual(rendered, expected)
+class ContextVarsTests(BaseTestCase):
+ class IsFilledVarsComponent(component.Component):
+ template_name = "template_is_filled.html"
+
+ @classmethod
+ def setUpClass(cls) -> None:
+ super().setUpClass()
+ component.registry.register("is_filled_vars", cls.IsFilledVarsComponent)
+
+ def test_is_filled_vars(self):
+ template = """
+ {% load component_tags %}
+ {% component "is_filled_vars" %}
+ {% fill "title" %}{% endfill %}
+ {% fill "my-title-2" %}{% endfill %}
+ {% fill "escape this: #$%^*()" %}{% endfill %}
+ {% endcomponent %}
+ """
+ rendered = Template(template).render(Context())
+ # NOTE: `'` are escaped quotes
+ expected = """
+
+ {'title': True,
+ 'my_title': False,
+ 'my_title_1': False,
+ 'my_title_2': True,
+ 'escape_this_________': True}
+
+ """
+ self.assertHTMLEqual(rendered, expected)
+
+
class RegressionTests(BaseTestCase):
"""Ensure we don't break the same thing AGAIN."""
@@ -1372,7 +1483,8 @@ class IterationFillTest(BaseTestCase):
def setUp(self):
django_components.component.registry.clear()
- def test_inner_slot_iteration_basic(self):
+ @override_settings(COMPONENTS={"context_behavior": "django"})
+ def test_inner_slot_iteration_basic__django(self):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
template = Template(
@@ -1396,7 +1508,27 @@ class IterationFillTest(BaseTestCase):
""",
)
- def test_inner_slot_iteration_with_variable_from_outer_scope(self):
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_inner_slot_iteration_basic__isolated(self):
+ component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
+
+ template = Template(
+ """
+ {% load component_tags %}
+ {% component "slot_in_a_loop" objects=objects %}
+ {% fill "slot_inner" %}
+ {{ object }}
+ {% endfill %}
+ {% endcomponent %}
+ """
+ )
+ objects = ["OBJECT1", "OBJECT2"]
+ rendered = template.render(Context({"objects": objects}))
+
+ self.assertHTMLEqual(rendered, "")
+
+ @override_settings(COMPONENTS={"context_behavior": "django"})
+ def test_inner_slot_iteration_with_variable_from_outer_scope__django(self):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
template = Template(
@@ -1430,7 +1562,41 @@ class IterationFillTest(BaseTestCase):
""",
)
- def test_inner_slot_iteration_nested(self):
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_inner_slot_iteration_with_variable_from_outer_scope__isolated(self):
+ component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
+
+ template = Template(
+ """
+ {% load component_tags %}
+ {% component "slot_in_a_loop" objects=objects %}
+ {% fill "slot_inner" %}
+ {{ outer_scope_variable }}
+ {{ object }}
+ {% endfill %}
+ {% endcomponent %}
+ """
+ )
+ objects = ["OBJECT1", "OBJECT2"]
+ rendered = template.render(
+ Context(
+ {
+ "objects": objects,
+ "outer_scope_variable": "OUTER_SCOPE_VARIABLE",
+ }
+ )
+ )
+
+ self.assertHTMLEqual(
+ rendered,
+ """
+ OUTER_SCOPE_VARIABLE
+ OUTER_SCOPE_VARIABLE
+ """,
+ )
+
+ @override_settings(COMPONENTS={"context_behavior": "django"})
+ def test_inner_slot_iteration_nested__django(self):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [
@@ -1464,7 +1630,35 @@ class IterationFillTest(BaseTestCase):
""",
)
- def test_inner_slot_iteration_nested_with_outer_scope_variable(self):
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_inner_slot_iteration_nested__isolated(self):
+ component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
+
+ objects = [
+ {"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
+ {"inner": ["ITER2_OBJ1", "ITER2_OBJ2"]},
+ ]
+
+ template = Template(
+ """
+ {% load component_tags %}
+ {% component "slot_in_a_loop" objects=objects %}
+ {% fill "slot_inner" %}
+ {% component "slot_in_a_loop" objects=object.inner %}
+ {% fill "slot_inner" %}
+ {{ object }}
+ {% endfill %}
+ {% endcomponent %}
+ {% endfill %}
+ {% endcomponent %}
+ """
+ )
+ rendered = template.render(Context({"objects": objects}))
+
+ self.assertHTMLEqual(rendered, "")
+
+ @override_settings(COMPONENTS={"context_behavior": "django"})
+ def test_inner_slot_iteration_nested_with_outer_scope_variable__django(self):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [
@@ -1514,7 +1708,51 @@ class IterationFillTest(BaseTestCase):
""",
)
- def test_inner_slot_iteration_nested_with_slot_default(self):
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_inner_slot_iteration_nested_with_outer_scope_variable__isolated(self):
+ component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
+
+ objects = [
+ {"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
+ {"inner": ["ITER2_OBJ1", "ITER2_OBJ2"]},
+ ]
+
+ template = Template(
+ """
+ {% load component_tags %}
+ {% component "slot_in_a_loop" objects=objects %}
+ {% fill "slot_inner" %}
+ {{ outer_scope_variable_1 }}
+ {% component "slot_in_a_loop" objects=object.inner %}
+ {% fill "slot_inner" %}
+ {{ outer_scope_variable_2 }}
+ {{ object }}
+ {% endfill %}
+ {% endcomponent %}
+ {% endfill %}
+ {% endcomponent %}
+ """
+ )
+ rendered = template.render(
+ Context(
+ {
+ "objects": objects,
+ "outer_scope_variable_1": "OUTER_SCOPE_VARIABLE1",
+ "outer_scope_variable_2": "OUTER_SCOPE_VARIABLE2",
+ }
+ )
+ )
+
+ self.assertHTMLEqual(
+ rendered,
+ """
+ OUTER_SCOPE_VARIABLE1
+ OUTER_SCOPE_VARIABLE1
+ """,
+ )
+
+ @override_settings(COMPONENTS={"context_behavior": "django"})
+ def test_inner_slot_iteration_nested_with_slot_default__django(self):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [
@@ -1548,7 +1786,34 @@ class IterationFillTest(BaseTestCase):
""",
)
- def test_inner_slot_iteration_nested_with_slot_default_and_outer_scope_variable(
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_inner_slot_iteration_nested_with_slot_default__isolated(self):
+ component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
+
+ objects = [
+ {"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
+ {"inner": ["ITER2_OBJ1", "ITER2_OBJ2"]},
+ ]
+
+ template = Template(
+ """
+ {% load component_tags %}
+ {% component "slot_in_a_loop" objects=objects %}
+ {% fill "slot_inner" %}
+ {% component "slot_in_a_loop" objects=object.inner %}
+ {% fill "slot_inner" as "super_slot_inner" %}
+ {{ super_slot_inner.default }}
+ {% endfill %}
+ {% endcomponent %}
+ {% endfill %}
+ {% endcomponent %}
+ """
+ )
+ rendered = template.render(Context({"objects": objects}))
+ self.assertHTMLEqual(rendered, "")
+
+ @override_settings(COMPONENTS={"context_behavior": "django"})
+ def test_inner_slot_iteration_nested_with_slot_default_and_outer_scope_variable__django(
self,
):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
@@ -1599,3 +1864,107 @@ class IterationFillTest(BaseTestCase):
ITER2_OBJ2 default
""",
)
+
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_inner_slot_iteration_nested_with_slot_default_and_outer_scope_variable__isolated_1(
+ self,
+ ):
+ component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
+
+ objects = [
+ {"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
+ {"inner": ["ITER2_OBJ1", "ITER2_OBJ2"]},
+ ]
+
+ # NOTE: In this case the `object.inner` in the inner "slot_in_a_loop"
+ # should be undefined, so the loop inside the inner `slot_in_a_loop`
+ # shouldn't run. Hence even the inner `slot_inner` fill should NOT run.
+ template = Template(
+ """
+ {% load component_tags %}
+ {% component "slot_in_a_loop" objects=objects %}
+ {% fill "slot_inner" %}
+ {{ outer_scope_variable_1 }}
+ {% component "slot_in_a_loop" objects=object.inner %}
+ {% fill "slot_inner" as "super_slot_inner" %}
+ {{ outer_scope_variable_2 }}
+ {{ super_slot_inner.default }}
+ {% endfill %}
+ {% endcomponent %}
+ {% endfill %}
+ {% endcomponent %}
+ """
+ )
+ rendered = template.render(
+ Context(
+ {
+ "objects": objects,
+ "outer_scope_variable_1": "OUTER_SCOPE_VARIABLE1",
+ "outer_scope_variable_2": "OUTER_SCOPE_VARIABLE2",
+ }
+ )
+ )
+
+ self.assertHTMLEqual(
+ rendered,
+ """
+ OUTER_SCOPE_VARIABLE1
+ OUTER_SCOPE_VARIABLE1
+ """,
+ )
+
+ @override_settings(COMPONENTS={"context_behavior": "isolated"})
+ def test_inner_slot_iteration_nested_with_slot_default_and_outer_scope_variable__isolated_2(
+ self,
+ ):
+ component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
+
+ objects = [
+ {"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
+ {"inner": ["ITER2_OBJ1", "ITER2_OBJ2"]},
+ ]
+
+ # NOTE: In this case we use `objects` in the inner "slot_in_a_loop", which
+ # is defined in the root context. So the loop inside the inner `slot_in_a_loop`
+ # should run.
+ template = Template(
+ """
+ {% load component_tags %}
+ {% component "slot_in_a_loop" objects=objects %}
+ {% fill "slot_inner" %}
+ {{ outer_scope_variable_1 }}
+ {% component "slot_in_a_loop" objects=objects %}
+ {% fill "slot_inner" as "super_slot_inner" %}
+ {{ outer_scope_variable_2 }}
+ {{ super_slot_inner.default }}
+ {% endfill %}
+ {% endcomponent %}
+ {% endfill %}
+ {% endcomponent %}
+ """
+ )
+ rendered = template.render(
+ Context(
+ {
+ "objects": objects,
+ "outer_scope_variable_1": "OUTER_SCOPE_VARIABLE1",
+ "outer_scope_variable_2": "OUTER_SCOPE_VARIABLE2",
+ }
+ )
+ )
+
+ self.assertHTMLEqual(
+ rendered,
+ """
+ OUTER_SCOPE_VARIABLE1
+ OUTER_SCOPE_VARIABLE2
+ {'inner': ['ITER1_OBJ1', 'ITER1_OBJ2']} default
+ OUTER_SCOPE_VARIABLE2
+ {'inner': ['ITER2_OBJ1', 'ITER2_OBJ2']} default
+ OUTER_SCOPE_VARIABLE1
+ OUTER_SCOPE_VARIABLE2
+ {'inner': ['ITER1_OBJ1', 'ITER1_OBJ2']} default
+ OUTER_SCOPE_VARIABLE2
+ {'inner': ['ITER2_OBJ1', 'ITER2_OBJ2']} default
+ """,
+ )