[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
This commit is contained in:
pre-commit-ci[bot] 2021-09-10 11:12:25 +00:00
parent 15a0e66219
commit 0648ad9a93
14 changed files with 983 additions and 432 deletions

View file

@ -3,10 +3,14 @@ from time import perf_counter
from django.template import Context, Template
from django.test import override_settings
from django_components.middleware import CSS_DEPENDENCY_PLACEHOLDER, JS_DEPENDENCY_PLACEHOLDER
from tests.django_test_setup import * # NOQA
from django_components import component
from tests.testutils import Django30CompatibleSimpleTestCase as SimpleTestCase, create_and_process_template_response
from django_components.middleware import (
CSS_DEPENDENCY_PLACEHOLDER,
JS_DEPENDENCY_PLACEHOLDER,
)
from tests.django_test_setup import * # NOQA
from tests.testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
from tests.testutils import create_and_process_template_response
class SlottedComponent(component.Component):
@ -31,14 +35,22 @@ class BreadcrumbComponent(component.Component):
template_name = "mdn_component_template.html"
LINKS = [
('https://developer.mozilla.org/en-US/docs/Learn',
'Learn web development'),
('https://developer.mozilla.org/en-US/docs/Learn/HTML',
'Structuring the web with HTML'),
('https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML',
'Introduction to HTML'),
('https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Document_and_website_structure',
'Document and website structure')
(
"https://developer.mozilla.org/en-US/docs/Learn",
"Learn web development",
),
(
"https://developer.mozilla.org/en-US/docs/Learn/HTML",
"Structuring the web with HTML",
),
(
"https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML",
"Introduction to HTML",
),
(
"https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Document_and_website_structure",
"Document and website structure",
),
]
def get_context_data(self, items):
@ -46,7 +58,7 @@ class BreadcrumbComponent(component.Component):
items = 4
elif items < 0:
items = 0
return {'links': self.LINKS[:items - 1]}
return {"links": self.LINKS[: items - 1]}
class Media:
css = {"all": ["test.css"]}
@ -57,13 +69,15 @@ EXPECTED_CSS = """<link href="test.css" media="all" rel="stylesheet">"""
EXPECTED_JS = """<script src="test.js"></script>"""
@override_settings(COMPONENTS={'RENDER_DEPENDENCIES': True})
@override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True})
class RenderBenchmarks(SimpleTestCase):
def setUp(self):
component.registry.clear()
component.registry.register('test_component', SlottedComponent)
component.registry.register('inner_component', SimpleComponent)
component.registry.register('breadcrumb_component', BreadcrumbComponent)
component.registry.register("test_component", SlottedComponent)
component.registry.register("inner_component", SimpleComponent)
component.registry.register(
"breadcrumb_component", BreadcrumbComponent
)
@staticmethod
def timed_loop(func, iterations=1000):
@ -76,55 +90,81 @@ class RenderBenchmarks(SimpleTestCase):
return total_elapsed * 1000 / iterations
def test_render_time_for_small_component(self):
template = Template("{% load component_tags %}{% component_block 'test_component' %}"
template = Template(
"{% load component_tags %}{% component_block 'test_component' %}"
"{% slot \"header\" %}{% component 'inner_component' variable='foo' %}{% endslot %}"
"{% endcomponent_block %}", name='root')
"{% endcomponent_block %}",
name="root",
)
print(f'{self.timed_loop(lambda: template.render(Context({})))} ms per iteration')
print(
f"{self.timed_loop(lambda: template.render(Context({})))} ms per iteration"
)
def test_middleware_time_with_dependency_for_small_page(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'test_component' %}{% slot \"header\" %}"
"{% component 'inner_component' variable='foo' %}{% endslot %}{% endcomponent_block %}",
name='root')
name="root",
)
# Sanity tests
response_content = create_and_process_template_response(template)
self.assertNotIn(CSS_DEPENDENCY_PLACEHOLDER, response_content)
self.assertNotIn(JS_DEPENDENCY_PLACEHOLDER, response_content)
self.assertIn('style.css', response_content)
self.assertIn('script.js', response_content)
self.assertIn("style.css", response_content)
self.assertIn("script.js", response_content)
without_middleware = self.timed_loop(lambda: create_and_process_template_response(template,
use_middleware=False))
with_middleware = self.timed_loop(lambda: create_and_process_template_response(template, use_middleware=True))
without_middleware = self.timed_loop(
lambda: create_and_process_template_response(
template, use_middleware=False
)
)
with_middleware = self.timed_loop(
lambda: create_and_process_template_response(
template, use_middleware=True
)
)
print('Small page middleware test')
print("Small page middleware test")
self.report_results(with_middleware, without_middleware)
def test_render_time_with_dependency_for_large_page(self):
from django.template.loader import get_template
template = get_template('mdn_complete_page.html')
template = get_template("mdn_complete_page.html")
response_content = create_and_process_template_response(template, {})
self.assertNotIn(CSS_DEPENDENCY_PLACEHOLDER, response_content)
self.assertNotIn(JS_DEPENDENCY_PLACEHOLDER, response_content)
self.assertIn('test.css', response_content)
self.assertIn('test.js', response_content)
self.assertIn("test.css", response_content)
self.assertIn("test.js", response_content)
without_middleware = self.timed_loop(
lambda: create_and_process_template_response(template, {}, use_middleware=False))
lambda: create_and_process_template_response(
template, {}, use_middleware=False
)
)
with_middleware = self.timed_loop(
lambda: create_and_process_template_response(template, {}, use_middleware=True))
lambda: create_and_process_template_response(
template, {}, use_middleware=True
)
)
print('Large page middleware test')
print("Large page middleware test")
self.report_results(with_middleware, without_middleware)
@staticmethod
def report_results(with_middleware, without_middleware):
print(f'Middleware active\t\t{with_middleware:.3f} ms per iteration')
print(f'Middleware inactive\t{without_middleware:.3f} ms per iteration')
print(f"Middleware active\t\t{with_middleware:.3f} ms per iteration")
print(
f"Middleware inactive\t{without_middleware:.3f} ms per iteration"
)
time_difference = with_middleware - without_middleware
if without_middleware > with_middleware:
print(f'Decrease of {-100 * time_difference / with_middleware:.2f}%')
print(
f"Decrease of {-100 * time_difference / with_middleware:.2f}%"
)
else:
print(f'Increase of {100 * time_difference / without_middleware:.2f}%')
print(
f"Increase of {100 * time_difference / without_middleware:.2f}%"
)

View file

@ -9,10 +9,16 @@ from django.template.loader import get_template
from django.utils.safestring import mark_safe
# Allow "component.AlreadyRegistered" instead of having to import these everywhere
from django_components.component_registry import AlreadyRegistered, ComponentRegistry, NotRegistered # noqa
from django_components.component_registry import ( # noqa
AlreadyRegistered,
ComponentRegistry,
NotRegistered,
)
TEMPLATE_CACHE_SIZE = getattr(settings, "COMPONENTS", {}).get('TEMPLATE_CACHE_SIZE', 128)
ACTIVE_SLOT_CONTEXT_KEY = '_DJANGO_COMPONENTS_ACTIVE_SLOTS'
TEMPLATE_CACHE_SIZE = getattr(settings, "COMPONENTS", {}).get(
"TEMPLATE_CACHE_SIZE", 128
)
ACTIVE_SLOT_CONTEXT_KEY = "_DJANGO_COMPONENTS_ACTIVE_SLOTS"
class SimplifiedInterfaceMediaDefiningClass(MediaDefiningClass):
@ -54,7 +60,9 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
def get_template_name(self, context=None):
if not self.template_name:
raise ImproperlyConfigured(f'Template name is not set for Component {self.__class__.__name__}')
raise ImproperlyConfigured(
f"Template name is not set for Component {self.__class__.__name__}"
)
return self.template_name
@ -75,13 +83,19 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
@staticmethod
def slots_in_template(template):
return {node.name: node.nodelist for node in template.template.nodelist if Component.is_slot_node(node)}
return {
node.name: node.nodelist
for node in template.template.nodelist
if Component.is_slot_node(node)
}
@staticmethod
def is_slot_node(node):
return (isinstance(node, Node)
return (
isinstance(node, Node)
and node.token.token_type == TokenType.BLOCK
and node.token.split_contents()[0] == "slot")
and node.token.split_contents()[0] == "slot"
)
@lru_cache(maxsize=TEMPLATE_CACHE_SIZE)
def get_processed_template(self, template_name):
@ -117,16 +131,16 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
return component_template
def render(self, context):
if hasattr(self, 'context'):
if hasattr(self, "context"):
warnings.warn(
f'{self.__class__.__name__}: `context` method is deprecated, use `get_context` instead',
DeprecationWarning
f"{self.__class__.__name__}: `context` method is deprecated, use `get_context` instead",
DeprecationWarning,
)
if hasattr(self, 'template'):
if hasattr(self, "template"):
warnings.warn(
f'{self.__class__.__name__}: `template` method is deprecated, set `template_name` or override `get_template_name` instead',
DeprecationWarning
f"{self.__class__.__name__}: `template` method is deprecated, set `template_name` or override `get_template_name` instead",
DeprecationWarning,
)
template_name = self.template(context)
else:

