"""
Here we check that the logic around dependency rendering outputs correct HTML.
During actual rendering, the HTML is then picked up by the JS-side dependency manager.
"""
import re
from django.template import Context, Template
from pytest_django.asserts import assertHTMLEqual, assertInHTML
from django_components import Component, registry, types
from django_components.testing import djc_test
from .testutils import setup_test_config
setup_test_config({"autodiscover": False})
def to_spaces(s: str):
return re.compile(r"\s+").sub(" ", s)
class SimpleComponent(Component):
template: types.django_html = """
Variable: {{ variable }}
"""
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"
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"
class SimpleComponentWithSharedDependency(Component):
template: types.django_html = """
Variable: {{ variable }}
"""
class Media:
css = ["style.css", "style2.css"]
js = ["script.js", "script2.js"]
class MultistyleComponent(Component):
template: types.django_html = """
Variable: {{ variable }}
"""
class Media:
css = ["style.css", "style2.css"]
js = ["script.js", "script2.js"]
@djc_test
class TestDependencyRendering:
def test_no_dependencies_when_no_components_used(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
"""
template = Template(template_str)
rendered: str = template.render(Context({}))
# Dependency manager script
assertInHTML('', rendered, count=0)
assert rendered.count("\n'
' '
)
def test_no_js_dependencies_when_no_components_used(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_js_dependencies %}
"""
template = Template(template_str)
rendered = template.render(Context({}))
# Dependency manager script
assertInHTML('', rendered, count=0)
assert rendered.count("'
def test_no_css_dependencies_when_no_components_used(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies %}
"""
template = Template(template_str)
rendered: str = template.render(Context({}))
assert rendered.count("', rendered, count=1)
assert rendered.count(' ') == 1 # Media.css
assert rendered.count("
{"loadedCssUrls": ["c3R5bGUuY3Nz"],
"loadedJsUrls": ["c2NyaXB0Lmpz"],
"toLoadCssTags": [],
"toLoadJsTags": []}
""",
rendered,
count=1,
)
def test_single_component_with_dash_or_slash_in_name(self):
registry.register(name="te-s/t", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
{% component 'te-s/t' variable='foo' / %}
"""
template = Template(template_str)
rendered = template.render(Context({}))
# Dependency manager script
assertInHTML('', rendered, count=1)
assert rendered.count(' ') == 1 # Media.css
assert rendered.count("
{"loadedCssUrls": ["c3R5bGUuY3Nz"],
"loadedJsUrls": ["c2NyaXB0Lmpz"],
"toLoadCssTags": [],
"toLoadJsTags": []}
""",
rendered,
count=1,
)
def test_single_component_placeholder_removed(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({}))
assert "_RENDERED" not in rendered
def test_single_component_css_dependencies(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies %}
{% component 'test' variable='foo' / %}
"""
template = Template(template_str)
rendered = template.render(Context({}))
# Dependency manager script - NOT present
assertInHTML('', rendered, count=0)
assert rendered.count(" ') == 1 # Media.css
def test_single_component_js_dependencies(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_js_dependencies %}
{% component 'test' variable='foo' %}{% endcomponent %}
"""
template = Template(template_str)
rendered = template.render(Context({}))
# Dependency manager script
assertInHTML('', rendered, count=1)
# CSS NOT included
assert rendered.count("
{"loadedCssUrls": ["c3R5bGUuY3Nz"],
"loadedJsUrls": ["c2NyaXB0Lmpz"],
"toLoadCssTags": [],
"toLoadJsTags": []}
""",
rendered,
count=1,
)
def test_all_dependencies_are_rendered_for_component_with_multiple_dependencies(
self,
):
registry.register(name="test", component=MultistyleComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
{% component 'test' / %}
"""
template = Template(template_str)
rendered = template.render(Context({}))
# Dependency manager script
assertInHTML('', rendered, count=1)
assert rendered.count("
""",
rendered,
count=1,
)
# Media.js
assertInHTML(
"""
""",
rendered,
count=1,
)
# Base64 encoding:
# `c3R5bGUuY3Nz` -> `style.css`
# `c3R5bGUyLmNzcw==` -> `style2.css`
# `c2NyaXB0Lmpz` -> `script.js`
# `c2NyaXB0Mi5qcw==` -> `script2.js`
assertInHTML(
"""
""",
rendered,
count=1,
)
def test_no_dependencies_with_multiple_unused_components(self):
registry.register(name="inner", component=SimpleComponent)
registry.register(name="outer", component=SimpleComponentNested)
template_str: types.django_html = """
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
"""
template = Template(template_str)
rendered: str = template.render(Context({}))
# Dependency manager script
assertInHTML('', rendered, count=0)
assert rendered.count("\n'
' '
)
def test_multiple_components_dependencies(self):
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: str = template.render(Context({}))
# Dependency manager script
# NOTE: Should be present only ONCE!
assertInHTML('', rendered, count=1)
assert rendered.count("
""",
rendered,
count=1,
)
# Base64 encoding:
# `c3R5bGUuY3Nz` -> `style.css`
# `c3R5bGUyLmNzcw==` -> `style2.css`
# `eHl6MS5jc3M=` -> `xyz1.css`
# `L2NvbXBvbmVudHMvY2FjaGUvT3RoZXJDb21wb25lbnRfNjMyOWFlLmNzcw==` -> `/components/cache/OtherComponent_6329ae.css`
# `L2NvbXBvbmVudHMvY2FjaGUvT3RoZXJDb21wb25lbnRfNjMyOWFlLmpz` -> `/components/cache/OtherComponent_6329ae.js`
# `L2NvbXBvbmVudHMvY2FjaGUvU2ltcGxlQ29tcG9uZW50TmVzdGVkX2YwMmQzMi5jc3M=` -> `/components/cache/SimpleComponentNested_f02d32.css`
# `L2NvbXBvbmVudHMvY2FjaGUvU2ltcGxlQ29tcG9uZW50TmVzdGVkX2YwMmQzMi5qcw==` -> `/components/cache/SimpleComponentNested_f02d32.js`
# `c2NyaXB0Lmpz` -> `script.js`
# `c2NyaXB0Mi5qcw==` -> `script2.js`
# `eHl6MS5qcw==` -> `xyz1.js`
assertInHTML(
"""
""",
rendered,
count=1,
)
def test_multiple_components_all_placeholders_removed(self):
registry.register(name="inner", component=SimpleComponent)
registry.register(name="outer", component=SimpleComponentNested)
registry.register(name="test", component=SimpleComponentWithSharedDependency)
template_str: types.django_html = """
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
{% component 'inner' variable='variable' / %}
{% component 'outer' variable='variable' / %}
{% component 'test' variable='variable' / %}
"""
template = Template(template_str)
rendered = template.render(Context({}))
assert "_RENDERED" not in rendered
def test_adds_component_id_html_attr_single(self):
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'test' variable='foo' / %}
"""
template = Template(template_str)
rendered = template.render(Context({}))
assertHTMLEqual(rendered, "Variable: foo ")
def test_adds_component_id_html_attr_single_multiroot(self):
class SimpleMultiroot(SimpleComponent):
template: types.django_html = """
Variable: {{ variable }}
Variable2: {{ variable }}
Variable3: {{ variable }}
"""
registry.register(name="test", component=SimpleMultiroot)
template_str: types.django_html = """
{% load component_tags %}
{% component 'test' variable='foo' / %}
"""
template = Template(template_str)
rendered = template.render(Context({}))
assertHTMLEqual(
rendered,
"""
Variable: foo
Variable2: foo
Variable3: foo
""",
)
# Test that, if multiple components share the same root HTML elements,
# then those elemens will have the `data-djc-id-` attribute added for each component.
def test_adds_component_id_html_attr_nested(self):
class SimpleMultiroot(SimpleComponent):
template: types.django_html = """
Variable: {{ variable }}
Variable2: {{ variable }}
Variable3: {{ variable }}
"""
class SimpleOuter(SimpleComponent):
template: types.django_html = """
{% load component_tags %}
{% component 'multiroot' variable='foo' / %}
Another
"""
registry.register(name="multiroot", component=SimpleMultiroot)
registry.register(name="outer", component=SimpleOuter)
template_str: types.django_html = """
{% load component_tags %}
{% component 'outer' variable='foo' / %}
"""
template = Template(template_str)
rendered = template.render(Context({}))
assertHTMLEqual(
rendered,
"""
Variable: foo
Variable2: foo
Variable3: foo
Another
""",
)
# `data-djc-id-` attribute should be added on each instance in the RESULTING HTML.
# So if in a loop, each iteration creates a new component, and each of those should
# have a unique `data-djc-id-` attribute.
def test_adds_component_id_html_attr_loops(self):
class SimpleMultiroot(SimpleComponent):
template: types.django_html = """
Variable: {{ variable }}
Variable2: {{ variable }}
Variable3: {{ variable }}
"""
class SimpleOuter(SimpleComponent):
template: types.django_html = """
{% load component_tags %}
{% component 'multiroot' variable='foo' / %}
Another
"""
registry.register(name="multiroot", component=SimpleMultiroot)
registry.register(name="outer", component=SimpleOuter)
template_str: types.django_html = """
{% load component_tags %}
{% for i in lst %}
{% component 'outer' variable='foo' / %}
{% endfor %}
"""
template = Template(template_str)
rendered = template.render(Context({"lst": range(3)}))
assertHTMLEqual(
rendered,
"""
Variable: foo
Variable2: foo
Variable3: foo
Another
Variable: foo
Variable2: foo
Variable3: foo
Another
Variable: foo
Variable2: foo
Variable3: foo
Another
""",
)