""" These tests check the public API side of managing dependencies - We check if calling `Component.render()` or `render_dependencies()` behave as expected. For checking the OUTPUT of the dependencies, see `test_dependency_rendering.py`. """ import re from unittest.mock import Mock import pytest from django.http import HttpResponseNotModified from django.template import Context, Template from pytest_django.asserts import assertHTMLEqual, assertInHTML 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.testing import djc_test from .testutils import create_and_process_template_response, setup_test_config 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" @djc_test class TestRenderDependencies: 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 assert rendered_raw.count('') == 1 assert rendered_raw.count('') == 1 assert rendered_raw.count("', rendered, count=1) assertInHTML("", rendered, count=1) # Inlined CSS assertInHTML('', rendered, count=1) # Inlined JS 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 assertInHTML('', rendered, count=1) assertInHTML("", rendered, count=1) # Inlined CSS assertInHTML('', rendered, count=1) # Inlined JS assertInHTML('', rendered, count=1) # Media.css assert rendered.count("', rendered, count=1) assertInHTML("", rendered, count=1) # Inlined CSS assertInHTML('', rendered, count=1) # Inlined JS assertInHTML('', rendered, count=1) # Media.css assert rendered.count("', rendered_raw, count=0) assertInHTML("", rendered_raw, count=0) # Inlined CSS assertInHTML('', rendered_raw, count=0) # Media.css 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 assertInHTML('', rendered, count=1) assertInHTML("", rendered, count=1) # Inlined CSS assertInHTML('', rendered, count=1) # Inlined JS assert rendered.count('') == 1 # Media.css assert rendered.count(" {% component "test" variable="foo" / %} """ rendered_raw = Template(template_str).render(Context({})) rendered = render_dependencies(rendered_raw) assert rendered.count(" """, rendered, count=1, ) body_re = re.compile(r"(.*?)", re.DOTALL) rendered_body = body_re.search(rendered).group(1) # type: ignore[union-attr] assertInHTML( """', 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) assert rendered.count(" Variable: foo """, rendered, count=1, ) head_re = re.compile(r"(.*?)", re.DOTALL) rendered_head = head_re.search(rendered).group(1) # type: ignore[union-attr] assertInHTML( """', 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") 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 %} {% endwith %} {% endfor %}
#
{{ row_number }}
""" rendered_raw = Template(template_str).render(Context({"formset": [1]})) rendered = render_dependencies(rendered_raw, type="fragment") expected = """
#
1
""" 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 %} {% endwith %} {% endfor %}
#
{{ row_number }} {% component "test" variable="hi" / %}
""" rendered_raw = Template(template_str).render(Context({"formset": [1]})) rendered = render_dependencies(rendered_raw, type="fragment") # Base64 encodings: # `PGxpbmsgaHJlZj0ic3R5bGUuY3NzIiBtZWRpYT0iYWxsIiByZWw9InN0eWxlc2hlZXQiPg==` -> `` # noqa: E501 # `PGxpbmsgaHJlZj0iL2NvbXBvbmVudHMvY2FjaGUvU2ltcGxlQ29tcG9uZW50XzMxMTA5Ny5jc3MiIG1lZGlhPSJhbGwiIHJlbD0ic3R5bGVzaGVldCI+` -> `` # noqa: E501 # `PHNjcmlwdCBzcmM9InNjcmlwdC5qcyI+PC9zY3JpcHQ+` -> `` # `PHNjcmlwdCBzcmM9Ii9jb21wb25lbnRzL2NhY2hlL1NpbXBsZUNvbXBvbmVudF8zMTEwOTcuanMiPjwvc2NyaXB0Pg==` -> `` # noqa: E501 expected = """
#
1 Variable: hi
""" # noqa: E501 assertHTMLEqual(expected, rendered) def test_raises_if_script_end_tag_inside_component_js(self): class ComponentWithScript(SimpleComponent): js: types.js = """ console.log(""); """ registry.register(name="test", component=ComponentWithScript) with pytest.raises( RuntimeError, match=re.escape("Content of `Component.js` for component 'ComponentWithScript' contains '' end tag."), # noqa: E501 ): ComponentWithScript.render(kwargs={"variable": "foo"}) def test_raises_if_script_end_tag_inside_component_css(self): class ComponentWithScript(SimpleComponent): css: types.css = """ /* */ .xyz { color: red; } """ registry.register(name="test", component=ComponentWithScript) with pytest.raises( RuntimeError, match=re.escape("Content of `Component.css` for component 'ComponentWithScript' contains '' end tag."), # noqa: E501 ): ComponentWithScript.render(kwargs={"variable": "foo"}) @djc_test class TestMiddleware: def test_middleware_response_without_content_type(self): response = HttpResponseNotModified() middleware = ComponentDependencyMiddleware(get_response=lambda _: response) request = Mock() assert response == middleware(request=request) def test_middleware_response_with_components_with_slash_dash_and_underscore(self): registry.register("dynamic", DynamicComponent) registry.register("test-component", component=SimpleComponent) registry.register("test/component", component=SimpleComponent) registry.register("test_component", 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) def assert_dependencies(content: str): # Dependency manager script (empty) assertInHTML('', content, count=1) # Inlined JS assertInHTML('', content, count=1) # Inlined CSS assertInHTML("", content, count=1) # Media.css assertInHTML('', content, count=1) rendered1 = create_and_process_template_response( template, context=Context({"component_name": "test-component"}), ) assert_dependencies(rendered1) assert rendered1.count('Variable: value') == 1 rendered2 = create_and_process_template_response( template, context=Context({"component_name": "test-component"}), ) assert_dependencies(rendered2) assert rendered2.count('Variable: value') == 1 rendered3 = create_and_process_template_response( template, context=Context({"component_name": "test_component"}), ) assert_dependencies(rendered3) assert rendered3.count('Variable: value') == 1