""" 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 import pytest 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.testing import djc_test from .testutils import 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_template_data(self, args, kwargs, slots, context): return { "variable": kwargs["variable"], "variable2": kwargs.get("variable2", "default"), } class Media: css = "style.css" js = "script.js" @djc_test class TestDependenciesLegacy: # TODO_v1 - Remove def test_render_with_type_arg(self): rendered = SimpleComponent.render(kwargs={"variable": "foo"}, type="append") # Dependency manager script NOT present assertInHTML('', rendered, count=0) # Check that it contains inlined JS and CSS, and Media.css assert rendered.strip() == ( 'Variable: foo\n' ' ' ) @djc_test class TestRenderDependencies: # Check that `render_dependencies()` works when called directly def test_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) # NOTE: `"ignore"` is a special value that means "do not render dependencies" rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"})) # 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 # Check that instead of `render_dependencies()`, we can simply call `Template.render()` def test_template_render(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 = template.render(Context({})) # 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=0) assertInHTML( "", rendered, count=1, ) # Inlined CSS assertInHTML( '', rendered, count=1 ) # Inlined JS assertInHTML('', rendered, count=1) # Media.css assert rendered.count("foo\n' ' \n' ' ' ) # Check that `Component.render()` renders dependencies def test_component_render(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) rendered = SimpleComponentWithDeps.render( kwargs={"variable": "foo"}, ) # 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_raw, count=0) assertInHTML("", rendered_raw, count=0) # Inlined CSS assertInHTML('', rendered_raw, count=0) # Media.css assertInHTML( '', rendered_raw, count=0, ) # Inlined JS # Check that `Component.render_to_response()` renders dependencies def test_component_render_to_response(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({"DJC_DEPS_STRATEGY": "ignore"})) 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({"DJC_DEPS_STRATEGY": "ignore"})) rendered: str = 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], "DJC_DEPS_STRATEGY": "ignore"})) rendered = render_dependencies(rendered_raw, strategy="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], "DJC_DEPS_STRATEGY": "ignore"})) rendered = render_dependencies(rendered_raw, strategy="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], "DJC_DEPS_STRATEGY": "ignore"})) rendered = render_dependencies(rendered_raw, strategy="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 TestDependenciesStrategyDocument: def test_inserts_styles_and_script_to_default_places_if_not_overriden(self): registry.register(name="test", component=SimpleComponent) template_str: types.django_html = """ {% load component_tags %} {% component "test" variable="foo" / %} """ rendered_raw = Template(template_str).render(Context({"DJC_DEPS_STRATEGY": "ignore"})) rendered = render_dependencies(rendered_raw, strategy="document") 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({"DJC_DEPS_STRATEGY": "ignore"})) rendered = render_dependencies(rendered_raw, strategy="document") 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, ) @djc_test class TestDependenciesStrategySimple: def test_single_component(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: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"})) # Placeholders assert rendered_raw.count('') == 1 assert rendered_raw.count('') == 1 assert rendered_raw.count("', rendered, count=0) # Check that it contains inlined JS and CSS, and Media.css assert rendered.strip() == ( '\n' " \n' " \n" ' Variable: foo' ) def test_multiple_components_dependencies(self): class SimpleComponentNested(Component): template: types.django_html = """ {% load component_tags %}
{% component "inner" variable=variable / %} {% slot "default" default / %}
""" css: types.css = """ .my-class { color: red; } """ js: types.js = """ console.log("Hello"); """ class Media: css = ["style.css", "style2.css"] js = "script2.js" class OtherComponent(Component): template: types.django_html = """ XYZ: {{ variable }} """ css: types.css = """ .xyz { color: red; } """ js: types.js = """ console.log("xyz"); """ class Media: css = "xyz1.css" js = "xyz1.js" registry.register(name="inner", component=SimpleComponent) registry.register(name="outer", component=SimpleComponentNested) registry.register(name="other", component=OtherComponent) template_str: types.django_html = """ {% load component_tags %} {% component_js_dependencies %} {% component_css_dependencies %} {% component 'outer' variable='variable' %} {% component 'other' variable='variable_inner' / %} {% endcomponent %} """ template = Template(template_str) rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"})) rendered = render_dependencies(rendered_raw, strategy="simple") # Dependency manager script NOT present assertInHTML('', rendered, count=0) assert rendered.count(".my-class { color: red; } """, rendered, count=1, ) # Components' Media.css # Order: # - "style.css", "style2.css" (from SimpleComponentNested) # - "style.css" (from SimpleComponent inside SimpleComponentNested) # - "xyz1.css" (from OtherComponent inserted into SimpleComponentNested) assertInHTML( """ """, rendered, count=1, ) # Components' Media.js followed by inlined JS # Order: # - "script2.js" (from SimpleComponentNested) # - "script.js" (from SimpleComponent inside SimpleComponentNested) # - "xyz1.js" (from OtherComponent inserted into SimpleComponentNested) assertInHTML( """ """, rendered, count=1, ) # Check that there's no payload like with "document" or "fragment" modes assert "application/json" not in rendered @djc_test class TestDependenciesStrategyPrepend: def test_single_component(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: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"})) # Placeholders assert rendered_raw.count('') == 1 assert rendered_raw.count('') == 1 assert rendered_raw.count("', rendered, count=0) # Check that it contains inlined JS and CSS, and Media.css assert rendered.strip() == ( '\n' " \n" " \n" " \n" " \n" ' Variable: foo' ) def test_multiple_components_dependencies(self): class SimpleComponentNested(Component): template: types.django_html = """ {% load component_tags %}
{% component "inner" variable=variable / %} {% slot "default" default / %}
""" css: types.css = """ .my-class { color: red; } """ js: types.js = """ console.log("Hello"); """ class Media: css = ["style.css", "style2.css"] js = "script2.js" class OtherComponent(Component): template: types.django_html = """ XYZ: {{ variable }} """ css: types.css = """ .xyz { color: red; } """ js: types.js = """ console.log("xyz"); """ class Media: css = "xyz1.css" js = "xyz1.js" registry.register(name="inner", component=SimpleComponent) registry.register(name="outer", component=SimpleComponentNested) registry.register(name="other", component=OtherComponent) template_str: types.django_html = """ {% load component_tags %} {% component_js_dependencies %} {% component_css_dependencies %} {% component 'outer' variable='variable' %} {% component 'other' variable='variable_inner' / %} {% endcomponent %} """ template = Template(template_str) rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"})) rendered = render_dependencies(rendered_raw, strategy="prepend") # Dependency manager script NOT present assertInHTML('', rendered, count=0) assert rendered.count(".my-class { color: red; } """, rendered, count=1, ) # Components' Media.css # Order: # - "style.css", "style2.css" (from SimpleComponentNested) # - "style.css" (from SimpleComponent inside SimpleComponentNested) # - "xyz1.css" (from OtherComponent inserted into SimpleComponentNested) assertInHTML( """ """, rendered, count=1, ) # Components' Media.js followed by inlined JS # Order: # - "script2.js" (from SimpleComponentNested) # - "script.js" (from SimpleComponent inside SimpleComponentNested) # - "xyz1.js" (from OtherComponent inserted into SimpleComponentNested) assertInHTML( """ """, rendered, count=1, ) # Check that there's no payload like with "document" or "fragment" modes assert "application/json" not in rendered @djc_test class TestDependenciesStrategyAppend: def test_single_component(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: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"})) # Placeholders assert rendered_raw.count('') == 1 assert rendered_raw.count('') == 1 assert rendered_raw.count("', rendered, count=0) # Check that it contains inlined JS and CSS, and Media.css assert rendered.strip() == ( 'Variable: foo\n' " \n" ' ' ) def test_multiple_components_dependencies(self): class SimpleComponentNested(Component): template: types.django_html = """ {% load component_tags %}
{% component "inner" variable=variable / %} {% slot "default" default / %}
""" css: types.css = """ .my-class { color: red; } """ js: types.js = """ console.log("Hello"); """ class Media: css = ["style.css", "style2.css"] js = "script2.js" class OtherComponent(Component): template: types.django_html = """ XYZ: {{ variable }} """ css: types.css = """ .xyz { color: red; } """ js: types.js = """ console.log("xyz"); """ class Media: css = "xyz1.css" js = "xyz1.js" registry.register(name="inner", component=SimpleComponent) registry.register(name="outer", component=SimpleComponentNested) registry.register(name="other", component=OtherComponent) template_str: types.django_html = """ {% load component_tags %} {% component_js_dependencies %} {% component_css_dependencies %} {% component 'outer' variable='variable' %} {% component 'other' variable='variable_inner' / %} {% endcomponent %} """ template = Template(template_str) rendered_raw: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"})) rendered = render_dependencies(rendered_raw, strategy="append") # Dependency manager script NOT present assertInHTML('', rendered, count=0) assert rendered.count(".my-class { color: red; } """, rendered, count=1, ) # Components' Media.css # Order: # - "style.css", "style2.css" (from SimpleComponentNested) # - "style.css" (from SimpleComponent inside SimpleComponentNested) # - "xyz1.css" (from OtherComponent inserted into SimpleComponentNested) assertInHTML( """ """, rendered, count=1, ) # Components' Media.js followed by inlined JS # Order: # - "script2.js" (from SimpleComponentNested) # - "script.js" (from SimpleComponent inside SimpleComponentNested) # - "xyz1.js" (from OtherComponent inserted into SimpleComponentNested) assertInHTML( """ """, rendered, count=1, ) # Check that there's no payload like with "document" or "fragment" modes assert "application/json" not in rendered @djc_test class TestDependenciesStrategyRaw: def test_single_component(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: str = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"})) # Placeholders assert rendered_raw.count('') == 1 assert rendered_raw.count('') == 1 assert rendered_raw.count("\n' ' \n' ' \n' ' Variable: foo' )