mirror of
https://github.com/django-components/django-components.git
synced 2025-07-07 17:34:59 +00:00
* refactor: Cache components' JS and CSS scripts at class creation time * refactor: add test for no template_rendered signal for component with no template
This commit is contained in:
parent
007009a480
commit
c692b7a310
13 changed files with 138 additions and 58 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,5 +1,23 @@
|
||||||
# Release notes
|
# Release notes
|
||||||
|
|
||||||
|
## v0.141.1
|
||||||
|
|
||||||
|
#### Fix
|
||||||
|
|
||||||
|
- Components' JS and CSS scripts (e.g. from `Component.js` or `Component.js_file`) are now cached at class creation time.
|
||||||
|
|
||||||
|
This means that when you now restart the server while having a page opened in the browser,
|
||||||
|
the JS / CSS files are immediately available.
|
||||||
|
|
||||||
|
Previously, the JS/CSS were cached only after the components were rendered. So you had to reload
|
||||||
|
the page to trigger the rendering, in order to make the JS/CSS files available.
|
||||||
|
|
||||||
|
- Fix the default cache for JS / CSS scripts to be unbounded.
|
||||||
|
|
||||||
|
Previously, the default cache for the JS/CSS scripts (`LocMemCache`) was accidentally limited to 300 entries (~150 components).
|
||||||
|
|
||||||
|
- Do not send `template_rendered` signal when rendering a component with no template. ([#1277](https://github.com/django-components/django-components/issues/1277))
|
||||||
|
|
||||||
## v0.141.0
|
## v0.141.0
|
||||||
|
|
||||||
#### Feat
|
#### Feat
|
||||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "django_components"
|
name = "django_components"
|
||||||
version = "0.141.0"
|
version = "0.141.1"
|
||||||
requires-python = ">=3.8, <4.0"
|
requires-python = ">=3.8, <4.0"
|
||||||
description = "A way to create simple reusable template components in Django."
|
description = "A way to create simple reusable template components in Django."
|
||||||
keywords = ["django", "components", "css", "js", "html"]
|
keywords = ["django", "components", "css", "js", "html"]
|
||||||
|
|
|
@ -800,6 +800,7 @@ class InternalSettings:
|
||||||
from django_components.extensions.cache import CacheExtension
|
from django_components.extensions.cache import CacheExtension
|
||||||
from django_components.extensions.debug_highlight import DebugHighlightExtension
|
from django_components.extensions.debug_highlight import DebugHighlightExtension
|
||||||
from django_components.extensions.defaults import DefaultsExtension
|
from django_components.extensions.defaults import DefaultsExtension
|
||||||
|
from django_components.extensions.dependencies import DependenciesExtension
|
||||||
from django_components.extensions.view import ViewExtension
|
from django_components.extensions.view import ViewExtension
|
||||||
|
|
||||||
extensions = cast(
|
extensions = cast(
|
||||||
|
@ -807,6 +808,7 @@ class InternalSettings:
|
||||||
[
|
[
|
||||||
CacheExtension,
|
CacheExtension,
|
||||||
DefaultsExtension,
|
DefaultsExtension,
|
||||||
|
DependenciesExtension,
|
||||||
ViewExtension,
|
ViewExtension,
|
||||||
DebugHighlightExtension,
|
DebugHighlightExtension,
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import sys
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.core.cache import BaseCache, caches
|
from django.core.cache import BaseCache, caches
|
||||||
|
@ -36,9 +37,14 @@ def get_component_media_cache() -> BaseCache:
|
||||||
component_media_cache = LocMemCache(
|
component_media_cache = LocMemCache(
|
||||||
"django-components-media",
|
"django-components-media",
|
||||||
{
|
{
|
||||||
"TIMEOUT": None, # No timeout
|
# No max size nor timeout
|
||||||
"MAX_ENTRIES": None, # No max size
|
# NOTE: Implementation of `BaseCache` coerces the `MAX_ENTRIES` value
|
||||||
"CULL_FREQUENCY": 3,
|
# to `int()` so we use exact max size instead of `inf` or `None`.
|
||||||
|
# See https://github.com/django/django/blob/94ebcf8366d62f6360851b40e9c4dfe3f71d202f/django/core/cache/backends/base.py#L73 # noqa: E501
|
||||||
|
"TIMEOUT": None,
|
||||||
|
"OPTIONS": {
|
||||||
|
"MAX_ENTRIES": sys.maxsize,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3486,11 +3486,12 @@ class Component(metaclass=ComponentMeta):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Process Component's JS and CSS
|
# Cache component's JS and CSS scripts, in case they have been evicted from the cache.
|
||||||
cache_component_js(comp_cls)
|
cache_component_js(comp_cls, force=False)
|
||||||
js_input_hash = cache_component_js_vars(comp_cls, js_data) if js_data else None
|
cache_component_css(comp_cls, force=False)
|
||||||
|
|
||||||
cache_component_css(comp_cls)
|
# Create JS/CSS scripts that will load the JS/CSS variables into the page.
|
||||||
|
js_input_hash = cache_component_js_vars(comp_cls, js_data) if js_data else None
|
||||||
css_input_hash = cache_component_css_vars(comp_cls, css_data) if css_data else None
|
css_input_hash = cache_component_css_vars(comp_cls, css_data) if css_data else None
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -3670,7 +3671,7 @@ class Component(metaclass=ComponentMeta):
|
||||||
# ```
|
# ```
|
||||||
def _gen_component_renderer(
|
def _gen_component_renderer(
|
||||||
self,
|
self,
|
||||||
template: Template,
|
template: Optional[Template],
|
||||||
context: Context,
|
context: Context,
|
||||||
component_path: List[str],
|
component_path: List[str],
|
||||||
css_input_hash: Optional[str],
|
css_input_hash: Optional[str],
|
||||||
|
@ -3695,7 +3696,8 @@ class Component(metaclass=ComponentMeta):
|
||||||
component.on_render_before(context, template)
|
component.on_render_before(context, template)
|
||||||
|
|
||||||
# Emit signal that the template is about to be rendered
|
# Emit signal that the template is about to be rendered
|
||||||
template_rendered.send(sender=template, template=template, context=context)
|
if template is not None:
|
||||||
|
template_rendered.send(sender=template, template=template, context=context)
|
||||||
|
|
||||||
# Get the component's HTML
|
# Get the component's HTML
|
||||||
# To access the *final* output (with all its children rendered) from within `Component.on_render()`,
|
# To access the *final* output (with all its children rendered) from within `Component.on_render()`,
|
||||||
|
|
|
@ -107,13 +107,16 @@ def _cache_script(
|
||||||
cache.set(cache_key, script.strip())
|
cache.set(cache_key, script.strip())
|
||||||
|
|
||||||
|
|
||||||
def cache_component_js(comp_cls: Type["Component"]) -> None:
|
def cache_component_js(comp_cls: Type["Component"], force: bool) -> None:
|
||||||
"""
|
"""
|
||||||
Cache the content from `Component.js`. This is the common JS that's shared
|
Cache the content from `Component.js`. This is the common JS that's shared
|
||||||
among all instances of the same component. So even if the component is rendered multiple
|
among all instances of the same component. So even if the component is rendered multiple
|
||||||
times, this JS is loaded only once.
|
times, this JS is loaded only once.
|
||||||
"""
|
"""
|
||||||
if not comp_cls.js or not is_nonempty_str(comp_cls.js) or _is_script_in_cache(comp_cls, "js", None):
|
if not comp_cls.js or not is_nonempty_str(comp_cls.js):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not force and _is_script_in_cache(comp_cls, "js", None):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
_cache_script(
|
_cache_script(
|
||||||
|
@ -167,13 +170,16 @@ def wrap_component_js(comp_cls: Type["Component"], content: str) -> str:
|
||||||
return f"<script>{content}</script>"
|
return f"<script>{content}</script>"
|
||||||
|
|
||||||
|
|
||||||
def cache_component_css(comp_cls: Type["Component"]) -> None:
|
def cache_component_css(comp_cls: Type["Component"], force: bool) -> None:
|
||||||
"""
|
"""
|
||||||
Cache the content from `Component.css`. This is the common CSS that's shared
|
Cache the content from `Component.css`. This is the common CSS that's shared
|
||||||
among all instances of the same component. So even if the component is rendered multiple
|
among all instances of the same component. So even if the component is rendered multiple
|
||||||
times, this CSS is loaded only once.
|
times, this CSS is loaded only once.
|
||||||
"""
|
"""
|
||||||
if not comp_cls.css or not is_nonempty_str(comp_cls.css) or _is_script_in_cache(comp_cls, "css", None):
|
if not comp_cls.css or not is_nonempty_str(comp_cls.css):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not force and _is_script_in_cache(comp_cls, "css", None):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
_cache_script(
|
_cache_script(
|
||||||
|
|
21
src/django_components/extensions/dependencies.py
Normal file
21
src/django_components/extensions/dependencies.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from django_components.dependencies import cache_component_css, cache_component_js
|
||||||
|
from django_components.extension import (
|
||||||
|
ComponentExtension,
|
||||||
|
OnComponentClassCreatedContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DependenciesExtension(ComponentExtension):
|
||||||
|
"""
|
||||||
|
This extension adds a nested `Dependencies` class to each `Component`.
|
||||||
|
|
||||||
|
This extension is automatically added to all components.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "dependencies"
|
||||||
|
|
||||||
|
# Cache the component's JS and CSS scripts when the class is created, so that
|
||||||
|
# components' JS/CSS files are accessible even before having to render the component first.
|
||||||
|
def on_component_class_created(self, ctx: OnComponentClassCreatedContext) -> None:
|
||||||
|
cache_component_js(ctx.component_cls, force=True)
|
||||||
|
cache_component_css(ctx.component_cls, force=True)
|
|
@ -50,8 +50,6 @@ class TestImportLibraries:
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def test_import_libraries(self):
|
def test_import_libraries(self):
|
||||||
# Ensure we start with a clean state
|
|
||||||
registry.clear()
|
|
||||||
all_components = registry.all().copy()
|
all_components = registry.all().copy()
|
||||||
assert "single_file_component" not in all_components
|
assert "single_file_component" not in all_components
|
||||||
assert "multi_file_component" not in all_components
|
assert "multi_file_component" not in all_components
|
||||||
|
@ -80,8 +78,6 @@ class TestImportLibraries:
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def test_import_libraries_map_modules(self):
|
def test_import_libraries_map_modules(self):
|
||||||
# Ensure we start with a clean state
|
|
||||||
registry.clear()
|
|
||||||
all_components = registry.all().copy()
|
all_components = registry.all().copy()
|
||||||
assert "single_file_component" not in all_components
|
assert "single_file_component" not in all_components
|
||||||
assert "multi_file_component" not in all_components
|
assert "multi_file_component" not in all_components
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from django.core.cache.backends.locmem import LocMemCache
|
|
||||||
|
|
||||||
from django_components import Component, register
|
from django_components import Component, register
|
||||||
from django_components.testing import djc_test
|
from django_components.testing import djc_test
|
||||||
from django_components.util.cache import LRUCache
|
from django_components.util.cache import LRUCache
|
||||||
|
@ -68,16 +66,26 @@ class TestCache:
|
||||||
|
|
||||||
@djc_test
|
@djc_test
|
||||||
class TestComponentMediaCache:
|
class TestComponentMediaCache:
|
||||||
@djc_test(components_settings={"cache": "test-cache"})
|
@djc_test(
|
||||||
def test_component_media_caching(self):
|
components_settings={"cache": "test-cache"},
|
||||||
test_cache = LocMemCache(
|
django_settings={
|
||||||
"test-cache",
|
"CACHES": {
|
||||||
{
|
# See https://docs.djangoproject.com/en/5.2/topics/cache/#local-memory-caching
|
||||||
"TIMEOUT": None, # No timeout
|
"test-cache": {
|
||||||
"MAX_ENTRIES": None, # No max size
|
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||||
"CULL_FREQUENCY": 3,
|
"LOCATION": "test-cache",
|
||||||
|
"TIMEOUT": None, # No timeout
|
||||||
|
"OPTIONS": {
|
||||||
|
"MAX_ENTRIES": 10_000,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
},
|
||||||
|
)
|
||||||
|
def test_component_media_caching(self):
|
||||||
|
from django.core.cache import caches
|
||||||
|
|
||||||
|
test_cache = caches["test-cache"]
|
||||||
|
|
||||||
@register("test_simple")
|
@register("test_simple")
|
||||||
class TestSimpleComponent(Component):
|
class TestSimpleComponent(Component):
|
||||||
|
@ -108,14 +116,6 @@ class TestComponentMediaCache:
|
||||||
def get_css_data(self, args, kwargs, slots, context):
|
def get_css_data(self, args, kwargs, slots, context):
|
||||||
return {"color": "blue"}
|
return {"color": "blue"}
|
||||||
|
|
||||||
# Register our test cache
|
|
||||||
from django.core.cache import caches
|
|
||||||
|
|
||||||
caches["test-cache"] = test_cache
|
|
||||||
|
|
||||||
# Render the components to trigger caching
|
|
||||||
TestMediaAndVarsComponent.render()
|
|
||||||
|
|
||||||
# Check that JS/CSS is cached for components that have them
|
# Check that JS/CSS is cached for components that have them
|
||||||
assert test_cache.has_key(f"__components:{TestMediaAndVarsComponent.class_id}:js")
|
assert test_cache.has_key(f"__components:{TestMediaAndVarsComponent.class_id}:js")
|
||||||
assert test_cache.has_key(f"__components:{TestMediaAndVarsComponent.class_id}:css")
|
assert test_cache.has_key(f"__components:{TestMediaAndVarsComponent.class_id}:css")
|
||||||
|
@ -128,6 +128,9 @@ class TestComponentMediaCache:
|
||||||
assert test_cache.get(f"__components:{TestMediaNoVarsComponent.class_id}:js").strip() == "console.log('Hello from JS');" # noqa: E501
|
assert test_cache.get(f"__components:{TestMediaNoVarsComponent.class_id}:js").strip() == "console.log('Hello from JS');" # noqa: E501
|
||||||
assert test_cache.get(f"__components:{TestMediaNoVarsComponent.class_id}:css").strip() == ".novars-component { color: blue; }" # noqa: E501
|
assert test_cache.get(f"__components:{TestMediaNoVarsComponent.class_id}:css").strip() == ".novars-component { color: blue; }" # noqa: E501
|
||||||
|
|
||||||
|
# Render the components to trigger caching of JS/CSS variables from `get_js_data` / `get_css_data`
|
||||||
|
TestMediaAndVarsComponent.render()
|
||||||
|
|
||||||
# Check that we cache JS / CSS scripts generated from `get_js_data` / `get_css_data`
|
# Check that we cache JS / CSS scripts generated from `get_js_data` / `get_css_data`
|
||||||
# NOTE: The hashes is generated from the data.
|
# NOTE: The hashes is generated from the data.
|
||||||
js_vars_hash = "216ecc"
|
js_vars_hash = "216ecc"
|
||||||
|
|
|
@ -103,6 +103,7 @@ class TestExtensionsListCommand:
|
||||||
"===============\n"
|
"===============\n"
|
||||||
"cache \n"
|
"cache \n"
|
||||||
"defaults \n"
|
"defaults \n"
|
||||||
|
"dependencies \n"
|
||||||
"view \n"
|
"view \n"
|
||||||
"debug_highlight"
|
"debug_highlight"
|
||||||
)
|
)
|
||||||
|
@ -121,6 +122,7 @@ class TestExtensionsListCommand:
|
||||||
"===============\n"
|
"===============\n"
|
||||||
"cache \n"
|
"cache \n"
|
||||||
"defaults \n"
|
"defaults \n"
|
||||||
|
"dependencies \n"
|
||||||
"view \n"
|
"view \n"
|
||||||
"debug_highlight\n"
|
"debug_highlight\n"
|
||||||
"empty \n"
|
"empty \n"
|
||||||
|
@ -141,6 +143,7 @@ class TestExtensionsListCommand:
|
||||||
"===============\n"
|
"===============\n"
|
||||||
"cache \n"
|
"cache \n"
|
||||||
"defaults \n"
|
"defaults \n"
|
||||||
|
"dependencies \n"
|
||||||
"view \n"
|
"view \n"
|
||||||
"debug_highlight\n"
|
"debug_highlight\n"
|
||||||
"empty \n"
|
"empty \n"
|
||||||
|
@ -161,6 +164,7 @@ class TestExtensionsListCommand:
|
||||||
"===============\n"
|
"===============\n"
|
||||||
"cache \n"
|
"cache \n"
|
||||||
"defaults \n"
|
"defaults \n"
|
||||||
|
"dependencies \n"
|
||||||
"view \n"
|
"view \n"
|
||||||
"debug_highlight\n"
|
"debug_highlight\n"
|
||||||
"empty \n"
|
"empty \n"
|
||||||
|
@ -179,6 +183,7 @@ class TestExtensionsListCommand:
|
||||||
assert output.strip() == (
|
assert output.strip() == (
|
||||||
"cache \n"
|
"cache \n"
|
||||||
"defaults \n"
|
"defaults \n"
|
||||||
|
"dependencies \n"
|
||||||
"view \n"
|
"view \n"
|
||||||
"debug_highlight\n"
|
"debug_highlight\n"
|
||||||
"empty \n"
|
"empty \n"
|
||||||
|
|
|
@ -15,7 +15,6 @@ from django.utils.safestring import mark_safe
|
||||||
from pytest_django.asserts import assertHTMLEqual, assertInHTML
|
from pytest_django.asserts import assertHTMLEqual, assertInHTML
|
||||||
|
|
||||||
from django_components import Component, autodiscover, registry, render_dependencies, types
|
from django_components import Component, autodiscover, registry, render_dependencies, types
|
||||||
from django_components.component_media import UNSET
|
|
||||||
|
|
||||||
from django_components.testing import djc_test
|
from django_components.testing import djc_test
|
||||||
from .testutils import setup_test_config
|
from .testutils import setup_test_config
|
||||||
|
@ -204,14 +203,18 @@ class TestMainMedia:
|
||||||
class TestComponent(AppLvlCompComponent):
|
class TestComponent(AppLvlCompComponent):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# NOTE: Since this is a subclass, actual CSS is defined on the parent class, and thus
|
# NOTE: Currently the components' JS/CSS are loaded eagerly, to make the JS/CSS
|
||||||
# the corresponding ComponentMedia instance is also on the parent class.
|
# files available via endpoints. If that is no longer true, uncomment the
|
||||||
assert AppLvlCompComponent._component_media.css is UNSET # type: ignore[attr-defined]
|
# following lines to test the lazy loading of the CSS.
|
||||||
assert AppLvlCompComponent._component_media.css_file == "app_lvl_comp.css" # type: ignore[attr-defined]
|
#
|
||||||
assert AppLvlCompComponent._component_media._template is UNSET # type: ignore[attr-defined]
|
# # Since this is a subclass, actual CSS is defined on the parent class, and thus
|
||||||
|
# # the corresponding ComponentMedia instance is also on the parent class.
|
||||||
# Access the property to load the CSS
|
# assert AppLvlCompComponent._component_media.css is UNSET # type: ignore[attr-defined]
|
||||||
_ = TestComponent.css
|
# assert AppLvlCompComponent._component_media.css_file == "app_lvl_comp.css" # type: ignore[attr-defined]
|
||||||
|
# assert AppLvlCompComponent._component_media._template is UNSET # type: ignore[attr-defined]
|
||||||
|
#
|
||||||
|
# # Access the property to load the CSS
|
||||||
|
# _ = TestComponent.css
|
||||||
|
|
||||||
assert AppLvlCompComponent._component_media.css == (".html-css-only {\n" " color: blue;\n" "}\n") # type: ignore[attr-defined]
|
assert AppLvlCompComponent._component_media.css == (".html-css-only {\n" " color: blue;\n" "}\n") # type: ignore[attr-defined]
|
||||||
assert AppLvlCompComponent._component_media.css_file == "app_lvl_comp/app_lvl_comp.css" # type: ignore[attr-defined]
|
assert AppLvlCompComponent._component_media.css_file == "app_lvl_comp/app_lvl_comp.css" # type: ignore[attr-defined]
|
||||||
|
|
|
@ -31,6 +31,7 @@ from django_components.extension import (
|
||||||
from django_components.extensions.cache import CacheExtension
|
from django_components.extensions.cache import CacheExtension
|
||||||
from django_components.extensions.debug_highlight import DebugHighlightExtension
|
from django_components.extensions.debug_highlight import DebugHighlightExtension
|
||||||
from django_components.extensions.defaults import DefaultsExtension
|
from django_components.extensions.defaults import DefaultsExtension
|
||||||
|
from django_components.extensions.dependencies import DependenciesExtension
|
||||||
from django_components.extensions.view import ViewExtension
|
from django_components.extensions.view import ViewExtension
|
||||||
|
|
||||||
from django_components.testing import djc_test
|
from django_components.testing import djc_test
|
||||||
|
@ -219,12 +220,13 @@ class OverrideAssetExtension(ComponentExtension):
|
||||||
class TestExtension:
|
class TestExtension:
|
||||||
@djc_test(components_settings={"extensions": [DummyExtension]})
|
@djc_test(components_settings={"extensions": [DummyExtension]})
|
||||||
def test_extensions_setting(self):
|
def test_extensions_setting(self):
|
||||||
assert len(app_settings.EXTENSIONS) == 5
|
assert len(app_settings.EXTENSIONS) == 6
|
||||||
assert isinstance(app_settings.EXTENSIONS[0], CacheExtension)
|
assert isinstance(app_settings.EXTENSIONS[0], CacheExtension)
|
||||||
assert isinstance(app_settings.EXTENSIONS[1], DefaultsExtension)
|
assert isinstance(app_settings.EXTENSIONS[1], DefaultsExtension)
|
||||||
assert isinstance(app_settings.EXTENSIONS[2], ViewExtension)
|
assert isinstance(app_settings.EXTENSIONS[2], DependenciesExtension)
|
||||||
assert isinstance(app_settings.EXTENSIONS[3], DebugHighlightExtension)
|
assert isinstance(app_settings.EXTENSIONS[3], ViewExtension)
|
||||||
assert isinstance(app_settings.EXTENSIONS[4], DummyExtension)
|
assert isinstance(app_settings.EXTENSIONS[4], DebugHighlightExtension)
|
||||||
|
assert isinstance(app_settings.EXTENSIONS[5], DummyExtension)
|
||||||
|
|
||||||
@djc_test(components_settings={"extensions": [DummyExtension]})
|
@djc_test(components_settings={"extensions": [DummyExtension]})
|
||||||
def test_access_component_from_extension(self):
|
def test_access_component_from_extension(self):
|
||||||
|
@ -263,7 +265,7 @@ class TestExtension:
|
||||||
class TestExtensionHooks:
|
class TestExtensionHooks:
|
||||||
@djc_test(components_settings={"extensions": [DummyExtension]})
|
@djc_test(components_settings={"extensions": [DummyExtension]})
|
||||||
def test_component_class_lifecycle_hooks(self):
|
def test_component_class_lifecycle_hooks(self):
|
||||||
extension = cast(DummyExtension, app_settings.EXTENSIONS[4])
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
|
||||||
|
|
||||||
assert len(extension.calls["on_component_class_created"]) == 0
|
assert len(extension.calls["on_component_class_created"]) == 0
|
||||||
assert len(extension.calls["on_component_class_deleted"]) == 0
|
assert len(extension.calls["on_component_class_deleted"]) == 0
|
||||||
|
@ -295,7 +297,7 @@ class TestExtensionHooks:
|
||||||
|
|
||||||
@djc_test(components_settings={"extensions": [DummyExtension]})
|
@djc_test(components_settings={"extensions": [DummyExtension]})
|
||||||
def test_registry_lifecycle_hooks(self):
|
def test_registry_lifecycle_hooks(self):
|
||||||
extension = cast(DummyExtension, app_settings.EXTENSIONS[4])
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
|
||||||
|
|
||||||
assert len(extension.calls["on_registry_created"]) == 0
|
assert len(extension.calls["on_registry_created"]) == 0
|
||||||
assert len(extension.calls["on_registry_deleted"]) == 0
|
assert len(extension.calls["on_registry_deleted"]) == 0
|
||||||
|
@ -332,7 +334,7 @@ class TestExtensionHooks:
|
||||||
return {"name": kwargs.get("name", "World")}
|
return {"name": kwargs.get("name", "World")}
|
||||||
|
|
||||||
registry.register("test_comp", TestComponent)
|
registry.register("test_comp", TestComponent)
|
||||||
extension = cast(DummyExtension, app_settings.EXTENSIONS[4])
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
|
||||||
|
|
||||||
# Verify on_component_registered was called
|
# Verify on_component_registered was called
|
||||||
assert len(extension.calls["on_component_registered"]) == 1
|
assert len(extension.calls["on_component_registered"]) == 1
|
||||||
|
@ -370,7 +372,7 @@ class TestExtensionHooks:
|
||||||
test_slots = {"content": "Some content"}
|
test_slots = {"content": "Some content"}
|
||||||
TestComponent.render(context=test_context, args=("arg1", "arg2"), kwargs={"name": "Test"}, slots=test_slots)
|
TestComponent.render(context=test_context, args=("arg1", "arg2"), kwargs={"name": "Test"}, slots=test_slots)
|
||||||
|
|
||||||
extension = cast(DummyExtension, app_settings.EXTENSIONS[4])
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
|
||||||
|
|
||||||
# Verify on_component_input was called with correct args
|
# Verify on_component_input was called with correct args
|
||||||
assert len(extension.calls["on_component_input"]) == 1
|
assert len(extension.calls["on_component_input"]) == 1
|
||||||
|
@ -419,7 +421,7 @@ class TestExtensionHooks:
|
||||||
slots={"content": "Some content"},
|
slots={"content": "Some content"},
|
||||||
)
|
)
|
||||||
|
|
||||||
extension = cast(DummyExtension, app_settings.EXTENSIONS[4])
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
|
||||||
|
|
||||||
# Verify on_component_rendered was called with correct args
|
# Verify on_component_rendered was called with correct args
|
||||||
assert len(extension.calls["on_component_rendered"]) == 1
|
assert len(extension.calls["on_component_rendered"]) == 1
|
||||||
|
@ -448,7 +450,7 @@ class TestExtensionHooks:
|
||||||
|
|
||||||
assert rendered == "Hello Some content!"
|
assert rendered == "Hello Some content!"
|
||||||
|
|
||||||
extension = cast(DummyExtension, app_settings.EXTENSIONS[4])
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
|
||||||
|
|
||||||
# Verify on_slot_rendered was called with correct args
|
# Verify on_slot_rendered was called with correct args
|
||||||
assert len(extension.calls["on_slot_rendered"]) == 1
|
assert len(extension.calls["on_slot_rendered"]) == 1
|
||||||
|
@ -516,7 +518,7 @@ class TestExtensionHooks:
|
||||||
# Render the component to trigger all hooks
|
# Render the component to trigger all hooks
|
||||||
TestComponent.render(args=(), kwargs={"name": "Test"})
|
TestComponent.render(args=(), kwargs={"name": "Test"})
|
||||||
|
|
||||||
extension = cast(DummyExtension, app_settings.EXTENSIONS[4])
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
|
||||||
|
|
||||||
# on_template_loaded
|
# on_template_loaded
|
||||||
assert len(extension.calls["on_template_loaded"]) == 1
|
assert len(extension.calls["on_template_loaded"]) == 1
|
||||||
|
@ -559,7 +561,7 @@ class TestExtensionHooks:
|
||||||
# Render the component to trigger all hooks
|
# Render the component to trigger all hooks
|
||||||
TestComponent.render(args=(), kwargs={"name": "Test"})
|
TestComponent.render(args=(), kwargs={"name": "Test"})
|
||||||
|
|
||||||
extension = cast(DummyExtension, app_settings.EXTENSIONS[4])
|
extension = cast(DummyExtension, app_settings.EXTENSIONS[5])
|
||||||
|
|
||||||
# on_template_loaded
|
# on_template_loaded
|
||||||
# NOTE: The template file gets picked up by 'django.template.loaders.filesystem.Loader',
|
# NOTE: The template file gets picked up by 'django.template.loaders.filesystem.Loader',
|
||||||
|
|
|
@ -96,3 +96,19 @@ class TestTemplateSignal:
|
||||||
templates_used = _get_templates_used_to_render(template)
|
templates_used = _get_templates_used_to_render(template)
|
||||||
assert "slotted_template.html" in templates_used
|
assert "slotted_template.html" in templates_used
|
||||||
assert "simple_template.html" in templates_used
|
assert "simple_template.html" in templates_used
|
||||||
|
|
||||||
|
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
||||||
|
@with_template_signal
|
||||||
|
def test_template_rendered_skipped_when_no_template(self, components_settings):
|
||||||
|
class EmptyComponent(Component):
|
||||||
|
pass
|
||||||
|
|
||||||
|
registry.register("empty", EmptyComponent)
|
||||||
|
|
||||||
|
template_str: types.django_html = """
|
||||||
|
{% load component_tags %}
|
||||||
|
{% component 'empty' / %}
|
||||||
|
"""
|
||||||
|
template = Template(template_str, name="root")
|
||||||
|
templates_used = _get_templates_used_to_render(template)
|
||||||
|
assert templates_used == ["root"]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue