from unittest.mock import Mock
from django.http import HttpResponseNotModified
from django.template import Context, Template
from django_components import Component, registry, render_dependencies, types
from django_components.components.dynamic import DynamicComponent
from django_components.middleware import ComponentDependencyMiddleware
from django_components.util.html import SoupNode
from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, create_and_process_template_response
setup_test_config({"autodiscover": False})
class SimpleComponent(Component):
template: types.django_html = """
Variable: {{ variable }}
"""
css: types.css = """
.xyz {
color: red;
}
"""
js: types.js = """
console.log("xyz");
"""
def get_context_data(self, variable, variable2="default"):
return {
"variable": variable,
"variable2": variable2,
}
class Media:
css = "style.css"
js = "script.js"
class RenderDependenciesTests(BaseTestCase):
def test_standalone_render_dependencies(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
{% component 'test' variable='foo' / %}
"""
template = Template(template_str)
rendered_raw = template.render(Context({}))
# Placeholders
self.assertEqual(rendered_raw.count(' '), 1)
self.assertEqual(rendered_raw.count(''), 1)
self.assertEqual(rendered_raw.count("', rendered, count=1)
self.assertInHTML("", rendered, count=1) # Inlined CSS
self.assertInHTML(
"", rendered, count=1
) # Inlined JS
self.assertInHTML(' ', rendered, count=1) # Media.css
def test_middleware_renders_dependencies(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
{% component 'test' variable='foo' / %}
"""
template = Template(template_str)
rendered = create_and_process_template_response(template, use_middleware=True)
# Dependency manager script
self.assertInHTML('', rendered, count=1)
self.assertInHTML("", rendered, count=1) # Inlined CSS
self.assertInHTML(
"", rendered, count=1
) # Inlined JS
self.assertInHTML(' ', rendered, count=1) # Media.css
self.assertEqual(rendered.count(" ', rendered, count=1)
self.assertInHTML("", rendered, count=1) # Inlined CSS
self.assertInHTML(
"", rendered, count=1
) # Inlined JS
self.assertInHTML(' ', rendered, count=1) # Media.css
self.assertEqual(rendered.count(" ', rendered_raw, count=0)
self.assertInHTML("", rendered_raw, count=0) # Inlined CSS
self.assertInHTML(' ', rendered_raw, count=0) # Media.css
self.assertInHTML(
"",
rendered_raw,
count=0,
) # Inlined JS
def test_component_render_to_response_renders_dependencies(self):
class SimpleComponentWithDeps(SimpleComponent):
template: types.django_html = (
"""
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
"""
+ SimpleComponent.template
)
registry.register(name="test", component=SimpleComponentWithDeps)
response = SimpleComponentWithDeps.render_to_response(
kwargs={"variable": "foo"},
)
rendered = response.content.decode()
# Dependency manager script
self.assertInHTML('', rendered, count=1)
self.assertInHTML("", rendered, count=1) # Inlined CSS
self.assertInHTML(
"", rendered, count=1
) # Inlined JS
self.assertEqual(rendered.count(' '), 1) # Media.css
self.assertEqual(rendered.count("
{% component "test" variable="foo" / %}
"""
rendered_raw = Template(template_str).render(Context({}))
rendered = render_dependencies(rendered_raw)
self.assertEqual(rendered.count("""",
rendered_body,
count=1,
)
def test_does_not_insert_styles_and_script_to_default_places_if_overriden(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component_js_dependencies %}
{% component "test" variable="foo" / %}
{% component_css_dependencies %}
"""
rendered_raw = Template(template_str).render(Context({}))
rendered = render_dependencies(rendered_raw)
self.assertEqual(rendered.count("""",
rendered_head,
count=1,
)
# NOTE: Some HTML parser libraries like selectolax or lxml try to "correct" the given HTML.
# We want to avoid this behavior, so user gets the exact same HTML back.
def test_does_not_try_to_add_close_tags(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
"""
rendered_raw = Template(template_str).render(Context({"formset": [1]}))
rendered = render_dependencies(rendered_raw, type="fragment")
self.assertHTMLEqual(rendered, "")
def test_does_not_modify_html_when_no_component_used(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
#
{% for form in formset %}
{% with row_number=forloop.counter %}
{{ row_number }}
{% endwith %}
{% endfor %}
"""
rendered_raw = Template(template_str).render(Context({"formset": [1]}))
rendered = render_dependencies(rendered_raw, type="fragment")
expected = """
"""
self.assertHTMLEqual(expected, rendered)
# Explanation: The component is used in the template, but the template doesn't use
# {% component_js_dependencies %} or {% component_css_dependencies %} tags,
# nor defines a `` or `` tag. In which case, the dependencies are not rendered.
def test_does_not_modify_html_when_component_used_but_nowhere_to_insert(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}
#
{% for form in formset %}
{% with row_number=forloop.counter %}
{{ row_number }}
{% component "test" variable="hi" / %}
{% endwith %}
{% endfor %}
"""
rendered_raw = Template(template_str).render(Context({"formset": [1]}))
rendered = render_dependencies(rendered_raw, type="fragment")
expected = """
"""
self.assertHTMLEqual(expected, rendered)
class MiddlewareTests(BaseTestCase):
def test_middleware_response_without_content_type(self):
response = HttpResponseNotModified()
middleware = ComponentDependencyMiddleware(get_response=lambda _: response)
request = Mock()
self.assertEqual(response, middleware(request=request))
def test_middleware_response_with_components_with_slash_dash_and_underscore(
self,
):
registry.register("dynamic", DynamicComponent)
component_names = [
"test-component",
"test/component",
"test_component",
]
for component_name in component_names:
registry.register(name=component_name, component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component_css_dependencies %}
{% component_js_dependencies %}
{% component "dynamic" is=component_name variable='value' / %}
"""
template = Template(template_str)
rendered = create_and_process_template_response(
template, context=Context({"component_name": component_name})
)
# Dependency manager script (empty)
self.assertInHTML('', rendered, count=1)
# Inlined JS
self.assertInHTML(
"", rendered, count=1
)
# Inlined CSS
self.assertInHTML("", rendered, count=1)
# Media.css
self.assertInHTML(' ', rendered, count=1)
self.assertEqual(rendered.count("Variable: value "), 1)