Skip to content

Commit 15d2f42

Browse files
Fix default content fallback behavior in slots (#91)
1 parent 20f3c50 commit 15d2f42

File tree

3 files changed

+228
-86
lines changed

3 files changed

+228
-86
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/
2323
- **Internal**: Consolidated Component and Asset registries into a single `ComponentRegistry`.
2424
- **Internal**: Added component discovery at app startup instead of on-demand in the template loader.
2525

26+
### Fixed
27+
28+
- Fixed default content not being rendered in slots when no content is provided.
29+
2630
## [0.7.2]
2731

2832
### Changed

src/django_bird/templatetags/tags/slot.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,14 @@ def render(self, context: Context) -> SafeString:
5757
slots = context.get("slots")
5858

5959
if not slots or not isinstance(slots, dict):
60-
slot_content = default_content
61-
else:
62-
slots_dict = cast(dict[str, str], slots)
63-
slot_content = slots_dict.get(self.name, default_content)
60+
return mark_safe(default_content)
61+
62+
slots_dict = cast(dict[str, str], slots)
63+
slot_content = slots_dict.get(self.name)
64+
65+
if slot_content is None or slot_content == "":
66+
return mark_safe(default_content)
6467

65-
# Recursively process the slot content
6668
t = template.Template(slot_content)
6769
content = t.render(context)
68-
6970
return mark_safe(content)

tests/templatetags/test_slot.py

Lines changed: 217 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from django.template.exceptions import TemplateSyntaxError
77

88
from django_bird.templatetags.tags.slot import parse_slot_name
9+
from tests.conftest import TestComponent
10+
from tests.conftest import TestComponentCase
911

1012

1113
@pytest.mark.parametrize(
@@ -30,100 +32,235 @@ def test_parse_slot_name_no_args():
3032

3133
class TestTemplateTag:
3234
@pytest.mark.parametrize(
33-
"template,context,expected",
35+
"component_content",
3436
[
35-
("{{ slot }}", {"slot": "test"}, "test"),
36-
("{% bird:slot %}{% endbird:slot %}", {"slot": "test"}, "test"),
37-
("{% bird:slot default %}{% endbird:slot %}", {"slot": "test"}, "test"),
38-
("{% bird:slot 'default' %}{% endbird:slot %}", {"slot": "test"}, "test"),
39-
('{% bird:slot "default" %}{% endbird:slot %}', {"slot": "test"}, "test"),
40-
(
41-
"{% bird:slot name='default' %}{% endbird:slot %}",
42-
{"slot": "test"},
43-
"test",
44-
),
45-
(
46-
'{% bird:slot name="default" %}{% endbird:slot %}',
47-
{"slot": "test"},
48-
"test",
49-
),
50-
(
51-
"{% bird:slot name='not-default' %}{% endbird:slot %}",
52-
{"slot": "test"},
53-
"",
37+
"{{ slot }}",
38+
"{% bird:slot %}{% endbird:slot %}",
39+
"{% bird:slot default %}{% endbird:slot %}",
40+
"{% bird:slot 'default' %}{% endbird:slot %}",
41+
'{% bird:slot "default" %}{% endbird:slot %}',
42+
"{% bird:slot name=default %}{% endbird:slot %}",
43+
"{% bird:slot name='default' %}{% endbird:slot %}",
44+
'{% bird:slot name="default" %}{% endbird:slot %}',
45+
],
46+
)
47+
def test_default_slot(self, component_content, templates_dir, normalize_whitespace):
48+
test_case = TestComponentCase(
49+
component=TestComponent(
50+
name="test",
51+
content=component_content,
5452
),
55-
(
56-
"{% bird:slot outer %}Outer {% bird:slot inner %}Inner{% endbird:slot %} Content{% endbird:slot %}",
57-
{"slots": {"outer": "Replaced Content"}},
58-
"Replaced Content",
53+
template_content="{% bird test %}Content{% endbird %}",
54+
template_context={"slot": "Content"},
55+
expected="Content",
56+
)
57+
test_case.component.create(templates_dir)
58+
59+
template = Template(test_case.template_content)
60+
rendered = template.render(Context(test_case.template_context))
61+
62+
assert normalize_whitespace(rendered) == test_case.expected
63+
64+
def test_default_content(self, templates_dir, normalize_whitespace):
65+
test_case = TestComponentCase(
66+
component=TestComponent(
67+
name="test",
68+
content="""
69+
<button>
70+
{% bird:slot leading-icon %}
71+
<span>Default icon</span>
72+
{% endbird:slot %}
73+
74+
{% bird:slot %}
75+
Click me
76+
{% endbird:slot %}
77+
</button>
78+
""",
5979
),
60-
(
61-
"{% bird:slot outer %}Outer {% bird:slot inner %}Inner{% endbird:slot %} Content{% endbird:slot %}",
62-
{"slots": {"inner": "Replaced Content"}},
63-
"Outer Replaced Content Content",
80+
template_content="{% bird test %}{% endbird %}",
81+
expected="<button><span>Default icon</span>Click me</button>",
82+
)
83+
test_case.component.create(templates_dir)
84+
85+
template = Template(test_case.template_content)
86+
rendered = template.render(Context({}))
87+
88+
assert normalize_whitespace(rendered) == test_case.expected
89+
90+
def test_default_content_override(self, templates_dir, normalize_whitespace):
91+
test_case = TestComponentCase(
92+
component=TestComponent(
93+
name="test",
94+
content="""
95+
<button>
96+
{% bird:slot leading-icon %}
97+
<span>Default icon</span>
98+
{% endbird:slot %}
99+
100+
{% bird:slot %}
101+
Click me
102+
{% endbird:slot %}
103+
</button>
104+
""",
64105
),
65-
(
66-
"{% bird:slot outer %}Outer {% bird:slot inner %}Inner Default{% endbird:slot %} Content{% endbird:slot %}",
67-
{
68-
"slots": {
69-
"outer": "Replaced {% bird:slot inner %}{% endbird:slot %} Outer",
70-
"inner": "Replaced Inner",
71-
},
72-
},
73-
"Replaced Replaced Inner Outer",
106+
template_content="""
107+
{% bird test %}
108+
{% bird:slot leading-icon %}→{% endbird:slot %}
109+
Submit
110+
{% endbird %}
111+
""",
112+
expected="<button>→ Submit</button>",
113+
)
114+
test_case.component.create(templates_dir)
115+
116+
template = Template(test_case.template_content)
117+
rendered = template.render(Context({}))
118+
119+
assert normalize_whitespace(rendered) == test_case.expected
120+
121+
@pytest.mark.parametrize(
122+
"component_content",
123+
[
124+
"{{ slots.named_slot }}",
125+
"{% bird:slot named_slot %}{% endbird:slot %}",
126+
"{% bird:slot name=named_slot %}{% endbird:slot %}",
127+
],
128+
)
129+
def test_named_slot(self, component_content, templates_dir, normalize_whitespace):
130+
test_case = TestComponentCase(
131+
component=TestComponent(
132+
name="test",
133+
content=component_content,
74134
),
75-
(
76-
"{{ slot }}",
77-
{
78-
"slot": "Replaced {% bird:slot inner %}{% endbird:slot %} Outer",
79-
"slots": {
80-
"inner": "Replaced Inner",
81-
},
135+
template_content="""
136+
{% bird test %}
137+
{% bird:slot named_slot %}{% endbird:slot %}
138+
{% endbird %}
139+
""",
140+
template_context={
141+
"slots": {
142+
"named_slot": "Content",
82143
},
83-
"Replaced Replaced Inner Outer",
84-
),
85-
(
86-
"{% bird:slot %}{% endbird:slot %}",
87-
{},
88-
"",
89-
),
90-
(
91-
"{% bird:slot %}Default content{% endbird:slot %}",
92-
{},
93-
"Default content",
144+
},
145+
expected="Content",
146+
)
147+
test_case.component.create(templates_dir)
148+
149+
template = Template(test_case.template_content)
150+
rendered = template.render(Context(test_case.template_context))
151+
152+
assert normalize_whitespace(rendered) == test_case.expected
153+
154+
@pytest.mark.parametrize(
155+
"test_case",
156+
[
157+
TestComponentCase(
158+
description="Outer slot replacement",
159+
component=TestComponent(
160+
name="test",
161+
content="""
162+
{% bird:slot outer %}
163+
Outer {% bird:slot inner %}Inner{% endbird:slot %} Content
164+
{% endbird:slot %}
165+
""",
166+
),
167+
template_content="""
168+
{% bird test %}
169+
{% bird:slot outer %}Replaced Content{% endbird:slot %}
170+
{% endbird %}
171+
""",
172+
expected="Replaced Content",
94173
),
95-
(
96-
"{% bird:slot 'mal formed' %}{% endbird:slot %}",
97-
{"slots": {"mal formed": "content"}},
98-
"content",
174+
TestComponentCase(
175+
description="Inner slot replacement",
176+
component=TestComponent(
177+
name="test",
178+
content="""
179+
{% bird:slot outer %}
180+
Outer {% bird:slot inner %}Inner{% endbird:slot %} Content
181+
{% endbird:slot %}
182+
""",
183+
),
184+
template_content="""
185+
{% bird test %}
186+
{% bird:slot inner %}Replaced Content{% endbird:slot %}
187+
{% endbird %}
188+
""",
189+
expected="Outer Replaced Content Content",
99190
),
100-
(
101-
"{% bird:slot CaseSensitive %}{% endbird:slot %}",
102-
{"slots": {"CaseSensitive": "Upper", "casesensitive": "Lower"}},
103-
"Upper",
191+
TestComponentCase(
192+
description="Both slots replaced",
193+
component=TestComponent(
194+
name="test",
195+
content="""
196+
{% bird:slot outer %}
197+
Outer {% bird:slot inner %}Inner{% endbird:slot %} Content
198+
{% endbird:slot %}
199+
""",
200+
),
201+
template_content="""
202+
{% bird test %}
203+
{% bird:slot outer %}
204+
New {% bird:slot inner %}Nested{% endbird:slot %} Text
205+
{% endbird:slot %}
206+
{% endbird %}
207+
""",
208+
expected="New Nested Text",
104209
),
105-
(
106-
"{% bird:slot %}{% endbird:slot %}",
107-
{"slots": {"default": 42}},
108-
"42",
210+
],
211+
ids=lambda x: x.description,
212+
)
213+
def test_nested_slots(self, test_case, templates_dir, normalize_whitespace):
214+
test_case.component.create(templates_dir)
215+
216+
template = Template(test_case.template_content)
217+
rendered = template.render(Context(test_case.template_context))
218+
219+
assert normalize_whitespace(rendered) == test_case.expected
220+
221+
@pytest.mark.parametrize(
222+
"test_case",
223+
[
224+
TestComponentCase(
225+
description="Variable in slot",
226+
component=TestComponent(
227+
name="test",
228+
content="{% bird:slot %}{% endbird:slot %}",
229+
),
230+
template_content="{% bird test %}{{ message }}{% endbird %}",
231+
template_context={"message": "Hello World"},
232+
expected="Hello World",
109233
),
110-
(
111-
"{% bird:slot %}{% endbird:slot %}",
112-
{"slots": {"default": "<b>Bold</b>"}},
113-
"<b>Bold</b>",
234+
TestComponentCase(
235+
description="Nested variable in slot",
236+
component=TestComponent(
237+
name="test",
238+
content="{% bird:slot %}{% endbird:slot %}",
239+
),
240+
template_content="{% bird test %}{{ user.name }}{% endbird %}",
241+
template_context={"user": {"name": "John"}},
242+
expected="John",
114243
),
115-
(
116-
"{% bird:slot unicode_slot %}{% endbird:slot %}",
117-
{"slots": {"unicode_slot": "こんにちは"}},
118-
"こんにちは",
244+
TestComponentCase(
245+
description="Template tag in slot",
246+
component=TestComponent(
247+
name="test",
248+
content="{% bird:slot %}{% endbird:slot %}",
249+
),
250+
template_content="{% bird test %}{% if show %}Show{% endif %}{% endbird %}",
251+
template_context={"show": True},
252+
expected="Show",
119253
),
120254
],
255+
ids=lambda x: x.description,
121256
)
122-
def test_rendering(self, template, context, expected, create_bird_template):
123-
create_bird_template("test", template)
124-
t = Template(f"{{% bird test %}}{template}{{% endbird %}}")
125-
rendered = t.render(context=Context(context))
126-
assert rendered == expected
257+
def test_template_content(self, test_case, templates_dir, normalize_whitespace):
258+
test_case.component.create(templates_dir)
259+
260+
template = Template(test_case.template_content)
261+
rendered = template.render(Context(test_case.template_context))
262+
263+
assert normalize_whitespace(rendered) == test_case.expected
127264

128265
def test_too_many_args(self):
129266
with pytest.raises(TemplateSyntaxError):

0 commit comments

Comments
 (0)