mirror of
https://github.com/django-components/django-components.git
synced 2025-07-07 17:34:59 +00:00

* 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
1604 lines
54 KiB
Python
1604 lines
54 KiB
Python
"""
|
|
Tests focusing on the Component class.
|
|
For tests focusing on the `component` tag, see `test_templatetags_component.py`
|
|
"""
|
|
|
|
import re
|
|
from typing import Any, NamedTuple
|
|
|
|
import pytest
|
|
from django.conf import settings
|
|
from django.http import HttpRequest, HttpResponse
|
|
from django.template import Context, RequestContext, Template
|
|
from django.template.base import TextNode
|
|
from django.test import Client
|
|
from django.urls import path
|
|
from pytest_django.asserts import assertHTMLEqual, assertInHTML
|
|
|
|
from django_components import (
|
|
Component,
|
|
ComponentView,
|
|
Slot,
|
|
SlotInput,
|
|
all_components,
|
|
get_component_by_class_id,
|
|
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
|
|
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
|
|
|
setup_test_config({"autodiscover": False})
|
|
|
|
|
|
# Client for testing endpoints via requests
|
|
class CustomClient(Client):
|
|
def __init__(self, urlpatterns=None, *args, **kwargs):
|
|
import types
|
|
|
|
if urlpatterns:
|
|
urls_module = types.ModuleType("urls")
|
|
urls_module.urlpatterns = urlpatterns + dc_urlpatterns # type: ignore
|
|
settings.ROOT_URLCONF = urls_module
|
|
else:
|
|
settings.ROOT_URLCONF = __name__
|
|
settings.SECRET_KEY = "secret" # noqa
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
@djc_test
|
|
class TestComponentLegacyApi:
|
|
# TODO_REMOVE_IN_V1 - Superseded by `self.get_template` in v1
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_get_template_string(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
def get_template_string(self, context):
|
|
content: types.django_html = """
|
|
Variable: <strong>{{ variable }}</strong>
|
|
"""
|
|
return content
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"variable": kwargs.get("variable", None),
|
|
}
|
|
|
|
class Media:
|
|
css = "style.css"
|
|
js = "script.js"
|
|
|
|
rendered = SimpleComponent.render(kwargs={"variable": "test"})
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
Variable: <strong data-djc-id-ca1bc3e>test</strong>
|
|
""",
|
|
)
|
|
|
|
# TODO_REMOVE_IN_V2 - `get_context_data()` was superseded by `self.get_template_data`
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_get_context_data(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
template = """
|
|
Variable: <strong>{{ variable }}</strong>
|
|
"""
|
|
|
|
def get_context_data(self, variable=None):
|
|
return {
|
|
"variable": variable,
|
|
}
|
|
|
|
class Media:
|
|
css = "style.css"
|
|
js = "script.js"
|
|
|
|
rendered = SimpleComponent.render(kwargs={"variable": "test"})
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
Variable: <strong data-djc-id-ca1bc3e>test</strong>
|
|
""",
|
|
)
|
|
|
|
# TODO_REMOVE_IN_V1 - Registry and registered name should be passed to `Component.render()`,
|
|
# not to the constructor.
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_component_instantiation(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
template = """
|
|
<div>
|
|
Name: {{ name }}
|
|
</div>
|
|
"""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"name": self.name,
|
|
}
|
|
|
|
# Old syntax
|
|
rendered = SimpleComponent("simple").render()
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
<div data-djc-id-ca1bc3f>
|
|
Name: simple
|
|
</div>
|
|
""",
|
|
)
|
|
|
|
# New syntax
|
|
rendered = SimpleComponent.render(registered_name="simple")
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
<div data-djc-id-ca1bc40>
|
|
Name: simple
|
|
</div>
|
|
""",
|
|
)
|
|
|
|
# Sanity check
|
|
rendered = SimpleComponent.render()
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
<div data-djc-id-ca1bc41>
|
|
Name: SimpleComponent
|
|
</div>
|
|
""",
|
|
)
|
|
|
|
# TODO_v1 - Remove
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
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,
|
|
}
|
|
|
|
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>
|
|
""",
|
|
)
|
|
|
|
# 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 {
|
|
"variable": kwargs.get("variable", None),
|
|
}
|
|
|
|
class Media:
|
|
css = "style.css"
|
|
js = "script.js"
|
|
|
|
rendered = SimpleComponent.render(kwargs={"variable": "test"})
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
Variable: <strong data-djc-id-ca1bc3e>test</strong>
|
|
""",
|
|
)
|
|
|
|
# TODO_v1 - Remove
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
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 = """
|
|
Variable: <strong>{{ variable }}</strong>
|
|
"""
|
|
return content
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"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"
|
|
|
|
rendered = SimpleComponent.render(kwargs={"variable": "test"})
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
Variable: <strong data-djc-id-ca1bc3e>test</strong>
|
|
""",
|
|
)
|
|
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_template_file_static(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
template_file = "simple_template.html"
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"variable": kwargs.get("variable", None),
|
|
}
|
|
|
|
class Media:
|
|
css = "style.css"
|
|
js = "script.js"
|
|
|
|
rendered = SimpleComponent.render(kwargs={"variable": "test"})
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
Variable: <strong data-djc-id-ca1bc3e>test</strong>
|
|
""",
|
|
)
|
|
|
|
# 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):
|
|
template_name = "simple_template.html"
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"variable": kwargs.get("variable", None),
|
|
}
|
|
|
|
class Media:
|
|
css = "style.css"
|
|
js = "script.js"
|
|
|
|
# Access fields on Component class
|
|
assert SimpleComponent.template_name == "simple_template.html"
|
|
assert SimpleComponent.template_file == "simple_template.html"
|
|
|
|
SimpleComponent.template_name = "other_template.html"
|
|
assert SimpleComponent.template_name == "other_template.html"
|
|
assert SimpleComponent.template_file == "other_template.html"
|
|
|
|
SimpleComponent.template_name = "simple_template.html"
|
|
rendered = SimpleComponent.render(kwargs={"variable": "test"})
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
Variable: <strong data-djc-id-ca1bc3e>test</strong>
|
|
""",
|
|
)
|
|
|
|
# Access fields on Component instance
|
|
comp = SimpleComponent()
|
|
assert comp.template_name == "simple_template.html"
|
|
assert comp.template_file == "simple_template.html"
|
|
|
|
# NOTE: Setting `template_file` on INSTANCE is not supported, as users should work
|
|
# with classes and not instances. This is tested for completeness.
|
|
comp.template_name = "other_template_2.html" # type: ignore[misc]
|
|
assert comp.template_name == "other_template_2.html"
|
|
assert comp.template_file == "other_template_2.html"
|
|
assert SimpleComponent.template_name == "other_template_2.html"
|
|
assert SimpleComponent.template_file == "other_template_2.html"
|
|
|
|
SimpleComponent.template_name = "simple_template.html"
|
|
rendered = comp.render(kwargs={"variable": "test"})
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
Variable: <strong data-djc-id-ca1bc40>test</strong>
|
|
""",
|
|
)
|
|
|
|
def test_get_component_by_id(self):
|
|
class SimpleComponent(Component):
|
|
pass
|
|
|
|
assert get_component_by_class_id(SimpleComponent.class_id) == SimpleComponent
|
|
|
|
def test_get_component_by_id_raises_on_missing_component(self):
|
|
with pytest.raises(KeyError):
|
|
get_component_by_class_id("nonexistent")
|
|
|
|
def test_get_context_data_returns_none(self):
|
|
class SimpleComponent(Component):
|
|
template = "Hello"
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return None
|
|
|
|
assert SimpleComponent.render() == "Hello"
|
|
|
|
|
|
@djc_test
|
|
class TestComponentRenderAPI:
|
|
def test_component_render_id(self):
|
|
class SimpleComponent(Component):
|
|
template = "render_id: {{ render_id }}"
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {"render_id": self.id}
|
|
|
|
rendered = SimpleComponent.render()
|
|
assert rendered == "render_id: ca1bc3e"
|
|
|
|
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}
|
|
assert isinstance(self.input.context, Context)
|
|
assert list(self.input.slots.keys()) == ["my_slot"]
|
|
my_slot = self.input.slots["my_slot"]
|
|
assert my_slot() == "MY_SLOT"
|
|
|
|
return {
|
|
"variable": kwargs["variable"],
|
|
}
|
|
|
|
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)
|
|
assert list(self.input.slots.keys()) == ["my_slot"]
|
|
my_slot = self.input.slots["my_slot"]
|
|
assert my_slot() == "MY_SLOT"
|
|
|
|
rendered = TestComponent.render(
|
|
kwargs={"variable": "test", "another": 1},
|
|
args=(123, "str"),
|
|
slots={"my_slot": "MY_SLOT"},
|
|
)
|
|
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
Variable: <strong data-djc-id-ca1bc3e>test</strong> MY_SLOT
|
|
""",
|
|
)
|
|
|
|
def test_args_kwargs_slots__simple(self):
|
|
called = False
|
|
|
|
class TestComponent(Component):
|
|
template = ""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
nonlocal called
|
|
called = True
|
|
|
|
assert self.args == [123, "str"]
|
|
assert self.kwargs == {"variable": "test", "another": 1}
|
|
assert list(self.slots.keys()) == ["my_slot"]
|
|
my_slot = self.slots["my_slot"]
|
|
assert my_slot() == "MY_SLOT"
|
|
|
|
TestComponent.render(
|
|
kwargs={"variable": "test", "another": 1},
|
|
args=(123, "str"),
|
|
slots={"my_slot": "MY_SLOT"},
|
|
)
|
|
|
|
assert called
|
|
|
|
def test_args_kwargs_slots__typed(self):
|
|
called = False
|
|
|
|
class TestComponent(Component):
|
|
template = ""
|
|
|
|
class Args(NamedTuple):
|
|
variable: int
|
|
another: str
|
|
|
|
class Kwargs(NamedTuple):
|
|
variable: str
|
|
another: int
|
|
|
|
class Slots(NamedTuple):
|
|
my_slot: SlotInput
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
nonlocal called
|
|
called = True
|
|
|
|
assert self.args == TestComponent.Args(123, "str")
|
|
assert self.kwargs == TestComponent.Kwargs(variable="test", another=1)
|
|
assert isinstance(self.slots, TestComponent.Slots)
|
|
assert isinstance(self.slots.my_slot, Slot)
|
|
assert self.slots.my_slot() == "MY_SLOT"
|
|
|
|
# Check that the instances are reused across multiple uses
|
|
assert self.args is self.args
|
|
assert self.kwargs is self.kwargs
|
|
assert self.slots is self.slots
|
|
|
|
TestComponent.render(
|
|
kwargs={"variable": "test", "another": 1},
|
|
args=(123, "str"),
|
|
slots={"my_slot": "MY_SLOT"},
|
|
)
|
|
|
|
assert called
|
|
|
|
def test_args_kwargs_slots__available_outside_render(self):
|
|
comp: Any = None
|
|
|
|
class TestComponent(Component):
|
|
template = ""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
nonlocal comp
|
|
comp = self
|
|
|
|
assert comp is None
|
|
|
|
TestComponent.render()
|
|
|
|
assert comp.args == [] # type: ignore[attr-defined]
|
|
assert comp.kwargs == {} # type: ignore[attr-defined]
|
|
assert comp.slots == {} # type: ignore[attr-defined]
|
|
assert comp.context == Context() # type: ignore[attr-defined]
|
|
|
|
|
|
@djc_test
|
|
class TestComponentTemplateVars:
|
|
def test_args_kwargs_slots__simple_untyped(self):
|
|
class TestComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
<div class="test-component">
|
|
{# Test whole objects #}
|
|
args: {{ component_vars.args|safe }}
|
|
kwargs: {{ component_vars.kwargs|safe }}
|
|
slots: {{ component_vars.slots|safe }}
|
|
|
|
{# Test individual values #}
|
|
arg: {{ component_vars.args.0|safe }}
|
|
kwarg: {{ component_vars.kwargs.variable|safe }}
|
|
slot: {{ component_vars.slots.my_slot|safe }}
|
|
</div>
|
|
"""
|
|
|
|
html = TestComponent.render(
|
|
args=[123, "str"],
|
|
kwargs={"variable": "test", "another": 1},
|
|
slots={"my_slot": "MY_SLOT"},
|
|
)
|
|
assertHTMLEqual(
|
|
html,
|
|
"""
|
|
<div class="test-component" data-djc-id-ca1bc3e="">
|
|
args: [123, 'str']
|
|
kwargs: {'variable': 'test', 'another': 1}
|
|
slots: {'my_slot': <Slot component_name='TestComponent' slot_name='my_slot'>}
|
|
arg: 123
|
|
kwarg: test
|
|
slot: <Slot component_name='TestComponent' slot_name='my_slot'>
|
|
</div>
|
|
""",
|
|
)
|
|
|
|
def test_args_kwargs_slots__simple_typed(self):
|
|
class TestComponent(Component):
|
|
class Args(NamedTuple):
|
|
variable: int
|
|
another: str
|
|
|
|
class Kwargs(NamedTuple):
|
|
variable: str
|
|
another: int
|
|
|
|
class Slots(NamedTuple):
|
|
my_slot: SlotInput
|
|
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
<div class="test-component">
|
|
{# Test whole objects #}
|
|
args: {{ component_vars.args|safe }}
|
|
kwargs: {{ component_vars.kwargs|safe }}
|
|
slots: {{ component_vars.slots|safe }}
|
|
|
|
{# Test individual values #}
|
|
arg: {{ component_vars.args.variable|safe }}
|
|
kwarg: {{ component_vars.kwargs.variable|safe }}
|
|
slot: {{ component_vars.slots.my_slot|safe }}
|
|
</div>
|
|
"""
|
|
|
|
html = TestComponent.render(
|
|
args=[123, "str"],
|
|
kwargs={"variable": "test", "another": 1},
|
|
slots={"my_slot": "MY_SLOT"},
|
|
)
|
|
assertHTMLEqual(
|
|
html,
|
|
"""
|
|
<div class="test-component" data-djc-id-ca1bc3e="">
|
|
args: Args(variable=123, another='str')
|
|
kwargs: Kwargs(variable='test', another=1)
|
|
slots: Slots(my_slot=<Slot component_name='TestComponent' slot_name='my_slot'>)
|
|
arg: 123
|
|
kwarg: test
|
|
slot: <Slot component_name='TestComponent' slot_name='my_slot'>
|
|
</div>
|
|
""",
|
|
)
|
|
|
|
def test_args_kwargs_slots__nested_untyped(self):
|
|
@register("wrapper")
|
|
class Wrapper(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
<div class="wrapper">
|
|
{% slot "content" default %}
|
|
<div class="test">DEFAULT</div>
|
|
{% endslot %}
|
|
</div>
|
|
"""
|
|
|
|
class TestComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
<div class="test-component">
|
|
{% component "wrapper" %}
|
|
{# Test whole objects #}
|
|
args: {{ component_vars.args|safe }}
|
|
kwargs: {{ component_vars.kwargs|safe }}
|
|
slots: {{ component_vars.slots|safe }}
|
|
|
|
{# Test individual values #}
|
|
arg: {{ component_vars.args.0|safe }}
|
|
kwarg: {{ component_vars.kwargs.variable|safe }}
|
|
slot: {{ component_vars.slots.my_slot|safe }}
|
|
{% endcomponent %}
|
|
</div>
|
|
"""
|
|
|
|
html = TestComponent.render(
|
|
args=[123, "str"],
|
|
kwargs={"variable": "test", "another": 1},
|
|
slots={"my_slot": "MY_SLOT"},
|
|
)
|
|
assertHTMLEqual(
|
|
html,
|
|
"""
|
|
<div class="test-component" data-djc-id-ca1bc3e="">
|
|
<div class="wrapper" data-djc-id-ca1bc40="">
|
|
args: [123, 'str']
|
|
kwargs: {'variable': 'test', 'another': 1}
|
|
slots: {'my_slot': <Slot component_name='TestComponent' slot_name='my_slot'>}
|
|
arg: 123
|
|
kwarg: test
|
|
slot: <Slot component_name='TestComponent' slot_name='my_slot'>
|
|
</div>
|
|
</div>
|
|
""",
|
|
)
|
|
|
|
def test_args_kwargs_slots__nested_typed(self):
|
|
@register("wrapper")
|
|
class Wrapper(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
<div class="wrapper">
|
|
{% slot "content" default %}
|
|
<div class="test">DEFAULT</div>
|
|
{% endslot %}
|
|
</div>
|
|
"""
|
|
|
|
class TestComponent(Component):
|
|
class Args(NamedTuple):
|
|
variable: int
|
|
another: str
|
|
|
|
class Kwargs(NamedTuple):
|
|
variable: str
|
|
another: int
|
|
|
|
class Slots(NamedTuple):
|
|
my_slot: SlotInput
|
|
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
<div class="test-component">
|
|
{% component "wrapper" %}
|
|
{# Test whole objects #}
|
|
args: {{ component_vars.args|safe }}
|
|
kwargs: {{ component_vars.kwargs|safe }}
|
|
slots: {{ component_vars.slots|safe }}
|
|
|
|
{# Test individual values #}
|
|
arg: {{ component_vars.args.variable|safe }}
|
|
kwarg: {{ component_vars.kwargs.variable|safe }}
|
|
slot: {{ component_vars.slots.my_slot|safe }}
|
|
{% endcomponent %}
|
|
</div>
|
|
"""
|
|
|
|
html = TestComponent.render(
|
|
args=[123, "str"],
|
|
kwargs={"variable": "test", "another": 1},
|
|
slots={"my_slot": "MY_SLOT"},
|
|
)
|
|
assertHTMLEqual(
|
|
html,
|
|
"""
|
|
<div class="test-component" data-djc-id-ca1bc3e="">
|
|
<div class="wrapper" data-djc-id-ca1bc40="">
|
|
args: Args(variable=123, another='str')
|
|
kwargs: Kwargs(variable='test', another=1)
|
|
slots: Slots(my_slot=<Slot component_name='TestComponent' slot_name='my_slot'>)
|
|
arg: 123
|
|
kwarg: test
|
|
slot: <Slot component_name='TestComponent' slot_name='my_slot'>
|
|
</div>
|
|
</div>
|
|
""",
|
|
)
|
|
|
|
def test_args_kwargs_slots__nested_conditional_slots(self):
|
|
@register("wrapper")
|
|
class Wrapper(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
<div class="wrapper">
|
|
{% slot "content" default %}
|
|
<div class="test">DEFAULT</div>
|
|
{% endslot %}
|
|
</div>
|
|
"""
|
|
|
|
class TestComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
<div class="test-component">
|
|
{% component "wrapper" %}
|
|
{% if component_vars.slots.subtitle %}
|
|
<div class="subtitle">
|
|
{% slot "subtitle" %}
|
|
Optional subtitle
|
|
{% endslot %}
|
|
</div>
|
|
{% endif %}
|
|
{% endcomponent %}
|
|
</div>
|
|
"""
|
|
|
|
html = TestComponent.render(
|
|
slots={"subtitle": "SUBTITLE_FILLED"},
|
|
)
|
|
assertHTMLEqual(
|
|
html,
|
|
"""
|
|
<div class="test-component" data-djc-id-ca1bc3e="">
|
|
<div class="wrapper" data-djc-id-ca1bc41="">
|
|
<div class="subtitle">SUBTITLE_FILLED</div>
|
|
</div>
|
|
</div>
|
|
""",
|
|
)
|
|
|
|
|
|
@djc_test
|
|
class TestComponentRender:
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_render_minimal(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
the_arg2: {{ the_arg2 }}
|
|
args: {{ args|safe }}
|
|
the_kwarg: {{ the_kwarg }}
|
|
kwargs: {{ kwargs|safe }}
|
|
---
|
|
from_context: {{ from_context }}
|
|
---
|
|
slot_second: {% slot "second" default %}
|
|
SLOT_SECOND_DEFAULT
|
|
{% endslot %}
|
|
"""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"the_arg2": args[0] if args else None,
|
|
"the_kwarg": kwargs.pop("the_kwarg", None),
|
|
"args": args[1:],
|
|
"kwargs": kwargs,
|
|
}
|
|
|
|
rendered = SimpleComponent.render()
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
the_arg2: None
|
|
args: []
|
|
the_kwarg: None
|
|
kwargs: {}
|
|
---
|
|
from_context:
|
|
---
|
|
slot_second: SLOT_SECOND_DEFAULT
|
|
""",
|
|
)
|
|
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_render_full(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
the_arg: {{ the_arg }}
|
|
the_arg2: {{ the_arg2 }}
|
|
args: {{ args|safe }}
|
|
the_kwarg: {{ the_kwarg }}
|
|
kwargs: {{ kwargs|safe }}
|
|
---
|
|
from_context: {{ from_context }}
|
|
---
|
|
slot_first: {% slot "first" required %}
|
|
{% endslot %}
|
|
---
|
|
slot_second: {% slot "second" default %}
|
|
SLOT_SECOND_DEFAULT
|
|
{% endslot %}
|
|
"""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"the_arg": args[0],
|
|
"the_arg2": args[1],
|
|
"the_kwarg": kwargs.pop("the_kwarg", None),
|
|
"args": args[2:],
|
|
"kwargs": kwargs,
|
|
}
|
|
|
|
rendered = SimpleComponent.render(
|
|
context={"from_context": 98},
|
|
args=["one", "two", "three"],
|
|
kwargs={"the_kwarg": "test", "kw2": "ooo"},
|
|
slots={"first": "FIRST_SLOT"},
|
|
)
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
the_arg: one
|
|
the_arg2: two
|
|
args: ['three']
|
|
the_kwarg: test
|
|
kwargs: {'kw2': 'ooo'}
|
|
---
|
|
from_context: 98
|
|
---
|
|
slot_first: FIRST_SLOT
|
|
---
|
|
slot_second: SLOT_SECOND_DEFAULT
|
|
""",
|
|
)
|
|
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_render_to_response_full(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
the_arg: {{ the_arg }}
|
|
the_arg2: {{ the_arg2 }}
|
|
args: {{ args|safe }}
|
|
the_kwarg: {{ the_kwarg }}
|
|
kwargs: {{ kwargs|safe }}
|
|
---
|
|
from_context: {{ from_context }}
|
|
---
|
|
slot_first: {% slot "first" required %}
|
|
{% endslot %}
|
|
---
|
|
slot_second: {% slot "second" default %}
|
|
SLOT_SECOND_DEFAULT
|
|
{% endslot %}
|
|
"""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"the_arg": args[0],
|
|
"the_arg2": args[1],
|
|
"the_kwarg": kwargs.pop("the_kwarg"),
|
|
"args": args[2:],
|
|
"kwargs": kwargs,
|
|
}
|
|
|
|
rendered = SimpleComponent.render_to_response(
|
|
context={"from_context": 98},
|
|
args=["one", "two", "three"],
|
|
kwargs={"the_kwarg": "test", "kw2": "ooo"},
|
|
slots={"first": "FIRST_SLOT"},
|
|
)
|
|
assert isinstance(rendered, HttpResponse)
|
|
|
|
assertHTMLEqual(
|
|
rendered.content.decode(),
|
|
"""
|
|
the_arg: one
|
|
the_arg2: two
|
|
args: ['three']
|
|
the_kwarg: test
|
|
kwargs: {'kw2': 'ooo'}
|
|
---
|
|
from_context: 98
|
|
---
|
|
slot_first: FIRST_SLOT
|
|
---
|
|
slot_second: SLOT_SECOND_DEFAULT
|
|
""",
|
|
)
|
|
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_render_to_response_change_response_class(self, components_settings):
|
|
class MyResponse:
|
|
def __init__(self, content: str) -> None:
|
|
self.content = bytes(content, "utf-8")
|
|
|
|
class SimpleComponent(Component):
|
|
response_class = MyResponse
|
|
template: types.django_html = "HELLO"
|
|
|
|
rendered = SimpleComponent.render_to_response()
|
|
assert isinstance(rendered, MyResponse)
|
|
|
|
assertHTMLEqual(
|
|
rendered.content.decode(),
|
|
"HELLO",
|
|
)
|
|
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_render_with_include(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
{% include 'slotted_template.html' %}
|
|
"""
|
|
|
|
rendered = SimpleComponent.render()
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
<custom-template data-djc-id-ca1bc3e>
|
|
<header>Default header</header>
|
|
<main>Default main</main>
|
|
<footer>Default footer</footer>
|
|
</custom-template>
|
|
""",
|
|
)
|
|
|
|
# See https://github.com/django-components/django-components/issues/580
|
|
# And https://github.com/django-components/django-components/commit/fee26ec1d8b46b5ee065ca1ce6143889b0f96764
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_render_with_include_and_context(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
{% include 'slotted_template.html' %}
|
|
"""
|
|
|
|
rendered = SimpleComponent.render(context=Context())
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
<custom-template data-djc-id-ca1bc3e>
|
|
<header>Default header</header>
|
|
<main>Default main</main>
|
|
<footer>Default footer</footer>
|
|
</custom-template>
|
|
""",
|
|
)
|
|
|
|
# See https://github.com/django-components/django-components/issues/580
|
|
# And https://github.com/django-components/django-components/issues/634
|
|
# And https://github.com/django-components/django-components/commit/fee26ec1d8b46b5ee065ca1ce6143889b0f96764
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_render_with_include_and_request_context(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
{% include 'slotted_template.html' %}
|
|
"""
|
|
|
|
rendered = SimpleComponent.render(context=RequestContext(HttpRequest()))
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
<custom-template data-djc-id-ca1bc3e>
|
|
<header>Default header</header>
|
|
<main>Default main</main>
|
|
<footer>Default footer</footer>
|
|
</custom-template>
|
|
""",
|
|
)
|
|
|
|
# See https://github.com/django-components/django-components/issues/580
|
|
# And https://github.com/django-components/django-components/issues/634
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_request_context_is_populated_from_context_processors(self, components_settings):
|
|
@register("thing")
|
|
class Thing(Component):
|
|
template: types.django_html = """
|
|
<kbd>Rendered {{ how }}</kbd>
|
|
<div>
|
|
CSRF token: {{ csrf_token|default:"<em>No CSRF token</em>" }}
|
|
</div>
|
|
"""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {"how": kwargs.pop("how")}
|
|
|
|
class View(ComponentView):
|
|
def get(self, request):
|
|
how = "via GET request"
|
|
|
|
return self.component.render_to_response(
|
|
context=RequestContext(self.request),
|
|
kwargs={"how": how},
|
|
)
|
|
|
|
client = CustomClient(urlpatterns=[path("test_thing/", Thing.as_view())])
|
|
response = client.get("/test_thing/")
|
|
|
|
assert response.status_code == 200
|
|
|
|
# Full response:
|
|
# """
|
|
# <kbd>
|
|
# Rendered via GET request
|
|
# </kbd>
|
|
# <div>
|
|
# CSRF token:
|
|
# <div>
|
|
# test_csrf_token
|
|
# </div>
|
|
# </div>
|
|
# """
|
|
assertInHTML(
|
|
"""
|
|
<kbd data-djc-id-ca1bc3f>
|
|
Rendered via GET request
|
|
</kbd>
|
|
""",
|
|
response.content.decode(),
|
|
)
|
|
|
|
token_re = re.compile(rb"CSRF token:\s+predictabletoken")
|
|
token = token_re.findall(response.content)[0]
|
|
|
|
assert token == b"CSRF token: predictabletoken"
|
|
|
|
def test_request_context_created_when_no_context(self):
|
|
@register("thing")
|
|
class Thing(Component):
|
|
template: types.django_html = """
|
|
CSRF token: {{ csrf_token|default:"<em>No CSRF token</em>" }}
|
|
"""
|
|
|
|
class View:
|
|
def get(self, request):
|
|
return Thing.render_to_response(request=request)
|
|
|
|
client = CustomClient(urlpatterns=[path("test_thing/", Thing.as_view())])
|
|
response = client.get("/test_thing/")
|
|
|
|
assert response.status_code == 200
|
|
|
|
token_re = re.compile(rb"CSRF token:\s+predictabletoken")
|
|
token = token_re.findall(response.content)[0]
|
|
|
|
assert token == b"CSRF token: predictabletoken"
|
|
|
|
def test_request_context_created_when_already_a_context_dict(self):
|
|
@register("thing")
|
|
class Thing(Component):
|
|
template: types.django_html = """
|
|
<p>CSRF token: {{ csrf_token|default:"<em>No CSRF token</em>" }}</p>
|
|
<p>Existing context: {{ existing_context|default:"<em>No existing context</em>" }}</p>
|
|
"""
|
|
|
|
class View:
|
|
def get(self, request):
|
|
return Thing.render_to_response(request=request, context={"existing_context": "foo"})
|
|
|
|
client = CustomClient(urlpatterns=[path("test_thing/", Thing.as_view())])
|
|
response = client.get("/test_thing/")
|
|
|
|
assert response.status_code == 200
|
|
|
|
token_re = re.compile(rb"CSRF token:\s+predictabletoken")
|
|
token = token_re.findall(response.content)[0]
|
|
|
|
assert token == b"CSRF token: predictabletoken"
|
|
assert "Existing context: foo" in response.content.decode()
|
|
|
|
def request_context_ignores_context_when_already_a_context(self):
|
|
@register("thing")
|
|
class Thing(Component):
|
|
template: types.django_html = """
|
|
<p>CSRF token: {{ csrf_token|default:"<em>No CSRF token</em>" }}</p>
|
|
<p>Existing context: {{ existing_context|default:"<em>No existing context</em>" }}</p>
|
|
"""
|
|
|
|
class View:
|
|
def get(self, request):
|
|
return Thing.render_to_response(
|
|
request=request,
|
|
context=Context({"existing_context": "foo"}),
|
|
)
|
|
|
|
client = CustomClient(urlpatterns=[path("test_thing/", Thing.as_view())])
|
|
response = client.get("/test_thing/")
|
|
|
|
assert response.status_code == 200
|
|
|
|
token_re = re.compile(rb"CSRF token:\s+(?P<token>[0-9a-zA-Z]{64})")
|
|
|
|
assert not token_re.findall(response.content)
|
|
assert "Existing context: foo" in response.content.decode()
|
|
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_render_with_extends(self, components_settings):
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% extends 'block.html' %}
|
|
{% block body %}
|
|
OVERRIDEN
|
|
{% endblock %}
|
|
"""
|
|
|
|
rendered = SimpleComponent.render(deps_strategy="ignore")
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
<!DOCTYPE html>
|
|
<html data-djc-id-ca1bc3e lang="en">
|
|
<body>
|
|
<main role="main">
|
|
<div class='container main-container'>
|
|
OVERRIDEN
|
|
</div>
|
|
</main>
|
|
</body>
|
|
</html>
|
|
""",
|
|
)
|
|
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_render_can_access_instance(self, components_settings):
|
|
class TestComponent(Component):
|
|
template = "Variable: <strong>{{ id }}</strong>"
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"id": self.id,
|
|
}
|
|
|
|
rendered = TestComponent.render()
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"Variable: <strong data-djc-id-ca1bc3e>ca1bc3e</strong>",
|
|
)
|
|
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_render_to_response_can_access_instance(self, components_settings):
|
|
class TestComponent(Component):
|
|
template = "Variable: <strong>{{ id }}</strong>"
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"id": self.id,
|
|
}
|
|
|
|
rendered_resp = TestComponent.render_to_response()
|
|
assertHTMLEqual(
|
|
rendered_resp.content.decode("utf-8"),
|
|
"Variable: <strong data-djc-id-ca1bc3e>ca1bc3e</strong>",
|
|
)
|
|
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_prepends_exceptions_with_component_path(self, components_settings):
|
|
@register("broken")
|
|
class Broken(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
<div> injected: {{ data|safe }} </div>
|
|
<main>
|
|
{% slot "content" default / %}
|
|
</main>
|
|
"""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
data = self.inject("my_provide")
|
|
data["data1"] # This should raise TypeError
|
|
return {"data": data}
|
|
|
|
@register("provider")
|
|
class Provider(Component):
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {"data": kwargs["data"]}
|
|
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
{% provide "my_provide" key="hi" data=data %}
|
|
{% slot "content" default / %}
|
|
{% endprovide %}
|
|
"""
|
|
|
|
@register("parent")
|
|
class Parent(Component):
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {"data": kwargs["data"]}
|
|
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
{% component "provider" data=data %}
|
|
{% component "broken" %}
|
|
{% slot "content" default / %}
|
|
{% endcomponent %}
|
|
{% endcomponent %}
|
|
"""
|
|
|
|
@register("root")
|
|
class Root(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
{% component "parent" data=123 %}
|
|
{% fill "content" %}
|
|
456
|
|
{% endfill %}
|
|
{% endcomponent %}
|
|
"""
|
|
|
|
with pytest.raises(
|
|
TypeError,
|
|
match=re.escape(
|
|
"An error occured while rendering components Root > parent > provider > provider(slot:content) > broken:\n" # noqa: E501
|
|
"tuple indices must be integers or slices, not str"
|
|
),
|
|
):
|
|
Root.render()
|
|
|
|
|
|
@djc_test
|
|
class TestComponentHook:
|
|
def test_on_render_before(self):
|
|
@register("nested")
|
|
class NestedComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
Hello from nested
|
|
<div>
|
|
{% slot "content" default / %}
|
|
</div>
|
|
"""
|
|
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
args: {{ args|safe }}
|
|
kwargs: {{ kwargs|safe }}
|
|
---
|
|
from_on_before: {{ from_on_before }}
|
|
---
|
|
{% component "nested" %}
|
|
Hello from simple
|
|
{% endcomponent %}
|
|
"""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"args": args,
|
|
"kwargs": kwargs,
|
|
}
|
|
|
|
def on_render_before(self, context: Context, template: Template) -> None:
|
|
# Insert value into the Context
|
|
context["from_on_before"] = ":)"
|
|
|
|
# Insert text into the Template
|
|
#
|
|
# NOTE: Users should NOT do this, because this will insert the text every time
|
|
# the component is rendered.
|
|
template.nodelist.append(TextNode("\n---\nFROM_ON_BEFORE"))
|
|
|
|
rendered = SimpleComponent.render()
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
args: []
|
|
kwargs: {}
|
|
---
|
|
from_on_before: :)
|
|
---
|
|
Hello from nested
|
|
<div data-djc-id-ca1bc3e data-djc-id-ca1bc40>
|
|
Hello from simple
|
|
</div>
|
|
---
|
|
FROM_ON_BEFORE
|
|
""",
|
|
)
|
|
|
|
# Check that modifying the context or template does nothing
|
|
def test_on_render_after(self):
|
|
captured_content = None
|
|
|
|
@register("nested")
|
|
class NestedComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
Hello from nested
|
|
<div>
|
|
{% slot "content" default / %}
|
|
</div>
|
|
"""
|
|
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
args: {{ args|safe }}
|
|
kwargs: {{ kwargs|safe }}
|
|
---
|
|
from_on_after: {{ from_on_after }}
|
|
---
|
|
{% component "nested" %}
|
|
Hello from simple
|
|
{% endcomponent %}
|
|
"""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"args": args,
|
|
"kwargs": kwargs,
|
|
}
|
|
|
|
# Check that modifying the context or template does nothing
|
|
def on_render_after(self, context: Context, template: Template, content: str) -> None:
|
|
# Insert value into the Context
|
|
context["from_on_after"] = ":)"
|
|
|
|
# Insert text into the Template
|
|
template.nodelist.append(TextNode("\n---\nFROM_ON_AFTER"))
|
|
|
|
nonlocal captured_content
|
|
captured_content = content
|
|
|
|
rendered = SimpleComponent.render()
|
|
|
|
assertHTMLEqual(
|
|
captured_content,
|
|
"""
|
|
args: []
|
|
kwargs: {}
|
|
---
|
|
from_on_after:
|
|
---
|
|
Hello from nested
|
|
<div data-djc-id-ca1bc3e data-djc-id-ca1bc40>
|
|
Hello from simple
|
|
</div>
|
|
""",
|
|
)
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
args: []
|
|
kwargs: {}
|
|
---
|
|
from_on_after:
|
|
---
|
|
Hello from nested
|
|
<div data-djc-id-ca1bc3e data-djc-id-ca1bc40>
|
|
Hello from simple
|
|
</div>
|
|
""",
|
|
)
|
|
|
|
# Check that modifying the context or template does nothing
|
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
|
def test_on_render_after_override_output(self, components_settings):
|
|
captured_content = None
|
|
|
|
@register("nested")
|
|
class NestedComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
Hello from nested
|
|
<div>
|
|
{% slot "content" default / %}
|
|
</div>
|
|
"""
|
|
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
args: {{ args|safe }}
|
|
kwargs: {{ kwargs|safe }}
|
|
---
|
|
from_on_before: {{ from_on_before }}
|
|
---
|
|
{% component "nested" %}
|
|
Hello from simple
|
|
{% endcomponent %}
|
|
"""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"args": args,
|
|
"kwargs": kwargs,
|
|
}
|
|
|
|
def on_render_after(self, context: Context, template: Template, content: str) -> str:
|
|
nonlocal captured_content
|
|
captured_content = content
|
|
|
|
return "Chocolate cookie recipe: " + content
|
|
|
|
rendered = SimpleComponent.render()
|
|
|
|
assertHTMLEqual(
|
|
captured_content,
|
|
"""
|
|
args: []
|
|
kwargs: {}
|
|
---
|
|
from_on_before:
|
|
---
|
|
Hello from nested
|
|
<div data-djc-id-ca1bc3e data-djc-id-ca1bc40>
|
|
Hello from simple
|
|
</div>
|
|
""",
|
|
)
|
|
assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
Chocolate cookie recipe:
|
|
args: []
|
|
kwargs: {}
|
|
---
|
|
from_on_before:
|
|
---
|
|
Hello from nested
|
|
<div data-djc-id-ca1bc3e data-djc-id-ca1bc40>
|
|
Hello from simple
|
|
</div>
|
|
""",
|
|
)
|
|
|
|
def test_on_render_before_after_same_context(self):
|
|
context_in_before = None
|
|
context_in_after = None
|
|
|
|
@register("nested")
|
|
class NestedComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
Hello from nested
|
|
<div>
|
|
{% slot "content" default / %}
|
|
</div>
|
|
"""
|
|
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
args: {{ args|safe }}
|
|
kwargs: {{ kwargs|safe }}
|
|
---
|
|
from_on_after: {{ from_on_after }}
|
|
---
|
|
{% component "nested" %}
|
|
Hello from simple
|
|
{% endcomponent %}
|
|
"""
|
|
|
|
def get_template_data(self, args, kwargs, slots, context):
|
|
return {
|
|
"args": args,
|
|
"kwargs": kwargs,
|
|
}
|
|
|
|
def on_render_before(self, context: Context, template: Template) -> None:
|
|
context["from_on_before"] = ":)"
|
|
nonlocal context_in_before
|
|
context_in_before = context
|
|
|
|
# Check that modifying the context or template does nothing
|
|
def on_render_after(self, context: Context, template: Template, html: str) -> None:
|
|
context["from_on_after"] = ":)"
|
|
nonlocal context_in_after
|
|
context_in_after = context
|
|
|
|
SimpleComponent.render()
|
|
|
|
assert context_in_before == context_in_after
|
|
assert "from_on_before" in context_in_before # type: ignore[operator]
|
|
assert "from_on_after" in context_in_after # type: ignore[operator]
|
|
|
|
|
|
@djc_test
|
|
class TestComponentHelpers:
|
|
def test_all_components(self):
|
|
# NOTE: When running all tests, this list may already have some components
|
|
# as some components in test files are defined on module level, outside of
|
|
# `djc_test` decorator.
|
|
all_comps_before = len(all_components())
|
|
|
|
# Components don't have to be registered to be included in the list
|
|
class TestComponent(Component):
|
|
template: types.django_html = """
|
|
Hello from test
|
|
"""
|
|
|
|
assert len(all_components()) == all_comps_before + 1
|
|
|
|
@register("test2")
|
|
class Test2Component(Component):
|
|
template: types.django_html = """
|
|
Hello from test2
|
|
"""
|
|
|
|
assert len(all_components()) == all_comps_before + 2
|