View file

@ -12,7 +12,9 @@ class ComponentRegistry(object):
def register(self, name=None, component=None):
if name in self._registry:
raise AlreadyRegistered('The component "%s" is already registered' % name)
raise AlreadyRegistered(
'The component "%s" is already registered' % name
)
self._registry[name] = component

View file

@ -8,11 +8,13 @@ RENDERED_COMPONENTS_CONTEXT_KEY = "_COMPONENT_DEPENDENCIES"
CSS_DEPENDENCY_PLACEHOLDER = '<link name="CSS_PLACEHOLDER">'
JS_DEPENDENCY_PLACEHOLDER = '<script name="JS_PLACEHOLDER">'
SCRIPT_TAG_REGEX = re.compile('<script')
COMPONENT_COMMENT_REGEX = re.compile(rb'<!-- _RENDERED (?P<name>\w+?) -->')
PLACEHOLDER_REGEX = re.compile(rb'<!-- _RENDERED (?P<name>\w+?) -->'
SCRIPT_TAG_REGEX = re.compile("<script")
COMPONENT_COMMENT_REGEX = re.compile(rb"<!-- _RENDERED (?P<name>\w+?) -->")
PLACEHOLDER_REGEX = re.compile(
rb"<!-- _RENDERED (?P<name>\w+?) -->"
rb'|<link name="CSS_PLACEHOLDER">'
rb'|<script name="JS_PLACEHOLDER">')
rb'|<script name="JS_PLACEHOLDER">'
)
class ComponentDependencyMiddleware:
@ -25,9 +27,13 @@ class ComponentDependencyMiddleware:
def __call__(self, request):
response = self.get_response(request)
if getattr(settings, "COMPONENTS", {}).get('RENDER_DEPENDENCIES', False)\
and not isinstance(response, StreamingHttpResponse)\
and response['Content-Type'].startswith('text/html'):
if (
getattr(settings, "COMPONENTS", {}).get(
"RENDER_DEPENDENCIES", False
)
and not isinstance(response, StreamingHttpResponse)
and response["Content-Type"].startswith("text/html")
):
response.content = process_response_content(response.content)
return response
@ -35,12 +41,23 @@ class ComponentDependencyMiddleware:
def process_response_content(content):
from django_components.component import registry
component_names_seen = {match.group('name') for match in COMPONENT_COMMENT_REGEX.finditer(content)}
all_components = [registry.get(name.decode('utf-8'))('') for name in component_names_seen]
component_names_seen = {
match.group("name")
for match in COMPONENT_COMMENT_REGEX.finditer(content)
}
all_components = [
registry.get(name.decode("utf-8"))("") for name in component_names_seen
]
all_media = join_media(all_components)
js_dependencies = b''.join(media.encode('utf-8') for media in all_media.render_js())
css_dependencies = b''.join(media.encode('utf-8') for media in all_media.render_css())
return PLACEHOLDER_REGEX.sub(DependencyReplacer(css_dependencies, js_dependencies), content)
js_dependencies = b"".join(
media.encode("utf-8") for media in all_media.render_js()
)
css_dependencies = b"".join(
media.encode("utf-8") for media in all_media.render_css()
)
return PLACEHOLDER_REGEX.sub(
DependencyReplacer(css_dependencies, js_dependencies), content
)
def add_module_attribute_to_scripts(scripts):
@ -51,8 +68,8 @@ class DependencyReplacer:
"""Replacer for use in re.sub that replaces the first placeholder CSS and JS
tags it encounters and removes any subsequent ones."""
CSS_PLACEHOLDER = bytes(CSS_DEPENDENCY_PLACEHOLDER, encoding='utf-8')
JS_PLACEHOLDER = bytes(JS_DEPENDENCY_PLACEHOLDER, encoding='utf-8')
CSS_PLACEHOLDER = bytes(CSS_DEPENDENCY_PLACEHOLDER, encoding="utf-8")
JS_PLACEHOLDER = bytes(JS_DEPENDENCY_PLACEHOLDER, encoding="utf-8")
def __init__(self, css_string, js_string):
self.js_string = js_string
@ -64,7 +81,7 @@ class DependencyReplacer:
elif match[0] == self.JS_PLACEHOLDER:
replacement, self.js_string = self.js_string, b""
else:
replacement = b''
replacement = b""
return replacement

View file

@ -8,4 +8,4 @@ from django.template.utils import get_app_template_dirs
class Loader(FilesystemLoader):
def get_dirs(self):
return get_app_template_dirs('components')
return get_app_template_dirs("components")

View file

