mirror of
https://github.com/django-components/django-components.git
synced 2025-08-04 06:18:17 +00:00
refactor: prepare registry for custom template tags and docs (#566)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
c202c5a901
commit
d6dec450ed
11 changed files with 491 additions and 94 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -64,7 +64,6 @@ target/
|
|||
# lock file is not needed for development
|
||||
# as project supports variety of Django versions
|
||||
poetry.lock
|
||||
pyproject.toml
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
|
122
README.md
122
README.md
|
@ -29,6 +29,7 @@ And this is what gets rendered (plus the CSS and Javascript you've specified):
|
|||
- [Using single-file components](#using-single-file-components)
|
||||
- [Use components in templates](#use-components-in-templates)
|
||||
- [Use components outside of templates](#use-components-outside-of-templates)
|
||||
- [Registering components](#registering-components)
|
||||
- [Use components as views](#use-components-as-views)
|
||||
- [Autodiscovery](#autodiscovery)
|
||||
- [Using slots in templates](#using-slots-in-templates)
|
||||
|
@ -643,6 +644,127 @@ Note: slots content are automatically escaped by default to prevent XSS attacks.
|
|||
|
||||
If you're planning on passing an HTML string, check Django's use of [`format_html`](https://docs.djangoproject.com/en/5.0/ref/utils/#django.utils.html.format_html) and [`mark_safe`](https://docs.djangoproject.com/en/5.0/ref/utils/#django.utils.safestring.mark_safe).
|
||||
|
||||
## Registering components
|
||||
|
||||
In previous examples you could repeatedly see us using `@register()` to "register"
|
||||
the components. In this section we dive deeper into what it actually means and how you can
|
||||
manage (add or remove) components.
|
||||
|
||||
As a reminder, we may have a component like this:
|
||||
|
||||
```python
|
||||
from django_components import Component, register
|
||||
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template_name = "template.html"
|
||||
|
||||
# This component takes one parameter, a date string to show in the template
|
||||
def get_context_data(self, date):
|
||||
return {
|
||||
"date": date,
|
||||
}
|
||||
```
|
||||
|
||||
which we then render in the template as:
|
||||
|
||||
```django
|
||||
{% component "calendar" date="1970-01-01" %}
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
As you can see, `@register` links up the component class
|
||||
with the `{% component %}` template tag. So when the template tag comes across
|
||||
a component called `"calendar"`, it can look up it's class and instantiate it.
|
||||
|
||||
### What is ComponentRegistry
|
||||
|
||||
The `@register` decorator is a shortcut for working with the `ComponentRegistry`.
|
||||
|
||||
`ComponentRegistry` manages which components can be used in the template tags.
|
||||
|
||||
Each `ComponentRegistry` instance is associated with an instance
|
||||
of Django's `Library`. And Libraries are inserted into Django template
|
||||
using the `{% load %}` tags.
|
||||
|
||||
The `@register` decorator accepts an optional kwarg `registry`, which specifies, the `ComponentRegistry` to register components into.
|
||||
If omitted, the default `ComponentRegistry` instance defined in django_components is used.
|
||||
|
||||
```py
|
||||
my_registry = ComponentRegistry()
|
||||
|
||||
@register(registry=my_registry)
|
||||
class MyComponent(Component):
|
||||
...
|
||||
```
|
||||
|
||||
The default `ComponentRegistry` is associated with the `Library` that
|
||||
you load when you call `{% load component_tags %}` inside your template, or when you
|
||||
add `django_components.templatetags.component_tags` to the template builtins.
|
||||
|
||||
So when you register or unregister a component to/from a component registry,
|
||||
then behind the scenes the registry automatically adds/removes the component's
|
||||
template tags to/from the Library, so you can call the component from within the templates
|
||||
such as `{% component "my_comp" %}`.
|
||||
|
||||
### Working with ComponentRegistry
|
||||
|
||||
The default `ComponentRegistry` instance can be imported as:
|
||||
|
||||
```py
|
||||
from django_components import registry
|
||||
```
|
||||
|
||||
You can use the registry to manually add/remove/get components:
|
||||
|
||||
```py
|
||||
from django_components import registry
|
||||
|
||||
# Register components
|
||||
registry.register("button", ButtonComponent)
|
||||
registry.register("card", CardComponent)
|
||||
|
||||
# Get all or single
|
||||
registry.all() # {"button": ButtonComponent, "card": CardComponent}
|
||||
registry.get("card") # CardComponent
|
||||
|
||||
# Unregister single component
|
||||
registry.unregister("card")
|
||||
|
||||
# Unregister all components
|
||||
registry.clear()
|
||||
```
|
||||
|
||||
### Registering components to custom ComponentRegistry
|
||||
|
||||
In rare cases, you may want to manage your own instance of `ComponentRegistry`,
|
||||
or register components onto a different `Library` instance than the default one.
|
||||
|
||||
The `Library` instance can be set at instantiation of `ComponentRegistry`. If omitted,
|
||||
then the default Library instance from django_components is used.
|
||||
|
||||
```py
|
||||
from django.template import Library
|
||||
from django_components import ComponentRegistry
|
||||
|
||||
my_library = Library(...)
|
||||
my_registry = ComponentRegistry(library=my_library)
|
||||
```
|
||||
|
||||
When you have defined your own `ComponentRegistry`, you can either register the components
|
||||
with `my_registry.register()`, or pass the registry to the `@component.register()` decorator
|
||||
via the `registry` kwarg:
|
||||
|
||||
```py
|
||||
from path.to.my.registry import my_registry
|
||||
|
||||
@register("my_component", registry=my_registry)
|
||||
class MyComponent(Component):
|
||||
...
|
||||
```
|
||||
|
||||
NOTE: The Library instance can be accessed under `library` attribute of `ComponentRegistry`.
|
||||
|
||||
## Autodiscovery
|
||||
|
||||
Every component that you want to use in the template with the `{% component %}` tag needs to be registered with the ComponentRegistry. Normally, we use the `@register` decorator for that:
|
||||
|
|
|
@ -1,9 +1,23 @@
|
|||
from typing import TYPE_CHECKING, Callable, Dict, Type, TypeVar
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Set, Type, TypeVar
|
||||
|
||||
from django.template import Library
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django_components import component
|
||||
from django_components.component import Component
|
||||
|
||||
_TC = TypeVar("_TC", bound=Type["component.Component"])
|
||||
_TComp = TypeVar("_TComp", bound=Type["Component"])
|
||||
|
||||
|
||||
PROTECTED_TAGS = [
|
||||
"component",
|
||||
"component_dependencies",
|
||||
"component_css_dependencies",
|
||||
"component_js_dependencies",
|
||||
"fill",
|
||||
"html_attrs",
|
||||
"provide",
|
||||
"slot",
|
||||
]
|
||||
|
||||
|
||||
class AlreadyRegistered(Exception):
|
||||
|
@ -14,50 +28,311 @@ class NotRegistered(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class ComponentRegistry:
|
||||
def __init__(self) -> None:
|
||||
self._registry: Dict[str, Type["component.Component"]] = {} # component name -> component_class mapping
|
||||
# Why do we store the tags with the component?
|
||||
#
|
||||
# Each component may be associated with two template tags - One for "block"
|
||||
# and one for "inline" usage. E.g. in the following snippets, the template
|
||||
# tags are `component` and `#component`:
|
||||
#
|
||||
# `{% component "abc" %}{% endcomponent %}`
|
||||
# `{% #component "abc" %}`
|
||||
#
|
||||
# (NOTE: While `endcomponent` also looks like a template tag, we don't have to register
|
||||
# it, because it simply marks the end of body.)
|
||||
#
|
||||
# With the component tag formatter (configurable tags per component class),
|
||||
# each component may have a unique set of template tags.
|
||||
#
|
||||
# For user's convenience, we automatically add/remove the tags from Django's tag Library,
|
||||
# when a component is (un)registered.
|
||||
#
|
||||
# Thus we need to remember which component used which template tags.
|
||||
class ComponentRegistryEntry(NamedTuple):
|
||||
cls: Type["Component"]
|
||||
block_tag: str
|
||||
inline_tag: str
|
||||
|
||||
def register(self, name: str, component: Type["component.Component"]) -> None:
|
||||
@property
|
||||
def tags(self) -> List[str]:
|
||||
return [self.block_tag, self.inline_tag]
|
||||
|
||||
|
||||
class ComponentRegistry:
|
||||
"""
|
||||
Manages which components can be used in the template tags.
|
||||
|
||||
Each ComponentRegistry instance is associated with an instance
|
||||
of Django's Library. So when you register or unregister a component
|
||||
to/from a component registry, behind the scenes the registry
|
||||
automatically adds/removes the component's template tag to/from
|
||||
the Library.
|
||||
|
||||
The Library instance can be set at instantiation. If omitted, then
|
||||
the default Library instance from django_components is used. The
|
||||
Library instance can be accessed under `library` attribute.
|
||||
|
||||
Example:
|
||||
|
||||
```py
|
||||
# Use with default Library
|
||||
registry = ComponentRegistry()
|
||||
|
||||
# Or a custom one
|
||||
my_lib = Library()
|
||||
registry = ComponentRegistry(library=my_lib)
|
||||
|
||||
# Usage
|
||||
registry.register("button", ButtonComponent)
|
||||
registry.register("card", CardComponent)
|
||||
registry.all()
|
||||
registry.clear()
|
||||
registry.get()
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, library: Optional[Library] = None) -> None:
|
||||
self._registry: Dict[str, ComponentRegistryEntry] = {} # component name -> component_entry mapping
|
||||
self._tags: Dict[str, Set[str]] = {} # tag -> list[component names]
|
||||
self._library = library
|
||||
|
||||
@property
|
||||
def library(self) -> Library:
|
||||
"""
|
||||
The template tag library with which the component registry is associated.
|
||||
"""
|
||||
# Lazily use the default library if none was passed
|
||||
if self._library is not None:
|
||||
lib = self._library
|
||||
else:
|
||||
from django_components.templatetags.component_tags import register as tag_library
|
||||
|
||||
# For the default library, we want to protect our template tags from
|
||||
# being overriden.
|
||||
# On the other hand, if user provided their own Library instance,
|
||||
# it is up to the user to use `mark_protected_tags` if they want
|
||||
# to protect any tags.
|
||||
mark_protected_tags(tag_library, PROTECTED_TAGS)
|
||||
lib = self._library = tag_library
|
||||
return lib
|
||||
|
||||
def register(self, name: str, component: Type["Component"]) -> None:
|
||||
"""
|
||||
Register a component with this registry under the given name.
|
||||
|
||||
A component MUST be registered before it can be used in a template such as:
|
||||
```django
|
||||
{% component "my_comp" %}{% endcomponent %}
|
||||
```
|
||||
|
||||
Raises `AlreadyRegistered` if a different component was already registered
|
||||
under the same name.
|
||||
|
||||
Example:
|
||||
|
||||
```py
|
||||
registry.register("button", ButtonComponent)
|
||||
```
|
||||
"""
|
||||
existing_component = self._registry.get(name)
|
||||
if existing_component and existing_component._class_hash != component._class_hash:
|
||||
if existing_component and existing_component.cls._class_hash != component._class_hash:
|
||||
raise AlreadyRegistered('The component "%s" has already been registered' % name)
|
||||
self._registry[name] = component
|
||||
|
||||
block_tag = "component"
|
||||
inline_tag = "#component"
|
||||
|
||||
entry = ComponentRegistryEntry(
|
||||
cls=component,
|
||||
block_tag=block_tag,
|
||||
inline_tag=inline_tag,
|
||||
)
|
||||
|
||||
# Keep track of which components use which tags, because multiple components may
|
||||
# use the same tag.
|
||||
for tag in entry.tags:
|
||||
if tag not in self._tags:
|
||||
self._tags[tag] = set()
|
||||
self._tags[tag].add(name)
|
||||
|
||||
self._registry[name] = entry
|
||||
|
||||
def unregister(self, name: str) -> None:
|
||||
"""
|
||||
Unlinks a previously-registered component from the registry under the given name.
|
||||
|
||||
Once a component is unregistered, it CANNOT be used in a template anymore.
|
||||
Following would raise an error:
|
||||
```django
|
||||
{% component "my_comp" %}{% endcomponent %}
|
||||
```
|
||||
|
||||
Raises `NotRegistered` if the given name is not registered.
|
||||
|
||||
Example:
|
||||
|
||||
```py
|
||||
# First register component
|
||||
registry.register("button", ButtonComponent)
|
||||
# Then unregister
|
||||
registry.unregister("button")
|
||||
```
|
||||
"""
|
||||
# Validate
|
||||
self.get(name)
|
||||
|
||||
entry = self._registry[name]
|
||||
|
||||
# Unregister the tag from library if this was the last component using this tag
|
||||
for tag in entry.tags:
|
||||
# Unlink component from tag
|
||||
self._tags[tag].remove(name)
|
||||
|
||||
# Cleanup
|
||||
is_tag_empty = not len(self._tags[tag])
|
||||
if is_tag_empty:
|
||||
del self._tags[tag]
|
||||
|
||||
# Do NOT unregister tag if it's protected
|
||||
is_protected = is_tag_protected(self.library, tag)
|
||||
if is_protected:
|
||||
continue
|
||||
|
||||
# Unregister the tag from library if this was the last component using this tag
|
||||
if is_tag_empty and tag in self.library.tags:
|
||||
del self.library.tags[tag]
|
||||
|
||||
del self._registry[name]
|
||||
|
||||
def get(self, name: str) -> Type["component.Component"]:
|
||||
def get(self, name: str) -> Type["Component"]:
|
||||
"""
|
||||
Retrieve a component class registered under the given name.
|
||||
|
||||
Raises `NotRegistered` if the given name is not registered.
|
||||
|
||||
Example:
|
||||
|
||||
```py
|
||||
# First register component
|
||||
registry.register("button", ButtonComponent)
|
||||
# Then get
|
||||
registry.get("button")
|
||||
# > ButtonComponent
|
||||
```
|
||||
"""
|
||||
if name not in self._registry:
|
||||
raise NotRegistered('The component "%s" is not registered' % name)
|
||||
|
||||
return self._registry[name]
|
||||
return self._registry[name].cls
|
||||
|
||||
def all(self) -> Dict[str, Type["component.Component"]]:
|
||||
return self._registry
|
||||
def all(self) -> Dict[str, Type["Component"]]:
|
||||
"""
|
||||
Retrieve all registered component classes.
|
||||
|
||||
Example:
|
||||
|
||||
```py
|
||||
# First register components
|
||||
registry.register("button", ButtonComponent)
|
||||
registry.register("card", CardComponent)
|
||||
# Then get all
|
||||
registry.all()
|
||||
# > {
|
||||
# > "button": ButtonComponent,
|
||||
# > "card": CardComponent,
|
||||
# > }
|
||||
```
|
||||
"""
|
||||
comps = {key: entry.cls for key, entry in self._registry.items()}
|
||||
return comps
|
||||
|
||||
def clear(self) -> None:
|
||||
"""
|
||||
Clears the registry, unregistering all components.
|
||||
|
||||
Example:
|
||||
|
||||
```py
|
||||
# First register components
|
||||
registry.register("button", ButtonComponent)
|
||||
registry.register("card", CardComponent)
|
||||
# Then clear
|
||||
registry.clear()
|
||||
# Then get all
|
||||
registry.all()
|
||||
# > {}
|
||||
```
|
||||
"""
|
||||
all_comp_names = list(self._registry.keys())
|
||||
for comp_name in all_comp_names:
|
||||
self.unregister(comp_name)
|
||||
|
||||
self._registry = {}
|
||||
self._tags = {}
|
||||
|
||||
|
||||
# This variable represents the global component registry
|
||||
registry: ComponentRegistry = ComponentRegistry()
|
||||
"""
|
||||
The default and global component registry. Use this instance to directly
|
||||
register or remove components:
|
||||
|
||||
```py
|
||||
# Register components
|
||||
registry.register("button", ButtonComponent)
|
||||
registry.register("card", CardComponent)
|
||||
# Get single
|
||||
registry.get("button")
|
||||
# Get all
|
||||
registry.all()
|
||||
# Unregister single
|
||||
registry.unregister("button")
|
||||
# Unregister all
|
||||
registry.clear()
|
||||
```
|
||||
"""
|
||||
|
||||
# NOTE: Aliased so that the arg to `@register` can also be called `registry`
|
||||
_the_registry = registry
|
||||
|
||||
|
||||
def register(name: str) -> Callable[[_TC], _TC]:
|
||||
"""Class decorator to register a component.
|
||||
def register(name: str, registry: Optional[ComponentRegistry] = None) -> Callable[[_TComp], _TComp]:
|
||||
"""
|
||||
Class decorator to register a component.
|
||||
|
||||
Usage:
|
||||
|
||||
```py
|
||||
@register("my_component")
|
||||
class MyComponent(component.Component):
|
||||
class MyComponent(Component):
|
||||
...
|
||||
"""
|
||||
```
|
||||
|
||||
def decorator(component: _TC) -> _TC:
|
||||
Optionally specify which `ComponentRegistry` the component should be registered to by
|
||||
setting the `registry` kwarg:
|
||||
|
||||
```py
|
||||
my_lib = django.template.Library()
|
||||
my_reg = ComponentRegistry(library=my_lib)
|
||||
|
||||
@register("my_component", registry=my_reg)
|
||||
class MyComponent(Component):
|
||||
...
|
||||
```
|
||||
"""
|
||||
if registry is None:
|
||||
registry = _the_registry
|
||||
|
||||
def decorator(component: _TComp) -> _TComp:
|
||||
registry.register(name=name, component=component)
|
||||
return component
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def mark_protected_tags(lib: Library, tags: List[str]) -> None:
|
||||
# By marking the library as default,
|
||||
lib._protected_tags = [*tags]
|
||||
|
||||
|
||||
def is_tag_protected(lib: Library, tag: str) -> bool:
|
||||
protected_tags = getattr(lib, "_protected_tags", [])
|
||||
return tag in protected_tags
|
||||
|
|
|
@ -73,14 +73,11 @@ class AppendAttributesTest(BaseTestCase):
|
|||
|
||||
|
||||
class HtmlAttrsTests(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "test" attrs:@click.stop="dispatch('click_event')" attrs:x-data="{hello: 'world'}" attrs:class=class_var %}
|
||||
{% endcomponent %}
|
||||
""" # noqa: E501
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "test" attrs:@click.stop="dispatch('click_event')" attrs:x-data="{hello: 'world'}" attrs:class=class_var %}
|
||||
{% endcomponent %}
|
||||
""" # noqa: E501
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_tag_positional_args(self):
|
||||
|
|
|
@ -615,10 +615,6 @@ class ContextVarsIsFilledTests(BaseTestCase):
|
|||
)
|
||||
registry.register("negated_conditional_slot", self.ComponentWithNegatedConditionalSlot)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
super().tearDown()
|
||||
registry.clear()
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_is_filled_vars(self):
|
||||
template: types.django_html = """
|
||||
|
|
|
@ -67,10 +67,6 @@ class MultistyleComponent(Component):
|
|||
|
||||
@override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True})
|
||||
class ComponentMediaRenderingTests(BaseTestCase):
|
||||
def setUp(self):
|
||||
# NOTE: registry is global, so need to clear before each test
|
||||
registry.clear()
|
||||
|
||||
def test_no_dependencies_when_no_components_used(self):
|
||||
registry.register(name="test", component=SimpleComponent)
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import unittest
|
||||
|
||||
from django.template import Library
|
||||
|
||||
from django_components import AlreadyRegistered, Component, ComponentRegistry, NotRegistered, register, registry
|
||||
|
||||
from .django_test_setup import setup_test_config
|
||||
|
@ -22,6 +24,7 @@ class MockComponentView(Component):
|
|||
|
||||
class ComponentRegistryTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.registry = ComponentRegistry()
|
||||
|
||||
def test_register_class_decorator(self):
|
||||
|
@ -31,6 +34,23 @@ class ComponentRegistryTest(unittest.TestCase):
|
|||
|
||||
self.assertEqual(registry.get("decorated_component"), TestComponent)
|
||||
|
||||
# Cleanup
|
||||
registry.unregister("decorated_component")
|
||||
|
||||
def test_register_class_decorator_custom_registry(self):
|
||||
my_lib = Library()
|
||||
my_reg = ComponentRegistry(library=my_lib)
|
||||
|
||||
self.assertDictEqual(my_reg.all(), {})
|
||||
self.assertDictEqual(registry.all(), {})
|
||||
|
||||
@register("decorated_component", registry=my_reg)
|
||||
class TestComponent(Component):
|
||||
pass
|
||||
|
||||
self.assertDictEqual(my_reg.all(), {"decorated_component": TestComponent})
|
||||
self.assertDictEqual(registry.all(), {})
|
||||
|
||||
def test_simple_register(self):
|
||||
self.registry.register(name="testcomponent", component=MockComponent)
|
||||
self.assertEqual(self.registry.all(), {"testcomponent": MockComponent})
|
||||
|
@ -46,6 +66,44 @@ class ComponentRegistryTest(unittest.TestCase):
|
|||
},
|
||||
)
|
||||
|
||||
def test_unregisters_only_unused_tags(self):
|
||||
self.assertDictEqual(self.registry._tags, {})
|
||||
# NOTE: We preserve the default component tags
|
||||
self.assertIn("component", self.registry.library.tags)
|
||||
|
||||
# Register two components that use the same tag
|
||||
self.registry.register(name="testcomponent", component=MockComponent)
|
||||
self.registry.register(name="testcomponent2", component=MockComponent)
|
||||
|
||||
self.assertDictEqual(
|
||||
self.registry._tags,
|
||||
{
|
||||
"#component": {"testcomponent", "testcomponent2"},
|
||||
"component": {"testcomponent", "testcomponent2"},
|
||||
},
|
||||
)
|
||||
|
||||
self.assertIn("component", self.registry.library.tags)
|
||||
|
||||
# Unregister only one of the components. The tags should remain
|
||||
self.registry.unregister(name="testcomponent")
|
||||
|
||||
self.assertDictEqual(
|
||||
self.registry._tags,
|
||||
{
|
||||
"#component": {"testcomponent2"},
|
||||
"component": {"testcomponent2"},
|
||||
},
|
||||
)
|
||||
|
||||
self.assertIn("component", self.registry.library.tags)
|
||||
|
||||
# Unregister the second components. The tags should be removed
|
||||
self.registry.unregister(name="testcomponent2")
|
||||
|
||||
self.assertDictEqual(self.registry._tags, {})
|
||||
self.assertIn("component", self.registry.library.tags)
|
||||
|
||||
def test_prevent_registering_different_components_with_the_same_name(self):
|
||||
self.registry.register(name="testcomponent", component=MockComponent)
|
||||
with self.assertRaises(AlreadyRegistered):
|
||||
|
|
|
@ -25,6 +25,7 @@ class TemplateInstrumentationTest(BaseTestCase):
|
|||
saved_render_method: Callable # Assigned during setup.
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
Template._render = self.saved_render_method
|
||||
|
||||
def setUp(self):
|
||||
|
@ -92,14 +93,6 @@ class TemplateInstrumentationTest(BaseTestCase):
|
|||
|
||||
|
||||
class BlockCompatTests(BaseTestCase):
|
||||
def setUp(self):
|
||||
registry.clear()
|
||||
super().setUp()
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
registry.clear()
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_slots_inside_extends(self):
|
||||
registry.register("slotted_component", SlottedComponent)
|
||||
|
|
|
@ -47,10 +47,6 @@ class ComponentTemplateTagTest(BaseTestCase):
|
|||
css = "style.css"
|
||||
js = "script.js"
|
||||
|
||||
def setUp(self):
|
||||
# NOTE: registry is global, so need to clear before each test
|
||||
registry.clear()
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_single_component(self):
|
||||
registry.register(name="test", component=self.SimpleComponent)
|
||||
|
@ -190,9 +186,6 @@ class ComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
|
||||
class MultiComponentTests(BaseTestCase):
|
||||
def setUp(self):
|
||||
registry.clear()
|
||||
|
||||
def register_components(self):
|
||||
registry.register("first_component", SlottedComponent)
|
||||
registry.register("second_component", SlottedComponentWithContext)
|
||||
|
@ -265,18 +258,19 @@ class MultiComponentTests(BaseTestCase):
|
|||
|
||||
|
||||
class ComponentIsolationTests(BaseTestCase):
|
||||
def setUp(self):
|
||||
class SlottedComponent(Component):
|
||||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
<custom-template>
|
||||
<header>{% slot "header" %}Default header{% endslot %}</header>
|
||||
<main>{% slot "main" %}Default main{% endslot %}</main>
|
||||
<footer>{% slot "footer" %}Default footer{% endslot %}</footer>
|
||||
</custom-template>
|
||||
"""
|
||||
class SlottedComponent(Component):
|
||||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
<custom-template>
|
||||
<header>{% slot "header" %}Default header{% endslot %}</header>
|
||||
<main>{% slot "main" %}Default main{% endslot %}</main>
|
||||
<footer>{% slot "footer" %}Default footer{% endslot %}</footer>
|
||||
</custom-template>
|
||||
"""
|
||||
|
||||
registry.register("test", SlottedComponent)
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
registry.register("test", self.SlottedComponent)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_instances_of_component_do_not_share_slots(self):
|
||||
|
@ -358,10 +352,6 @@ class ComponentTemplateSyntaxErrorTests(BaseTestCase):
|
|||
super().setUp()
|
||||
registry.register("test", SlottedComponent)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
super().tearDown()
|
||||
registry.clear()
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_variable_outside_fill_tag_compiles_w_out_error(self):
|
||||
# As of v0.28 this is valid, provided the component registered under "test"
|
||||
|
|
|
@ -32,10 +32,6 @@ class SlottedComponentWithContext(SlottedComponent):
|
|||
|
||||
|
||||
class ComponentSlottedTemplateTagTest(BaseTestCase):
|
||||
def setUp(self):
|
||||
# NOTE: registry is global, so need to clear before each test
|
||||
registry.clear()
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_slotted_template_basic(self):
|
||||
registry.register(name="test1", component=SlottedComponent)
|
||||
|
@ -477,10 +473,6 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
|
|||
|
||||
|
||||
class SlottedTemplateRegressionTests(BaseTestCase):
|
||||
def setUp(self):
|
||||
# NOTE: registry is global, so need to clear before each test
|
||||
registry.clear()
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_slotted_template_that_uses_missing_variable(self):
|
||||
@register("test")
|
||||
|
@ -517,13 +509,8 @@ class SlottedTemplateRegressionTests(BaseTestCase):
|
|||
class SlotDefaultTests(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
registry.clear()
|
||||
registry.register("test", SlottedComponent)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
registry.clear()
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_basic(self):
|
||||
template_str: types.django_html = """
|
||||
|
@ -1115,10 +1102,6 @@ class SlotFillTemplateSyntaxErrorTests(BaseTestCase):
|
|||
super().setUp()
|
||||
registry.register("test", SlottedComponent)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
registry.clear()
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_fill_with_no_parent_is_error(self):
|
||||
with self.assertRaises(TemplateSyntaxError):
|
||||
|
|
|
@ -178,13 +178,8 @@ class ConditionalSlotTests(BaseTestCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
registry.clear()
|
||||
registry.register("test", self.ConditionalComponent)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
registry.clear()
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_no_content_if_branches_are_false(self):
|
||||
template_str: types.django_html = """
|
||||
|
@ -260,9 +255,6 @@ class SlotIterationTest(BaseTestCase):
|
|||
"objects": objects,
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
registry.clear()
|
||||
|
||||
# NOTE: Second arg in tuple is expected result. In isolated mode, loops should NOT leak.
|
||||
@parametrize_context_behavior(
|
||||
[
|
||||
|
@ -627,10 +619,6 @@ class ComponentNestingTests(BaseTestCase):
|
|||
registry.register("complex_child", self.ComplexChildComponent)
|
||||
registry.register("complex_parent", self.ComplexParentComponent)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
super().tearDown()
|
||||
registry.clear()
|
||||
|
||||
# NOTE: Second arg in tuple are expected names in nested fills. In "django" mode,
|
||||
# the value should be overridden by the component, while in "isolated" it should
|
||||
# remain top-level context.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue