refactor: Use top-level exports as public API (#562)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Juro Oravec 2024-08-03 08:30:39 +02:00 committed by GitHub
parent d819f3ff49
commit e771a0aaaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 615 additions and 598 deletions

191
README.md
View file

@ -1,4 +1,5 @@
# <img src="logo/logo-black-on-white.svg" alt="django-components" style="max-width: 100%; background: white; color: black;"> # <img src="logo/logo-black-on-white.svg" alt="django-components" style="max-width: 100%; background: white; color: black;">
<a href="https://github.com/EmilStenstrom/django-components/actions?query=workflow%3A%22Run+tests%22"><img align="right" src="https://github.com/EmilStenstrom/django-components/workflows/Run%20tests/badge.svg" alt="Show test status"></a> <a href="https://github.com/EmilStenstrom/django-components/actions?query=workflow%3A%22Run+tests%22"><img align="right" src="https://github.com/EmilStenstrom/django-components/workflows/Run%20tests/badge.svg" alt="Show test status"></a>
<a href="https://pepy.tech/project/django-components"><img align="right" src="https://pepy.tech/badge/django-components" alt="Show download stats"></a> <a href="https://pepy.tech/project/django-components"><img align="right" src="https://pepy.tech/badge/django-components" alt="Show download stats"></a>
@ -47,6 +48,7 @@ And this is what gets rendered (plus the CSS and Javascript you've specified):
## Release notes ## Release notes
🚨📢 **Version 0.85** Autodiscovery module resolution changed. Following undocumented behavior was removed: 🚨📢 **Version 0.85** Autodiscovery module resolution changed. Following undocumented behavior was removed:
- Previously, autodiscovery also imported any `[app]/components.py` files, and used `SETTINGS_MODULE` to search for component dirs. - Previously, autodiscovery also imported any `[app]/components.py` files, and used `SETTINGS_MODULE` to search for component dirs.
- To migrate from: - To migrate from:
- `[app]/components.py` - Define each module in `COMPONENTS.libraries` setting, - `[app]/components.py` - Define each module in `COMPONENTS.libraries` setting,
@ -64,8 +66,8 @@ And this is what gets rendered (plus the CSS and Javascript you've specified):
- BREAKING CHANGE: Default value for the `COMPONENTS.context_behavior` setting was changes from `"isolated"` to `"django"`. If you did not set this value explicitly before, this may be a breaking change. See the rationale for change [here](https://github.com/EmilStenstrom/django-components/issues/498). - BREAKING CHANGE: Default value for the `COMPONENTS.context_behavior` setting was changes from `"isolated"` to `"django"`. If you did not set this value explicitly before, this may be a breaking change. See the rationale for change [here](https://github.com/EmilStenstrom/django-components/issues/498).
🚨📢 **Version 0.77** CHANGED the syntax for accessing default slot content. 🚨📢 **Version 0.77** CHANGED the syntax for accessing default slot content.
- Previously, the syntax was - Previously, the syntax was
`{% fill "my_slot" as "alias" %}` and `{{ alias.default }}`. `{% fill "my_slot" as "alias" %}` and `{{ alias.default }}`.
- Now, the syntax is - Now, the syntax is
@ -88,7 +90,7 @@ This change is done to simplify the API in anticipation of a 1.0 release of djan
**Version 0.28** introduces 'implicit' slot filling and the `default` option for `slot` tags. **Version 0.28** introduces 'implicit' slot filling and the `default` option for `slot` tags.
**Version 0.27** adds a second installable app: *django_components.safer_staticfiles*. It provides the same behavior as *django.contrib.staticfiles* but with extra security guarantees (more info below in Security Notes). **Version 0.27** adds a second installable app: _django_components.safer_staticfiles_. It provides the same behavior as _django.contrib.staticfiles_ but with extra security guarantees (more info below in Security Notes).
**Version 0.26** changes the syntax for `{% slot %}` tags. From now on, we separate defining a slot (`{% slot %}`) from filling a slot with content (`{% fill %}`). This means you will likely need to change a lot of slot tags to fill. We understand this is annoying, but it's the only way we can get support for nested slots that fill in other slots, which is a very nice featuPpre to have access to. Hoping that this will feel worth it! **Version 0.26** changes the syntax for `{% slot %}` tags. From now on, we separate defining a slot (`{% slot %}`) from filling a slot with content (`{% fill %}`). This means you will likely need to change a lot of slot tags to fill. We understand this is annoying, but it's the only way we can get support for nested slots that fill in other slots, which is a very nice featuPpre to have access to. Hoping that this will feel worth it!
@ -98,7 +100,7 @@ This change is done to simplify the API in anticipation of a 1.0 release of djan
## Security notes 🚨 ## Security notes 🚨
*You are advised to read this section before using django-components in production.* _You are advised to read this section before using django-components in production._
### Static files ### Static files
@ -108,12 +110,12 @@ This means that files containing backend logic, such as Python modules and HTML
If your are using _django.contrib.staticfiles_ to collect static files, no distinction is made between the different kinds of files. If your are using _django.contrib.staticfiles_ to collect static files, no distinction is made between the different kinds of files.
As a result, your Python code and templates may inadvertently become available on your static file server. As a result, your Python code and templates may inadvertently become available on your static file server.
You probably don't want this, as parts of your backend logic will be exposed, posing a __potential security vulnerability__. You probably don't want this, as parts of your backend logic will be exposed, posing a **potential security vulnerability**.
As of *v0.27*, django-components ships with an additional installable app *django_components.__safer_staticfiles__*. As of _v0.27_, django-components ships with an additional installable app _django_components.**safer_staticfiles**_.
It is a drop-in replacement for *django.contrib.staticfiles*. It is a drop-in replacement for _django.contrib.staticfiles_.
Its behavior is 100% identical except it ignores .py and .html files, meaning these will not end up on your static files server. Its behavior is 100% identical except it ignores .py and .html files, meaning these will not end up on your static files server.
To use it, add it to INSTALLED_APPS and remove _django.contrib.staticfiles_. To use it, add it to INSTALLED*APPS and remove \_django.contrib.staticfiles*.
```python ```python
INSTALLED_APPS = [ INSTALLED_APPS = [
@ -163,7 +165,8 @@ For a step-by-step guide on deploying production server with static files,
``` ```
4. Modify `TEMPLATES` section of settings.py as follows: 4. Modify `TEMPLATES` section of settings.py as follows:
- *Remove `'APP_DIRS': True,`*
- _Remove `'APP_DIRS': True,`_
- Add `loaders` to `OPTIONS` list and set it to following value: - Add `loaders` to `OPTIONS` list and set it to following value:
```python ```python
@ -227,7 +230,7 @@ Read on to find out how to build your first component!
Django-components supports all supported combinations versions of [Django](https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django) and [Python](https://devguide.python.org/versions/#versions). Django-components supports all supported combinations versions of [Django](https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django) and [Python](https://devguide.python.org/versions/#versions).
| Python version | Django version | | Python version | Django version |
|----------------|--------------------------| | -------------- | -------------- |
| 3.8 | 4.2 | | 3.8 | 4.2 |
| 3.9 | 4.2 | | 3.9 | 4.2 |
| 3.10 | 4.2, 5.0 | | 3.10 | 4.2, 5.0 |
@ -293,10 +296,10 @@ Inside this file we create a Component by inheriting from the Component class an
```python ```python
# In a file called [project root]/components/calendar/calendar.py # In a file called [project root]/components/calendar/calendar.py
from django_components import component from django_components import Component, register
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
# Templates inside `[your apps]/components` dir and `[project root]/components` dir # Templates inside `[your apps]/components` dir and `[project root]/components` dir
# will be automatically found. To customize which template to use based on context # will be automatically found. To customize which template to use based on context
# you can override method `get_template_name` instead of specifying `template_name`. # you can override method `get_template_name` instead of specifying `template_name`.
@ -324,26 +327,25 @@ Components can also be defined in a single file, which is useful for small compo
```python ```python
# In a file called [project root]/components/calendar.py # In a file called [project root]/components/calendar.py
from django_components import component from django_components import Component, register, types
from django_components import types as t
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
def get_context_data(self, date): def get_context_data(self, date):
return { return {
"date": date, "date": date,
} }
template: t.django_html = """ template: types.django_html = """
<div class="calendar-component">Today's date is <span>{{ date }}</span></div> <div class="calendar-component">Today's date is <span>{{ date }}</span></div>
""" """
css: t.css = """ css: types.css = """
.calendar-component { width: 200px; background: pink; } .calendar-component { width: 200px; background: pink; }
.calendar-component span { font-weight: bold; } .calendar-component span { font-weight: bold; }
""" """
js: t.js = """ js: types.js = """
(function(){ (function(){
if (document.querySelector(".calendar-component")) { if (document.querySelector(".calendar-component")) {
document.querySelector(".calendar-component").onclick = function(){ alert("Clicked calendar!"); }; document.querySelector(".calendar-component").onclick = function(){ alert("Clicked calendar!"); };
@ -365,10 +367,10 @@ Note, in the above example, that the `t.django_html`, `t.css`, and `t.js` types
If you're a Pycharm user (or any other editor from Jetbrains), you can have coding assistance as well: If you're a Pycharm user (or any other editor from Jetbrains), you can have coding assistance as well:
```python ```python
from django_components import component from django_components import Component, register
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
def get_context_data(self, date): def get_context_data(self, date):
return { return {
"date": date, "date": date,
@ -395,7 +397,7 @@ class Calendar(component.Component):
""" """
``` ```
You don't need to use `t.django_html`, `t.css`, `t.js` and `from django_components import types as t` since Pycharm uses [language injections](https://www.jetbrains.com/help/pycharm/using-language-injections.html). You don't need to use `types.django_html`, `types.css`, `types.js` since Pycharm uses [language injections](https://www.jetbrains.com/help/pycharm/using-language-injections.html).
You only need to write the comments `# language=<lang>` above the variables. You only need to write the comments `# language=<lang>` above the variables.
## Use components in templates ## Use components in templates
@ -452,7 +454,7 @@ Components can be rendered outside of Django templates, calling them as regular
The component class defines `render` and `render_to_response` class methods. These methods accept positional args, kwargs, and slots, offering the same flexibility as the `{% component %}` tag: The component class defines `render` and `render_to_response` class methods. These methods accept positional args, kwargs, and slots, offering the same flexibility as the `{% component %}` tag:
```py ```py
class SimpleComponent(component.Component): class SimpleComponent(Component):
template = """ template = """
{% load component_tags %} {% load component_tags %}
hello: {{ hello }} hello: {{ hello }}
@ -537,6 +539,7 @@ def render_func(
- NOTE: The slot is lazily evaluated. To render the slot, convert it to string with `str(slot_ref)`. - NOTE: The slot is lazily evaluated. To render the slot, convert it to string with `str(slot_ref)`.
Example: Example:
```py ```py
def footer_slot(ctx, data, slot_ref): def footer_slot(ctx, data, slot_ref):
return f""" return f"""
@ -565,7 +568,7 @@ class MyResponse(HttpResponse):
self.headers = ... self.headers = ...
self.status = ... self.status = ...
class SimpleComponent(component.Component): class SimpleComponent(Component):
response_class = MyResponse response_class = MyResponse
template: types.django_html = "HELLO" template: types.django_html = "HELLO"
@ -585,10 +588,10 @@ Here's an example of a calendar component defined as a view:
```python ```python
# In a file called [project root]/components/calendar.py # In a file called [project root]/components/calendar.py
from django_components import component from django_components import Component, register
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
template = """ template = """
<div class="calendar-component"> <div class="calendar-component">
@ -642,13 +645,13 @@ If you're planning on passing an HTML string, check Django's use of [`format_htm
## Autodiscovery ## 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 `@component.register` decorator for that: 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:
```py ```py
from django_components import component from django_components import Component, register
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
... ...
``` ```
@ -679,7 +682,7 @@ Autodiscovery occurs when Django is loaded, during the `ready` hook of the `apps
If you are using autodiscovery, keep a few points in mind: If you are using autodiscovery, keep a few points in mind:
- Avoid defining any logic on the module-level inside the `components` dir, that you would not want to run anyway. - Avoid defining any logic on the module-level inside the `components` dir, that you would not want to run anyway.
- Components inside the auto-imported files still need to be registered with `@component.register()` - Components inside the auto-imported files still need to be registered with `@register()`
- Auto-imported component files must be valid Python modules, they must use suffix `.py`, and module name should follow [PEP-8](https://peps.python.org/pep-0008/#package-and-module-names). - Auto-imported component files must be valid Python modules, they must use suffix `.py`, and module name should follow [PEP-8](https://peps.python.org/pep-0008/#package-and-module-names).
Autodiscovery can be disabled in the [settings](#disable-autodiscovery). Autodiscovery can be disabled in the [settings](#disable-autodiscovery).
@ -784,12 +787,8 @@ The rendered result (exactly the same as before):
```html ```html
<div class="calendar-component"> <div class="calendar-component">
<div class="header"> <div class="header">Calendar header</div>
Calendar header <div class="body">Can you believe it's already <span>2020-06-06</span>??</div>
</div>
<div class="body">
Can you believe it's already <span>2020-06-06</span>??
</div>
</div> </div>
``` ```
@ -897,10 +896,13 @@ Which you can then use are regular default slot:
_Added in version 0.26_ _Added in version 0.26_
> NOTE: In version 0.77, the syntax was changed from > NOTE: In version 0.77, the syntax was changed from
>
> ```django > ```django
> {% fill "my_slot" as "alias" %} {{ alias.default }} > {% fill "my_slot" as "alias" %} {{ alias.default }}
> ``` > ```
>
> to > to
>
> ```django > ```django
> {% fill "my_slot" default="slot_default" %} {{ slot_default }} > {% fill "my_slot" default="slot_default" %} {{ slot_default }}
> ``` > ```
@ -959,11 +961,8 @@ explicit fills, the div containing the slot is still rendered, as shown below:
```html ```html
<div class="frontmatter-component"> <div class="frontmatter-component">
<div class="title"> <div class="title">Title</div>
Title <div class="subtitle"></div>
</div>
<div class="subtitle">
</div>
</div> </div>
``` ```
@ -1033,8 +1032,8 @@ _Added in version 0.76_:
Consider a component with slot(s). This component may do some processing on the inputs, and then use the processed variable in the slot's default template: Consider a component with slot(s). This component may do some processing on the inputs, and then use the processed variable in the slot's default template:
```py ```py
@component.register("my_comp") @register("my_comp")
class MyComp(component.Component): class MyComp(Component):
template = """ template = """
<div> <div>
{% slot "content" default %} {% slot "content" default %}
@ -1062,8 +1061,8 @@ Using scoped slots consists of two steps:
To pass the data to the `slot` tag, simply pass them as keyword attributes (`key=value`): To pass the data to the `slot` tag, simply pass them as keyword attributes (`key=value`):
```py ```py
@component.register("my_comp") @register("my_comp")
class MyComp(component.Component): class MyComp(Component):
template = """ template = """
<div> <div>
{% slot "content" default input=input %} {% slot "content" default input=input %}
@ -1153,8 +1152,8 @@ so are still valid:
These can then be accessed inside `get_context_data` so: These can then be accessed inside `get_context_data` so:
```py ```py
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
# Since # . @ - are not valid identifiers, we have to # Since # . @ - are not valid identifiers, we have to
# use `**kwargs` so the method can accept these args. # use `**kwargs` so the method can accept these args.
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -1178,8 +1177,8 @@ In such cases, we may want to define some HTML attributes statically, and other
But for that, we need to define this dictionary on Python side: But for that, we need to define this dictionary on Python side:
```py ```py
@component.register("my_comp") @register("my_comp")
class MyComp(component.Component): class MyComp(Component):
template = """ template = """
{% component "other" attrs=attrs %} {% component "other" attrs=attrs %}
{% endcomponent %} {% endcomponent %}
@ -1206,8 +1205,8 @@ we prefix the key with the name of the dict and `:`. So key `class` of input `at
`attrs:class`. And our example becomes: `attrs:class`. And our example becomes:
```py ```py
@component.register("my_comp") @register("my_comp")
class MyComp(component.Component): class MyComp(Component):
template = """ template = """
{% component "other" {% component "other"
attrs:class="pa-4 flex" attrs:class="pa-4 flex"
@ -1296,8 +1295,7 @@ And template:
Then this renders: Then this renders:
```html ```html
<div class="text-green"> <div class="text-green"></div>
</div>
``` ```
### Boolean attributes ### Boolean attributes
@ -1305,8 +1303,7 @@ Then this renders:
In HTML, boolean attributes are usually rendered with no value. Consider the example below where the first button is disabled and the second is not: In HTML, boolean attributes are usually rendered with no value. Consider the example below where the first button is disabled and the second is not:
```html ```html
<button disabled> Click me! </button> <button disabled>Click me!</button> <button>Click me!</button>
<button> Click me! </button>
``` ```
HTML rendering with `html_attrs` tag or `attributes_to_string` works the same way, where `key=True` is rendered simply as `key`, and `key=False` is not render at all. HTML rendering with `html_attrs` tag or `attributes_to_string` works the same way, where `key=True` is rendered simply as `key`, and `key=False` is not render at all.
@ -1330,8 +1327,7 @@ And template:
Then this renders: Then this renders:
```html ```html
<div disabled> <div disabled></div>
</div>
``` ```
### Default attributes ### Default attributes
@ -1380,8 +1376,7 @@ And on `html_attrs` tag, we set the key `class`:
Then these will be merged and rendered as: Then these will be merged and rendered as:
```html ```html
<div data-value="my-class pa-4 some-class"> <div data-value="my-class pa-4 some-class"></div>
</div>
``` ```
To simplify merging of variables, you can supply the same key multiple times, and these will be all joined together: To simplify merging of variables, you can supply the same key multiple times, and these will be all joined together:
@ -1499,8 +1494,8 @@ Then:
### Full example for `html_attrs` ### Full example for `html_attrs`
```py ```py
@component.register("my_comp") @register("my_comp")
class MyComp(component.Component): class MyComp(Component):
template: t.django_html = """ template: t.django_html = """
<div <div
{% html_attrs attrs {% html_attrs attrs
@ -1521,8 +1516,8 @@ class MyComp(component.Component):
"class_from_var": "extra-class" "class_from_var": "extra-class"
} }
@component.register("parent") @register("parent")
class Parent(component.Component): class Parent(Component):
template: t.django_html = """ template: t.django_html = """
{% component "my_comp" {% component "my_comp"
date=date date=date
@ -1555,8 +1550,7 @@ will be appended to it.
So by default, `MyComp` renders: So by default, `MyComp` renders:
```html ```html
<div class="pa-4 text-red my-comp-date extra-class" data-id="123"> <div class="pa-4 text-red my-comp-date extra-class" data-id="123">...</div>
...
``` ```
Next, let's consider what will be rendered when we call `MyComp` from `Parent` Next, let's consider what will be rendered when we call `MyComp` from `Parent`
@ -1626,6 +1620,7 @@ attributes_to_string(attrs)
_New in version 0.80_: _New in version 0.80_:
Django components supports dependency injection with the combination of: Django components supports dependency injection with the combination of:
1. `{% provide %}` tag 1. `{% provide %}` tag
1. `inject()` method of the `Component` class 1. `inject()` method of the `Component` class
@ -1646,6 +1641,7 @@ This feature is inspired by Vue's [Provide / Inject](https://vuejs.org/guide/com
### How to use provide / inject ### How to use provide / inject
As the name suggest, using provide / inject consists of 2 steps As the name suggest, using provide / inject consists of 2 steps
1. Providing data 1. Providing data
2. Injecting provided data 2. Injecting provided data
@ -1668,6 +1664,7 @@ First we use the `{% provide %}` tag to define the data we want to "provide" (ma
Notice that the `provide` tag REQUIRES a name as a first argument. This is the _key_ by which we can then access the data passed to this tag. Notice that the `provide` tag REQUIRES a name as a first argument. This is the _key_ by which we can then access the data passed to this tag.
`provide` tag _key_, similarly to the _name_ argument in `component` or `slot` tags, has these requirements: `provide` tag _key_, similarly to the _name_ argument in `component` or `slot` tags, has these requirements:
- The _key_ must be a string literal - The _key_ must be a string literal
- It must be a valid identifier (AKA a valid Python variable name) - It must be a valid identifier (AKA a valid Python variable name)
@ -1675,6 +1672,7 @@ Once you've set the name, you define the data you want to "provide" by passing i
> NOTE: Kwargs passed to `{% provide %}` are NOT added to the context. > NOTE: Kwargs passed to `{% provide %}` are NOT added to the context.
> In the example below, the `{{ key }}` won't render anything: > In the example below, the `{{ key }}` won't render anything:
>
> ```django > ```django
> {% provide "my_data" key="hi" another=123 %} > {% provide "my_data" key="hi" another=123 %}
> {{ key }} > {{ key }}
@ -1690,7 +1688,7 @@ For a component to be able to "inject" some data, the component (`{% component %
In the example from previous section, we've defined two kwargs: `key="hi" another=123`. That means that if we now inject `"my_data"`, we get an object with 2 attributes - `key` and `another`. In the example from previous section, we've defined two kwargs: `key="hi" another=123`. That means that if we now inject `"my_data"`, we get an object with 2 attributes - `key` and `another`.
```py ```py
class ChildComponent(component.Component): class ChildComponent(Component):
def get_context_data(self): def get_context_data(self):
my_data = self.inject("my_data") my_data = self.inject("my_data")
print(my_data.key) # hi print(my_data.key) # hi
@ -1705,7 +1703,7 @@ with given key is found, `inject` raises a `KeyError`.
To avoid the error, you can pass a second argument to `inject` to which will act as a default value, similar to `dict.get(key, default)`: To avoid the error, you can pass a second argument to `inject` to which will act as a default value, similar to `dict.get(key, default)`:
```py ```py
class ChildComponent(component.Component): class ChildComponent(Component):
def get_context_data(self): def get_context_data(self):
my_data = self.inject("invalid_key", DEFAULT_DATA) my_data = self.inject("invalid_key", DEFAULT_DATA)
assert my_data == DEFAUKT_DATA assert my_data == DEFAUKT_DATA
@ -1717,12 +1715,11 @@ have all the keys that were passed to the `provide` tag.
> NOTE: `inject()` works strictly only in `get_context_data`. If you try to call it from elsewhere, it will raise an error. > NOTE: `inject()` works strictly only in `get_context_data`. If you try to call it from elsewhere, it will raise an error.
### Full example ### Full example
```py ```py
@component.register("child") @register("child")
class ChildComponent(component.Component): class ChildComponent(Component):
template = """ template = """
<div> {{ my_data.key }} </div> <div> {{ my_data.key }} </div>
<div> {{ my_data.another }} </div> <div> {{ my_data.another }} </div>
@ -1771,6 +1768,7 @@ Components can also access the outer context in their context methods like `get_
django_component's management of files builds on top of [Django's `Media` class](https://docs.djangoproject.com/en/5.0/topics/forms/media/). django_component's management of files builds on top of [Django's `Media` class](https://docs.djangoproject.com/en/5.0/topics/forms/media/).
To be familiar with how Django handles static files, we recommend reading also: To be familiar with how Django handles static files, we recommend reading also:
- [How to manage static files (e.g. images, JavaScript, CSS)](https://docs.djangoproject.com/en/5.0/howto/static-files/) - [How to manage static files (e.g. images, JavaScript, CSS)](https://docs.djangoproject.com/en/5.0/howto/static-files/)
### Defining file paths relative to component or static dirs ### Defining file paths relative to component or static dirs
@ -1780,10 +1778,10 @@ files with a component, you set them as `template_name`, `Media.js` and `Media.c
```py ```py
# In a file [project root]/components/calendar/calendar.py # In a file [project root]/components/calendar/calendar.py
from django_components import component from django_components import Component, register
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
template_name = "template.html" template_name = "template.html"
class Media: class Media:
@ -1799,10 +1797,10 @@ Assuming that `STATICFILES_DIRS` contains path `[project root]/components`, we c
```py ```py
# In a file [project root]/components/calendar/calendar.py # In a file [project root]/components/calendar/calendar.py
from django_components import component from django_components import Component, register
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
template_name = "calendar/template.html" template_name = "calendar/template.html"
class Media: class Media:
@ -1817,7 +1815,7 @@ NOTE: In case of conflict, the preference goes to resolving the files relative t
Each component can have only a single template. However, you can define as many JS or CSS files as you want using a list. Each component can have only a single template. However, you can define as many JS or CSS files as you want using a list.
```py ```py
class MyComponent(component.Component): class MyComponent(Component):
class Media: class Media:
js = ["path/to/script1.js", "path/to/script2.js"] js = ["path/to/script1.js", "path/to/script2.js"]
css = ["path/to/style1.css", "path/to/style2.css"] css = ["path/to/style1.css", "path/to/style2.css"]
@ -1833,7 +1831,7 @@ See the corresponding [Django Documentation](https://docs.djangoproject.com/en/5
Again, you can set either a single file or a list of files per media type: Again, you can set either a single file or a list of files per media type:
```py ```py
class MyComponent(component.Component): class MyComponent(Component):
class Media: class Media:
css = { css = {
"all": "path/to/style1.css", "all": "path/to/style1.css",
@ -1842,7 +1840,7 @@ class MyComponent(component.Component):
``` ```
```py ```py
class MyComponent(component.Component): class MyComponent(Component):
class Media: class Media:
css = { css = {
"all": ["path/to/style1.css", "path/to/style2.css"], "all": ["path/to/style1.css", "path/to/style2.css"],
@ -1852,10 +1850,10 @@ class MyComponent(component.Component):
NOTE: When you define CSS as a string or a list, the `all` media type is implied. NOTE: When you define CSS as a string or a list, the `all` media type is implied.
### Supported types for file paths ### Supported types for file paths
File paths can be any of: File paths can be any of:
- `str` - `str`
- `bytes` - `bytes`
- `PathLike` (`__fspath__` method) - `PathLike` (`__fspath__` method)
@ -1867,7 +1865,7 @@ from pathlib import Path
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
class SimpleComponent(component.Component): class SimpleComponent(Component):
class Media: class Media:
css = [ css = [
mark_safe('<link href="/static/calendar/style.css" rel="stylesheet" />'), mark_safe('<link href="/static/calendar/style.css" rel="stylesheet" />'),
@ -1906,8 +1904,8 @@ class LazyJsPath:
f'<script type="module" src="{full_path}"></script>' f'<script type="module" src="{full_path}"></script>'
) )
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
template_name = "calendar/template.html" template_name = "calendar/template.html"
def get_context_data(self, date): def get_context_data(self, date):
@ -1935,7 +1933,7 @@ To change how the tags are constructed, you can override the [`Media.render_js`
```py ```py
from django.forms.widgets import Media from django.forms.widgets import Media
from django_components import component from django_components import Component, register
class MyMedia(Media): class MyMedia(Media):
# Same as original Media.render_js, except # Same as original Media.render_js, except
@ -1952,8 +1950,8 @@ class MyMedia(Media):
) )
return tags return tags
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
template_name = "calendar/template.html" template_name = "calendar/template.html"
class Media: class Media:
@ -1970,6 +1968,7 @@ NOTE: The instance of the `Media` class (or it's subclass) is available under `C
The JS and CSS files included in components are not automatically rendered. The JS and CSS files included in components are not automatically rendered.
Instead, use the following tags to specify where to render the dependencies: Instead, use the following tags to specify where to render the dependencies:
- `component_dependencies` - Renders both JS and CSS - `component_dependencies` - Renders both JS and CSS
- `component_js_dependencies` - Renders only JS - `component_js_dependencies` - Renders only JS
- `component_css_dependencies` - Reneders only CSS - `component_css_dependencies` - Reneders only CSS
@ -2021,16 +2020,16 @@ COMPONENTS = {
Where `mysite/components/forms.py` may look like this: Where `mysite/components/forms.py` may look like this:
```py ```py
@component.register("form_simple") @register("form_simple")
class FormSimple(component.Component): class FormSimple(Component):
template = """ template = """
<form> <form>
... ...
</form> </form>
""" """
@component.register("form_other") @register("form_other")
class FormOther(component.Component): class FormOther(Component):
template = """ template = """
<form> <form>
... ...
@ -2081,12 +2080,14 @@ This has two modes:
- `"django"` - Default - The default Django template behavior. - `"django"` - Default - The default Django template behavior.
Inside the `{% fill %}` tag, the context variables you can access are a union of: Inside the `{% fill %}` tag, the context variables you can access are a union of:
- All the variables that were OUTSIDE the fill tag, including any loops or with tag - All the variables that were OUTSIDE the fill tag, including any loops or with tag
- Data returned from `get_context_data()` of the component that wraps the fill tag. - Data returned from `get_context_data()` of the component that wraps the fill tag.
- `"isolated"` - Similar behavior to [Vue](https://vuejs.org/guide/components/slots.html#render-scope) or React, this is useful if you want to make sure that components don't accidentally access variables defined outside of the component. - `"isolated"` - Similar behavior to [Vue](https://vuejs.org/guide/components/slots.html#render-scope) or React, this is useful if you want to make sure that components don't accidentally access variables defined outside of the component.
Inside the `{% fill %}` tag, you can ONLY access variables from 2 places: Inside the `{% fill %}` tag, you can ONLY access variables from 2 places:
- `get_context_data()` of the component which defined the template (AKA the "root" component) - `get_context_data()` of the component which defined the template (AKA the "root" component)
- Any loops (`{% for ... %}`) that the `{% fill %}` tag is part of. - Any loops (`{% for ... %}`) that the `{% fill %}` tag is part of.
@ -2101,7 +2102,7 @@ COMPONENTS = {
Given this template: Given this template:
```py ```py
class RootComp(component.Component): class RootComp(Component):
template = """ template = """
{% with cheese="feta" %} {% with cheese="feta" %}
{% component 'my_comp' %} {% component 'my_comp' %}
@ -2138,7 +2139,7 @@ all the data defined in the outer layers, like the `{% with %}` tag.
Given this template: Given this template:
```py ```py
class RootComp(component.Component): class RootComp(Component):
template = """ template = """
{% with cheese="feta" %} {% with cheese="feta" %}
{% component 'my_comp' %} {% component 'my_comp' %}

View file

@ -3,13 +3,13 @@ from time import perf_counter
from django.template import Context, Template from django.template import Context, Template
from django.test import override_settings from django.test import override_settings
from django_components import component, types from django_components import Component, registry, types
from django_components.middleware import CSS_DEPENDENCY_PLACEHOLDER, JS_DEPENDENCY_PLACEHOLDER from django_components.middleware import CSS_DEPENDENCY_PLACEHOLDER, JS_DEPENDENCY_PLACEHOLDER
from tests.django_test_setup import * # NOQA from tests.django_test_setup import * # NOQA
from tests.testutils import BaseTestCase, create_and_process_template_response from tests.testutils import BaseTestCase, create_and_process_template_response
class SlottedComponent(component.Component): class SlottedComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<custom-template> <custom-template>
@ -20,7 +20,7 @@ class SlottedComponent(component.Component):
""" """
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -36,7 +36,7 @@ class SimpleComponent(component.Component):
js = ["script.js"] js = ["script.js"]
class BreadcrumbComponent(component.Component): class BreadcrumbComponent(Component):
template_name = "mdn_component_template.html" template_name = "mdn_component_template.html"
LINKS = [ LINKS = [
@ -77,10 +77,10 @@ EXPECTED_JS = """<script src="test.js"></script>"""
@override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True}) @override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True})
class RenderBenchmarks(BaseTestCase): class RenderBenchmarks(BaseTestCase):
def setUp(self): def setUp(self):
component.registry.clear() registry.clear()
component.registry.register("test_component", SlottedComponent) registry.register("test_component", SlottedComponent)
component.registry.register("inner_component", SimpleComponent) registry.register("inner_component", SimpleComponent)
component.registry.register("breadcrumb_component", BreadcrumbComponent) registry.register("breadcrumb_component", BreadcrumbComponent)
@staticmethod @staticmethod
def timed_loop(func, iterations=1000): def timed_loop(func, iterations=1000):

View file

@ -15,12 +15,14 @@
And components that make use of `abc.html` via `include` or `extends`: And components that make use of `abc.html` via `include` or `extends`:
```py ```py
@component.register("my_comp_extends") from django_components import Component, register
class MyCompWithExtends(component.Component):
@register("my_comp_extends")
class MyCompWithExtends(Component):
template = """{% extends "abc.html" %}""" template = """{% extends "abc.html" %}"""
@component.register("my_comp_include") @register("my_comp_include")
class MyCompWithInclude(component.Component): class MyCompWithInclude(Component):
template = """{% include "abc.html" %}""" template = """{% include "abc.html" %}"""
``` ```
@ -61,8 +63,8 @@
and component `my_comp`: and component `my_comp`:
```py ```py
@component.register("my_comp") @register("my_comp")
class MyComp(component.Component): class MyComp(Component):
template_name = "abc.html" template_name = "abc.html"
``` ```
@ -108,8 +110,8 @@
`abc.html` will render `OVERRIDEN`: `abc.html` will render `OVERRIDEN`:
````py ````py
@component.register("my_comp") @register("my_comp")
class MyComp(component.Component): class MyComp(Component):
template_name = """ template_name = """
{% extends "abc.html" %} {% extends "abc.html" %}
@ -125,8 +127,8 @@
new `slots` inside these "overriding" blocks: new `slots` inside these "overriding" blocks:
```py ```py
@component.register("my_comp") @register("my_comp")
class MyComp(component.Component): class MyComp(Component):
template_name = """ template_name = """
{% extends "abc.html" %} {% extends "abc.html" %}

View file

@ -1,8 +1,8 @@
from django_components import component from django_components import Component, register
@component.register("calendar") @register("calendar")
class Calendar(component.Component): class Calendar(Component):
# Note that Django will look for templates inside `[your apps]/components` dir and # Note that Django will look for templates inside `[your apps]/components` dir and
# `[project root]/components` dir. To customize which template to use based on context # `[project root]/components` dir. To customize which template to use based on context
# you can override def get_template_name() instead of specifying the below variable. # you can override def get_template_name() instead of specifying the below variable.
@ -25,8 +25,8 @@ class Calendar(component.Component):
js = "calendar/calendar.js" js = "calendar/calendar.js"
@component.register("calendar_relative") @register("calendar_relative")
class CalendarRelative(component.Component): class CalendarRelative(Component):
# Note that Django will look for templates inside `[your apps]/components` dir and # Note that Django will look for templates inside `[your apps]/components` dir and
# `[project root]/components` dir. To customize which template to use based on context # `[project root]/components` dir. To customize which template to use based on context
# you can override def get_template_name() instead of specifying the below variable. # you can override def get_template_name() instead of specifying the below variable.

View file

@ -1,11 +1,10 @@
from typing import Any, Dict from typing import Any, Dict
from django_components import component from django_components import Component, register, types
from django_components import types as t
@component.register("greeting") @register("greeting")
class Greeting(component.Component): class Greeting(Component):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
slots = {"message": "Hello, world!"} slots = {"message": "Hello, world!"}
context = {"name": request.GET.get("name", "")} context = {"name": request.GET.get("name", "")}
@ -14,12 +13,12 @@ class Greeting(component.Component):
def get_context_data(self, name, *args, **kwargs) -> Dict[str, Any]: def get_context_data(self, name, *args, **kwargs) -> Dict[str, Any]:
return {"name": name} return {"name": name}
template: t.django_html = """ template: types.django_html = """
<div id="greeting">Hello, {{ name }}!</div> <div id="greeting">Hello, {{ name }}!</div>
{% slot "message" %}{% endslot %} {% slot "message" %}{% endslot %}
""" """
css: t.css = """ css: types.css = """
#greeting { #greeting {
display: inline-block; display: inline-block;
color: blue; color: blue;
@ -27,7 +26,7 @@ class Greeting(component.Component):
} }
""" """
js: t.js = """ js: types.js = """
document.getElementById("greeting").addEventListener("click", (event) => { document.getElementById("greeting").addEventListener("click", (event) => {
alert("Hello!"); alert("Hello!");
}); });

View file

@ -1,8 +1,8 @@
from django_components import component from django_components import Component, register
@component.register("calendar_nested") @register("calendar_nested")
class CalendarNested(component.Component): class CalendarNested(Component):
# Note that Django will look for templates inside `[your apps]/components` dir and # Note that Django will look for templates inside `[your apps]/components` dir and
# `[project root]/components` dir. To customize which template to use based on context # `[project root]/components` dir. To customize which template to use based on context
# you can override def get_template_name() instead of specifying the below variable. # you can override def get_template_name() instead of specifying the below variable.

View file

@ -1,8 +1,8 @@
from django_components import component from django_components import Component, register
@component.register("todo") @register("todo")
class Calendar(component.Component): class Calendar(Component):
# Note that Django will look for templates inside `[your apps]/components` dir and # Note that Django will look for templates inside `[your apps]/components` dir and
# `[project root]/components` dir. To customize which template to use based on context # `[project root]/components` dir. To customize which template to use based on context
# you can override def get_template_name() instead of specifying the below variable. # you can override def get_template_name() instead of specifying the below variable.

View file

@ -1,7 +1,19 @@
# flake8: noqa F401
import django import django
from django_components.autodiscover import autodiscover as autodiscover # NOQA # Public API
from django_components.autodiscover import import_libraries as import_libraries # NOQA # isort: off
from django_components.autodiscover import autodiscover as autodiscover
from django_components.autodiscover import import_libraries as import_libraries
from django_components.component import Component as Component
from django_components.component_registry import AlreadyRegistered as AlreadyRegistered
from django_components.component_registry import ComponentRegistry as ComponentRegistry
from django_components.component_registry import NotRegistered as NotRegistered
from django_components.component_registry import register as register
from django_components.component_registry import registry as registry
import django_components.types as types
# isort: on
if django.VERSION < (3, 2): if django.VERSION < (3, 2):
default_app_config = "django_components.apps.ComponentsConfig" default_app_config = "django_components.apps.ComponentsConfig"

View file

@ -15,16 +15,7 @@ from django.utils.safestring import SafeString, mark_safe
from django.views import View from django.views import View
from django_components.component_media import ComponentMediaInput, MediaMeta from django_components.component_media import ComponentMediaInput, MediaMeta
from django_components.component_registry import registry
# Global registry var and register() function moved to separate module.
# Defining them here made little sense, since 1) component_tags.py and component.py
# rely on them equally, and 2) it made it difficult to avoid circularity in the
# way the two modules depend on one another.
from django_components.component_registry import AlreadyRegistered as AlreadyRegistered # NOQA
from django_components.component_registry import ComponentRegistry as ComponentRegistry # NOQA
from django_components.component_registry import NotRegistered as NotRegistered # NOQA
from django_components.component_registry import register as register # NOQA
from django_components.component_registry import registry # NOQA
from django_components.context import ( from django_components.context import (
_FILLED_SLOTS_CONTENT_CONTEXT_KEY, _FILLED_SLOTS_CONTENT_CONTEXT_KEY,
_PARENT_COMP_CONTEXT_KEY, _PARENT_COMP_CONTEXT_KEY,
@ -50,6 +41,16 @@ from django_components.slots import (
from django_components.template_parser import process_aggregate_kwargs from django_components.template_parser import process_aggregate_kwargs
from django_components.utils import gen_id from django_components.utils import gen_id
# TODO_DEPRECATE_V1 - REMOVE IN V1, users should use top-level import instead
# isort: off
from django_components.component_registry import AlreadyRegistered as AlreadyRegistered # NOQA
from django_components.component_registry import ComponentRegistry as ComponentRegistry # NOQA
from django_components.component_registry import NotRegistered as NotRegistered # NOQA
from django_components.component_registry import register as register # NOQA
from django_components.component_registry import registry as registry # NOQA
# isort: on
RENDERED_COMMENT_TEMPLATE = "<!-- _RENDERED {name} -->" RENDERED_COMMENT_TEMPLATE = "<!-- _RENDERED {name} -->"
@ -202,8 +203,10 @@ class Component(View, metaclass=ComponentMeta):
And given this definition of "my_comp" component: And given this definition of "my_comp" component:
```py ```py
@component.register("my_comp") from django_components import Component, register
class MyComp(component.Component):
@register("my_comp")
class MyComp(Component):
template = "hi {{ data.hello }}!" template = "hi {{ data.hello }}!"
def get_context_data(self): def get_context_data(self):
data = self.inject("provider") data = self.inject("provider")

View file

@ -34,7 +34,7 @@ class MediaMeta(MediaDefiningClass):
1. As plain strings 1. As plain strings
```py ```py
class MyComponent(component.Component): class MyComponent(Component):
class Media: class Media:
js = "path/to/script.js" js = "path/to/script.js"
css = "path/to/style.css" css = "path/to/style.css"
@ -42,7 +42,7 @@ class MediaMeta(MediaDefiningClass):
2. As lists 2. As lists
```py ```py
class MyComponent(component.Component): class MyComponent(Component):
class Media: class Media:
js = ["path/to/script1.js", "path/to/script2.js"] js = ["path/to/script1.js", "path/to/script2.js"]
css = ["path/to/style1.css", "path/to/style2.css"] css = ["path/to/style1.css", "path/to/style2.css"]
@ -50,7 +50,7 @@ class MediaMeta(MediaDefiningClass):
3. [CSS ONLY] Dicts of strings 3. [CSS ONLY] Dicts of strings
```py ```py
class MyComponent(component.Component): class MyComponent(Component):
class Media: class Media:
css = { css = {
"all": "path/to/style1.css", "all": "path/to/style1.css",
@ -60,7 +60,7 @@ class MediaMeta(MediaDefiningClass):
4. [CSS ONLY] Dicts of lists 4. [CSS ONLY] Dicts of lists
```py ```py
class MyComponent(component.Component): class MyComponent(Component):
class Media: class Media:
css = { css = {
"all": ["path/to/style1.css"], "all": ["path/to/style1.css"],
@ -74,7 +74,7 @@ class MediaMeta(MediaDefiningClass):
and `my_comp.py` looks like this: and `my_comp.py` looks like this:
```py ```py
class MyComponent(component.Component): class MyComponent(Component):
class Media: class Media:
js = "script.js" js = "script.js"
``` ```
@ -90,7 +90,7 @@ class MediaMeta(MediaDefiningClass):
# do something # do something
return path return path
class MyComponent(component.Component): class MyComponent(Component):
class Media: class Media:
js = b"script.js" js = b"script.js"
css = lazy_eval_css css = lazy_eval_css
@ -106,7 +106,7 @@ class MediaMeta(MediaDefiningClass):
def render_js(self): def render_js(self):
... ...
class MyComponent(component.Component): class MyComponent(Component):
media_class = MyMedia media_class = MyMedia
def get_context_data(self): def get_context_data(self):
assert isinstance(self.media, MyMedia) assert isinstance(self.media, MyMedia)

View file

@ -125,10 +125,10 @@ class Command(BaseCommand):
with open(os.path.join(component_path, f"{name}.py"), "w") as f: with open(os.path.join(component_path, f"{name}.py"), "w") as f:
py_content = dedent( py_content = dedent(
f""" f"""
from django_components import component from django_components import Component, register
@component.register("{name}") @register("{name}")
class {name.capitalize()}(component.Component): class {name.capitalize()}(Component):
template_name = "{name}/{template_filename}" template_name = "{name}/{template_filename}"
def get_context_data(self, value): def get_context_data(self, value):

View file

@ -2,11 +2,11 @@ from typing import Any, Dict
from django.http import HttpResponse from django.http import HttpResponse
from django_components import component from django_components import Component, register
@component.register("multi_file_component") @register("multi_file_component")
class MultFileComponent(component.Component): class MultFileComponent(Component):
template_name = "multi_file/multi_file.html" template_name = "multi_file/multi_file.html"
def post(self, request, *args, **kwargs) -> HttpResponse: def post(self, request, *args, **kwargs) -> HttpResponse:

View file

@ -2,11 +2,11 @@ from typing import Any, Dict
from django.http import HttpResponse from django.http import HttpResponse
from django_components import component from django_components import Component, register
@component.register("relative_file_component") @register("relative_file_component")
class RelativeFileComponent(component.Component): class RelativeFileComponent(Component):
template_name = "relative_file.html" template_name = "relative_file.html"
class Media: class Media:

View file

@ -3,7 +3,7 @@ from typing import Any, Dict
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.html import format_html, html_safe from django.utils.html import format_html, html_safe
from django_components import component from django_components import Component, register
# Format as mentioned in https://github.com/EmilStenstrom/django-components/issues/522#issuecomment-2173577094 # Format as mentioned in https://github.com/EmilStenstrom/django-components/issues/522#issuecomment-2173577094
@ -21,8 +21,8 @@ class PathObj:
return format_html('<script type="module" src="{}"></script>', static(self.static_path)) return format_html('<script type="module" src="{}"></script>', static(self.static_path))
@component.register("relative_file_pathobj_component") @register("relative_file_pathobj_component")
class RelativeFileWithPathObjComponent(component.Component): class RelativeFileWithPathObjComponent(Component):
template_name = "relative_file_pathobj.html" template_name = "relative_file_pathobj.html"
class Media: class Media:

View file

@ -1,11 +1,11 @@
from typing import Any, Dict from typing import Any, Dict
from django_components import component from django_components import Component, register
# Used for testing the safer_staticfiles app in `test_safer_staticfiles.py` # Used for testing the safer_staticfiles app in `test_safer_staticfiles.py`
@component.register("safer_staticfiles_component") @register("safer_staticfiles_component")
class RelativeFileWithPathObjComponent(component.Component): class RelativeFileWithPathObjComponent(Component):
template_name = "safer_staticfiles.html" template_name = "safer_staticfiles.html"
class Media: class Media:

View file

@ -2,11 +2,11 @@ from typing import Any, Dict
from django.http import HttpResponse from django.http import HttpResponse
from django_components import component, types from django_components import Component, register, types
@component.register("single_file_component") @register("single_file_component")
class SingleFileComponent(component.Component): class SingleFileComponent(Component):
template: types.django_html = """ template: types.django_html = """
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}

View file

@ -1,7 +1,7 @@
from django.template import Context, Template, TemplateSyntaxError from django.template import Context, Template, TemplateSyntaxError
from django.utils.safestring import SafeString, mark_safe from django.utils.safestring import SafeString, mark_safe
from django_components import component, types from django_components import Component, register, types
from django_components.attributes import append_attributes, attributes_to_string from django_components.attributes import append_attributes, attributes_to_string
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
@ -84,8 +84,8 @@ class HtmlAttrsTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_tag_positional_args(self): def test_tag_positional_args(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs attrs defaults class="added_class" class="another-class" data-id=123 %}> <div {% html_attrs attrs defaults class="added_class" class="another-class" data-id=123 %}>
@ -112,8 +112,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered) self.assertNotIn("override-me", rendered)
def test_tag_raises_on_extra_positional_args(self): def test_tag_raises_on_extra_positional_args(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs attrs defaults class %}> <div {% html_attrs attrs defaults class %}>
@ -136,8 +136,8 @@ class HtmlAttrsTests(BaseTestCase):
template.render(Context({"class_var": "padding-top-8"})) template.render(Context({"class_var": "padding-top-8"}))
def test_tag_kwargs(self): def test_tag_kwargs(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs attrs=attrs defaults=defaults class="added_class" class="another-class" data-id=123 %}> <div {% html_attrs attrs=attrs defaults=defaults class="added_class" class="another-class" data-id=123 %}>
@ -164,8 +164,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered) self.assertNotIn("override-me", rendered)
def test_tag_kwargs_2(self): def test_tag_kwargs_2(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs class="added_class" class="another-class" data-id=123 defaults=defaults attrs=attrs %}> <div {% html_attrs class="added_class" class="another-class" data-id=123 defaults=defaults attrs=attrs %}>
@ -192,8 +192,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered) self.assertNotIn("override-me", rendered)
def test_tag_aggregate_args(self): def test_tag_aggregate_args(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs attrs:class="from_agg_key" attrs:type="submit" defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}> <div {% html_attrs attrs:class="from_agg_key" attrs:type="submit" defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}>
@ -219,8 +219,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered) self.assertNotIn("override-me", rendered)
def test_tag_raises_on_aggregate_and_positional_args_for_attrs(self): def test_tag_raises_on_aggregate_and_positional_args_for_attrs(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs attrs attrs:class="from_agg_key" defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}> <div {% html_attrs attrs attrs:class="from_agg_key" defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}>
@ -237,8 +237,8 @@ class HtmlAttrsTests(BaseTestCase):
template.render(Context({"class_var": "padding-top-8"})) template.render(Context({"class_var": "padding-top-8"}))
def test_tag_raises_on_aggregate_and_positional_args_for_defaults(self): def test_tag_raises_on_aggregate_and_positional_args_for_defaults(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs defaults=defaults attrs:class="from_agg_key" defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}> <div {% html_attrs defaults=defaults attrs:class="from_agg_key" defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}>
@ -258,8 +258,8 @@ class HtmlAttrsTests(BaseTestCase):
template.render(Context({"class_var": "padding-top-8"})) template.render(Context({"class_var": "padding-top-8"}))
def test_tag_no_attrs(self): def test_tag_no_attrs(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}> <div {% html_attrs defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}>
@ -282,8 +282,8 @@ class HtmlAttrsTests(BaseTestCase):
) )
def test_tag_no_defaults(self): def test_tag_no_defaults(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs attrs class="added_class" class="another-class" data-id=123 %}> <div {% html_attrs attrs class="added_class" class="another-class" data-id=123 %}>
@ -312,8 +312,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered) self.assertNotIn("override-me", rendered)
def test_tag_no_attrs_no_defaults(self): def test_tag_no_attrs_no_defaults(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs class="added_class" class="another-class" data-id=123 %}> <div {% html_attrs class="added_class" class="another-class" data-id=123 %}>
@ -337,8 +337,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered) self.assertNotIn("override-me", rendered)
def test_tag_empty(self): def test_tag_empty(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div {% html_attrs %}> <div {% html_attrs %}>

View file

@ -4,7 +4,7 @@ from unittest import TestCase, mock
from django.conf import settings from django.conf import settings
from django_components import component, component_registry from django_components import AlreadyRegistered, registry
from django_components.autodiscover import _filepath_to_python_module, autodiscover, import_libraries from django_components.autodiscover import _filepath_to_python_module, autodiscover, import_libraries
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
@ -15,14 +15,14 @@ from .django_test_setup import setup_test_config
class _TestCase(TestCase): class _TestCase(TestCase):
def tearDown(self) -> None: def tearDown(self) -> None:
super().tearDown() super().tearDown()
component.registry.clear() registry.clear()
class TestAutodiscover(_TestCase): class TestAutodiscover(_TestCase):
def test_autodiscover(self): def test_autodiscover(self):
setup_test_config({"autodiscover": False}) setup_test_config({"autodiscover": False})
all_components = component.registry.all().copy() all_components = registry.all().copy()
self.assertNotIn("single_file_component", all_components) self.assertNotIn("single_file_component", all_components)
self.assertNotIn("multi_file_component", all_components) self.assertNotIn("multi_file_component", all_components)
self.assertNotIn("relative_file_component", all_components) self.assertNotIn("relative_file_component", all_components)
@ -30,7 +30,7 @@ class TestAutodiscover(_TestCase):
try: try:
modules = autodiscover(map_module=lambda p: "tests." + p) modules = autodiscover(map_module=lambda p: "tests." + p)
except component_registry.AlreadyRegistered: except AlreadyRegistered:
self.fail("Autodiscover should not raise AlreadyRegistered exception") self.fail("Autodiscover should not raise AlreadyRegistered exception")
self.assertIn("tests.components.single_file", modules) self.assertIn("tests.components.single_file", modules)
@ -38,7 +38,7 @@ class TestAutodiscover(_TestCase):
self.assertIn("tests.components.relative_file_pathobj.relative_file_pathobj", modules) self.assertIn("tests.components.relative_file_pathobj.relative_file_pathobj", modules)
self.assertIn("tests.components.relative_file.relative_file", modules) self.assertIn("tests.components.relative_file.relative_file", modules)
all_components = component.registry.all().copy() all_components = registry.all().copy()
self.assertIn("single_file_component", all_components) self.assertIn("single_file_component", all_components)
self.assertIn("multi_file_component", all_components) self.assertIn("multi_file_component", all_components)
self.assertIn("relative_file_component", all_components) self.assertIn("relative_file_component", all_components)
@ -56,8 +56,8 @@ class TestImportLibraries(_TestCase):
settings.COMPONENTS["libraries"] = ["tests.components.single_file", "tests.components.multi_file.multi_file"] settings.COMPONENTS["libraries"] = ["tests.components.single_file", "tests.components.multi_file.multi_file"]
# Ensure we start with a clean state # Ensure we start with a clean state
component.registry.clear() registry.clear()
all_components = component.registry.all().copy() all_components = registry.all().copy()
self.assertNotIn("single_file_component", all_components) self.assertNotIn("single_file_component", all_components)
self.assertNotIn("multi_file_component", all_components) self.assertNotIn("multi_file_component", all_components)
@ -69,13 +69,13 @@ class TestImportLibraries(_TestCase):
try: try:
modules = import_libraries() modules = import_libraries()
except component_registry.AlreadyRegistered: except AlreadyRegistered:
self.fail("Autodiscover should not raise AlreadyRegistered exception") self.fail("Autodiscover should not raise AlreadyRegistered exception")
self.assertIn("tests.components.single_file", modules) self.assertIn("tests.components.single_file", modules)
self.assertIn("tests.components.multi_file.multi_file", modules) self.assertIn("tests.components.multi_file.multi_file", modules)
all_components = component.registry.all().copy() all_components = registry.all().copy()
self.assertIn("single_file_component", all_components) self.assertIn("single_file_component", all_components)
self.assertIn("multi_file_component", all_components) self.assertIn("multi_file_component", all_components)
@ -91,8 +91,8 @@ class TestImportLibraries(_TestCase):
settings.COMPONENTS["libraries"] = ["components.single_file", "components.multi_file.multi_file"] settings.COMPONENTS["libraries"] = ["components.single_file", "components.multi_file.multi_file"]
# Ensure we start with a clean state # Ensure we start with a clean state
component.registry.clear() registry.clear()
all_components = component.registry.all().copy() all_components = registry.all().copy()
self.assertNotIn("single_file_component", all_components) self.assertNotIn("single_file_component", all_components)
self.assertNotIn("multi_file_component", all_components) self.assertNotIn("multi_file_component", all_components)
@ -104,13 +104,13 @@ class TestImportLibraries(_TestCase):
try: try:
modules = import_libraries(map_module=lambda p: "tests." + p) modules = import_libraries(map_module=lambda p: "tests." + p)
except component_registry.AlreadyRegistered: except AlreadyRegistered:
self.fail("Autodiscover should not raise AlreadyRegistered exception") self.fail("Autodiscover should not raise AlreadyRegistered exception")
self.assertIn("tests.components.single_file", modules) self.assertIn("tests.components.single_file", modules)
self.assertIn("tests.components.multi_file.multi_file", modules) self.assertIn("tests.components.multi_file.multi_file", modules)
all_components = component.registry.all().copy() all_components = registry.all().copy()
self.assertIn("single_file_component", all_components) self.assertIn("single_file_component", all_components)
self.assertIn("multi_file_component", all_components) self.assertIn("multi_file_component", all_components)

View file

@ -9,7 +9,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponse from django.http import HttpResponse
from django.template import Context, Template, TemplateSyntaxError from django.template import Context, Template, TemplateSyntaxError
from django_components import component, types from django_components import Component, registry, types
from django_components.slots import SlotRef from django_components.slots import SlotRef
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
@ -19,7 +19,7 @@ setup_test_config({"autodiscover": False})
class ComponentTest(BaseTestCase): class ComponentTest(BaseTestCase):
class ParentComponent(component.Component): class ParentComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -39,7 +39,7 @@ class ComponentTest(BaseTestCase):
def get_context_data(self): def get_context_data(self):
return {"shadowing_variable": "NOT SHADOWED"} return {"shadowing_variable": "NOT SHADOWED"}
class VariableDisplay(component.Component): class VariableDisplay(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<h1>Shadowing variable = {{ shadowing_variable }}</h1> <h1>Shadowing variable = {{ shadowing_variable }}</h1>
@ -56,12 +56,12 @@ class ComponentTest(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register(name="parent_component", component=self.ParentComponent) registry.register(name="parent_component", component=self.ParentComponent)
component.registry.register(name="variable_display", component=self.VariableDisplay) registry.register(name="variable_display", component=self.VariableDisplay)
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_empty_component(self): def test_empty_component(self):
class EmptyComponent(component.Component): class EmptyComponent(Component):
pass pass
with self.assertRaises(ImproperlyConfigured): with self.assertRaises(ImproperlyConfigured):
@ -69,7 +69,7 @@ class ComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_template_string_static_inlined(self): def test_template_string_static_inlined(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -93,7 +93,7 @@ class ComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_template_string_dynamic(self): def test_template_string_dynamic(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
def get_template_string(self, context): def get_template_string(self, context):
content: types.django_html = """ content: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
@ -119,7 +119,7 @@ class ComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_template_name_static(self): def test_template_name_static(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template_name = "simple_template.html" template_name = "simple_template.html"
def get_context_data(self, variable=None): def get_context_data(self, variable=None):
@ -141,7 +141,7 @@ class ComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_template_name_dynamic(self): def test_template_name_dynamic(self):
class SvgComponent(component.Component): class SvgComponent(Component):
def get_context_data(self, name, css_class="", title="", **attrs): def get_context_data(self, name, css_class="", title="", **attrs):
return { return {
"name": name, "name": name,
@ -168,7 +168,7 @@ class ComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_allows_to_override_get_template(self): def test_allows_to_override_get_template(self):
class TestComponent(component.Component): class TestComponent(Component):
def get_context_data(self, variable, **attrs): def get_context_data(self, variable, **attrs):
return { return {
"variable": variable, "variable": variable,
@ -190,7 +190,7 @@ class ComponentTest(BaseTestCase):
class ComponentRenderTest(BaseTestCase): class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_render_minimal(self): def test_render_minimal(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
the_arg2: {{ the_arg2 }} the_arg2: {{ the_arg2 }}
@ -230,7 +230,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_render_full(self): def test_render_full(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
the_arg: {{ the_arg }} the_arg: {{ the_arg }}
@ -283,7 +283,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_render_to_response_full(self): def test_render_to_response_full(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
the_arg: {{ the_arg }} the_arg: {{ the_arg }}
@ -342,7 +342,7 @@ class ComponentRenderTest(BaseTestCase):
def __init__(self, content: str) -> None: def __init__(self, content: str) -> None:
self.content = bytes(content, "utf-8") self.content = bytes(content, "utf-8")
class SimpleComponent(component.Component): class SimpleComponent(Component):
response_class = MyResponse response_class = MyResponse
template: types.django_html = "HELLO" template: types.django_html = "HELLO"
@ -358,7 +358,7 @@ class ComponentRenderTest(BaseTestCase):
def test_render_slot_as_func(self, context_behavior_data): def test_render_slot_as_func(self, context_behavior_data):
is_isolated = context_behavior_data is_isolated = context_behavior_data
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% slot "first" required data1="abc" data2:hello="world" data2:one=123 %} {% slot "first" required data1="abc" data2:hello="world" data2:one=123 %}
@ -414,7 +414,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_render_raises_on_missing_slot(self): def test_render_raises_on_missing_slot(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% slot "first" required %} {% slot "first" required %}
@ -430,7 +430,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_render_with_include(self): def test_render_with_include(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% include 'slotted_template.html' %} {% include 'slotted_template.html' %}
@ -450,7 +450,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_render_with_extends(self): def test_render_with_extends(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% extends 'block.html' %} {% extends 'block.html' %}
{% block body %} {% block body %}
@ -478,7 +478,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_render_can_access_instance(self): def test_render_can_access_instance(self):
class TestComponent(component.Component): class TestComponent(Component):
template = "Variable: <strong>{{ id }}</strong>" template = "Variable: <strong>{{ id }}</strong>"
def get_context_data(self, **attrs): def get_context_data(self, **attrs):
@ -494,7 +494,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_render_to_response_can_access_instance(self): def test_render_to_response_can_access_instance(self):
class TestComponent(component.Component): class TestComponent(Component):
template = "Variable: <strong>{{ id }}</strong>" template = "Variable: <strong>{{ id }}</strong>"
def get_context_data(self, **attrs): def get_context_data(self, **attrs):

View file

@ -6,7 +6,7 @@ from django.template import Context, Template
from django.test import Client from django.test import Client
from django.urls import path from django.urls import path
from django_components import component from django_components import Component, register
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior from .testutils import BaseTestCase, parametrize_context_behavior
@ -30,8 +30,8 @@ class CustomClient(Client):
class TestComponentAsView(BaseTestCase): class TestComponentAsView(BaseTestCase):
def test_render_component_from_template(self): def test_render_component_from_template(self):
@component.register("testcomponent") @register("testcomponent")
class MockComponentRequest(component.Component): class MockComponentRequest(Component):
template = """ template = """
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
@ -64,7 +64,7 @@ class TestComponentAsView(BaseTestCase):
) )
def test_get_request(self): def test_get_request(self):
class MockComponentRequest(component.Component): class MockComponentRequest(Component):
template = """ template = """
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
@ -88,7 +88,7 @@ class TestComponentAsView(BaseTestCase):
) )
def test_post_request(self): def test_post_request(self):
class MockComponentRequest(component.Component): class MockComponentRequest(Component):
template = """ template = """
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
@ -114,7 +114,7 @@ class TestComponentAsView(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_replace_slot_in_view(self): def test_replace_slot_in_view(self):
class MockComponentSlot(component.Component): class MockComponentSlot(Component):
template = """ template = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -143,7 +143,7 @@ class TestComponentAsView(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_replace_slot_in_view_with_insecure_content(self): def test_replace_slot_in_view_with_insecure_content(self):
class MockInsecureComponentSlot(component.Component): class MockInsecureComponentSlot(Component):
template = """ template = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -165,7 +165,7 @@ class TestComponentAsView(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_replace_context_in_view(self): def test_replace_context_in_view(self):
class TestComponent(component.Component): class TestComponent(Component):
template = """ template = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -186,7 +186,7 @@ class TestComponentAsView(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_replace_context_in_view_with_insecure_content(self): def test_replace_context_in_view_with_insecure_content(self):
class MockInsecureComponentContext(component.Component): class MockInsecureComponentContext(Component):
template = """ template = """
{% load component_tags %} {% load component_tags %}
<div> <div>

View file

@ -9,7 +9,7 @@ from django.test import override_settings
from django.utils.html import format_html, html_safe from django.utils.html import format_html, html_safe
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django_components import component, types from django_components import Component, registry, types
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, autodiscover_with_cleanup from .testutils import BaseTestCase, autodiscover_with_cleanup
@ -19,7 +19,7 @@ setup_test_config()
class InlineComponentTest(BaseTestCase): class InlineComponentTest(BaseTestCase):
def test_html(self): def test_html(self):
class InlineHTMLComponent(component.Component): class InlineHTMLComponent(Component):
template = "<div class='inline'>Hello Inline</div>" template = "<div class='inline'>Hello Inline</div>"
comp = InlineHTMLComponent("inline_html_component") comp = InlineHTMLComponent("inline_html_component")
@ -29,7 +29,7 @@ class InlineComponentTest(BaseTestCase):
) )
def test_html_and_css(self): def test_html_and_css(self):
class HTMLCSSComponent(component.Component): class HTMLCSSComponent(Component):
template = "<div class='html-css-only'>Content</div>" template = "<div class='html-css-only'>Content</div>"
css = ".html-css-only { color: blue; }" css = ".html-css-only { color: blue; }"
@ -44,7 +44,7 @@ class InlineComponentTest(BaseTestCase):
) )
def test_html_and_js(self): def test_html_and_js(self):
class HTMLJSComponent(component.Component): class HTMLJSComponent(Component):
template = "<div class='html-js-only'>Content</div>" template = "<div class='html-js-only'>Content</div>"
js = "console.log('HTML and JS only');" js = "console.log('HTML and JS only');"
@ -59,7 +59,7 @@ class InlineComponentTest(BaseTestCase):
) )
def test_html_inline_and_css_js_files(self): def test_html_inline_and_css_js_files(self):
class HTMLStringFileCSSJSComponent(component.Component): class HTMLStringFileCSSJSComponent(Component):
template = "<div class='html-string-file'>Content</div>" template = "<div class='html-string-file'>Content</div>"
class Media: class Media:
@ -80,7 +80,7 @@ class InlineComponentTest(BaseTestCase):
) )
def test_html_js_inline_and_css_file(self): def test_html_js_inline_and_css_file(self):
class HTMLStringFileCSSJSComponent(component.Component): class HTMLStringFileCSSJSComponent(Component):
template = "<div class='html-string-file'>Content</div>" template = "<div class='html-string-file'>Content</div>"
js = "console.log('HTML and JS only');" js = "console.log('HTML and JS only');"
@ -101,7 +101,7 @@ class InlineComponentTest(BaseTestCase):
) )
def test_html_css_inline_and_js_file(self): def test_html_css_inline_and_js_file(self):
class HTMLStringFileCSSJSComponent(component.Component): class HTMLStringFileCSSJSComponent(Component):
template = "<div class='html-string-file'>Content</div>" template = "<div class='html-string-file'>Content</div>"
css = ".html-string-file { color: blue; }" css = ".html-string-file { color: blue; }"
@ -121,7 +121,7 @@ class InlineComponentTest(BaseTestCase):
) )
def test_html_variable(self): def test_html_variable(self):
class VariableHTMLComponent(component.Component): class VariableHTMLComponent(Component):
def get_template(self, context): def get_template(self, context):
return Template("<div class='variable-html'>{{ variable }}</div>") return Template("<div class='variable-html'>{{ variable }}</div>")
@ -133,7 +133,7 @@ class InlineComponentTest(BaseTestCase):
) )
def test_html_variable_filtered(self): def test_html_variable_filtered(self):
class FilteredComponent(component.Component): class FilteredComponent(Component):
template: types.django_html = """ template: types.django_html = """
Var1: <strong>{{ var1 }}</strong> Var1: <strong>{{ var1 }}</strong>
Var2 (uppercased): <strong>{{ var2|upper }}</strong> Var2 (uppercased): <strong>{{ var2|upper }}</strong>
@ -157,7 +157,7 @@ class InlineComponentTest(BaseTestCase):
class ComponentMediaTests(BaseTestCase): class ComponentMediaTests(BaseTestCase):
def test_css_and_js(self): def test_css_and_js(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -176,7 +176,7 @@ class ComponentMediaTests(BaseTestCase):
) )
def test_css_only(self): def test_css_only(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -194,7 +194,7 @@ class ComponentMediaTests(BaseTestCase):
) )
def test_js_only(self): def test_js_only(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -212,7 +212,7 @@ class ComponentMediaTests(BaseTestCase):
) )
def test_empty_media(self): def test_empty_media(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -225,7 +225,7 @@ class ComponentMediaTests(BaseTestCase):
self.assertHTMLEqual(comp.render_dependencies(), "") self.assertHTMLEqual(comp.render_dependencies(), "")
def test_missing_media(self): def test_missing_media(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -235,7 +235,7 @@ class ComponentMediaTests(BaseTestCase):
self.assertHTMLEqual(comp.render_dependencies(), "") self.assertHTMLEqual(comp.render_dependencies(), "")
def test_css_js_as_lists(self): def test_css_js_as_lists(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
class Media: class Media:
css = ["path/to/style.css", "path/to/style2.css"] css = ["path/to/style.css", "path/to/style2.css"]
js = ["path/to/script.js"] js = ["path/to/script.js"]
@ -251,7 +251,7 @@ class ComponentMediaTests(BaseTestCase):
) )
def test_css_js_as_string(self): def test_css_js_as_string(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
class Media: class Media:
css = "path/to/style.css" css = "path/to/style.css"
js = "path/to/script.js" js = "path/to/script.js"
@ -266,7 +266,7 @@ class ComponentMediaTests(BaseTestCase):
) )
def test_css_as_dict(self): def test_css_as_dict(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
class Media: class Media:
css = { css = {
"all": "path/to/style.css", "all": "path/to/style.css",
@ -295,7 +295,7 @@ class ComponentMediaTests(BaseTestCase):
tags.append(f'<my_script_tag src="{abs_path}"></my_script_tag>') tags.append(f'<my_script_tag src="{abs_path}"></my_script_tag>')
return tags return tags
class SimpleComponent(component.Component): class SimpleComponent(Component):
media_class = MyMedia media_class = MyMedia
class Media: class Media:
@ -320,7 +320,7 @@ class ComponentMediaTests(BaseTestCase):
tags.append(f'<my_link href="{path}" media="{medium}" rel="stylesheet" />') tags.append(f'<my_link href="{path}" media="{medium}" rel="stylesheet" />')
return tags return tags
class SimpleComponent(component.Component): class SimpleComponent(Component):
media_class = MyMedia media_class = MyMedia
class Media: class Media:
@ -376,7 +376,7 @@ class MediaPathAsObjectTests(BaseTestCase):
def __str__(self): def __str__(self):
return format_html('<script type="module" src="{}"></script>', static(self.static_path)) return format_html('<script type="module" src="{}"></script>', static(self.static_path))
class SimpleComponent(component.Component): class SimpleComponent(Component):
class Media: class Media:
css = { css = {
"all": [ "all": [
@ -424,7 +424,7 @@ class MediaPathAsObjectTests(BaseTestCase):
def __fspath__(self): def __fspath__(self):
return self.path return self.path
class SimpleComponent(component.Component): class SimpleComponent(Component):
class Media: class Media:
css = { css = {
"all": [ "all": [
@ -466,7 +466,7 @@ class MediaPathAsObjectTests(BaseTestCase):
class MyStr(str): class MyStr(str):
pass pass
class SimpleComponent(component.Component): class SimpleComponent(Component):
class Media: class Media:
css = { css = {
"all": [ "all": [
@ -506,7 +506,7 @@ class MediaPathAsObjectTests(BaseTestCase):
class MyBytes(bytes): class MyBytes(bytes):
pass pass
class SimpleComponent(component.Component): class SimpleComponent(Component):
class Media: class Media:
css = { css = {
"all": [ "all": [
@ -538,7 +538,7 @@ class MediaPathAsObjectTests(BaseTestCase):
) )
def test_function(self): def test_function(self):
class SimpleComponent(component.Component): class SimpleComponent(Component):
class Media: class Media:
css = [ css = [
lambda: mark_safe('<link hi href="calendar/style.css" rel="stylesheet" />'), # Literal lambda: mark_safe('<link hi href="calendar/style.css" rel="stylesheet" />'), # Literal
@ -573,7 +573,7 @@ class MediaPathAsObjectTests(BaseTestCase):
def test_works_with_static(self): def test_works_with_static(self):
"""Test that all the different ways of defining media files works with Django's staticfiles""" """Test that all the different ways of defining media files works with Django's staticfiles"""
class SimpleComponent(component.Component): class SimpleComponent(Component):
class Media: class Media:
css = [ css = [
mark_safe(f'<link hi href="{static("calendar/style.css")}" rel="stylesheet" />'), # Literal mark_safe(f'<link hi href="{static("calendar/style.css")}" rel="stylesheet" />'), # Literal
@ -636,7 +636,7 @@ class MediaStaticfilesTests(BaseTestCase):
tags.append(f'<my_script_tag src="{abs_path}"></my_script_tag>') tags.append(f'<my_script_tag src="{abs_path}"></my_script_tag>')
return tags return tags
class SimpleComponent(component.Component): class SimpleComponent(Component):
media_class = MyMedia media_class = MyMedia
class Media: class Media:
@ -693,7 +693,7 @@ class MediaStaticfilesTests(BaseTestCase):
tags.append(f'<my_script_tag src="{abs_path}"></my_script_tag>') tags.append(f'<my_script_tag src="{abs_path}"></my_script_tag>')
return tags return tags
class SimpleComponent(component.Component): class SimpleComponent(Component):
media_class = MyMedia media_class = MyMedia
class Media: class Media:
@ -714,7 +714,7 @@ class MediaStaticfilesTests(BaseTestCase):
class MediaRelativePathTests(BaseTestCase): class MediaRelativePathTests(BaseTestCase):
class ParentComponent(component.Component): class ParentComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -734,7 +734,7 @@ class MediaRelativePathTests(BaseTestCase):
def get_context_data(self): def get_context_data(self):
return {"shadowing_variable": "NOT SHADOWED"} return {"shadowing_variable": "NOT SHADOWED"}
class VariableDisplay(component.Component): class VariableDisplay(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<h1>Shadowing variable = {{ shadowing_variable }}</h1> <h1>Shadowing variable = {{ shadowing_variable }}</h1>
@ -751,8 +751,8 @@ class MediaRelativePathTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register(name="parent_component", component=self.ParentComponent) registry.register(name="parent_component", component=self.ParentComponent)
component.registry.register(name="variable_display", component=self.VariableDisplay) registry.register(name="variable_display", component=self.VariableDisplay)
# Settings required for autodiscover to work # Settings required for autodiscover to work
@override_settings( @override_settings(
@ -771,11 +771,11 @@ class MediaRelativePathTests(BaseTestCase):
# Make sure that only relevant components are registered: # Make sure that only relevant components are registered:
comps_to_remove = [ comps_to_remove = [
comp_name comp_name
for comp_name in component.registry.all() for comp_name in registry.all()
if comp_name not in ["relative_file_component", "parent_component", "variable_display"] if comp_name not in ["relative_file_component", "parent_component", "variable_display"]
] ]
for comp_name in comps_to_remove: for comp_name in comps_to_remove:
component.registry.unregister(comp_name) registry.unregister(comp_name)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
@ -810,7 +810,7 @@ class MediaRelativePathTests(BaseTestCase):
# Fix the paths, since the "components" dir is nested # Fix the paths, since the "components" dir is nested
with autodiscover_with_cleanup(map_module=lambda p: f"tests.{p}"): with autodiscover_with_cleanup(map_module=lambda p: f"tests.{p}"):
component.registry.unregister("relative_file_pathobj_component") registry.unregister("relative_file_pathobj_component")
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
@ -852,7 +852,7 @@ class MediaRelativePathTests(BaseTestCase):
with autodiscover_with_cleanup(map_module=lambda p: f"tests.{p}"): with autodiscover_with_cleanup(map_module=lambda p: f"tests.{p}"):
# Mark the PathObj instances of 'relative_file_pathobj_component' so they won raise # Mark the PathObj instances of 'relative_file_pathobj_component' so they won raise
# error PathObj.__str__ is triggered. # error PathObj.__str__ is triggered.
CompCls = component.registry.get("relative_file_pathobj_component") CompCls = registry.get("relative_file_pathobj_component")
CompCls.Media.js[0].throw_on_calling_str = False # type: ignore CompCls.Media.js[0].throw_on_calling_str = False # type: ignore
CompCls.Media.css["all"][0].throw_on_calling_str = False # type: ignore CompCls.Media.css["all"][0].throw_on_calling_str = False # type: ignore

View file

@ -1,6 +1,6 @@
from django.template import Context, Template from django.template import Context, Template
from django_components import component, types from django_components import Component, registry, types
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior from .testutils import BaseTestCase, parametrize_context_behavior
@ -13,7 +13,7 @@ setup_test_config()
######################### #########################
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -26,7 +26,7 @@ class SimpleComponent(component.Component):
return "Variable: < strong > {} < / strong >".format(variable_value) return "Variable: < strong > {} < / strong >".format(variable_value)
class VariableDisplay(component.Component): class VariableDisplay(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<h1>Shadowing variable = {{ shadowing_variable }}</h1> <h1>Shadowing variable = {{ shadowing_variable }}</h1>
@ -42,7 +42,7 @@ class VariableDisplay(component.Component):
return context return context
class IncrementerComponent(component.Component): class IncrementerComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<p class="incrementer">value={{ value }};calls={{ calls }}</p> <p class="incrementer">value={{ value }};calls={{ calls }}</p>
@ -68,7 +68,7 @@ class IncrementerComponent(component.Component):
class ContextTests(BaseTestCase): class ContextTests(BaseTestCase):
class ParentComponent(component.Component): class ParentComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -90,8 +90,8 @@ class ContextTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register(name="variable_display", component=VariableDisplay) registry.register(name="variable_display", component=VariableDisplay)
component.registry.register(name="parent_component", component=self.ParentComponent) registry.register(name="parent_component", component=self.ParentComponent)
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_nested_component_context_shadows_parent_with_unfilled_slots_and_component_tag( def test_nested_component_context_shadows_parent_with_unfilled_slots_and_component_tag(
@ -221,7 +221,7 @@ class ContextTests(BaseTestCase):
class ParentArgsTests(BaseTestCase): class ParentArgsTests(BaseTestCase):
class ParentComponentWithArgs(component.Component): class ParentComponentWithArgs(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -243,9 +243,9 @@ class ParentArgsTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register(name="incrementer", component=IncrementerComponent) registry.register(name="incrementer", component=IncrementerComponent)
component.registry.register(name="parent_with_args", component=self.ParentComponentWithArgs) registry.register(name="parent_with_args", component=self.ParentComponentWithArgs)
component.registry.register(name="variable_display", component=VariableDisplay) registry.register(name="variable_display", component=VariableDisplay)
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_parent_args_can_be_drawn_from_context(self): def test_parent_args_can_be_drawn_from_context(self):
@ -326,7 +326,7 @@ class ParentArgsTests(BaseTestCase):
class ContextCalledOnceTests(BaseTestCase): class ContextCalledOnceTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register(name="incrementer", component=IncrementerComponent) registry.register(name="incrementer", component=IncrementerComponent)
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_one_context_call_with_simple_component(self): def test_one_context_call_with_simple_component(self):
@ -416,7 +416,7 @@ class ContextCalledOnceTests(BaseTestCase):
class ComponentsCanAccessOuterContext(BaseTestCase): class ComponentsCanAccessOuterContext(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register(name="simple_component", component=SimpleComponent) registry.register(name="simple_component", component=SimpleComponent)
# NOTE: Second arg in tuple is expected value. # NOTE: Second arg in tuple is expected value.
@parametrize_context_behavior( @parametrize_context_behavior(
@ -443,7 +443,7 @@ class ComponentsCanAccessOuterContext(BaseTestCase):
class IsolatedContextTests(BaseTestCase): class IsolatedContextTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register(name="simple_component", component=SimpleComponent) registry.register(name="simple_component", component=SimpleComponent)
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_simple_component_can_pass_outer_context_in_args(self): def test_simple_component_can_pass_outer_context_in_args(self):
@ -469,7 +469,7 @@ class IsolatedContextTests(BaseTestCase):
class IsolatedContextSettingTests(BaseTestCase): class IsolatedContextSettingTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register(name="simple_component", component=SimpleComponent) registry.register(name="simple_component", component=SimpleComponent)
@parametrize_context_behavior(["isolated"]) @parametrize_context_behavior(["isolated"])
def test_component_tag_includes_variable_with_isolated_context_from_settings( def test_component_tag_includes_variable_with_isolated_context_from_settings(
@ -523,7 +523,7 @@ class IsolatedContextSettingTests(BaseTestCase):
class OuterContextPropertyTests(BaseTestCase): class OuterContextPropertyTests(BaseTestCase):
class OuterContextComponent(component.Component): class OuterContextComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -533,7 +533,7 @@ class OuterContextPropertyTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register(name="outer_context_component", component=self.OuterContextComponent) registry.register(name="outer_context_component", component=self.OuterContextComponent)
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_outer_context_property_with_component(self): def test_outer_context_property_with_component(self):
@ -547,7 +547,7 @@ class OuterContextPropertyTests(BaseTestCase):
class ContextVarsIsFilledTests(BaseTestCase): class ContextVarsIsFilledTests(BaseTestCase):
class IsFilledVarsComponent(component.Component): class IsFilledVarsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div class="frontmatter-component"> <div class="frontmatter-component">
@ -560,7 +560,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
</div> </div>
""" """
class ComponentWithConditionalSlots(component.Component): class ComponentWithConditionalSlots(Component):
template: types.django_html = """ template: types.django_html = """
{# Example from django-components/issues/98 #} {# Example from django-components/issues/98 #}
{% load component_tags %} {% load component_tags %}
@ -575,7 +575,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
</div> </div>
""" """
class ComponentWithComplexConditionalSlots(component.Component): class ComponentWithComplexConditionalSlots(Component):
template: types.django_html = """ template: types.django_html = """
{# Example from django-components/issues/98 #} {# Example from django-components/issues/98 #}
{% load component_tags %} {% load component_tags %}
@ -591,7 +591,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
</div> </div>
""" """
class ComponentWithNegatedConditionalSlot(component.Component): class ComponentWithNegatedConditionalSlot(Component):
template: types.django_html = """ template: types.django_html = """
{# Example from django-components/issues/98 #} {# Example from django-components/issues/98 #}
{% load component_tags %} {% load component_tags %}
@ -607,17 +607,17 @@ class ContextVarsIsFilledTests(BaseTestCase):
def setUp(self) -> None: def setUp(self) -> None:
super().setUp() super().setUp()
component.registry.register("is_filled_vars", self.IsFilledVarsComponent) registry.register("is_filled_vars", self.IsFilledVarsComponent)
component.registry.register("conditional_slots", self.ComponentWithConditionalSlots) registry.register("conditional_slots", self.ComponentWithConditionalSlots)
component.registry.register( registry.register(
"complex_conditional_slots", "complex_conditional_slots",
self.ComponentWithComplexConditionalSlots, self.ComponentWithComplexConditionalSlots,
) )
component.registry.register("negated_conditional_slot", self.ComponentWithNegatedConditionalSlot) registry.register("negated_conditional_slot", self.ComponentWithNegatedConditionalSlot)
def tearDown(self) -> None: def tearDown(self) -> None:
super().tearDown() super().tearDown()
component.registry.clear() registry.clear()
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_is_filled_vars(self): def test_is_filled_vars(self):

View file

@ -4,7 +4,7 @@ from django.http import HttpResponseNotModified
from django.template import Template from django.template import Template
from django.test import override_settings from django.test import override_settings
from django_components import component, types from django_components import Component, registry, types
from django_components.middleware import ComponentDependencyMiddleware from django_components.middleware import ComponentDependencyMiddleware
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
@ -13,7 +13,7 @@ from .testutils import BaseTestCase, create_and_process_template_response
setup_test_config() setup_test_config()
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -29,7 +29,7 @@ class SimpleComponent(component.Component):
js = "script.js" js = "script.js"
class SimpleComponentAlternate(component.Component): class SimpleComponentAlternate(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -42,7 +42,7 @@ class SimpleComponentAlternate(component.Component):
js = "script2.js" js = "script2.js"
class SimpleComponentWithSharedDependency(component.Component): class SimpleComponentWithSharedDependency(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -55,7 +55,7 @@ class SimpleComponentWithSharedDependency(component.Component):
js = ["script.js", "script2.js"] js = ["script.js", "script2.js"]
class MultistyleComponent(component.Component): class MultistyleComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
""" """
@ -68,11 +68,11 @@ class MultistyleComponent(component.Component):
@override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True}) @override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True})
class ComponentMediaRenderingTests(BaseTestCase): class ComponentMediaRenderingTests(BaseTestCase):
def setUp(self): def setUp(self):
# NOTE: component.registry is global, so need to clear before each test # NOTE: registry is global, so need to clear before each test
component.registry.clear() registry.clear()
def test_no_dependencies_when_no_components_used(self): def test_no_dependencies_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
@ -87,7 +87,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
) )
def test_no_js_dependencies_when_no_components_used(self): def test_no_js_dependencies_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_js_dependencies %} {% load component_tags %}{% component_js_dependencies %}
@ -97,7 +97,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertInHTML('<script src="script.js">', rendered, count=0) self.assertInHTML('<script src="script.js">', rendered, count=0)
def test_no_css_dependencies_when_no_components_used(self): def test_no_css_dependencies_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies %} {% load component_tags %}{% component_css_dependencies %}
@ -111,7 +111,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
) )
def test_preload_dependencies_render_when_no_components_used(self): def test_preload_dependencies_render_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies preload='test' %} {% load component_tags %}{% component_dependencies preload='test' %}
@ -126,7 +126,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
) )
def test_preload_css_dependencies_render_when_no_components_used(self): def test_preload_css_dependencies_render_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies preload='test' %} {% load component_tags %}{% component_css_dependencies preload='test' %}
@ -140,7 +140,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
) )
def test_single_component_dependencies_render_when_used(self): def test_single_component_dependencies_render_when_used(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
@ -156,7 +156,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertInHTML('<script src="script.js">', rendered, count=1) self.assertInHTML('<script src="script.js">', rendered, count=1)
def test_single_component_with_dash_or_slash_in_name(self): def test_single_component_with_dash_or_slash_in_name(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
@ -172,7 +172,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertInHTML('<script src="script.js">', rendered, count=1) self.assertInHTML('<script src="script.js">', rendered, count=1)
def test_preload_dependencies_render_once_when_used(self): def test_preload_dependencies_render_once_when_used(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies preload='test' %} {% load component_tags %}{% component_dependencies preload='test' %}
@ -188,7 +188,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertInHTML('<script src="script.js">', rendered, count=1) self.assertInHTML('<script src="script.js">', rendered, count=1)
def test_placeholder_removed_when_single_component_rendered(self): def test_placeholder_removed_when_single_component_rendered(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
@ -199,7 +199,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertNotIn("_RENDERED", rendered) self.assertNotIn("_RENDERED", rendered)
def test_placeholder_removed_when_preload_rendered(self): def test_placeholder_removed_when_preload_rendered(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies preload='test' %} {% load component_tags %}{% component_dependencies preload='test' %}
@ -209,7 +209,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertNotIn("_RENDERED", rendered) self.assertNotIn("_RENDERED", rendered)
def test_single_component_css_dependencies(self): def test_single_component_css_dependencies(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies %} {% load component_tags %}{% component_css_dependencies %}
@ -224,7 +224,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
) )
def test_single_component_js_dependencies(self): def test_single_component_js_dependencies(self):
component.registry.register(name="test", component=SimpleComponent) registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_js_dependencies %} {% load component_tags %}{% component_js_dependencies %}
@ -237,7 +237,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
def test_all_dependencies_are_rendered_for_component_with_multiple_dependencies( def test_all_dependencies_are_rendered_for_component_with_multiple_dependencies(
self, self,
): ):
component.registry.register(name="test", component=MultistyleComponent) registry.register(name="test", component=MultistyleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
{% component 'test' %}{% endcomponent %} {% component 'test' %}{% endcomponent %}
@ -260,7 +260,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
def test_all_js_dependencies_are_rendered_for_component_with_multiple_dependencies( def test_all_js_dependencies_are_rendered_for_component_with_multiple_dependencies(
self, self,
): ):
component.registry.register(name="test", component=MultistyleComponent) registry.register(name="test", component=MultistyleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_js_dependencies %} {% load component_tags %}{% component_js_dependencies %}
{% component 'test' %}{% endcomponent %} {% component 'test' %}{% endcomponent %}
@ -283,7 +283,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
def test_all_css_dependencies_are_rendered_for_component_with_multiple_dependencies( def test_all_css_dependencies_are_rendered_for_component_with_multiple_dependencies(
self, self,
): ):
component.registry.register(name="test", component=MultistyleComponent) registry.register(name="test", component=MultistyleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies %} {% load component_tags %}{% component_css_dependencies %}
{% component 'test' %}{% endcomponent %} {% component 'test' %}{% endcomponent %}
@ -304,8 +304,8 @@ class ComponentMediaRenderingTests(BaseTestCase):
) )
def test_no_dependencies_with_multiple_unused_components(self): def test_no_dependencies_with_multiple_unused_components(self):
component.registry.register(name="test1", component=SimpleComponent) registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate) registry.register(name="test2", component=SimpleComponentAlternate)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
@ -326,8 +326,8 @@ class ComponentMediaRenderingTests(BaseTestCase):
) )
def test_correct_css_dependencies_with_multiple_components(self): def test_correct_css_dependencies_with_multiple_components(self):
component.registry.register(name="test1", component=SimpleComponent) registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate) registry.register(name="test2", component=SimpleComponentAlternate)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies %} {% load component_tags %}{% component_css_dependencies %}
@ -347,8 +347,8 @@ class ComponentMediaRenderingTests(BaseTestCase):
) )
def test_correct_js_dependencies_with_multiple_components(self): def test_correct_js_dependencies_with_multiple_components(self):
component.registry.register(name="test1", component=SimpleComponent) registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate) registry.register(name="test2", component=SimpleComponentAlternate)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_js_dependencies %} {% load component_tags %}{% component_js_dependencies %}
@ -360,8 +360,8 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertInHTML('<script src="script2.js">', rendered, count=0) self.assertInHTML('<script src="script2.js">', rendered, count=0)
def test_correct_dependencies_with_multiple_components(self): def test_correct_dependencies_with_multiple_components(self):
component.registry.register(name="test1", component=SimpleComponent) registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate) registry.register(name="test2", component=SimpleComponentAlternate)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
@ -383,9 +383,9 @@ class ComponentMediaRenderingTests(BaseTestCase):
) )
def test_shared_dependencies_rendered_once(self): def test_shared_dependencies_rendered_once(self):
component.registry.register(name="test1", component=SimpleComponent) registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate) registry.register(name="test2", component=SimpleComponentAlternate)
component.registry.register(name="test3", component=SimpleComponentWithSharedDependency) registry.register(name="test3", component=SimpleComponentWithSharedDependency)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
@ -409,9 +409,9 @@ class ComponentMediaRenderingTests(BaseTestCase):
) )
def test_placeholder_removed_when_multiple_component_rendered(self): def test_placeholder_removed_when_multiple_component_rendered(self):
component.registry.register(name="test1", component=SimpleComponent) registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate) registry.register(name="test2", component=SimpleComponentAlternate)
component.registry.register(name="test3", component=SimpleComponentWithSharedDependency) registry.register(name="test3", component=SimpleComponentWithSharedDependency)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %} {% load component_tags %}{% component_dependencies %}
@ -438,7 +438,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
"test_component", "test_component",
] ]
for component_name in component_names: for component_name in component_names:
component.registry.register(name=component_name, component=SimpleComponent) registry.register(name=component_name, component=SimpleComponent)
template_str: types.django_html = f""" template_str: types.django_html = f"""
{{% load component_tags %}} {{% load component_tags %}}
{{% component_js_dependencies %}} {{% component_js_dependencies %}}

View file

@ -1,35 +1,35 @@
import unittest import unittest
from django_components import component from django_components import AlreadyRegistered, Component, ComponentRegistry, NotRegistered, register, registry
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
setup_test_config() setup_test_config()
class MockComponent(component.Component): class MockComponent(Component):
pass pass
class MockComponent2(component.Component): class MockComponent2(Component):
pass pass
class MockComponentView(component.Component): class MockComponentView(Component):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
pass pass
class ComponentRegistryTest(unittest.TestCase): class ComponentRegistryTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.registry = component.ComponentRegistry() self.registry = ComponentRegistry()
def test_register_class_decorator(self): def test_register_class_decorator(self):
@component.register("decorated_component") @register("decorated_component")
class TestComponent(component.Component): class TestComponent(Component):
pass pass
self.assertEqual(component.registry.get("decorated_component"), TestComponent) self.assertEqual(registry.get("decorated_component"), TestComponent)
def test_simple_register(self): def test_simple_register(self):
self.registry.register(name="testcomponent", component=MockComponent) self.registry.register(name="testcomponent", component=MockComponent)
@ -48,14 +48,14 @@ class ComponentRegistryTest(unittest.TestCase):
def test_prevent_registering_different_components_with_the_same_name(self): def test_prevent_registering_different_components_with_the_same_name(self):
self.registry.register(name="testcomponent", component=MockComponent) self.registry.register(name="testcomponent", component=MockComponent)
with self.assertRaises(component.AlreadyRegistered): with self.assertRaises(AlreadyRegistered):
self.registry.register(name="testcomponent", component=MockComponent2) self.registry.register(name="testcomponent", component=MockComponent2)
def test_allow_duplicated_registration_of_the_same_component(self): def test_allow_duplicated_registration_of_the_same_component(self):
try: try:
self.registry.register(name="testcomponent", component=MockComponentView) self.registry.register(name="testcomponent", component=MockComponentView)
self.registry.register(name="testcomponent", component=MockComponentView) self.registry.register(name="testcomponent", component=MockComponentView)
except component.AlreadyRegistered: except AlreadyRegistered:
self.fail("Should not raise AlreadyRegistered") self.fail("Should not raise AlreadyRegistered")
def test_simple_unregister(self): def test_simple_unregister(self):
@ -64,5 +64,5 @@ class ComponentRegistryTest(unittest.TestCase):
self.assertEqual(self.registry.all(), {}) self.assertEqual(self.registry.all(), {})
def test_raises_on_failed_unregister(self): def test_raises_on_failed_unregister(self):
with self.assertRaises(component.NotRegistered): with self.assertRaises(NotRegistered):
self.registry.unregister(name="testcomponent") self.registry.unregister(name="testcomponent")

View file

@ -1,7 +1,7 @@
from django.template import Context, Template from django.template import Context, Template
from django.template.base import Parser from django.template.base import Parser
from django_components import component, types from django_components import Component, registry, types
from django_components.component import safe_resolve_dict, safe_resolve_list from django_components.component import safe_resolve_dict, safe_resolve_list
from django_components.template_parser import process_aggregate_kwargs from django_components.template_parser import process_aggregate_kwargs
from django_components.templatetags.component_tags import _parse_component_with_args from django_components.templatetags.component_tags import _parse_component_with_args
@ -56,7 +56,7 @@ class ParserTest(BaseTestCase):
class ParserComponentTest(BaseTestCase): class ParserComponentTest(BaseTestCase):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template: types.django_html = """ template: types.django_html = """
{{ date }} {{ date }}
{{ id }} {{ id }}
@ -72,7 +72,7 @@ class ParserComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_special_chars_accessible_via_kwargs(self): def test_special_chars_accessible_via_kwargs(self):
component.registry.register("test", self.SimpleComponent) registry.register("test", self.SimpleComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}

View file

@ -4,7 +4,7 @@ from typing import Callable
from django.template import Context, Template from django.template import Context, Template
from django_components import component, types from django_components import Component, register, registry, types
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior from .testutils import BaseTestCase, parametrize_context_behavior
@ -12,7 +12,7 @@ from .testutils import BaseTestCase, parametrize_context_behavior
setup_test_config() setup_test_config()
class SlottedComponent(component.Component): class SlottedComponent(Component):
template_name = "slotted_template.html" template_name = "slotted_template.html"
@ -34,11 +34,11 @@ class TemplateInstrumentationTest(BaseTestCase):
self.saved_render_method = Template._render self.saved_render_method = Template._render
Template._render = instrumented_test_render Template._render = instrumented_test_render
component.registry.clear() registry.clear()
component.registry.register("test_component", SlottedComponent) registry.register("test_component", SlottedComponent)
@component.register("inner_component") @register("inner_component")
class SimpleComponent(component.Component): class SimpleComponent(Component):
template_name = "simple_template.html" template_name = "simple_template.html"
def get_context_data(self, variable, variable2="default"): def get_context_data(self, variable, variable2="default"):
@ -93,19 +93,19 @@ class TemplateInstrumentationTest(BaseTestCase):
class BlockCompatTests(BaseTestCase): class BlockCompatTests(BaseTestCase):
def setUp(self): def setUp(self):
component.registry.clear() registry.clear()
super().setUp() super().setUp()
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
component.registry.clear() registry.clear()
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slots_inside_extends(self): def test_slots_inside_extends(self):
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_extends") @register("slot_inside_extends")
class SlotInsideExtendsComponent(component.Component): class SlotInsideExtendsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% extends "block_in_slot_in_component.html" %} {% extends "block_in_slot_in_component.html" %}
""" """
@ -135,10 +135,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slots_inside_include(self): def test_slots_inside_include(self):
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_include") @register("slot_inside_include")
class SlotInsideIncludeComponent(component.Component): class SlotInsideIncludeComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% include "block_in_slot_in_component.html" %} {% include "block_in_slot_in_component.html" %}
""" """
@ -168,7 +168,7 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_component_inside_block(self): def test_component_inside_block(self):
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
template: types.django_html = """ template: types.django_html = """
{% extends "block.html" %} {% extends "block.html" %}
{% load component_tags %} {% load component_tags %}
@ -203,7 +203,7 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_block_inside_component(self): def test_block_inside_component(self):
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
template: types.django_html = """ template: types.django_html = """
{% extends "block_in_component.html" %} {% extends "block_in_component.html" %}
@ -233,10 +233,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_block_inside_component_parent(self): def test_block_inside_component_parent(self):
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
@component.register("block_in_component_parent") @register("block_in_component_parent")
class BlockInCompParent(component.Component): class BlockInCompParent(Component):
template_name = "block_in_component_parent.html" template_name = "block_in_component_parent.html"
template: types.django_html = """ template: types.django_html = """
@ -266,10 +266,10 @@ class BlockCompatTests(BaseTestCase):
Assert that when we call a component with `{% component %}`, that Assert that when we call a component with `{% component %}`, that
the `{% block %}` will NOT affect the inner component. the `{% block %}` will NOT affect the inner component.
""" """
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
@component.register("block_inside_slot_v1") @register("block_inside_slot_v1")
class BlockInSlotInComponent(component.Component): class BlockInSlotInComponent(Component):
template_name = "block_in_slot_in_component.html" template_name = "block_in_slot_in_component.html"
template: types.django_html = """ template: types.django_html = """
@ -301,10 +301,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_default_block_default(self): def test_slot_inside_block__slot_default_block_default(self):
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_block") @register("slot_inside_block")
class _SlotInsideBlockComponent(component.Component): class _SlotInsideBlockComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% extends "slot_inside_block.html" %} {% extends "slot_inside_block.html" %}
""" """
@ -333,11 +333,11 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_default_block_override(self): def test_slot_inside_block__slot_default_block_override(self):
component.registry.clear() registry.clear()
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_block") @register("slot_inside_block")
class _SlotInsideBlockComponent(component.Component): class _SlotInsideBlockComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% extends "slot_inside_block.html" %} {% extends "slot_inside_block.html" %}
{% block inner %} {% block inner %}
@ -369,10 +369,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["isolated", "django"]) @parametrize_context_behavior(["isolated", "django"])
def test_slot_inside_block__slot_overriden_block_default(self): def test_slot_inside_block__slot_overriden_block_default(self):
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_block") @register("slot_inside_block")
class _SlotInsideBlockComponent(component.Component): class _SlotInsideBlockComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% extends "slot_inside_block.html" %} {% extends "slot_inside_block.html" %}
""" """
@ -405,10 +405,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_overriden_block_overriden(self): def test_slot_inside_block__slot_overriden_block_overriden(self):
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_block") @register("slot_inside_block")
class _SlotInsideBlockComponent(component.Component): class _SlotInsideBlockComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% extends "slot_inside_block.html" %} {% extends "slot_inside_block.html" %}
{% block inner %} {% block inner %}
@ -451,10 +451,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_inject_inside_block(self): def test_inject_inside_block(self):
component.registry.register("slotted_component", SlottedComponent) registry.register("slotted_component", SlottedComponent)
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """

View file

@ -2,7 +2,7 @@ import textwrap
from django.template import Context, Template, TemplateSyntaxError from django.template import Context, Template, TemplateSyntaxError
from django_components import component, component_registry, types from django_components import Component, NotRegistered, register, registry, types
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior from .testutils import BaseTestCase, parametrize_context_behavior
@ -10,11 +10,11 @@ from .testutils import BaseTestCase, parametrize_context_behavior
setup_test_config() setup_test_config()
class SlottedComponent(component.Component): class SlottedComponent(Component):
template_name = "slotted_template.html" template_name = "slotted_template.html"
class SlottedComponentWithContext(component.Component): class SlottedComponentWithContext(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<custom-template> <custom-template>
@ -34,7 +34,7 @@ class SlottedComponentWithContext(component.Component):
class ComponentTemplateTagTest(BaseTestCase): class ComponentTemplateTagTest(BaseTestCase):
class SimpleComponent(component.Component): class SimpleComponent(Component):
template_name = "simple_template.html" template_name = "simple_template.html"
def get_context_data(self, variable, variable2="default"): def get_context_data(self, variable, variable2="default"):
@ -48,12 +48,12 @@ class ComponentTemplateTagTest(BaseTestCase):
js = "script.js" js = "script.js"
def setUp(self): def setUp(self):
# NOTE: component.registry is global, so need to clear before each test # NOTE: registry is global, so need to clear before each test
component.registry.clear() registry.clear()
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_single_component(self): def test_single_component(self):
component.registry.register(name="test", component=self.SimpleComponent) registry.register(name="test", component=self.SimpleComponent)
simple_tag_template: types.django_html = """ simple_tag_template: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -74,12 +74,12 @@ class ComponentTemplateTagTest(BaseTestCase):
""" """
template = Template(simple_tag_template) template = Template(simple_tag_template)
with self.assertRaises(component_registry.NotRegistered): with self.assertRaises(NotRegistered):
template.render(Context({})) template.render(Context({}))
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_component_called_with_positional_name(self): def test_component_called_with_positional_name(self):
component.registry.register(name="test", component=self.SimpleComponent) registry.register(name="test", component=self.SimpleComponent)
simple_tag_template: types.django_html = """ simple_tag_template: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -92,8 +92,8 @@ class ComponentTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_call_component_with_two_variables(self): def test_call_component_with_two_variables(self):
@component.register("test") @register("test")
class IffedComponent(component.Component): class IffedComponent(Component):
template: types.django_html = """ template: types.django_html = """
Variable: <strong>{{ variable }}</strong> Variable: <strong>{{ variable }}</strong>
{% if variable2 != "default" %} {% if variable2 != "default" %}
@ -123,7 +123,7 @@ class ComponentTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_component_called_with_singlequoted_name(self): def test_component_called_with_singlequoted_name(self):
component.registry.register(name="test", component=self.SimpleComponent) registry.register(name="test", component=self.SimpleComponent)
simple_tag_template: types.django_html = """ simple_tag_template: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -136,7 +136,7 @@ class ComponentTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_component_called_with_variable_as_name(self): def test_component_called_with_variable_as_name(self):
component.registry.register(name="test", component=self.SimpleComponent) registry.register(name="test", component=self.SimpleComponent)
simple_tag_template: types.django_html = """ simple_tag_template: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -151,7 +151,7 @@ class ComponentTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_component_called_with_invalid_variable_as_name(self): def test_component_called_with_invalid_variable_as_name(self):
component.registry.register(name="test", component=self.SimpleComponent) registry.register(name="test", component=self.SimpleComponent)
simple_tag_template: types.django_html = """ simple_tag_template: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -161,13 +161,13 @@ class ComponentTemplateTagTest(BaseTestCase):
""" """
template = Template(simple_tag_template) template = Template(simple_tag_template)
with self.assertRaises(component_registry.NotRegistered): with self.assertRaises(NotRegistered):
template.render(Context({})) template.render(Context({}))
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_component_accepts_provided_and_default_parameters(self): def test_component_accepts_provided_and_default_parameters(self):
@component.register("test") @register("test")
class ComponentWithProvidedAndDefaultParameters(component.Component): class ComponentWithProvidedAndDefaultParameters(Component):
template: types.django_html = """ template: types.django_html = """
Provided variable: <strong>{{ variable }}</strong> Provided variable: <strong>{{ variable }}</strong>
Default: <p>{{ default_param }}</p> Default: <p>{{ default_param }}</p>
@ -191,11 +191,11 @@ class ComponentTemplateTagTest(BaseTestCase):
class MultiComponentTests(BaseTestCase): class MultiComponentTests(BaseTestCase):
def setUp(self): def setUp(self):
component.registry.clear() registry.clear()
def register_components(self): def register_components(self):
component.registry.register("first_component", SlottedComponent) registry.register("first_component", SlottedComponent)
component.registry.register("second_component", SlottedComponentWithContext) registry.register("second_component", SlottedComponentWithContext)
def make_template(self, first_slot: str = "", second_slot: str = "") -> Template: def make_template(self, first_slot: str = "", second_slot: str = "") -> Template:
template_str: types.django_html = f""" template_str: types.django_html = f"""
@ -266,7 +266,7 @@ class MultiComponentTests(BaseTestCase):
class ComponentIsolationTests(BaseTestCase): class ComponentIsolationTests(BaseTestCase):
def setUp(self): def setUp(self):
class SlottedComponent(component.Component): class SlottedComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<custom-template> <custom-template>
@ -276,7 +276,7 @@ class ComponentIsolationTests(BaseTestCase):
</custom-template> </custom-template>
""" """
component.registry.register("test", SlottedComponent) registry.register("test", SlottedComponent)
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_instances_of_component_do_not_share_slots(self): def test_instances_of_component_do_not_share_slots(self):
@ -322,8 +322,8 @@ class ComponentIsolationTests(BaseTestCase):
class AggregateInputTests(BaseTestCase): class AggregateInputTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_agg_input_accessible_in_get_context_data(self): def test_agg_input_accessible_in_get_context_data(self):
@component.register("test") @register("test")
class AttrsComponent(component.Component): class AttrsComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -356,11 +356,11 @@ class AggregateInputTests(BaseTestCase):
class ComponentTemplateSyntaxErrorTests(BaseTestCase): class ComponentTemplateSyntaxErrorTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register("test", SlottedComponent) registry.register("test", SlottedComponent)
def tearDown(self) -> None: def tearDown(self) -> None:
super().tearDown() super().tearDown()
component.registry.clear() registry.clear()
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_variable_outside_fill_tag_compiles_w_out_error(self): def test_variable_outside_fill_tag_compiles_w_out_error(self):

View file

@ -1,6 +1,6 @@
from django.template import Context, Template, TemplateSyntaxError from django.template import Context, Template, TemplateSyntaxError
from django_components import component, types from django_components import Component, register, types
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior from .testutils import BaseTestCase, parametrize_context_behavior
@ -11,8 +11,8 @@ setup_test_config()
class ProvideTemplateTagTest(BaseTestCase): class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_provide_basic(self): def test_provide_basic(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -40,8 +40,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_provide_access_keys_in_python(self): def test_provide_access_keys_in_python(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> key: {{ key }} </div> <div> key: {{ key }} </div>
<div> another: {{ another }} </div> <div> another: {{ another }} </div>
@ -74,8 +74,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_provide_access_keys_in_django(self): def test_provide_access_keys_in_django(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> key: {{ my_provide.key }} </div> <div> key: {{ my_provide.key }} </div>
<div> another: {{ my_provide.another }} </div> <div> another: {{ my_provide.another }} </div>
@ -107,8 +107,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_provide_does_not_leak(self): def test_provide_does_not_leak(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -138,8 +138,8 @@ class ProvideTemplateTagTest(BaseTestCase):
def test_provide_empty(self): def test_provide_empty(self):
"""Check provide tag with no kwargs""" """Check provide tag with no kwargs"""
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -172,8 +172,8 @@ class ProvideTemplateTagTest(BaseTestCase):
def test_provide_no_inject(self): def test_provide_no_inject(self):
"""Check that nothing breaks if we do NOT inject even if some data is provided""" """Check that nothing breaks if we do NOT inject even if some data is provided"""
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div></div> <div></div>
""" """
@ -203,8 +203,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_provide_key_single_quotes(self): def test_provide_key_single_quotes(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -235,8 +235,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_provide_no_key_raises(self): def test_provide_no_key_raises(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -259,8 +259,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_provide_key_must_be_string_literal(self): def test_provide_key_must_be_string_literal(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -283,8 +283,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_provide_key_must_be_identifier(self): def test_provide_key_must_be_identifier(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -308,8 +308,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_provide_aggregate_dics(self): def test_provide_aggregate_dics(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -339,8 +339,8 @@ class ProvideTemplateTagTest(BaseTestCase):
def test_provide_does_not_expose_kwargs_to_context(self): def test_provide_does_not_expose_kwargs_to_context(self):
"""Check that `provide` tag doesn't assign the keys to the context like `with` tag does""" """Check that `provide` tag doesn't assign the keys to the context like `with` tag does"""
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -375,8 +375,8 @@ class ProvideTemplateTagTest(BaseTestCase):
def test_provide_nested_in_provide_same_key(self): def test_provide_nested_in_provide_same_key(self):
"""Check that inner `provide` with same key overshadows outer `provide`""" """Check that inner `provide` with same key overshadows outer `provide`"""
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -415,8 +415,8 @@ class ProvideTemplateTagTest(BaseTestCase):
def test_provide_nested_in_provide_different_key(self): def test_provide_nested_in_provide_different_key(self):
"""Check that `provide` tag with different keys don't affect each other""" """Check that `provide` tag with different keys don't affect each other"""
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> first_provide: {{ first_provide|safe }} </div> <div> first_provide: {{ first_provide|safe }} </div>
<div> second_provide: {{ second_provide|safe }} </div> <div> second_provide: {{ second_provide|safe }} </div>
@ -452,8 +452,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_provide_in_include(self): def test_provide_in_include(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -482,8 +482,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_in_provide(self): def test_slot_in_provide(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -492,8 +492,8 @@ class ProvideTemplateTagTest(BaseTestCase):
var = self.inject("my_provide", "default") var = self.inject("my_provide", "default")
return {"var": var} return {"var": var}
@component.register("parent") @register("parent")
class ParentComponent(component.Component): class ParentComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% provide "my_provide" key="hi" another=123 %} {% provide "my_provide" key="hi" another=123 %}
@ -523,8 +523,8 @@ class ProvideTemplateTagTest(BaseTestCase):
class InjectTest(BaseTestCase): class InjectTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_inject_basic(self): def test_inject_basic(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -552,8 +552,8 @@ class InjectTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_inject_missing_key_raises_without_default(self): def test_inject_missing_key_raises_without_default(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -574,8 +574,8 @@ class InjectTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_inject_missing_key_ok_with_default(self): def test_inject_missing_key_ok_with_default(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -600,8 +600,8 @@ class InjectTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_inject_empty_string(self): def test_inject_empty_string(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """
@ -626,8 +626,8 @@ class InjectTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_inject_raises_on_called_outside_get_context_data(self): def test_inject_raises_on_called_outside_get_context_data(self):
@component.register("injectee") @register("injectee")
class InjectComponent(component.Component): class InjectComponent(Component):
template: types.django_html = """ template: types.django_html = """
<div> injected: {{ var|safe }} </div> <div> injected: {{ var|safe }} </div>
""" """

View file

@ -2,7 +2,7 @@ from typing import Any, Dict, List, Optional
from django.template import Context, Template, TemplateSyntaxError from django.template import Context, Template, TemplateSyntaxError
from django_components import component, types from django_components import Component, register, registry, types
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior from .testutils import BaseTestCase, parametrize_context_behavior
@ -10,7 +10,7 @@ from .testutils import BaseTestCase, parametrize_context_behavior
setup_test_config() setup_test_config()
class SlottedComponent(component.Component): class SlottedComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<custom-template> <custom-template>
@ -33,15 +33,15 @@ class SlottedComponentWithContext(SlottedComponent):
class ComponentSlottedTemplateTagTest(BaseTestCase): class ComponentSlottedTemplateTagTest(BaseTestCase):
def setUp(self): def setUp(self):
# NOTE: component.registry is global, so need to clear before each test # NOTE: registry is global, so need to clear before each test
component.registry.clear() registry.clear()
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slotted_template_basic(self): def test_slotted_template_basic(self):
component.registry.register(name="test1", component=SlottedComponent) registry.register(name="test1", component=SlottedComponent)
@component.register("test2") @register("test2")
class SimpleComponent(component.Component): class SimpleComponent(Component):
template = """Variable: <strong>{{ variable }}</strong>""" template = """Variable: <strong>{{ variable }}</strong>"""
def get_context_data(self, variable, variable2="default"): def get_context_data(self, variable, variable2="default"):
@ -82,7 +82,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
# NOTE: Second arg is the expected output of `{{ variable }}` # NOTE: Second arg is the expected output of `{{ variable }}`
@parametrize_context_behavior([("django", "test456"), ("isolated", "")]) @parametrize_context_behavior([("django", "test456"), ("isolated", "")])
def test_slotted_template_with_context_var(self, context_behavior_data): def test_slotted_template_with_context_var(self, context_behavior_data):
component.registry.register(name="test1", component=SlottedComponentWithContext) registry.register(name="test1", component=SlottedComponentWithContext)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -113,7 +113,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slotted_template_no_slots_filled(self): def test_slotted_template_no_slots_filled(self):
component.registry.register(name="test", component=SlottedComponent) registry.register(name="test", component=SlottedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -135,8 +135,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slotted_template_without_slots(self): def test_slotted_template_without_slots(self):
@component.register("test") @register("test")
class SlottedComponentNoSlots(component.Component): class SlottedComponentNoSlots(Component):
template: types.django_html = """ template: types.django_html = """
<custom-template></custom-template> <custom-template></custom-template>
""" """
@ -152,8 +152,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slotted_template_without_slots_and_single_quotes(self): def test_slotted_template_without_slots_and_single_quotes(self):
@component.register("test") @register("test")
class SlottedComponentNoSlots(component.Component): class SlottedComponentNoSlots(Component):
template: types.django_html = """ template: types.django_html = """
<custom-template></custom-template> <custom-template></custom-template>
""" """
@ -169,7 +169,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_variable_fill_name(self): def test_variable_fill_name(self):
component.registry.register(name="test", component=SlottedComponent) registry.register(name="test", component=SlottedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% with slotname="header" %} {% with slotname="header" %}
@ -191,7 +191,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_missing_required_slot_raises_error(self): def test_missing_required_slot_raises_error(self):
class Component(component.Component): class Comp(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div class="header-box"> <div class="header-box">
@ -200,7 +200,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
</div> </div>
""" """
component.registry.register("test", Component) registry.register("test", Comp)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -214,8 +214,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_default_slot_is_fillable_by_implicit_fill_content(self): def test_default_slot_is_fillable_by_implicit_fill_content(self):
@component.register("test_comp") @register("test_comp")
class ComponentWithDefaultSlot(component.Component): class ComponentWithDefaultSlot(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -241,8 +241,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_default_slot_is_fillable_by_explicit_fill_content(self): def test_default_slot_is_fillable_by_explicit_fill_content(self):
@component.register("test_comp") @register("test_comp")
class ComponentWithDefaultSlot(component.Component): class ComponentWithDefaultSlot(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -267,8 +267,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_error_raised_when_default_and_required_slot_not_filled(self): def test_error_raised_when_default_and_required_slot_not_filled(self):
@component.register("test_comp") @register("test_comp")
class ComponentWithDefaultAndRequiredSlot(component.Component): class ComponentWithDefaultAndRequiredSlot(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -289,10 +289,10 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_fill_tag_can_occur_within_component_nested_in_implicit_fill(self): def test_fill_tag_can_occur_within_component_nested_in_implicit_fill(self):
component.registry.register("slotted", SlottedComponent) registry.register("slotted", SlottedComponent)
@component.register("test_comp") @register("test_comp")
class ComponentWithDefaultSlot(component.Component): class ComponentWithDefaultSlot(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -327,8 +327,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_error_from_mixed_implicit_and_explicit_fill_content(self): def test_error_from_mixed_implicit_and_explicit_fill_content(self):
@component.register("test_comp") @register("test_comp")
class ComponentWithDefaultSlot(component.Component): class ComponentWithDefaultSlot(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -348,8 +348,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_comments_permitted_inside_implicit_fill_content(self): def test_comments_permitted_inside_implicit_fill_content(self):
@component.register("test_comp") @register("test_comp")
class ComponentWithDefaultSlot(component.Component): class ComponentWithDefaultSlot(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -372,7 +372,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_component_without_default_slot_refuses_implicit_fill(self): def test_component_without_default_slot_refuses_implicit_fill(self):
component.registry.register("test_comp", SlottedComponent) registry.register("test_comp", SlottedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% component 'test_comp' %} {% component 'test_comp' %}
@ -387,7 +387,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_component_template_cannot_have_multiple_default_slots(self): def test_component_template_cannot_have_multiple_default_slots(self):
class BadComponent(component.Component): class BadComponent(Component):
def get_template(self, context, template_name: Optional[str] = None) -> Template: def get_template(self, context, template_name: Optional[str] = None) -> Template:
template_str: types.django_html = """ template_str: types.django_html = """
{% load django_components %} {% load django_components %}
@ -405,7 +405,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_name_fill_typo_gives_helpful_error_message(self): def test_slot_name_fill_typo_gives_helpful_error_message(self):
component.registry.register(name="test1", component=SlottedComponent) registry.register(name="test1", component=SlottedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -433,7 +433,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
# NOTE: This is relevant only for the "isolated" mode # NOTE: This is relevant only for the "isolated" mode
@parametrize_context_behavior(["isolated"]) @parametrize_context_behavior(["isolated"])
def test_slots_of_top_level_comps_can_access_full_outer_ctx(self): def test_slots_of_top_level_comps_can_access_full_outer_ctx(self):
class SlottedComponent(component.Component): class SlottedComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -446,7 +446,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
"name": name, "name": name,
} }
component.registry.register("test", SlottedComponent) registry.register("test", SlottedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -478,13 +478,13 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
class SlottedTemplateRegressionTests(BaseTestCase): class SlottedTemplateRegressionTests(BaseTestCase):
def setUp(self): def setUp(self):
# NOTE: component.registry is global, so need to clear before each test # NOTE: registry is global, so need to clear before each test
component.registry.clear() registry.clear()
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slotted_template_that_uses_missing_variable(self): def test_slotted_template_that_uses_missing_variable(self):
@component.register("test") @register("test")
class SlottedComponentWithMissingVariable(component.Component): class SlottedComponentWithMissingVariable(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<custom-template> <custom-template>
@ -517,12 +517,12 @@ class SlottedTemplateRegressionTests(BaseTestCase):
class SlotDefaultTests(BaseTestCase): class SlotDefaultTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.clear() registry.clear()
component.registry.register("test", SlottedComponent) registry.register("test", SlottedComponent)
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
component.registry.clear() registry.clear()
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_basic(self): def test_basic(self):
@ -654,8 +654,8 @@ class SlotDefaultTests(BaseTestCase):
class ScopedSlotTest(BaseTestCase): class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_data(self): def test_slot_data(self):
@component.register("test") @register("test")
class TestComponent(component.Component): class TestComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -689,8 +689,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_data_with_flags(self): def test_slot_data_with_flags(self):
@component.register("test") @register("test")
class TestComponent(component.Component): class TestComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -724,8 +724,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_data_with_slot_default(self): def test_slot_data_with_slot_default(self):
@component.register("test") @register("test")
class TestComponent(component.Component): class TestComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -761,8 +761,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_data_raises_on_slot_data_and_slot_default_same_var(self): def test_slot_data_raises_on_slot_data_and_slot_default_same_var(self):
@component.register("test") @register("test")
class TestComponent(component.Component): class TestComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -792,8 +792,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_data_fill_without_data(self): def test_slot_data_fill_without_data(self):
@component.register("test") @register("test")
class TestComponent(component.Component): class TestComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -821,8 +821,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_data_fill_without_slot_data(self): def test_slot_data_fill_without_slot_data(self):
@component.register("test") @register("test")
class TestComponent(component.Component): class TestComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -844,8 +844,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_slot_data_no_fill(self): def test_slot_data_no_fill(self):
@component.register("test") @register("test")
class TestComponent(component.Component): class TestComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -870,8 +870,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_nested_fills(self): def test_nested_fills(self):
@component.register("test") @register("test")
class TestComponent(component.Component): class TestComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -917,7 +917,7 @@ class ScopedSlotTest(BaseTestCase):
class DuplicateSlotTest(BaseTestCase): class DuplicateSlotTest(BaseTestCase):
class DuplicateSlotComponent(component.Component): class DuplicateSlotComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<header>{% slot "header" %}Default header{% endslot %}</header> <header>{% slot "header" %}Default header{% endslot %}</header>
@ -931,7 +931,7 @@ class DuplicateSlotTest(BaseTestCase):
"name": name, "name": name,
} }
class DuplicateSlotNestedComponent(component.Component): class DuplicateSlotNestedComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% slot "header" %}START{% endslot %} {% slot "header" %}START{% endslot %}
@ -956,7 +956,7 @@ class DuplicateSlotTest(BaseTestCase):
"items": items, "items": items,
} }
class CalendarComponent(component.Component): class CalendarComponent(Component):
"""Nested in ComponentWithNestedComponent""" """Nested in ComponentWithNestedComponent"""
template: types.django_html = """ template: types.django_html = """
@ -975,9 +975,9 @@ class DuplicateSlotTest(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register(name="duplicate_slot", component=self.DuplicateSlotComponent) registry.register(name="duplicate_slot", component=self.DuplicateSlotComponent)
component.registry.register(name="duplicate_slot_nested", component=self.DuplicateSlotNestedComponent) registry.register(name="duplicate_slot_nested", component=self.DuplicateSlotNestedComponent)
component.registry.register(name="calendar", component=self.CalendarComponent) registry.register(name="calendar", component=self.CalendarComponent)
# NOTE: Second arg is the input for the "name" component kwarg # NOTE: Second arg is the input for the "name" component kwarg
@parametrize_context_behavior( @parametrize_context_behavior(
@ -1113,11 +1113,11 @@ class DuplicateSlotTest(BaseTestCase):
class SlotFillTemplateSyntaxErrorTests(BaseTestCase): class SlotFillTemplateSyntaxErrorTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.register("test", SlottedComponent) registry.register("test", SlottedComponent)
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
component.registry.clear() registry.clear()
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_fill_with_no_parent_is_error(self): def test_fill_with_no_parent_is_error(self):
@ -1130,8 +1130,8 @@ class SlotFillTemplateSyntaxErrorTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_isolated_slot_is_error(self): def test_isolated_slot_is_error(self):
@component.register("broken_component") @register("broken_component")
class BrokenComponent(component.Component): class BrokenComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% include 'slotted_template.html' with context=None only %} {% include 'slotted_template.html' with context=None only %}
@ -1180,7 +1180,7 @@ class SlotBehaviorTests(BaseTestCase):
# NOTE: This is standalone function instead of setUp, so we can configure # NOTE: This is standalone function instead of setUp, so we can configure
# Django settings per test with `@override_settings` # Django settings per test with `@override_settings`
def make_template(self) -> Template: def make_template(self) -> Template:
class SlottedComponent(component.Component): class SlottedComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<custom-template> <custom-template>
@ -1195,7 +1195,7 @@ class SlotBehaviorTests(BaseTestCase):
"name": name, "name": name,
} }
component.registry.register("test", SlottedComponent) registry.register("test", SlottedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}

View file

@ -4,7 +4,7 @@ from typing import Any, Dict, Optional
from django.template import Context, Template from django.template import Context, Template
from django_components import component, types from django_components import Component, registry, types
from .django_test_setup import setup_test_config from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior from .testutils import BaseTestCase, parametrize_context_behavior
@ -12,7 +12,7 @@ from .testutils import BaseTestCase, parametrize_context_behavior
setup_test_config() setup_test_config()
class SlottedComponent(component.Component): class SlottedComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<custom-template> <custom-template>
@ -34,7 +34,7 @@ class SlottedComponentWithContext(SlottedComponent):
class NestedSlotTests(BaseTestCase): class NestedSlotTests(BaseTestCase):
class NestedComponent(component.Component): class NestedComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% slot 'outer' %} {% slot 'outer' %}
@ -44,8 +44,8 @@ class NestedSlotTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_default_slot_contents_render_correctly(self): def test_default_slot_contents_render_correctly(self):
component.registry.clear() registry.clear()
component.registry.register("test", self.NestedComponent) registry.register("test", self.NestedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% component 'test' %}{% endcomponent %} {% component 'test' %}{% endcomponent %}
@ -56,8 +56,8 @@ class NestedSlotTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_inner_slot_overriden(self): def test_inner_slot_overriden(self):
component.registry.clear() registry.clear()
component.registry.register("test", self.NestedComponent) registry.register("test", self.NestedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% component 'test' %} {% component 'test' %}
@ -70,8 +70,8 @@ class NestedSlotTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_outer_slot_overriden(self): def test_outer_slot_overriden(self):
component.registry.clear() registry.clear()
component.registry.register("test", self.NestedComponent) registry.register("test", self.NestedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% component 'test' %}{% fill 'outer' %}<p>Override</p>{% endfill %}{% endcomponent %} {% component 'test' %}{% fill 'outer' %}<p>Override</p>{% endfill %}{% endcomponent %}
@ -82,8 +82,8 @@ class NestedSlotTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_both_overriden_and_inner_removed(self): def test_both_overriden_and_inner_removed(self):
component.registry.clear() registry.clear()
component.registry.register("test", self.NestedComponent) registry.register("test", self.NestedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% component 'test' %} {% component 'test' %}
@ -100,7 +100,7 @@ class NestedSlotTests(BaseTestCase):
# remain top-level context. # remain top-level context.
@parametrize_context_behavior([("django", "Joe2"), ("isolated", "Jannete")]) @parametrize_context_behavior([("django", "Joe2"), ("isolated", "Jannete")])
def test_fill_inside_fill_with_same_name(self, context_behavior_data): def test_fill_inside_fill_with_same_name(self, context_behavior_data):
class SlottedComponent(component.Component): class SlottedComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<custom-template> <custom-template>
@ -115,8 +115,8 @@ class NestedSlotTests(BaseTestCase):
"name": name, "name": name,
} }
component.registry.clear() registry.clear()
component.registry.register("test", SlottedComponent) registry.register("test", SlottedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -163,7 +163,7 @@ class NestedSlotTests(BaseTestCase):
# NOTE: This test group are kept for backward compatibility, as the same logic # NOTE: This test group are kept for backward compatibility, as the same logic
# as provided by {% if %} tags was previously provided by this library. # as provided by {% if %} tags was previously provided by this library.
class ConditionalSlotTests(BaseTestCase): class ConditionalSlotTests(BaseTestCase):
class ConditionalComponent(component.Component): class ConditionalComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% if branch == 'a' %} {% if branch == 'a' %}
@ -178,12 +178,12 @@ class ConditionalSlotTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
component.registry.clear() registry.clear()
component.registry.register("test", self.ConditionalComponent) registry.register("test", self.ConditionalComponent)
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
component.registry.clear() registry.clear()
@parametrize_context_behavior(["django", "isolated"]) @parametrize_context_behavior(["django", "isolated"])
def test_no_content_if_branches_are_false(self): def test_no_content_if_branches_are_false(self):
@ -245,7 +245,7 @@ class ConditionalSlotTests(BaseTestCase):
class SlotIterationTest(BaseTestCase): class SlotIterationTest(BaseTestCase):
"""Tests a behaviour of {% fill .. %} tag which is inside a template {% for .. %} loop.""" """Tests a behaviour of {% fill .. %} tag which is inside a template {% for .. %} loop."""
class ComponentSimpleSlotInALoop(component.Component): class ComponentSimpleSlotInALoop(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
{% for object in objects %} {% for object in objects %}
@ -261,7 +261,7 @@ class SlotIterationTest(BaseTestCase):
} }
def setUp(self): def setUp(self):
component.registry.clear() registry.clear()
# NOTE: Second arg in tuple is expected result. In isolated mode, loops should NOT leak. # NOTE: Second arg in tuple is expected result. In isolated mode, loops should NOT leak.
@parametrize_context_behavior( @parametrize_context_behavior(
@ -271,7 +271,7 @@ class SlotIterationTest(BaseTestCase):
] ]
) )
def test_inner_slot_iteration_basic(self, context_behavior_data): def test_inner_slot_iteration_basic(self, context_behavior_data):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop) registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -296,7 +296,7 @@ class SlotIterationTest(BaseTestCase):
] ]
) )
def test_inner_slot_iteration_with_variable_from_outer_scope(self, context_behavior_data): def test_inner_slot_iteration_with_variable_from_outer_scope(self, context_behavior_data):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop) registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}
@ -328,7 +328,7 @@ class SlotIterationTest(BaseTestCase):
] ]
) )
def test_inner_slot_iteration_nested(self, context_behavior_data): def test_inner_slot_iteration_nested(self, context_behavior_data):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop) registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [ objects = [
{"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]}, {"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
@ -375,7 +375,7 @@ class SlotIterationTest(BaseTestCase):
] ]
) )
def test_inner_slot_iteration_nested_with_outer_scope_variable(self, context_behavior_data): def test_inner_slot_iteration_nested_with_outer_scope_variable(self, context_behavior_data):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop) registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [ objects = [
{"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]}, {"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
@ -417,7 +417,7 @@ class SlotIterationTest(BaseTestCase):
] ]
) )
def test_inner_slot_iteration_nested_with_slot_default(self, context_behavior_data): def test_inner_slot_iteration_nested_with_slot_default(self, context_behavior_data):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop) registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [ objects = [
{"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]}, {"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
@ -469,7 +469,7 @@ class SlotIterationTest(BaseTestCase):
self, self,
context_behavior_data, context_behavior_data,
): ):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop) registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [ objects = [
{"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]}, {"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
@ -506,7 +506,7 @@ class SlotIterationTest(BaseTestCase):
def test_inner_slot_iteration_nested_with_slot_default_and_outer_scope_variable__isolated_2( def test_inner_slot_iteration_nested_with_slot_default_and_outer_scope_variable__isolated_2(
self, self,
): ):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop) registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [ objects = [
{"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]}, {"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
@ -559,7 +559,7 @@ class SlotIterationTest(BaseTestCase):
class ComponentNestingTests(BaseTestCase): class ComponentNestingTests(BaseTestCase):
class CalendarComponent(component.Component): class CalendarComponent(Component):
"""Nested in ComponentWithNestedComponent""" """Nested in ComponentWithNestedComponent"""
template: types.django_html = """ template: types.django_html = """
@ -576,7 +576,7 @@ class ComponentNestingTests(BaseTestCase):
</div> </div>
""" """
class DashboardComponent(component.Component): class DashboardComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div class="dashboard-component"> <div class="dashboard-component">
@ -594,7 +594,7 @@ class ComponentNestingTests(BaseTestCase):
</div> </div>
""" """
class ComplexChildComponent(component.Component): class ComplexChildComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<div> <div>
@ -604,7 +604,7 @@ class ComponentNestingTests(BaseTestCase):
</div> </div>
""" """
class ComplexParentComponent(component.Component): class ComplexParentComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
ITEMS: {{ items|safe }} ITEMS: {{ items|safe }}
@ -622,14 +622,14 @@ class ComponentNestingTests(BaseTestCase):
def setUp(self) -> None: def setUp(self) -> None:
super().setUp() super().setUp()
component.registry.register("dashboard", self.DashboardComponent) registry.register("dashboard", self.DashboardComponent)
component.registry.register("calendar", self.CalendarComponent) registry.register("calendar", self.CalendarComponent)
component.registry.register("complex_child", self.ComplexChildComponent) registry.register("complex_child", self.ComplexChildComponent)
component.registry.register("complex_parent", self.ComplexParentComponent) registry.register("complex_parent", self.ComplexParentComponent)
def tearDown(self) -> None: def tearDown(self) -> None:
super().tearDown() super().tearDown()
component.registry.clear() registry.clear()
# NOTE: Second arg in tuple are expected names in nested fills. In "django" mode, # 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 # the value should be overridden by the component, while in "isolated" it should
@ -638,7 +638,7 @@ class ComponentNestingTests(BaseTestCase):
def test_component_inside_slot(self, context_behavior_data): def test_component_inside_slot(self, context_behavior_data):
first_name, second_name = context_behavior_data first_name, second_name = context_behavior_data
class SlottedComponent(component.Component): class SlottedComponent(Component):
template: types.django_html = """ template: types.django_html = """
{% load component_tags %} {% load component_tags %}
<custom-template> <custom-template>
@ -653,7 +653,7 @@ class ComponentNestingTests(BaseTestCase):
"name": name, "name": name,
} }
component.registry.register("test", SlottedComponent) registry.register("test", SlottedComponent)
template_str: types.django_html = """ template_str: types.django_html = """
{% load component_tags %} {% load component_tags %}