@ -7,7 +7,10 @@ from django.template.library import parse_bits
from django.utils.safestring import mark_safe
from django_components.component import ACTIVE_SLOT_CONTEXT_KEY, registry
from django_components.middleware import CSS_DEPENDENCY_PLACEHOLDER, JS_DEPENDENCY_PLACEHOLDER
from django_components.middleware import (
CSS_DEPENDENCY_PLACEHOLDER,
JS_DEPENDENCY_PLACEHOLDER,
)
register = template.Library()
@ -29,7 +32,9 @@ def component_dependencies_tag():
"""Marks location where CSS link and JS script tags should be rendered."""
if is_dependency_middleware_active():
return mark_safe(CSS_DEPENDENCY_PLACEHOLDER + JS_DEPENDENCY_PLACEHOLDER)
return mark_safe(
CSS_DEPENDENCY_PLACEHOLDER + JS_DEPENDENCY_PLACEHOLDER
)
else:
rendered_dependencies = []
for component in get_components_from_registry(registry):
@ -66,12 +71,19 @@ def component_js_dependencies_tag():
return mark_safe("\n".join(rendered_dependencies))
@register.tag(name='component')
@register.tag(name="component")
def do_component(parser, token):
bits = token.split_contents()
bits, isolated_context = check_for_isolated_context_keyword(bits)
component, context_args, context_kwargs = parse_component_with_args(parser, bits, 'component')
return ComponentNode(component, context_args, context_kwargs, isolated_context=isolated_context)
component, context_args, context_kwargs = parse_component_with_args(
parser, bits, "component"
)
return ComponentNode(
component,
context_args,
context_kwargs,
isolated_context=isolated_context,
)
class SlotNode(Node):
@ -92,17 +104,25 @@ class SlotNode(Node):
cloned_node.parent_component = self.parent_component
cloned_node.context = context
with context.update({'slot': cloned_node}):
with context.update({"slot": cloned_node}):
return self.get_nodelist(context).render(context)
def get_nodelist(self, context):
if ACTIVE_SLOT_CONTEXT_KEY not in context:
raise TemplateSyntaxError(f'Attempted to render SlotNode {self.name} outside of a parent Component or '
'without access to context provided by its parent Component. This will not'
'work properly.')
raise TemplateSyntaxError(
f"Attempted to render SlotNode {self.name} outside of a parent Component or "
"without access to context provided by its parent Component. This will not"
"work properly."
)
overriding_nodelist = context[ACTIVE_SLOT_CONTEXT_KEY].get(self.name, None)
return overriding_nodelist if overriding_nodelist is not None else self.nodelist
overriding_nodelist = context[ACTIVE_SLOT_CONTEXT_KEY].get(
self.name, None
)
return (
overriding_nodelist
if overriding_nodelist is not None
else self.nodelist
)
def super(self):
"""Render default slot content."""
@ -125,9 +145,18 @@ def do_slot(parser, token, component=None):
class ComponentNode(Node):
class InvalidSlot:
def super(self):
raise TemplateSyntaxError('slot.super may only be called within a {% slot %}/{% endslot %} block.')
raise TemplateSyntaxError(
"slot.super may only be called within a {% slot %}/{% endslot %} block."
)
def __init__(self, component, context_args, context_kwargs, slots=None, isolated_context=False):
def __init__(
self,
component,
context_args,
context_kwargs,
slots=None,
isolated_context=False,
):
self.context_args = context_args or []
self.context_kwargs = context_kwargs or {}
self.component, self.isolated_context = component, isolated_context
@ -139,17 +168,26 @@ class ComponentNode(Node):
self.should_render_dependencies = is_dependency_middleware_active()
def __repr__(self):
return "<Component Node: %s. Contents: %r>" % (self.component,
getattr(self.component.instance_template, 'nodelist', None))
return "<Component Node: %s. Contents: %r>" % (
self.component,
getattr(self.component.instance_template, "nodelist", None),
)
def render(self, context):
self.component.outer_context = context.flatten()
# Resolve FilterExpressions and Variables that were passed as args to the component, then call component's
# context method to get values to insert into the context
resolved_context_args = [safe_resolve(arg, context) for arg in self.context_args]
resolved_context_kwargs = {key: safe_resolve(kwarg, context) for key, kwarg in self.context_kwargs.items()}
component_context = self.component.get_context_data(*resolved_context_args, **resolved_context_kwargs)
resolved_context_args = [
safe_resolve(arg, context) for arg in self.context_args
]
resolved_context_kwargs = {
key: safe_resolve(kwarg, context)
for key, kwarg in self.context_kwargs.items()
}
component_context = self.component.get_context_data(
*resolved_context_args, **resolved_context_kwargs
)
# Create a fresh context if requested
if self.isolated_context:
@ -158,7 +196,10 @@ class ComponentNode(Node):
with context.update(component_context):
rendered_component = self.component.render(context)
if self.should_render_dependencies:
return f'<!-- _RENDERED {self.component._component_name} -->' + rendered_component
return (
f"<!-- _RENDERED {self.component._component_name} -->"
+ rendered_component
)
else:
return rendered_component
@ -181,12 +222,20 @@ def do_component_block(parser, token):
bits = token.split_contents()
bits, isolated_context = check_for_isolated_context_keyword(bits)
component, context_args, context_kwargs = parse_component_with_args(parser, bits, 'component_block')
component, context_args, context_kwargs = parse_component_with_args(
parser, bits, "component_block"
)
return ComponentNode(component, context_args, context_kwargs,
slots=[do_slot(parser, slot_token, component=component)
for slot_token in slot_tokens(parser)],
isolated_context=isolated_context)
return ComponentNode(
component,
context_args,
context_kwargs,
slots=[
do_slot(parser, slot_token, component=component)
for slot_token in slot_tokens(parser)
],
isolated_context=isolated_context,
)
def slot_tokens(parser):
@ -195,28 +244,37 @@ def slot_tokens(parser):
Raises TemplateSyntaxError if there are other content tokens or if there is no endcomponent_block token."""
def is_whitespace(token):
return token.token_type == TokenType.TEXT and not token.contents.strip()
return (
token.token_type == TokenType.TEXT and not token.contents.strip()
)
def is_block_tag(token, name):
return token.token_type == TokenType.BLOCK and token.split_contents()[0] == name
return (
token.token_type == TokenType.BLOCK
and token.split_contents()[0] == name
)
while True:
try:
token = parser.next_token()
except IndexError:
raise TemplateSyntaxError('Unclosed component_block tag')
if is_block_tag(token, name='endcomponent_block'):
raise TemplateSyntaxError("Unclosed component_block tag")
if is_block_tag(token, name="endcomponent_block"):
return
elif is_block_tag(token, name='slot'):
elif is_block_tag(token, name="slot"):
yield token
elif not is_whitespace(token) and token.token_type != TokenType.COMMENT:
raise TemplateSyntaxError(f'Content tokens in component blocks must be inside of slot tags: {token}')
elif (
not is_whitespace(token) and token.token_type != TokenType.COMMENT
):
raise TemplateSyntaxError(
f"Content tokens in component blocks must be inside of slot tags: {token}"
)
def check_for_isolated_context_keyword(bits):
"""Return True and strip the last word if token ends with 'only' keyword."""
if bits[-1] == 'only':
if bits[-1] == "only":
return bits[:-1], True
return bits, False
@ -235,20 +293,26 @@ def parse_component_with_args(parser, bits, tag_name):
kwonly_defaults=None,
)
assert tag_name == tag_args[0].token, "Internal error: Expected tag_name to be {}, but it was {}".format(
tag_name, tag_args[0].token)
if len(tag_args) > 1: # At least one position arg, so take the first as the component name
assert (
tag_name == tag_args[0].token
), "Internal error: Expected tag_name to be {}, but it was {}".format(
tag_name, tag_args[0].token
)
if (
len(tag_args) > 1
): # At least one position arg, so take the first as the component name
component_name = tag_args[1].token
context_args = tag_args[2:]
context_kwargs = tag_kwargs
else: # No positional args, so look for component name as keyword arg
try:
component_name = tag_kwargs.pop('name').token
component_name = tag_kwargs.pop("name").token
context_args = []
context_kwargs = tag_kwargs
except IndexError:
raise TemplateSyntaxError(
"Call the '%s' tag with a component name as the first parameter" % tag_name
"Call the '%s' tag with a component name as the first parameter"
% tag_name
)
if not is_wrapped_in_quotes(component_name):
@ -256,7 +320,7 @@ def parse_component_with_args(parser, bits, tag_name):
"Component name '%s' should be in quotes" % component_name
)
trimmed_component_name = component_name[1: -1]
trimmed_component_name = component_name[1:-1]
component_class = registry.get(trimmed_component_name)
component = component_class(trimmed_component_name)
@ -266,7 +330,11 @@ def parse_component_with_args(parser, bits, tag_name):
def safe_resolve(context_item, context):
"""Resolve FilterExpressions and Variables in context if possible. Return other items unchanged."""
return context_item.resolve(context) if hasattr(context_item, 'resolve') else context_item
return (
context_item.resolve(context)
if hasattr(context_item, "resolve")
else context_item
)
def is_wrapped_in_quotes(s):
@ -274,4 +342,6 @@ def is_wrapped_in_quotes(s):
def is_dependency_middleware_active():
return getattr(settings, "COMPONENTS", {}).get('RENDER_DEPENDENCIES', False)
return getattr(settings, "COMPONENTS", {}).get(
"RENDER_DEPENDENCIES", False
)

View file

@ -3,14 +3,16 @@ import os
from setuptools import find_packages, setup
VERSION = '0.16'
VERSION = "0.16"
setup(
name="django_components",
packages=find_packages(exclude=["tests"]),
version=VERSION,
description="A way to create simple reusable template components in Django.",
long_description=open(os.path.join(os.path.dirname(__file__), "README.md")).read(),
long_description=open(
os.path.join(os.path.dirname(__file__), "README.md")
).read(),
long_description_content_type="text/markdown",
author=u"Emil Stenström",
author_email="emil@emilstenstrom.se",

View file

@ -3,15 +3,17 @@ from django.conf import settings
if not settings.configured:
settings.configure(
INSTALLED_APPS=('django_components',),
TEMPLATES=[{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ["tests/templates/"],
}],
COMPONENTS={
'TEMPLATE_CACHE_SIZE': 128
},
MIDDLEWARE=['django_components.middleware.ComponentDependencyMiddleware'],
INSTALLED_APPS=("django_components",),
TEMPLATES=[
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": ["tests/templates/"],
}
],
COMPONENTS={"TEMPLATE_CACHE_SIZE": 128},
MIDDLEWARE=[
"django_components.middleware.ComponentDependencyMiddleware"
],
DATABASES={},
)

View file

@ -1,11 +1,11 @@
from textwrap import dedent
from django.template import Context, Template
from django.core.exceptions import ImproperlyConfigured
from .django_test_setup import * # NOQA
from django.template import Context, Template
from django_components import component
from .django_test_setup import * # NOQA
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
@ -33,14 +33,24 @@ class ComponentTest(SimpleTestCase):
comp = SimpleComponent("simple_component")
context = Context(comp.get_context_data(variable="test"))
self.assertHTMLEqual(comp.render_dependencies(), dedent("""
self.assertHTMLEqual(
comp.render_dependencies(),
dedent(
"""
<link href="style.css" type="text/css" media="all" rel="stylesheet">
<script src="script.js"></script>
""").strip())
"""
).strip(),
)
self.assertHTMLEqual(comp.render(context), dedent("""
self.assertHTMLEqual(
comp.render(context),
dedent(
"""
Variable: <strong>test</strong>
""").lstrip())
"""
).lstrip(),
)
def test_component_with_list_of_styles(self):
class MultistyleComponent(component.Component):
@ -50,12 +60,17 @@ class ComponentTest(SimpleTestCase):
comp = MultistyleComponent("multistyle_component")
self.assertHTMLEqual(comp.render_dependencies(), dedent("""
self.assertHTMLEqual(
comp.render_dependencies(),
dedent(
"""
<link href="style.css" type="text/css" media="all" rel="stylesheet">
<link href="style2.css" type="text/css" media="all" rel="stylesheet">
<script src="script.js"></script>
<script src="script2.js"></script>
""").strip())
"""
).strip(),
)
def test_component_with_filtered_template(self):
class FilteredComponent(component.Component):
@ -70,15 +85,25 @@ class ComponentTest(SimpleTestCase):
comp = FilteredComponent("filtered_component")
context = Context(comp.get_context_data(var1="test1", var2="test2"))
self.assertHTMLEqual(comp.render(context), dedent("""
self.assertHTMLEqual(
comp.render(context),
dedent(
"""
Var1: <strong>test1</strong>
Var2 (uppercased): <strong>TEST2</strong>
""").lstrip())
"""
).lstrip(),
)
def test_component_with_dynamic_template(self):
class SvgComponent(component.Component):
def get_context_data(self, name, css_class="", title="", **attrs):
return {"name": name, "css_class": css_class, "title": title, **attrs}
return {
"name": name,
"css_class": css_class,
"title": title,
**attrs,
}
def get_template_name(self, context):
return f"svg_{context['name']}.svg"
@ -86,15 +111,19 @@ class ComponentTest(SimpleTestCase):
comp = SvgComponent("svg_component")
self.assertHTMLEqual(
comp.render(Context(comp.get_context_data(name="dynamic1"))),
dedent("""\
dedent(
"""\
<svg>Dynamic1</svg>
""")
"""
),
)
self.assertHTMLEqual(
comp.render(Context(comp.get_context_data(name="dynamic2"))),
dedent("""\
dedent(
"""\
<svg>Dynamic2</svg>
""")
"""
),
)
@ -108,10 +137,12 @@ class ComponentMediaTests(SimpleTestCase):
comp = SimpleComponent("")
self.assertHTMLEqual(
comp.render_dependencies(),
dedent("""\
dedent(
"""\
<link href="path/to/style.css" type="text/css" media="all" rel="stylesheet">
<script src="path/to/script.js"></script>
""")
"""
),
)
def test_component_media_with_lists(self):
@ -123,11 +154,13 @@ class ComponentMediaTests(SimpleTestCase):
comp = SimpleComponent("")
self.assertHTMLEqual(
comp.render_dependencies(),
dedent("""\
dedent(
"""\
<link href="path/to/style.css" type="text/css" media="all" rel="stylesheet">
<link href="path/to/style2.css" type="text/css" media="all" rel="stylesheet">
<script src="path/to/script.js"></script>
""")
"""
),
)
def test_component_media_with_dict_and_list(self):
@ -143,12 +176,14 @@ class ComponentMediaTests(SimpleTestCase):
comp = SimpleComponent("")
self.assertHTMLEqual(
comp.render_dependencies(),
dedent("""\
dedent(
"""\
<link href="path/to/style.css" type="text/css" media="all" rel="stylesheet">
<link href="path/to/style2.css" type="text/css" media="print" rel="stylesheet">
<link href="path/to/style3.css" type="text/css" media="screen" rel="stylesheet">
<script src="path/to/script.js"></script>
""")
"""
),
)
def test_component_media_with_dict_with_list_and_list(self):
@ -160,10 +195,12 @@ class ComponentMediaTests(SimpleTestCase):
comp = SimpleComponent("")
self.assertHTMLEqual(
comp.render_dependencies(),
dedent("""\
dedent(
"""\
<link href="path/to/style.css" type="text/css" media="all" rel="stylesheet">
<script src="path/to/script.js"></script>
""")
"""
),
)
@ -172,7 +209,7 @@ class ComponentIsolationTests(SimpleTestCase):
class SlottedComponent(component.Component):
template_name = "slotted_template.html"
component.registry.register('test', SlottedComponent)
component.registry.register("test", SlottedComponent)
def test_instances_of_component_do_not_share_slots(self):
template = Template(
@ -211,17 +248,17 @@ class ComponentIsolationTests(SimpleTestCase):
<main>Default main</main>
<footer>Override footer</footer>
</custom-template>
"""
""",
)
class RecursiveSlotNameTest(SimpleTestCase):
def setUp(self):
@component.register('outer')
@component.register("outer")
class OuterComponent(component.Component):
template_name = "slotted_template.html"
@component.register('inner')
@component.register("inner")
class InnerComponent(component.Component):
template_name = "slotted_template.html"
@ -251,5 +288,5 @@ class RecursiveSlotNameTest(SimpleTestCase):
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
"""
""",
)

View file

@ -1,9 +1,8 @@
from django.template import Context, Template
from .django_test_setup import * # NOQA
from django_components import component
from .django_test_setup import * # NOQA
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
@ -15,16 +14,14 @@ class SimpleComponent(component.Component):
@staticmethod
def expected_output(variable_value):
return 'Variable: < strong > {} < / strong >'.format(variable_value)
return "Variable: < strong > {} < / strong >".format(variable_value)
class ParentComponent(component.Component):
template_name = "parent_template.html"
def get_context_data(self):
return {
"shadowing_variable": 'NOT SHADOWED'
}
return {"shadowing_variable": "NOT SHADOWED"}
class ParentComponentWithArgs(component.Component):
@ -40,9 +37,9 @@ class VariableDisplay(component.Component):
def get_context_data(self, shadowing_variable=None, new_variable=None):
context = {}
if shadowing_variable is not None:
context['shadowing_variable'] = shadowing_variable
context["shadowing_variable"] = shadowing_variable
if new_variable is not None:
context['unique_variable'] = new_variable
context["unique_variable"] = new_variable
return context
@ -51,15 +48,11 @@ class IncrementerComponent(component.Component):
def get_context_data(self, value=0):
value = int(value)
if hasattr(self, 'call_count'):
if hasattr(self, "call_count"):
self.call_count += 1
else:
self.call_count = 1
return {
"value": value + 1,
"calls": self.call_count
}
return {"value": value + 1, "calls": self.call_count}
class OuterContextComponent(component.Component):
@ -69,212 +62,394 @@ class OuterContextComponent(component.Component):
return self.outer_context
component.registry.register(name='parent_component', component=ParentComponent)
component.registry.register(name='parent_with_args', component=ParentComponentWithArgs)
component.registry.register(name='variable_display', component=VariableDisplay)
component.registry.register(name='incrementer', component=IncrementerComponent)
component.registry.register(name='simple_component', component=SimpleComponent)
component.registry.register(name='outer_context_component', component=OuterContextComponent)
component.registry.register(name="parent_component", component=ParentComponent)
component.registry.register(
name="parent_with_args", component=ParentComponentWithArgs
)
component.registry.register(name="variable_display", component=VariableDisplay)
component.registry.register(name="incrementer", component=IncrementerComponent)
component.registry.register(name="simple_component", component=SimpleComponent)
component.registry.register(
name="outer_context_component", component=OuterContextComponent
)
class ContextTests(SimpleTestCase):
def test_nested_component_context_shadows_parent_with_unfilled_slots_and_component_tag(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component 'parent_component' %}")
def test_nested_component_context_shadows_parent_with_unfilled_slots_and_component_tag(
self,
):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'parent_component' %}"
)
rendered = template.render(Context())
self.assertIn('<h1>Shadowing variable = override</h1>', rendered, rendered)
self.assertIn('<h1>Shadowing variable = slot_default_override</h1>', rendered, rendered)
self.assertNotIn('<h1>Shadowing variable = NOT SHADOWED</h1>', rendered, rendered)
self.assertIn(
"<h1>Shadowing variable = override</h1>", rendered, rendered
)
self.assertIn(
"<h1>Shadowing variable = slot_default_override</h1>",
rendered,
rendered,
)
self.assertNotIn(
"<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered
)
def test_nested_component_instances_have_unique_context_with_unfilled_slots_and_component_tag(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component name='parent_component' %}")
def test_nested_component_instances_have_unique_context_with_unfilled_slots_and_component_tag(
self,
):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component name='parent_component' %}"
)
rendered = template.render(Context())
self.assertIn('<h1>Uniquely named variable = unique_val</h1>', rendered, rendered)
self.assertIn('<h1>Uniquely named variable = slot_default_unique</h1>', rendered, rendered)
self.assertIn(
"<h1>Uniquely named variable = unique_val</h1>", rendered, rendered
)
self.assertIn(
"<h1>Uniquely named variable = slot_default_unique</h1>",
rendered,
rendered,
)
def test_nested_component_context_shadows_parent_with_unfilled_slots_and_component_block_tag(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_component' %}{% endcomponent_block %}")
def test_nested_component_context_shadows_parent_with_unfilled_slots_and_component_block_tag(
self,
):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_component' %}{% endcomponent_block %}"
)
rendered = template.render(Context())
self.assertIn('<h1>Shadowing variable = override</h1>', rendered, rendered)
self.assertIn('<h1>Shadowing variable = slot_default_override</h1>', rendered, rendered)
self.assertNotIn('<h1>Shadowing variable = NOT SHADOWED</h1>', rendered, rendered)
self.assertIn(
"<h1>Shadowing variable = override</h1>", rendered, rendered
)
self.assertIn(
"<h1>Shadowing variable = slot_default_override</h1>",
rendered,
rendered,
)
self.assertNotIn(
"<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered
)
def test_nested_component_instances_have_unique_context_with_unfilled_slots_and_component_block_tag(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_component' %}{% endcomponent_block %}")
def test_nested_component_instances_have_unique_context_with_unfilled_slots_and_component_block_tag(
self,
):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_component' %}{% endcomponent_block %}"
)
rendered = template.render(Context())
self.assertIn('<h1>Uniquely named variable = unique_val</h1>', rendered, rendered)
self.assertIn('<h1>Uniquely named variable = slot_default_unique</h1>', rendered, rendered)
self.assertIn(
"<h1>Uniquely named variable = unique_val</h1>", rendered, rendered
)
self.assertIn(
"<h1>Uniquely named variable = slot_default_unique</h1>",
rendered,
rendered,
)
def test_nested_component_context_shadows_parent_with_filled_slots(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_component' %}"
"{% slot 'content' %}{% component name='variable_display' "
"shadowing_variable='shadow_from_slot' new_variable='unique_from_slot' %}{% endslot %}"
"{% endcomponent_block %}")
"{% endcomponent_block %}"
)
rendered = template.render(Context())
self.assertIn('<h1>Shadowing variable = override</h1>', rendered, rendered)
self.assertIn('<h1>Shadowing variable = shadow_from_slot</h1>', rendered, rendered)
self.assertNotIn('<h1>Shadowing variable = NOT SHADOWED</h1>', rendered, rendered)
self.assertIn(
"<h1>Shadowing variable = override</h1>", rendered, rendered
)
self.assertIn(
"<h1>Shadowing variable = shadow_from_slot</h1>",
rendered,
rendered,
)
self.assertNotIn(
"<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered
)
def test_nested_component_instances_have_unique_context_with_filled_slots(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
def test_nested_component_instances_have_unique_context_with_filled_slots(
self,
):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_component' %}"
"{% slot 'content' %}{% component name='variable_display' "
"shadowing_variable='shadow_from_slot' new_variable='unique_from_slot' %}{% endslot %}"
"{% endcomponent_block %}")
"{% endcomponent_block %}"
)
rendered = template.render(Context())
self.assertIn('<h1>Uniquely named variable = unique_val</h1>', rendered, rendered)
self.assertIn('<h1>Uniquely named variable = unique_from_slot</h1>', rendered, rendered)
self.assertIn(
"<h1>Uniquely named variable = unique_val</h1>", rendered, rendered
)
self.assertIn(
"<h1>Uniquely named variable = unique_from_slot</h1>",
rendered,
rendered,
)
def test_nested_component_context_shadows_outer_context_with_unfilled_slots_and_component_tag(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component name='parent_component' %}")
rendered = template.render(Context({'shadowing_variable': 'NOT SHADOWED'}))
def test_nested_component_context_shadows_outer_context_with_unfilled_slots_and_component_tag(
self,
):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component name='parent_component' %}"
)
rendered = template.render(
Context({"shadowing_variable": "NOT SHADOWED"})
)
self.assertIn('<h1>Shadowing variable = override</h1>', rendered, rendered)
self.assertIn('<h1>Shadowing variable = slot_default_override</h1>', rendered, rendered)
self.assertNotIn('<h1>Shadowing variable = NOT SHADOWED</h1>', rendered, rendered)
self.assertIn(
"<h1>Shadowing variable = override</h1>", rendered, rendered
)
self.assertIn(
"<h1>Shadowing variable = slot_default_override</h1>",
rendered,
rendered,
)
self.assertNotIn(
"<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered
)
def test_nested_component_context_shadows_outer_context_with_unfilled_slots_and_component_block_tag(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_component' %}{% endcomponent_block %}")
rendered = template.render(Context({'shadowing_variable': 'NOT SHADOWED'}))
def test_nested_component_context_shadows_outer_context_with_unfilled_slots_and_component_block_tag(
self,
):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_component' %}{% endcomponent_block %}"
)
rendered = template.render(
Context({"shadowing_variable": "NOT SHADOWED"})
)
self.assertIn('<h1>Shadowing variable = override</h1>', rendered, rendered)
self.assertIn('<h1>Shadowing variable = slot_default_override</h1>', rendered, rendered)
self.assertNotIn('<h1>Shadowing variable = NOT SHADOWED</h1>', rendered, rendered)
self.assertIn(
"<h1>Shadowing variable = override</h1>", rendered, rendered
)
self.assertIn(
"<h1>Shadowing variable = slot_default_override</h1>",
rendered,
rendered,
)
self.assertNotIn(
"<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered
)
def test_nested_component_context_shadows_outer_context_with_filled_slots(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
def test_nested_component_context_shadows_outer_context_with_filled_slots(
self,
):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_component' %}"
"{% slot 'content' %}{% component name='variable_display' "
"shadowing_variable='shadow_from_slot' new_variable='unique_from_slot' %}{% endslot %}"
"{% endcomponent_block %}")
rendered = template.render(Context({'shadowing_variable': 'NOT SHADOWED'}))
"{% endcomponent_block %}"
)
rendered = template.render(
Context({"shadowing_variable": "NOT SHADOWED"})
)
self.assertIn('<h1>Shadowing variable = override</h1>', rendered, rendered)
self.assertIn('<h1>Shadowing variable = shadow_from_slot</h1>', rendered, rendered)
self.assertNotIn('<h1>Shadowing variable = NOT SHADOWED</h1>', rendered, rendered)
self.assertIn(
"<h1>Shadowing variable = override</h1>", rendered, rendered
)
self.assertIn(
"<h1>Shadowing variable = shadow_from_slot</h1>",
rendered,
rendered,
)
self.assertNotIn(
"<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered
)
class ParentArgsTests(SimpleTestCase):
def test_parent_args_can_be_drawn_from_context(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_with_args' parent_value=parent_value %}"
"{% endcomponent_block %}")
rendered = template.render(Context({'parent_value': 'passed_in'}))
"{% endcomponent_block %}"
)
rendered = template.render(Context({"parent_value": "passed_in"}))
self.assertIn('<h1>Shadowing variable = passed_in</h1>', rendered, rendered)
self.assertIn('<h1>Uniquely named variable = passed_in</h1>', rendered, rendered)
self.assertNotIn('<h1>Shadowing variable = NOT SHADOWED</h1>', rendered, rendered)
self.assertIn(
"<h1>Shadowing variable = passed_in</h1>", rendered, rendered
)
self.assertIn(
"<h1>Uniquely named variable = passed_in</h1>", rendered, rendered
)
self.assertNotIn(
"<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered
)
def test_parent_args_available_outside_slots(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_with_args' parent_value='passed_in' %}{%endcomponent_block %}")
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_with_args' parent_value='passed_in' %}{%endcomponent_block %}"
)
rendered = template.render(Context())
self.assertIn('<h1>Shadowing variable = passed_in</h1>', rendered, rendered)
self.assertIn('<h1>Uniquely named variable = passed_in</h1>', rendered, rendered)
self.assertNotIn('<h1>Shadowing variable = NOT SHADOWED</h1>', rendered, rendered)
self.assertIn(
"<h1>Shadowing variable = passed_in</h1>", rendered, rendered
)
self.assertIn(
"<h1>Uniquely named variable = passed_in</h1>", rendered, rendered
)
self.assertNotIn(
"<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered
)
def test_parent_args_available_in_slots(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'parent_with_args' parent_value='passed_in' %}"
"{% slot 'content' %}{% component name='variable_display' "
"shadowing_variable='value_from_slot' new_variable=inner_parent_value %}{% endslot %}"
"{%endcomponent_block %}")
"{%endcomponent_block %}"
)
rendered = template.render(Context())
self.assertIn('<h1>Shadowing variable = value_from_slot</h1>', rendered, rendered)
self.assertIn('<h1>Uniquely named variable = passed_in</h1>', rendered, rendered)
self.assertNotIn('<h1>Shadowing variable = NOT SHADOWED</h1>', rendered, rendered)
self.assertIn(
"<h1>Shadowing variable = value_from_slot</h1>", rendered, rendered
)
self.assertIn(
"<h1>Uniquely named variable = passed_in</h1>", rendered, rendered
)
self.assertNotIn(
"<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered
)
class ContextCalledOnceTests(SimpleTestCase):
def test_one_context_call_with_simple_component(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component name='incrementer' %}")
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component name='incrementer' %}"
)
rendered = template.render(Context()).strip()
self.assertEqual(rendered, '<p class="incrementer">value=1;calls=1</p>', rendered)
self.assertEqual(
rendered, '<p class="incrementer">value=1;calls=1</p>', rendered
)
def test_one_context_call_with_simple_component_and_arg(self):
template = Template("{% load component_tags %}{% component name='incrementer' value='2' %}")
template = Template(
"{% load component_tags %}{% component name='incrementer' value='2' %}"
)
rendered = template.render(Context()).strip()
self.assertEqual(rendered, '<p class="incrementer">value=3;calls=1</p>', rendered)
self.assertEqual(
rendered, '<p class="incrementer">value=3;calls=1</p>', rendered
)
def test_one_context_call_with_component_block(self):
template = Template("{% load component_tags %}"
"{% component_block 'incrementer' %}{% endcomponent_block %}")
template = Template(
"{% load component_tags %}"
"{% component_block 'incrementer' %}{% endcomponent_block %}"
)
rendered = template.render(Context()).strip()
self.assertEqual(rendered, '<p class="incrementer">value=1;calls=1</p>', rendered)
self.assertEqual(
rendered, '<p class="incrementer">value=1;calls=1</p>', rendered
)
def test_one_context_call_with_component_block_and_arg(self):
template = Template("{% load component_tags %}"
"{% component_block 'incrementer' value='3' %}{% endcomponent_block %}")
template = Template(
"{% load component_tags %}"
"{% component_block 'incrementer' value='3' %}{% endcomponent_block %}"
)
rendered = template.render(Context()).strip()
self.assertEqual(rendered, '<p class="incrementer">value=4;calls=1</p>', rendered)
self.assertEqual(
rendered, '<p class="incrementer">value=4;calls=1</p>', rendered
)
def test_one_context_call_with_slot(self):
template = Template("{% load component_tags %}"
template = Template(
"{% load component_tags %}"
"{% component_block 'incrementer' %}{% slot 'content' %}"
"<p>slot</p>{% endslot %}{% endcomponent_block %}")
"<p>slot</p>{% endslot %}{% endcomponent_block %}"
)
rendered = template.render(Context()).strip()
self.assertEqual(rendered, '<p class="incrementer">value=1;calls=1</p>\n<p>slot</p>', rendered)
self.assertEqual(
rendered,
'<p class="incrementer">value=1;calls=1</p>\n<p>slot</p>',
rendered,
)
def test_one_context_call_with_slot_and_arg(self):
template = Template("{% load component_tags %}"
template = Template(
"{% load component_tags %}"
"{% component_block 'incrementer' value='3' %}{% slot 'content' %}"
"<p>slot</p>{% endslot %}{% endcomponent_block %}")
"<p>slot</p>{% endslot %}{% endcomponent_block %}"
)
rendered = template.render(Context()).strip()
self.assertEqual(rendered, '<p class="incrementer">value=4;calls=1</p>\n<p>slot</p>', rendered)
self.assertEqual(
rendered,
'<p class="incrementer">value=4;calls=1</p>\n<p>slot</p>',
rendered,
)
class ComponentsCanAccessOuterContext(SimpleTestCase):
def test_simple_component_can_use_outer_context(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component 'simple_component' %}")
rendered = template.render(Context({'variable': 'outer_value'})).strip()
self.assertIn('outer_value', rendered, rendered)
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'simple_component' %}"
)
rendered = template.render(
Context({"variable": "outer_value"})
).strip()
self.assertIn("outer_value", rendered, rendered)
class IsolatedContextTests(SimpleTestCase):
def test_simple_component_can_pass_outer_context_in_args(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component 'simple_component' variable only %}")
rendered = template.render(Context({'variable': 'outer_value'})).strip()
self.assertIn('outer_value', rendered, rendered)
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'simple_component' variable only %}"
)
rendered = template.render(
Context({"variable": "outer_value"})
).strip()
self.assertIn("outer_value", rendered, rendered)
def test_simple_component_cannot_use_outer_context(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component 'simple_component' only %}")
rendered = template.render(Context({'variable': 'outer_value'})).strip()
self.assertNotIn('outer_value', rendered, rendered)
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'simple_component' only %}"
)
rendered = template.render(
Context({"variable": "outer_value"})
).strip()
self.assertNotIn("outer_value", rendered, rendered)
class OuterContextPropertyTests(SimpleTestCase):
def test_outer_context_property_with_component(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component 'outer_context_component' only %}")
rendered = template.render(Context({'variable': 'outer_value'})).strip()
self.assertIn('outer_value', rendered, rendered)
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'outer_context_component' only %}"
)
rendered = template.render(
Context({"variable": "outer_value"})
).strip()
self.assertIn("outer_value", rendered, rendered)
def test_outer_context_property_with_component_block(self):
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component_block 'outer_context_component' only %}{% endcomponent_block %}")
rendered = template.render(Context({'variable': 'outer_value'})).strip()
self.assertIn('outer_value', rendered, rendered)
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component_block 'outer_context_component' only %}{% endcomponent_block %}"
)
rendered = template.render(
Context({"variable": "outer_value"})
).strip()
self.assertIn("outer_value", rendered, rendered)

View file

@ -1,11 +1,12 @@
from django.template import Template
from django.test import override_settings
from .django_test_setup import * # NOQA
from django_components import component
from .django_test_setup import * # NOQA
from .test_templatetags import SimpleComponent
from .testutils import create_and_process_template_response, Django30CompatibleSimpleTestCase as SimpleTestCase
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
from .testutils import create_and_process_template_response
class SimpleComponentAlternate(component.Component):
@ -38,7 +39,7 @@ class MultistyleComponent(component.Component):
js = ["script.js", "script2.js"]
@override_settings(COMPONENTS={'RENDER_DEPENDENCIES': True})
@override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True})
class ComponentMediaRenderingTests(SimpleTestCase):
def setUp(self):
# NOTE: component.registry is global, so need to clear before each test
@ -47,150 +48,280 @@ class ComponentMediaRenderingTests(SimpleTestCase):
def test_no_dependencies_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent)
template = Template("{% load component_tags %}{% component_dependencies %}")
template = Template(
"{% load component_tags %}{% component_dependencies %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<script src="script.js>"', rendered, count=0)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=0)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=0)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=0,
)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=0,
)
def test_no_js_dependencies_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent)
template = Template("{% load component_tags %}{% component_js_dependencies %}")
template = Template(
"{% load component_tags %}{% component_js_dependencies %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<script src="script.js>"', rendered, count=0)
def test_no_css_dependencies_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent)
template = Template("{% load component_tags %}{% component_css_dependencies %}")
template = Template(
"{% load component_tags %}{% component_css_dependencies %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=0)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=0,
)
def test_single_component_dependencies_render_when_used(self):
component.registry.register(name="test", component=SimpleComponent)
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component 'test' variable='foo' %}")
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'test' variable='foo' %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=1)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=1,
)
self.assertInHTML('<script src="script.js">', rendered, count=1)
def test_placeholder_removed_when_single_component_rendered(self):
component.registry.register(name="test", component=SimpleComponent)
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component 'test' variable='foo' %}")
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'test' variable='foo' %}"
)
rendered = create_and_process_template_response(template)
self.assertNotIn('_RENDERED', rendered)
self.assertNotIn("_RENDERED", rendered)
def test_single_component_css_dependencies(self):
component.registry.register(name="test", component=SimpleComponent)
template = Template("{% load component_tags %}{% component_css_dependencies %}"
"{% component 'test' variable='foo' %}")
template = Template(
"{% load component_tags %}{% component_css_dependencies %}"
"{% component 'test' variable='foo' %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=1)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=1,
)
def test_single_component_js_dependencies(self):
component.registry.register(name="test", component=SimpleComponent)
template = Template("{% load component_tags %}{% component_js_dependencies %}"
"{% component 'test' variable='foo' %}")
template = Template(
"{% load component_tags %}{% component_js_dependencies %}"
"{% component 'test' variable='foo' %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<script src="script.js">', rendered, count=1)
def test_all_dependencies_are_rendered_for_component_with_multiple_dependencies(self):
component.registry.register(name='test', component=MultistyleComponent)
template = Template("{% load component_tags %}{% component_dependencies %}{% component 'test' %}")
def test_all_dependencies_are_rendered_for_component_with_multiple_dependencies(
self,
):
component.registry.register(name="test", component=MultistyleComponent)
template = Template(
"{% load component_tags %}{% component_dependencies %}{% component 'test' %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<script src="script.js">', rendered, count=1)
self.assertInHTML('<script src="script2.js">', rendered, count=1)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=1)
self.assertInHTML('<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=1)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=1,
)
self.assertInHTML(
'<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=1,
)
def test_all_js_dependencies_are_rendered_for_component_with_multiple_dependencies(self):
component.registry.register(name='test', component=MultistyleComponent)
template = Template("{% load component_tags %}{% component_js_dependencies %}{% component 'test' %}")
def test_all_js_dependencies_are_rendered_for_component_with_multiple_dependencies(
self,
):
component.registry.register(name="test", component=MultistyleComponent)
template = Template(
"{% load component_tags %}{% component_js_dependencies %}{% component 'test' %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<script src="script.js">', rendered, count=1)
self.assertInHTML('<script src="script2.js">', rendered, count=1)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=0)
self.assertInHTML('<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=0)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=0,
)
self.assertInHTML(
'<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=0,
)
def test_all_css_dependencies_are_rendered_for_component_with_multiple_dependencies(self):
component.registry.register(name='test', component=MultistyleComponent)
template = Template("{% load component_tags %}{% component_css_dependencies %}{% component 'test' %}")
def test_all_css_dependencies_are_rendered_for_component_with_multiple_dependencies(
self,
):
component.registry.register(name="test", component=MultistyleComponent)
template = Template(
"{% load component_tags %}{% component_css_dependencies %}{% component 'test' %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<script src="script.js">', rendered, count=0)
self.assertInHTML('<script src="script2.js">', rendered, count=0)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=1)
self.assertInHTML('<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=1)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=1,
)
self.assertInHTML(
'<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=1,
)
def test_no_dependencies_with_multiple_unused_components(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
component.registry.register(
name="test2", component=SimpleComponentAlternate
)
template = Template("{% load component_tags %}{% component_dependencies %}")
template = Template(
"{% load component_tags %}{% component_dependencies %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<script src="script.js">', rendered, count=0)
self.assertInHTML('<script src="script2.js">', rendered, count=0)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=0)
self.assertInHTML('<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=0)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=0,
)
self.assertInHTML(
'<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=0,
)
def test_correct_css_dependencies_with_multiple_components(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
component.registry.register(
name="test2", component=SimpleComponentAlternate
)
template = Template("{% load component_tags %}{% component_css_dependencies %}"
"{% component 'test1' 'variable' %}")
template = Template(
"{% load component_tags %}{% component_css_dependencies %}"
"{% component 'test1' 'variable' %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=1)
self.assertInHTML('<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=0)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=1,
)
self.assertInHTML(
'<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=0,
)
def test_correct_js_dependencies_with_multiple_components(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
component.registry.register(
name="test2", component=SimpleComponentAlternate
)
template = Template("{% load component_tags %}{% component_js_dependencies %}"
"{% component 'test1' 'variable' %}")
template = Template(
"{% load component_tags %}{% component_js_dependencies %}"
"{% component 'test1' 'variable' %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<script src="script.js">', rendered, count=1)
self.assertInHTML('<script src="script2.js">', rendered, count=0)
def test_correct_dependencies_with_multiple_components(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
component.registry.register(
name="test2", component=SimpleComponentAlternate
)
template = Template("{% load component_tags %}{% component_dependencies %}"
"{% component 'test2' variable='variable' %}")
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'test2' variable='variable' %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<script src="script.js">', rendered, count=0)
self.assertInHTML('<script src="script2.js">', rendered, count=1)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=0)
self.assertInHTML('<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=1)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=0,
)
self.assertInHTML(
'<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=1,
)
def test_shared_dependencies_rendered_once(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
component.registry.register(name="test3", component=SimpleComponentWithSharedDependency)
component.registry.register(
name="test2", component=SimpleComponentAlternate
)
component.registry.register(
name="test3", component=SimpleComponentWithSharedDependency
)
template = Template("{% load component_tags %}{% component_dependencies %}"
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'test1' variable='variable' %}{% component 'test2' variable='variable' %}"
"{% component 'test1' variable='variable' %}")
"{% component 'test1' variable='variable' %}"
)
rendered = create_and_process_template_response(template)
self.assertInHTML('<script src="script.js">', rendered, count=1)
self.assertInHTML('<script src="script2.js">', rendered, count=1)
self.assertInHTML('<link href="style.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=1)
self.assertInHTML('<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>', rendered, count=1)
self.assertInHTML(
'<link href="style.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=1,
)
self.assertInHTML(
'<link href="style2.css" type="text/css" media="all" rel="stylesheet"/>',
rendered,
count=1,
)
def test_placeholder_removed_when_multiple_component_rendered(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
component.registry.register(name="test3", component=SimpleComponentWithSharedDependency)
component.registry.register(
name="test2", component=SimpleComponentAlternate
)
component.registry.register(
name="test3", component=SimpleComponentWithSharedDependency
)
template = Template("{% load component_tags %}{% component_dependencies %}"
template = Template(
"{% load component_tags %}{% component_dependencies %}"
"{% component 'test1' variable='variable' %}{% component 'test2' variable='variable' %}"
"{% component 'test1' variable='variable' %}")
"{% component 'test1' variable='variable' %}"
)
rendered = create_and_process_template_response(template)
self.assertNotIn('_RENDERED', rendered)
self.assertNotIn("_RENDERED", rendered)

View file

@ -1,8 +1,9 @@
import unittest
from .django_test_setup import * # NOQA
from django_components import component
from .django_test_setup import * # NOQA
class MockComponent(object):
pass
@ -17,14 +18,13 @@ class ComponentRegistryTest(unittest.TestCase):
class TestComponent(component.Component):
pass
self.assertEqual(component.registry.get("decorated_component"), TestComponent)
self.assertEqual(
component.registry.get("decorated_component"), TestComponent
)
def test_simple_register(self):
self.registry.register(name="testcomponent", component=MockComponent)
self.assertEqual(
self.registry.all(),
{"testcomponent": MockComponent}
)
self.assertEqual(self.registry.all(), {"testcomponent": MockComponent})
def test_register_two_components(self):
self.registry.register(name="testcomponent", component=MockComponent)
@ -34,13 +34,15 @@ class ComponentRegistryTest(unittest.TestCase):
{
"testcomponent": MockComponent,
"testcomponent2": MockComponent,
}
},
)
def test_prevent_registering_twice(self):
self.registry.register(name="testcomponent", component=MockComponent)
with self.assertRaises(component.AlreadyRegistered):
self.registry.register(name="testcomponent", component=MockComponent)
self.registry.register(
name="testcomponent", component=MockComponent
)
def test_simple_unregister(self):
self.registry.register(name="testcomponent", component=MockComponent)

View file

@ -2,8 +2,9 @@ from textwrap import dedent
from django.template import Context, Template, TemplateSyntaxError
from .django_test_setup import * # NOQA
from django_components import component
from .django_test_setup import * # NOQA
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
@ -52,7 +53,7 @@ class ComponentWithProvidedAndDefaultParameters(component.Component):
template_name = "template_with_provided_and_default_parameters.html"
def get_context_data(self, variable, default_param="default text"):
return {"variable": variable, 'default_param': default_param}
return {"variable": variable, "default_param": default_param}
class ComponentTemplateTagTest(SimpleTestCase):
@ -105,7 +106,7 @@ class ComponentTemplateTagTest(SimpleTestCase):
component.registry.register(name="test", component=SimpleComponent)
template = Template(
'{% load component_tags %}{% component \'test\' variable="variable" %}'
"{% load component_tags %}{% component 'test' variable=\"variable\" %}"
)
rendered = template.render(Context({}))
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
@ -147,7 +148,9 @@ class ComponentSlottedTemplateTagTest(SimpleTestCase):
)
def test_slotted_template_with_context_var(self):
component.registry.register(name="test1", component=SlottedComponentWithContext)
component.registry.register(
name="test1", component=SlottedComponentWithContext
)
template = Template(
"""
@ -197,7 +200,9 @@ class ComponentSlottedTemplateTagTest(SimpleTestCase):
)
def test_slotted_template_without_slots(self):
component.registry.register(name="test", component=SlottedComponentNoSlots)
component.registry.register(
name="test", component=SlottedComponentNoSlots
)
template = Template(
"""
{% load component_tags %}
@ -209,7 +214,9 @@ class ComponentSlottedTemplateTagTest(SimpleTestCase):
self.assertHTMLEqual(rendered, "<custom-template></custom-template>")
def test_slotted_template_without_slots_and_single_quotes(self):
component.registry.register(name="test", component=SlottedComponentNoSlots)
component.registry.register(
name="test", component=SlottedComponentNoSlots
)
template = Template(
"""
{% load component_tags %}
@ -227,7 +234,9 @@ class SlottedTemplateRegressionTests(SimpleTestCase):
component.registry.clear()
def test_slotted_template_that_uses_missing_variable(self):
component.registry.register(name="test", component=SlottedComponentWithMissingVariable)
component.registry.register(
name="test", component=SlottedComponentWithMissingVariable
)
template = Template(
"""
{% load component_tags %}
@ -236,23 +245,30 @@ class SlottedTemplateRegressionTests(SimpleTestCase):
)
rendered = template.render(Context({}))
self.assertHTMLEqual(rendered, """
self.assertHTMLEqual(
rendered,
"""
<custom-template>
<header>Default header</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
""")
""",
)
def test_component_block_accepts_provided_and_default_parameters(self):
component.registry.register(name="test", component=ComponentWithProvidedAndDefaultParameters)
component.registry.register(
name="test", component=ComponentWithProvidedAndDefaultParameters
)
template = Template(
'{% load component_tags %}{% component_block "test" variable="provided value" %}{% endcomponent_block %}'
)
rendered = template.render(Context({}))
self.assertHTMLEqual(rendered,
"Provided variable: <strong>provided value</strong>\nDefault: <p>default text</p>")
self.assertHTMLEqual(
rendered,
"Provided variable: <strong>provided value</strong>\nDefault: <p>default text</p>",
)
class MultiComponentTests(SimpleTestCase):
@ -260,24 +276,38 @@ class MultiComponentTests(SimpleTestCase):
component.registry.clear()
def register_components(self):
component.registry.register('first_component', SlottedComponent)
component.registry.register('second_component', SlottedComponentWithContext)
component.registry.register("first_component", SlottedComponent)
component.registry.register(
"second_component", SlottedComponentWithContext
)
def make_template(self, first_component_slot='', second_component_slot=''):
return Template('{% load component_tags %}'
def make_template(self, first_component_slot="", second_component_slot=""):
return Template(
"{% load component_tags %}"
"{% component_block 'first_component' %}"
+ first_component_slot + '{% endcomponent_block %}'
+ first_component_slot
+ "{% endcomponent_block %}"
"{% component_block 'second_component' variable='xyz' %}"
+ second_component_slot + '{% endcomponent_block %}')
+ second_component_slot
+ "{% endcomponent_block %}"
)
def expected_result(self, first_component_slot='', second_component_slot=''):
return ('<custom-template><header>{}</header>'.format(first_component_slot or "Default header")
+ '<main>Default main</main><footer>Default footer</footer></custom-template>'
+ '<custom-template><header>{}</header>'.format(second_component_slot or "Default header")
+ '<main>Default main</main><footer>Default footer</footer></custom-template>')
def expected_result(
self, first_component_slot="", second_component_slot=""
):
return (
"<custom-template><header>{}</header>".format(
first_component_slot or "Default header"
)
+ "<main>Default main</main><footer>Default footer</footer></custom-template>"
+ "<custom-template><header>{}</header>".format(
second_component_slot or "Default header"
)
+ "<main>Default main</main><footer>Default footer</footer></custom-template>"
)
def wrap_with_slot_tags(self, s):
return '{% slot "header" %}' + s + '{% endslot %}'
return '{% slot "header" %}' + s + "{% endslot %}"
def test_both_components_render_correctly_with_no_slots(self):
self.register_components()
@ -286,26 +316,35 @@ class MultiComponentTests(SimpleTestCase):
def test_both_components_render_correctly_with_slots(self):
self.register_components()
first_slot_content = '<p>Slot #1</p>'
second_slot_content = '<div>Slot #2</div>'
first_slot_content = "<p>Slot #1</p>"
second_slot_content = "<div>Slot #2</div>"
first_slot = self.wrap_with_slot_tags(first_slot_content)
second_slot = self.wrap_with_slot_tags(second_slot_content)
rendered = self.make_template(first_slot, second_slot).render(Context({}))
self.assertHTMLEqual(rendered, self.expected_result(first_slot_content, second_slot_content))
rendered = self.make_template(first_slot, second_slot).render(
Context({})
)
self.assertHTMLEqual(
rendered,
self.expected_result(first_slot_content, second_slot_content),
)
def test_both_components_render_correctly_when_only_first_has_slots(self):
self.register_components()
first_slot_content = '<p>Slot #1</p>'
first_slot_content = "<p>Slot #1</p>"
first_slot = self.wrap_with_slot_tags(first_slot_content)
rendered = self.make_template(first_slot).render(Context({}))
self.assertHTMLEqual(rendered, self.expected_result(first_slot_content))
self.assertHTMLEqual(
rendered, self.expected_result(first_slot_content)
)
def test_both_components_render_correctly_when_only_second_has_slots(self):
self.register_components()
second_slot_content = '<div>Slot #2</div>'
second_slot_content = "<div>Slot #2</div>"
second_slot = self.wrap_with_slot_tags(second_slot_content)
rendered = self.make_template('', second_slot).render(Context({}))
self.assertHTMLEqual(rendered, self.expected_result('', second_slot_content))
rendered = self.make_template("", second_slot).render(Context({}))
self.assertHTMLEqual(
rendered, self.expected_result("", second_slot_content)
)
class TemplateInstrumentationTest(SimpleTestCase):
@ -323,8 +362,8 @@ class TemplateInstrumentationTest(SimpleTestCase):
def setUp(self):
component.registry.clear()
component.registry.register('test_component', SlottedComponent)
component.registry.register('inner_component', SimpleComponent)
component.registry.register("test_component", SlottedComponent)
component.registry.register("inner_component", SimpleComponent)
def templates_used_to_render(self, subject_template, render_context=None):
"""Emulate django.test.client.Client (see request method)."""
@ -335,23 +374,31 @@ class TemplateInstrumentationTest(SimpleTestCase):
def receive_template_signal(sender, template, context, **_kwargs):
templates_used.append(template.name)
template_rendered.connect(receive_template_signal, dispatch_uid='test_method')
template_rendered.connect(
receive_template_signal, dispatch_uid="test_method"
)
subject_template.render(render_context or Context({}))
template_rendered.disconnect(dispatch_uid='test_method')
template_rendered.disconnect(dispatch_uid="test_method")
return templates_used
def test_template_shown_as_used(self):
template = Template("{% load component_tags %}{% component 'test_component' %}", name='root')
template = Template(
"{% load component_tags %}{% component 'test_component' %}",
name="root",
)
templates_used = self.templates_used_to_render(template)
self.assertIn('slotted_template.html', templates_used)
self.assertIn("slotted_template.html", templates_used)
def test_nested_component_templates_all_shown_as_used(self):
template = Template("{% load component_tags %}{% component_block 'test_component' %}"
template = Template(
"{% load component_tags %}{% component_block 'test_component' %}"
"{% slot \"header\" %}{% component 'inner_component' variable='foo' %}{% endslot %}"
"{% endcomponent_block %}", name='root')
"{% endcomponent_block %}",
name="root",
)
templates_used = self.templates_used_to_render(template)
self.assertIn('slotted_template.html', templates_used)
self.assertIn('simple_template.html', templates_used)
self.assertIn("slotted_template.html", templates_used)
self.assertIn("simple_template.html", templates_used)
class NestedSlotTests(SimpleTestCase):
@ -362,7 +409,7 @@ class NestedSlotTests(SimpleTestCase):
def setUpClass(cls):
super().setUpClass()
component.registry.clear()
component.registry.register('test', cls.NestedComponent)
component.registry.register("test", cls.NestedComponent)
@classmethod
def tearDownClass(cls):
@ -397,7 +444,7 @@ class NestedSlotTests(SimpleTestCase):
"""
)
rendered = template.render(Context({}))
self.assertHTMLEqual(rendered, '<p>Override</p>')
self.assertHTMLEqual(rendered, "<p>Override</p>")
def test_both_overriden_and_inner_removed(self):
template = Template(
@ -410,7 +457,7 @@ class NestedSlotTests(SimpleTestCase):
"""
)
rendered = template.render(Context({}))
self.assertHTMLEqual(rendered, '<p>Override</p>')
self.assertHTMLEqual(rendered, "<p>Override</p>")
class ConditionalSlotTests(SimpleTestCase):
@ -418,13 +465,13 @@ class ConditionalSlotTests(SimpleTestCase):
template_name = "conditional_template.html"
def get_context_data(self, branch=None):
return {'branch': branch}
return {"branch": branch}
@classmethod
def setUpClass(cls):
super().setUpClass()
component.registry.clear()
component.registry.register('test', cls.ConditionalComponent)
component.registry.register("test", cls.ConditionalComponent)
@classmethod
def tearDownClass(cls):
@ -442,7 +489,7 @@ class ConditionalSlotTests(SimpleTestCase):
"""
)
rendered = template.render(Context({}))
self.assertHTMLEqual(rendered, '')
self.assertHTMLEqual(rendered, "")
def test_default_content_if_no_slots(self):
template = Template(
@ -453,7 +500,9 @@ class ConditionalSlotTests(SimpleTestCase):
"""
)
rendered = template.render(Context({}))
self.assertHTMLEqual(rendered, '<p id="a">Default A</p><p id="b">Default B</p>')
self.assertHTMLEqual(
rendered, '<p id="a">Default A</p><p id="b">Default B</p>'
)
def test_one_slot_overridden(self):
template = Template(
@ -468,7 +517,9 @@ class ConditionalSlotTests(SimpleTestCase):
"""
)
rendered = template.render(Context({}))
self.assertHTMLEqual(rendered, '<p id="a">Default A</p><p id="b">Override B</p>')
self.assertHTMLEqual(
rendered, '<p id="a">Default A</p><p id="b">Override B</p>'
)
def test_both_slots_overridden(self):
template = Template(
@ -485,7 +536,9 @@ class ConditionalSlotTests(SimpleTestCase):
"""
)
rendered = template.render(Context({}))
self.assertHTMLEqual(rendered, '<p id="a">Override A</p><p id="b">Override B</p>')
self.assertHTMLEqual(
rendered, '<p id="a">Override A</p><p id="b">Override B</p>'
)
class SlotSuperTests(SimpleTestCase):
@ -493,7 +546,7 @@ class SlotSuperTests(SimpleTestCase):
def setUpClass(cls):
super().setUpClass()
component.registry.clear()
component.registry.register('test', SlottedComponent)
component.registry.register("test", SlottedComponent)
@classmethod
def tearDownClass(cls):
@ -561,7 +614,7 @@ class SlotSuperTests(SimpleTestCase):
{% endcomponent_block %}
"""
)
rendered = template.render(Context({'range': range(3)}))
rendered = template.render(Context({"range": range(3)}))
self.assertHTMLEqual(
rendered,
@ -579,8 +632,8 @@ class TemplateSyntaxErrorTests(SimpleTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
component.registry.register('test', SlottedComponent)
component.registry.register('broken_component', BrokenComponent)
component.registry.register("test", SlottedComponent)
component.registry.register("broken_component", BrokenComponent)
def test_variable_outside_slot_tag_is_error(self):
with self.assertRaises(TemplateSyntaxError):

View file

@ -8,16 +8,20 @@ from django_components.middleware import ComponentDependencyMiddleware
# Create middleware instance
response_stash = None
middleware = ComponentDependencyMiddleware(get_response=lambda _: response_stash)
middleware = ComponentDependencyMiddleware(
get_response=lambda _: response_stash
)
class Django30CompatibleSimpleTestCase(SimpleTestCase):
def assertHTMLEqual(self, left, right):
left = left.replace(' type="text/javascript"', '')
super(Django30CompatibleSimpleTestCase, self).assertHTMLEqual(left, right)
left = left.replace(' type="text/javascript"', "")
super(Django30CompatibleSimpleTestCase, self).assertHTMLEqual(
left, right
)
def assertInHTML(self, needle, haystack, count=None, msg_prefix=''):
haystack = haystack.replace(' type="text/javascript"', '')
def assertInHTML(self, needle, haystack, count=None, msg_prefix=""):
haystack = haystack.replace(' type="text/javascript"', "")
super().assertInHTML(needle, haystack, count, msg_prefix)
@ -29,7 +33,9 @@ request = Mock()
mock_template = Mock()
def create_and_process_template_response(template, context=None, use_middleware=True):
def create_and_process_template_response(
template, context=None, use_middleware=True
):
context = context if context is not None else Context({})
mock_template.render = lambda context, _: template.render(context)
response = TemplateResponse(request, mock_template, context)
@ -40,4 +46,4 @@ def create_and_process_template_response(template, context=None, use_middleware=
response = middleware(request)
else:
response.render()
return response.content.decode('utf-8')
return response.content.decode("utf-8")