mirror of
https://github.com/django-components/django-components.git
synced 2025-09-03 20:50:31 +00:00

* feat: skeleton of dependency manager backend (#688) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: selectolax update and tests cleanup (#702) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: move release notes to own file (#704) * chore: merge changes from master (#705) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Yassin Rakha <yaso2go@gmail.com> Co-authored-by: Emil Stenström <emil@emilstenstrom.se> fix for nested slots (#698) (#699) * refactor: remove joint {% component_dependencies %} tag (#706) Co-authored-by: Emil Stenström <emil@emilstenstrom.se> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: split up utils file and move utils to util dir (#707) * docs: Move docs inside src/ to allow imports in python scripts (#708) * refactor: Docs prep 1 (#715) * refactor: Document template tags (#716) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: pass slot fills in template via slots param (#719) * chore: Merge master to dev (#729) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Yassin Rakha <yaso2go@gmail.com> Co-authored-by: Emil Stenström <emil@emilstenstrom.se> Co-authored-by: Tom Larsen <larsent@gmail.com> fix for nested slots (#698) (#699) * fix: Do not raise error if multiple slots with same name are flagged as default (#727) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: tag formatter - allow fwd slash in end tag (#730) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * refactor: Use lowercase names for registry settings (#731) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * docs: add docstrings (#732) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * feat: define settings as a data class for type hints, intellisense, and docs (#733) * refactor: fix reload-on-change logic, expose autodiscover's dirs-getting logic, rename settings (#734) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * docs: document settings (#743) * docs: document settings * refactor: fix linter errors * feat: passthrough slots and more (#758) * feat: passthrough slots and more * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: remove ComponentSlotContext.slots * refactor: update comment * docs: update changelog * refactor: update docstrings * refactor: document and test-cover more changes * refactor: revert fill without name * docs: update README --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * fix: apostrophes in tags (#765) * refactor: fix merge error - duplicate code --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Emil Stenström <emil@emilstenstrom.se>
249 lines
7.9 KiB
Python
249 lines
7.9 KiB
Python
import unittest
|
|
|
|
from django.template import Context, Engine, Library, Template
|
|
from django.test import override_settings
|
|
|
|
from django_components import (
|
|
AlreadyRegistered,
|
|
Component,
|
|
ComponentRegistry,
|
|
ContextBehavior,
|
|
NotRegistered,
|
|
RegistrySettings,
|
|
TagProtectedError,
|
|
component_formatter,
|
|
component_shorthand_formatter,
|
|
register,
|
|
registry,
|
|
types,
|
|
)
|
|
|
|
from .django_test_setup import setup_test_config
|
|
from .testutils import BaseTestCase, parametrize_context_behavior
|
|
|
|
setup_test_config({"autodiscover": False})
|
|
|
|
|
|
class MockComponent(Component):
|
|
pass
|
|
|
|
|
|
class MockComponent2(Component):
|
|
pass
|
|
|
|
|
|
class MockComponentView(Component):
|
|
def get(self, request, *args, **kwargs):
|
|
pass
|
|
|
|
|
|
class ComponentRegistryTest(unittest.TestCase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.registry = ComponentRegistry()
|
|
|
|
def test_register_class_decorator(self):
|
|
@register("decorated_component")
|
|
class TestComponent(Component):
|
|
pass
|
|
|
|
self.assertEqual(registry.get("decorated_component"), TestComponent)
|
|
|
|
# Cleanup
|
|
registry.unregister("decorated_component")
|
|
|
|
def test_register_class_decorator_custom_registry(self):
|
|
my_lib = Library()
|
|
my_reg = ComponentRegistry(library=my_lib)
|
|
|
|
default_registry_comps_before = len(registry.all())
|
|
|
|
self.assertDictEqual(my_reg.all(), {})
|
|
|
|
@register("decorated_component", registry=my_reg)
|
|
class TestComponent(Component):
|
|
pass
|
|
|
|
self.assertDictEqual(my_reg.all(), {"decorated_component": TestComponent})
|
|
|
|
# Check that the component was NOT added to the default registry
|
|
default_registry_comps_after = len(registry.all())
|
|
self.assertEqual(default_registry_comps_before, default_registry_comps_after)
|
|
|
|
def test_simple_register(self):
|
|
self.registry.register(name="testcomponent", component=MockComponent)
|
|
self.assertEqual(self.registry.all(), {"testcomponent": MockComponent})
|
|
|
|
def test_register_two_components(self):
|
|
self.registry.register(name="testcomponent", component=MockComponent)
|
|
self.registry.register(name="testcomponent2", component=MockComponent)
|
|
self.assertEqual(
|
|
self.registry.all(),
|
|
{
|
|
"testcomponent": MockComponent,
|
|
"testcomponent2": MockComponent,
|
|
},
|
|
)
|
|
|
|
def test_unregisters_only_unused_tags(self):
|
|
self.assertDictEqual(self.registry._tags, {})
|
|
# NOTE: We preserve the default component tags
|
|
self.assertNotIn("component", self.registry.library.tags)
|
|
|
|
# Register two components that use the same tag
|
|
self.registry.register(name="testcomponent", component=MockComponent)
|
|
self.registry.register(name="testcomponent2", component=MockComponent)
|
|
|
|
self.assertDictEqual(
|
|
self.registry._tags,
|
|
{
|
|
"component": {"testcomponent", "testcomponent2"},
|
|
},
|
|
)
|
|
|
|
self.assertIn("component", self.registry.library.tags)
|
|
|
|
# Unregister only one of the components. The tags should remain
|
|
self.registry.unregister(name="testcomponent")
|
|
|
|
self.assertDictEqual(
|
|
self.registry._tags,
|
|
{
|
|
"component": {"testcomponent2"},
|
|
},
|
|
)
|
|
|
|
self.assertIn("component", self.registry.library.tags)
|
|
|
|
# Unregister the second components. The tags should be removed
|
|
self.registry.unregister(name="testcomponent2")
|
|
|
|
self.assertDictEqual(self.registry._tags, {})
|
|
self.assertNotIn("component", self.registry.library.tags)
|
|
|
|
def test_prevent_registering_different_components_with_the_same_name(self):
|
|
self.registry.register(name="testcomponent", component=MockComponent)
|
|
with self.assertRaises(AlreadyRegistered):
|
|
self.registry.register(name="testcomponent", component=MockComponent2)
|
|
|
|
def test_allow_duplicated_registration_of_the_same_component(self):
|
|
try:
|
|
self.registry.register(name="testcomponent", component=MockComponentView)
|
|
self.registry.register(name="testcomponent", component=MockComponentView)
|
|
except AlreadyRegistered:
|
|
self.fail("Should not raise AlreadyRegistered")
|
|
|
|
def test_simple_unregister(self):
|
|
self.registry.register(name="testcomponent", component=MockComponent)
|
|
self.registry.unregister(name="testcomponent")
|
|
self.assertEqual(self.registry.all(), {})
|
|
|
|
def test_raises_on_failed_unregister(self):
|
|
with self.assertRaises(NotRegistered):
|
|
self.registry.unregister(name="testcomponent")
|
|
|
|
|
|
class MultipleComponentRegistriesTest(BaseTestCase):
|
|
@parametrize_context_behavior(["django", "isolated"])
|
|
def test_different_registries_have_different_settings(self):
|
|
library_a = Library()
|
|
registry_a = ComponentRegistry(
|
|
library=library_a,
|
|
settings=RegistrySettings(
|
|
context_behavior=ContextBehavior.ISOLATED.value,
|
|
tag_formatter=component_shorthand_formatter,
|
|
),
|
|
)
|
|
|
|
library_b = Library()
|
|
registry_b = ComponentRegistry(
|
|
library=library_b,
|
|
settings=RegistrySettings(
|
|
context_behavior=ContextBehavior.DJANGO.value,
|
|
tag_formatter=component_formatter,
|
|
),
|
|
)
|
|
|
|
# NOTE: We cannot load the Libraries above using `{% load xxx %}` tag, because
|
|
# for that we'd need to register a Django app and whatnot.
|
|
# Instead, we insert the Libraries directly into the engine's builtins.
|
|
engine = Engine.get_default()
|
|
|
|
# Add the custom template tags to Django's built-in tags
|
|
engine.template_builtins.append(library_a)
|
|
engine.template_builtins.append(library_b)
|
|
|
|
class SimpleComponent(Component):
|
|
template: types.django_html = """
|
|
{% load component_tags %}
|
|
Variable: <strong>{{ variable }}</strong>
|
|
Slot: {% slot "default" default / %}
|
|
"""
|
|
|
|
def get_context_data(self, variable=None):
|
|
return {
|
|
"variable": variable,
|
|
}
|
|
|
|
registry_a.register("simple_a", SimpleComponent)
|
|
registry_b.register("simple_b", SimpleComponent)
|
|
|
|
template_str: types.django_html = """
|
|
{% simple_a variable=123 %}
|
|
SLOT 123
|
|
{% endsimple_a %}
|
|
{% component "simple_b" variable=123 %}
|
|
SLOT ABC
|
|
{% endcomponent %}
|
|
"""
|
|
template = Template(template_str)
|
|
|
|
rendered = template.render(Context({}))
|
|
|
|
self.assertHTMLEqual(
|
|
rendered,
|
|
"""
|
|
Variable: <strong>123</strong>
|
|
Slot:
|
|
SLOT 123
|
|
|
|
Variable: <strong>123</strong>
|
|
Slot:
|
|
SLOT ABC
|
|
""",
|
|
)
|
|
|
|
# Remove the custom template tags to clean up after tests
|
|
engine.template_builtins.remove(library_a)
|
|
engine.template_builtins.remove(library_b)
|
|
|
|
|
|
class ProtectedTagsTest(unittest.TestCase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.registry = ComponentRegistry()
|
|
|
|
# NOTE: Use the `component_shorthand_formatter` formatter, so the components
|
|
# are registered under that tag
|
|
@override_settings(COMPONENTS={"tag_formatter": "django_components.component_shorthand_formatter"})
|
|
def test_raises_on_overriding_our_tags(self):
|
|
for tag in [
|
|
"component_css_dependencies",
|
|
"component_js_dependencies",
|
|
"fill",
|
|
"html_attrs",
|
|
"provide",
|
|
"slot",
|
|
]:
|
|
with self.assertRaises(TagProtectedError):
|
|
|
|
@register(tag)
|
|
class TestComponent(Component):
|
|
pass
|
|
|
|
@register("sth_else")
|
|
class TestComponent2(Component):
|
|
pass
|
|
|
|
# Cleanup
|
|
registry.unregister("sth_else")
|