import os
import re
import sys
from pathlib import Path
from textwrap import dedent
from typing import Optional
import pytest
from django.core.exceptions import ImproperlyConfigured
from django.forms.widgets import Media
from django.template import Context, Template
from django.templatetags.static import static
from django.utils.html import format_html, html_safe
from django.utils.safestring import mark_safe
from pytest_django.asserts import assertHTMLEqual, assertInHTML
from django_components import Component, autodiscover, registry, render_dependencies, types
from django_components.testing import djc_test
from .testutils import setup_test_config
setup_test_config({"autodiscover": False})
# "Main media" refer to the HTML, JS, and CSS set on the Component class itself
# (as opposed via the `Media` class). These have special handling in the Component.
@djc_test
class TestMainMedia:
def test_html_js_css_inlined(self):
class TestComponent(Component):
template = dedent(
"""
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
Content
"""
)
css = ".html-css-only { color: blue; }"
js = "console.log('HTML and JS only');"
assert TestComponent.css == ".html-css-only { color: blue; }"
assert TestComponent.js == "console.log('HTML and JS only');"
rendered = TestComponent.render()
assertInHTML(
'Content
',
rendered,
)
assertInHTML(
"",
rendered,
)
assertInHTML(
"",
rendered,
)
# Check that the HTML / JS / CSS can be accessed on the component class
assert TestComponent.template == dedent(
"""
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
Content
"""
)
assert TestComponent.css == ".html-css-only { color: blue; }"
assert TestComponent.js == "console.log('HTML and JS only');"
assert isinstance(TestComponent._template, Template)
assert TestComponent._template.origin.component_cls is TestComponent
@djc_test(
django_settings={
"STATICFILES_DIRS": [
os.path.join(Path(__file__).resolve().parent, "static_root"),
],
}
)
def test_html_js_css_filepath_rel_to_component(self):
from tests.test_app.components.app_lvl_comp.app_lvl_comp import AppLvlCompComponent
class TestComponent(AppLvlCompComponent):
pass
registry.register("test", TestComponent)
assert ".html-css-only {\n color: blue;\n}" in TestComponent.css # type: ignore[operator]
assert 'console.log("JS file");' in TestComponent.js # type: ignore[operator]
rendered = Template(
"""
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
{% component "test" variable="test" / %}
"""
).render(Context())
assertInHTML(
"""
""",
rendered,
)
assertInHTML(
"",
rendered,
)
assertInHTML(
'',
rendered,
)
# Check that the HTML / JS / CSS can be accessed on the component class
assert TestComponent.template == (
'\n"
)
assert TestComponent.css == ".html-css-only {\n color: blue;\n}\n"
assert TestComponent.js == 'console.log("JS file");\n'
assert isinstance(TestComponent._template, Template)
assert TestComponent._template.origin.component_cls is TestComponent
@djc_test(
django_settings={
"STATICFILES_DIRS": [
os.path.join(Path(__file__).resolve().parent, "static_root"),
],
}
)
def test_html_js_css_filepath_from_static(self):
class TestComponent(Component):
template_file = "test_app_simple_template.html"
css_file = "style.css"
js_file = "script.js"
def get_template_data(self, args, kwargs, slots, context):
return {
"variable": kwargs["variable"],
}
registry.register("test", TestComponent)
assert "Variable: {{ variable }}" in TestComponent.template # type: ignore[operator]
assert ".html-css-only {\n color: blue;\n}" in TestComponent.css # type: ignore[operator]
assert 'console.log("HTML and JS only");' in TestComponent.js # type: ignore[operator]
assert isinstance(TestComponent._template, Template)
assert TestComponent._template.origin.component_cls is TestComponent
rendered = Template(
"""
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
{% component "test" variable="test" / %}
"""
).render(Context())
assert 'Variable: test' in rendered
assertInHTML(
"",
rendered,
)
assertInHTML(
'',
rendered,
)
# Check that the HTML / JS / CSS can be accessed on the component class
assert TestComponent.template == "Variable: {{ variable }}\n"
assert TestComponent.css == (
"/* Used in `MainMediaTest` tests in `test_component_media.py` */\n"
".html-css-only {\n"
" color: blue;\n"
"}"
)
assert TestComponent.js == (
'/* Used in `MainMediaTest` tests in `test_component_media.py` */\nconsole.log("HTML and JS only");\n'
)
@djc_test(
django_settings={
"STATICFILES_DIRS": [
os.path.join(Path(__file__).resolve().parent, "static_root"),
],
}
)
def test_html_js_css_filepath_lazy_loaded(self):
from tests.test_app.components.app_lvl_comp.app_lvl_comp import AppLvlCompComponent
class TestComponent(AppLvlCompComponent):
pass
# NOTE: Currently the components' JS/CSS are loaded eagerly, to make the JS/CSS
# files available via endpoints. If that is no longer true, uncomment the
# following lines to test the lazy loading of the CSS.
#
# # Since this is a subclass, actual CSS is defined on the parent class, and thus
# # the corresponding ComponentMedia instance is also on the parent class.
# assert AppLvlCompComponent._component_media.css is UNSET # type: ignore[attr-defined]
# assert AppLvlCompComponent._component_media.css_file == "app_lvl_comp.css" # type: ignore[attr-defined]
# assert AppLvlCompComponent._component_media._template is UNSET # type: ignore[attr-defined]
#
# # Access the property to load the CSS
# _ = TestComponent.css
assert AppLvlCompComponent._component_media.css == (".html-css-only {\n" " color: blue;\n" "}\n") # type: ignore[attr-defined]
assert AppLvlCompComponent._component_media.css_file == "app_lvl_comp/app_lvl_comp.css" # type: ignore[attr-defined]
# Also check JS and HTML while we're at it
assert AppLvlCompComponent._component_media.template == ( # type: ignore[attr-defined]
'\n"
)
assert AppLvlCompComponent._component_media.template_file == "app_lvl_comp/app_lvl_comp.html" # type: ignore[attr-defined]
assert AppLvlCompComponent._component_media.js == 'console.log("JS file");\n' # type: ignore[attr-defined]
assert AppLvlCompComponent._component_media.js_file == "app_lvl_comp/app_lvl_comp.js" # type: ignore[attr-defined]
assert isinstance(AppLvlCompComponent._component_media._template, Template) # type: ignore[attr-defined]
assert AppLvlCompComponent._component_media._template.origin.component_cls is AppLvlCompComponent # type: ignore[attr-defined]
def test_html_variable_filtered(self):
class FilteredComponent(Component):
template: types.django_html = """
Var1: {{ var1 }}
Var2 (uppercased): {{ var2|upper }}
"""
def get_template_data(self, args, kwargs, slots, context):
return {
"var1": kwargs["var1"],
"var2": kwargs["var2"],
}
rendered = FilteredComponent.render(kwargs={"var1": "test1", "var2": "test2"})
assertHTMLEqual(
rendered,
"""
Var1: test1
Var2 (uppercased): TEST2
""",
)
@djc_test
class TestComponentMedia:
def test_empty_media(self):
class SimpleComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
Variable: {{ variable }}
"""
class Media:
pass
rendered = SimpleComponent.render()
assert rendered.count("