mirror of
https://github.com/django-components/django-components.git
synced 2025-08-03 22:08:17 +00:00
238 lines
9 KiB
Python
238 lines
9 KiB
Python
import gc
|
|
from typing import Any, Callable, Dict, List, cast
|
|
|
|
from django.template import Context
|
|
|
|
from django_components import Component, Slot, register, registry
|
|
from django_components.app_settings import app_settings
|
|
from django_components.component_registry import ComponentRegistry
|
|
from django_components.extension import (
|
|
ComponentExtension,
|
|
OnComponentClassCreatedContext,
|
|
OnComponentClassDeletedContext,
|
|
OnRegistryCreatedContext,
|
|
OnRegistryDeletedContext,
|
|
OnComponentRegisteredContext,
|
|
OnComponentUnregisteredContext,
|
|
OnComponentInputContext,
|
|
OnComponentDataContext,
|
|
)
|
|
from django_components.extensions.view import ViewExtension
|
|
|
|
from django_components.testing import djc_test
|
|
from .testutils import setup_test_config
|
|
|
|
setup_test_config({"autodiscover": False})
|
|
|
|
|
|
class DummyExtension(ComponentExtension):
|
|
"""
|
|
Test extension that tracks all hook calls and their arguments.
|
|
"""
|
|
|
|
name = "test_extension"
|
|
|
|
def __init__(self) -> None:
|
|
self.calls: Dict[str, List[Any]] = {
|
|
"on_component_class_created": [],
|
|
"on_component_class_deleted": [],
|
|
"on_registry_created": [],
|
|
"on_registry_deleted": [],
|
|
"on_component_registered": [],
|
|
"on_component_unregistered": [],
|
|
"on_component_input": [],
|
|
"on_component_data": [],
|
|
}
|
|
|
|
def on_component_class_created(self, ctx: OnComponentClassCreatedContext) -> None:
|
|
# NOTE: Store only component name to avoid strong references
|
|
self.calls["on_component_class_created"].append(ctx.component_cls.__name__)
|
|
|
|
def on_component_class_deleted(self, ctx: OnComponentClassDeletedContext) -> None:
|
|
# NOTE: Store only component name to avoid strong references
|
|
self.calls["on_component_class_deleted"].append(ctx.component_cls.__name__)
|
|
|
|
def on_registry_created(self, ctx: OnRegistryCreatedContext) -> None:
|
|
# NOTE: Store only registry object ID to avoid strong references
|
|
self.calls["on_registry_created"].append(id(ctx.registry))
|
|
|
|
def on_registry_deleted(self, ctx: OnRegistryDeletedContext) -> None:
|
|
# NOTE: Store only registry object ID to avoid strong references
|
|
self.calls["on_registry_deleted"].append(id(ctx.registry))
|
|
|
|
def on_component_registered(self, ctx: OnComponentRegisteredContext) -> None:
|
|
self.calls["on_component_registered"].append(ctx)
|
|
|
|
def on_component_unregistered(self, ctx: OnComponentUnregisteredContext) -> None:
|
|
self.calls["on_component_unregistered"].append(ctx)
|
|
|
|
def on_component_input(self, ctx: OnComponentInputContext) -> None:
|
|
self.calls["on_component_input"].append(ctx)
|
|
|
|
def on_component_data(self, ctx: OnComponentDataContext) -> None:
|
|
self.calls["on_component_data"].append(ctx)
|
|
|
|
|
|
def with_component_cls(on_created: Callable):
|
|
class TestComponent(Component):
|
|
template = "Hello {{ name }}!"
|
|
|
|
def get_context_data(self, name="World"):
|
|
return {"name": name}
|
|
|
|
on_created()
|
|
|
|
|
|
def with_registry(on_created: Callable):
|
|
registry = ComponentRegistry()
|
|
|
|
on_created(registry)
|
|
|
|
|
|
@djc_test
|
|
class TestExtension:
|
|
@djc_test(
|
|
components_settings={"extensions": [DummyExtension]}
|
|
)
|
|
def test_extensios_setting(self):
|
|
assert len(app_settings.EXTENSIONS) == 2
|
|
assert isinstance(app_settings.EXTENSIONS[0], ViewExtension)
|
|
assert isinstance(app_settings.EXTENSIONS[1], DummyExtension)
|
|
|
|
@djc_test(
|
|
components_settings={"extensions": [DummyExtension]}
|
|
)
|
|
def test_component_class_lifecycle_hooks(self):
|
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[1])
|
|
|
|
assert len(extension.calls["on_component_class_created"]) == 0
|
|
assert len(extension.calls["on_component_class_deleted"]) == 0
|
|
|
|
did_call_on_comp_cls_created = False
|
|
|
|
def on_comp_cls_created():
|
|
nonlocal did_call_on_comp_cls_created
|
|
did_call_on_comp_cls_created = True
|
|
|
|
# Verify on_component_class_created was called
|
|
assert len(extension.calls["on_component_class_created"]) == 1
|
|
assert extension.calls["on_component_class_created"][0] == "TestComponent"
|
|
|
|
# Create a component class in a separate scope, to avoid any references from within
|
|
# this test function, so we can garbage collect it after the function returns
|
|
with_component_cls(on_comp_cls_created)
|
|
assert did_call_on_comp_cls_created
|
|
|
|
# This should trigger the garbage collection of the component class
|
|
gc.collect()
|
|
|
|
# Verify on_component_class_deleted was called
|
|
assert len(extension.calls["on_component_class_deleted"]) == 1
|
|
assert extension.calls["on_component_class_deleted"][0] == "TestComponent"
|
|
|
|
@djc_test(
|
|
components_settings={"extensions": [DummyExtension]}
|
|
)
|
|
def test_registry_lifecycle_hooks(self):
|
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[1])
|
|
|
|
assert len(extension.calls["on_registry_created"]) == 0
|
|
assert len(extension.calls["on_registry_deleted"]) == 0
|
|
|
|
did_call_on_registry_created = False
|
|
reg_id = None
|
|
|
|
def on_registry_created(reg):
|
|
nonlocal did_call_on_registry_created
|
|
nonlocal reg_id
|
|
did_call_on_registry_created = True
|
|
reg_id = id(reg)
|
|
|
|
# Verify on_registry_created was called
|
|
assert len(extension.calls["on_registry_created"]) == 1
|
|
assert extension.calls["on_registry_created"][0] == reg_id
|
|
|
|
with_registry(on_registry_created)
|
|
assert did_call_on_registry_created
|
|
assert reg_id is not None
|
|
|
|
gc.collect()
|
|
|
|
# Verify on_registry_deleted was called
|
|
assert len(extension.calls["on_registry_deleted"]) == 1
|
|
assert extension.calls["on_registry_deleted"][0] == reg_id
|
|
|
|
@djc_test(
|
|
components_settings={"extensions": [DummyExtension]}
|
|
)
|
|
def test_component_registration_hooks(self):
|
|
class TestComponent(Component):
|
|
template = "Hello {{ name }}!"
|
|
|
|
def get_context_data(self, name="World"):
|
|
return {"name": name}
|
|
|
|
registry.register("test_comp", TestComponent)
|
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[1])
|
|
|
|
# Verify on_component_registered was called
|
|
assert len(extension.calls["on_component_registered"]) == 1
|
|
reg_call: OnComponentRegisteredContext = extension.calls["on_component_registered"][0]
|
|
assert reg_call.registry == registry
|
|
assert reg_call.name == "test_comp"
|
|
assert reg_call.component_cls == TestComponent
|
|
|
|
registry.unregister("test_comp")
|
|
|
|
# Verify on_component_unregistered was called
|
|
assert len(extension.calls["on_component_unregistered"]) == 1
|
|
unreg_call: OnComponentUnregisteredContext = extension.calls["on_component_unregistered"][0]
|
|
assert unreg_call.registry == registry
|
|
assert unreg_call.name == "test_comp"
|
|
assert unreg_call.component_cls == TestComponent
|
|
|
|
@djc_test(
|
|
components_settings={"extensions": [DummyExtension]}
|
|
)
|
|
def test_component_render_hooks(self):
|
|
@register("test_comp")
|
|
class TestComponent(Component):
|
|
template = "Hello {{ name }}!"
|
|
|
|
def get_context_data(self, arg1, arg2, name="World"):
|
|
return {"name": name}
|
|
|
|
def get_js_data(self, *args, **kwargs):
|
|
return {"script": "console.log('Hello!')"}
|
|
|
|
def get_css_data(self, *args, **kwargs):
|
|
return {"style": "body { color: blue; }"}
|
|
|
|
# Render the component with some args and kwargs
|
|
test_context = Context({"foo": "bar"})
|
|
test_slots = {"content": "Some content"}
|
|
TestComponent.render(
|
|
context=test_context, args=("arg1", "arg2"), kwargs={"name": "Test"}, slots=test_slots
|
|
)
|
|
|
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[1])
|
|
|
|
# Verify on_component_input was called with correct args
|
|
assert len(extension.calls["on_component_input"]) == 1
|
|
input_call: OnComponentInputContext = extension.calls["on_component_input"][0]
|
|
assert input_call.component_cls == TestComponent
|
|
assert isinstance(input_call.component_id, str)
|
|
assert input_call.args == ("arg1", "arg2")
|
|
assert input_call.kwargs == {"name": "Test"}
|
|
assert len(input_call.slots) == 1
|
|
assert isinstance(input_call.slots["content"], Slot)
|
|
assert input_call.context == test_context
|
|
|
|
# Verify on_component_data was called with correct args
|
|
assert len(extension.calls["on_component_data"]) == 1
|
|
data_call: OnComponentDataContext = extension.calls["on_component_data"][0]
|
|
assert data_call.component_cls == TestComponent
|
|
assert isinstance(data_call.component_id, str)
|
|
assert data_call.context_data == {"name": "Test"}
|
|
assert data_call.js_data == {"script": "console.log('Hello!')"}
|
|
assert data_call.css_data == {"style": "body { color: blue; }"}
|