mirror of
https://github.com/django-components/django-components.git
synced 2025-08-17 12:40:15 +00:00
refactor: Backbone for passing JS and CSS variables (#861)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
296da4a1e8
commit
fe67d90547
28 changed files with 1181 additions and 594 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
|||
# Release notes
|
||||
|
||||
## v0.124
|
||||
|
||||
#### Refactor
|
||||
|
||||
- The undocumented `Component.component_id` was removed. Instead, use `Component.id`. Changes:
|
||||
|
||||
- While `component_id` was unique every time you instantiated `Component`, The new `id` is unique
|
||||
every time you render the component (e.g. with `Component.render()`)
|
||||
- The new `id` is available only during render, so e.g. from within `get_context_data()`
|
||||
|
||||
## v0.123
|
||||
|
||||
#### Fix
|
||||
|
|
|
@ -45,7 +45,14 @@ from django_components.context import (
|
|||
get_injected_context_var,
|
||||
make_isolated_context_copy,
|
||||
)
|
||||
from django_components.dependencies import RenderType, cache_inlined_css, cache_inlined_js, postprocess_component_html
|
||||
from django_components.dependencies import (
|
||||
RenderType,
|
||||
cache_component_css,
|
||||
cache_component_css_vars,
|
||||
cache_component_js,
|
||||
cache_component_js_vars,
|
||||
postprocess_component_html,
|
||||
)
|
||||
from django_components.expression import Expression, RuntimeKwargs, safe_resolve_list
|
||||
from django_components.node import BaseNode
|
||||
from django_components.slots import (
|
||||
|
@ -92,6 +99,7 @@ _type = type
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class RenderInput(Generic[ArgsType, KwargsType, SlotsType]):
|
||||
id: str
|
||||
context: Context
|
||||
args: ArgsType
|
||||
kwargs: KwargsType
|
||||
|
@ -297,7 +305,6 @@ class Component(
|
|||
def __init__(
|
||||
self,
|
||||
registered_name: Optional[str] = None,
|
||||
component_id: Optional[str] = None,
|
||||
outer_context: Optional[Context] = None,
|
||||
registry: Optional[ComponentRegistry] = None, # noqa F811
|
||||
):
|
||||
|
@ -318,7 +325,6 @@ class Component(
|
|||
|
||||
self.registered_name: Optional[str] = registered_name
|
||||
self.outer_context: Context = outer_context or Context()
|
||||
self.component_id = component_id or gen_id()
|
||||
self.registry = registry or registry_
|
||||
self._render_stack: Deque[RenderStackItem[ArgsType, KwargsType, SlotsType]] = deque()
|
||||
# None == uninitialized, False == No types, Tuple == types
|
||||
|
@ -331,6 +337,19 @@ class Component(
|
|||
def name(self) -> str:
|
||||
return self.registered_name or self.__class__.__name__
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
"""
|
||||
Render ID - This ID is unique for every time a `Component.render()` (or equivalent) is called.
|
||||
|
||||
This is useful for logging or debugging.
|
||||
|
||||
Render IDs have the chance of collision 1 in 3.3M.
|
||||
|
||||
Raises RuntimeError if called outside of rendering execution.
|
||||
"""
|
||||
return self.input.id
|
||||
|
||||
@property
|
||||
def input(self) -> RenderInput[ArgsType, KwargsType, SlotsType]:
|
||||
"""
|
||||
|
@ -702,6 +721,7 @@ class Component(
|
|||
self._render_stack.append(
|
||||
RenderStackItem(
|
||||
input=RenderInput(
|
||||
id=gen_id(),
|
||||
context=context,
|
||||
slots=slots,
|
||||
args=args,
|
||||
|
@ -716,9 +736,14 @@ class Component(
|
|||
context_data = self.get_context_data(*args, **kwargs)
|
||||
self._validate_outputs(data=context_data)
|
||||
|
||||
# Process JS and CSS files
|
||||
cache_inlined_js(self.__class__, self.js or "")
|
||||
cache_inlined_css(self.__class__, self.css or "")
|
||||
# Process Component's JS and CSS
|
||||
js_data: Dict = {} # TODO
|
||||
cache_component_js(self.__class__)
|
||||
js_input_hash = cache_component_js_vars(self.__class__, js_data) if js_data else None
|
||||
|
||||
css_data: Dict = {} # TODO
|
||||
cache_component_css(self.__class__)
|
||||
css_input_hash = cache_component_css_vars(self.__class__, css_data) if css_data else None
|
||||
|
||||
with _prepare_template(self, context, context_data) as template:
|
||||
# For users, we expose boolean variables that they may check
|
||||
|
@ -763,8 +788,10 @@ class Component(
|
|||
|
||||
output = postprocess_component_html(
|
||||
component_cls=self.__class__,
|
||||
component_id=self.component_id,
|
||||
component_id=self.id,
|
||||
html_content=html_content,
|
||||
css_input_hash=css_input_hash,
|
||||
js_input_hash=js_input_hash,
|
||||
type=type,
|
||||
render_dependencies=render_dependencies,
|
||||
)
|
||||
|
@ -951,7 +978,6 @@ class ComponentNode(BaseNode):
|
|||
component: Component = component_cls(
|
||||
registered_name=self.name,
|
||||
outer_context=context,
|
||||
component_id=self.node_id,
|
||||
registry=self.registry,
|
||||
)
|
||||
|
||||
|
|
|
@ -113,7 +113,6 @@ class DynamicComponent(Component):
|
|||
# NOTE: Slots are passed at component instantiation
|
||||
comp = comp_class(
|
||||
registered_name=self.registered_name,
|
||||
component_id=self.component_id,
|
||||
outer_context=self.outer_context,
|
||||
registry=self.registry,
|
||||
)
|
||||
|
|
|
@ -11,11 +11,10 @@ from typing import (
|
|||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
|
@ -35,7 +34,7 @@ from django.utils.decorators import sync_and_async_middleware
|
|||
from django.utils.safestring import SafeString, mark_safe
|
||||
|
||||
from django_components.util.html import SoupNode
|
||||
from django_components.util.misc import get_import_path
|
||||
from django_components.util.misc import get_import_path, is_nonempty_str
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django_components.component import Component
|
||||
|
@ -112,16 +111,21 @@ def _hash_comp_cls(comp_cls: Type["Component"]) -> str:
|
|||
def _gen_cache_key(
|
||||
comp_cls_hash: str,
|
||||
script_type: ScriptType,
|
||||
input_hash: Optional[str],
|
||||
) -> str:
|
||||
return f"__components:{comp_cls_hash}:{script_type}"
|
||||
if input_hash:
|
||||
return f"__components:{comp_cls_hash}:{script_type}:{input_hash}"
|
||||
else:
|
||||
return f"__components:{comp_cls_hash}:{script_type}"
|
||||
|
||||
|
||||
def _is_script_in_cache(
|
||||
comp_cls: Type["Component"],
|
||||
script_type: ScriptType,
|
||||
input_hash: Optional[str],
|
||||
) -> bool:
|
||||
comp_cls_hash = _hash_comp_cls(comp_cls)
|
||||
cache_key = _gen_cache_key(comp_cls_hash, script_type)
|
||||
cache_key = _gen_cache_key(comp_cls_hash, script_type, input_hash)
|
||||
return comp_media_cache.has(cache_key)
|
||||
|
||||
|
||||
|
@ -129,6 +133,7 @@ def _cache_script(
|
|||
comp_cls: Type["Component"],
|
||||
script: str,
|
||||
script_type: ScriptType,
|
||||
input_hash: Optional[str],
|
||||
) -> None:
|
||||
"""
|
||||
Given a component and it's inlined JS or CSS, store the JS/CSS in a cache,
|
||||
|
@ -138,7 +143,7 @@ def _cache_script(
|
|||
|
||||
# E.g. `__components:MyButton:js:df7c6d10`
|
||||
if script_type in ("js", "css"):
|
||||
cache_key = _gen_cache_key(comp_cls_hash, script_type)
|
||||
cache_key = _gen_cache_key(comp_cls_hash, script_type, input_hash)
|
||||
else:
|
||||
raise ValueError(f"Unexpected script_type '{script_type}'")
|
||||
|
||||
|
@ -147,33 +152,115 @@ def _cache_script(
|
|||
comp_media_cache.set(cache_key, script.strip())
|
||||
|
||||
|
||||
def cache_inlined_js(comp_cls: Type["Component"], content: str) -> None:
|
||||
if not _is_nonempty_str(comp_cls.js):
|
||||
return
|
||||
def cache_component_js(comp_cls: Type["Component"]) -> None:
|
||||
"""
|
||||
Cache the content from `Component.js`. This is the common JS that's shared
|
||||
among all instances of the same component. So even if the component is rendered multiple
|
||||
times, this JS is loaded only once.
|
||||
"""
|
||||
if not comp_cls.js or not is_nonempty_str(comp_cls.js) or _is_script_in_cache(comp_cls, "js", None):
|
||||
return None
|
||||
|
||||
# Prepare the script that's common to all instances of the same component
|
||||
# E.g. `my_table.js`
|
||||
if not _is_script_in_cache(comp_cls, "js"):
|
||||
_cache_script(
|
||||
comp_cls=comp_cls,
|
||||
script=comp_cls.js,
|
||||
script_type="js",
|
||||
input_hash=None,
|
||||
)
|
||||
|
||||
|
||||
# NOTE: In CSS, we link the CSS vars to the component via a stylesheet that defines
|
||||
# the CSS vars under `[data-djc-css-a1b2c3]`. Because of this we define the variables
|
||||
# separately from the rest of the CSS definition.
|
||||
#
|
||||
# We use conceptually similar approach for JS, except in JS we have to manually associate
|
||||
# the JS variables ("stylesheet") with the target HTML element ("component").
|
||||
#
|
||||
# It involves 3 steps:
|
||||
# 1. Register the common logic (equivalent to registering common CSS).
|
||||
# with `Components.manager.registerComponent`.
|
||||
# 2. Register the unique set of JS variables (equivalent to defining CSS vars)
|
||||
# with `Components.manager.registerComponentData`.
|
||||
# 3. Actually run a component's JS instance with `Components.manager.callComponent`,
|
||||
# specifying the components HTML elements with `component_id`, and JS vars with `input_hash`.
|
||||
def cache_component_js_vars(comp_cls: Type["Component"], js_vars: Dict) -> Optional[str]:
|
||||
if not is_nonempty_str(comp_cls.js):
|
||||
return None
|
||||
|
||||
# The hash for the file that holds the JS variables is derived from the variables themselves.
|
||||
json_data = json.dumps(js_vars)
|
||||
input_hash = md5(json_data.encode()).hexdigest()[0:6]
|
||||
|
||||
# Generate and cache a JS script that contains the JS variables.
|
||||
if not _is_script_in_cache(comp_cls, "js", input_hash):
|
||||
_cache_script(
|
||||
comp_cls=comp_cls,
|
||||
script=content,
|
||||
script="", # TODO
|
||||
script_type="js",
|
||||
input_hash=input_hash,
|
||||
)
|
||||
|
||||
return input_hash
|
||||
|
||||
def cache_inlined_css(comp_cls: Type["Component"], content: str) -> None:
|
||||
if not _is_nonempty_str(comp_cls.js):
|
||||
return
|
||||
|
||||
# Prepare the script that's common to all instances of the same component
|
||||
if not _is_script_in_cache(comp_cls, "css"):
|
||||
# E.g. `my_table.css`
|
||||
def wrap_component_js(comp_cls: Type["Component"], content: str) -> SafeString:
|
||||
if "</script" in content:
|
||||
raise RuntimeError(
|
||||
f"Content of `Component.js` for component '{comp_cls.__name__}' contains '</script>' end tag. "
|
||||
"This is not allowed, as it would break the HTML."
|
||||
)
|
||||
return f"<script>{content}</script>"
|
||||
|
||||
|
||||
def cache_component_css(comp_cls: Type["Component"]) -> None:
|
||||
"""
|
||||
Cache the content from `Component.css`. This is the common CSS that's shared
|
||||
among all instances of the same component. So even if the component is rendered multiple
|
||||
times, this CSS is loaded only once.
|
||||
"""
|
||||
if not comp_cls.css or not is_nonempty_str(comp_cls.css) or _is_script_in_cache(comp_cls, "css", None):
|
||||
return None
|
||||
|
||||
_cache_script(
|
||||
comp_cls=comp_cls,
|
||||
script=comp_cls.css,
|
||||
script_type="css",
|
||||
input_hash=None,
|
||||
)
|
||||
|
||||
|
||||
# NOTE: In CSS, we link the CSS vars to the component via a stylesheet that defines
|
||||
# the CSS vars under the CSS selector `[data-djc-css-a1b2c3]`. We define the stylesheet
|
||||
# with variables separately from `Component.css`, because different instances may return different
|
||||
# data from `get_css_data()`, which will live in different stylesheets.
|
||||
def cache_component_css_vars(comp_cls: Type["Component"], css_vars: Dict) -> Optional[str]:
|
||||
if not is_nonempty_str(comp_cls.css):
|
||||
return None
|
||||
|
||||
# The hash for the file that holds the CSS variables is derived from the variables themselves.
|
||||
json_data = json.dumps(css_vars)
|
||||
input_hash = md5(json_data.encode()).hexdigest()[0:6]
|
||||
|
||||
# Generate and cache a CSS stylesheet that contains the CSS variables.
|
||||
if not _is_script_in_cache(comp_cls, "css", input_hash):
|
||||
_cache_script(
|
||||
comp_cls=comp_cls,
|
||||
script=content,
|
||||
script="", # TODO
|
||||
script_type="css",
|
||||
input_hash=input_hash,
|
||||
)
|
||||
|
||||
return input_hash
|
||||
|
||||
|
||||
def wrap_component_css(comp_cls: Type["Component"], content: str) -> SafeString:
|
||||
if "</style" in content:
|
||||
raise RuntimeError(
|
||||
f"Content of `Component.css` for component '{comp_cls.__name__}' contains '</style>' end tag. "
|
||||
"This is not allowed, as it would break the HTML."
|
||||
)
|
||||
return f"<style>{content}</style>"
|
||||
|
||||
|
||||
#########################################################
|
||||
# 2. Modify the HTML to use the same IDs defined in previous
|
||||
|
@ -183,16 +270,43 @@ def cache_inlined_css(comp_cls: Type["Component"], content: str) -> None:
|
|||
#########################################################
|
||||
|
||||
|
||||
class Dependencies(NamedTuple):
|
||||
# NOTE: We pass around the component CLASS, so the dependencies logic is not
|
||||
# dependent on ComponentRegistries
|
||||
component_cls: Type["Component"]
|
||||
component_id: str
|
||||
def _link_dependencies_with_component_html(
|
||||
component_id: str,
|
||||
html_content: str,
|
||||
css_input_hash: Optional[str],
|
||||
) -> str:
|
||||
elems = SoupNode.from_fragment(html_content)
|
||||
|
||||
# Insert component ID
|
||||
for elem in elems:
|
||||
# Ignore comments, text, doctype, etc.
|
||||
if not elem.is_element():
|
||||
continue
|
||||
|
||||
# Component ID is used for executing JS script, e.g. `data-djc-id-a1b2c3`
|
||||
#
|
||||
# NOTE: We use `data-djc-css-a1b2c3` and `data-djc-id-a1b2c3` instead of
|
||||
# `data-djc-css="a1b2c3"` and `data-djc-id="a1b2c3"`, to allow
|
||||
# multiple values to be associated with the same element, which may happen if
|
||||
# One component renders another.
|
||||
elem.set_attr(f"data-djc-id-{component_id}", True)
|
||||
|
||||
# Attribute by which we bind the CSS variables to the component's CSS,
|
||||
# e.g. `data-djc-css-a1b2c3`
|
||||
if css_input_hash:
|
||||
elem.set_attr(f"data-djc-css-{css_input_hash}", True)
|
||||
|
||||
return SoupNode.to_html_multiroot(elems)
|
||||
|
||||
|
||||
def _insert_component_comment(
|
||||
content: str,
|
||||
deps: Dependencies,
|
||||
# NOTE: We pass around the component CLASS, so the dependencies logic is not
|
||||
# dependent on ComponentRegistries
|
||||
component_cls: Type["Component"],
|
||||
component_id: str,
|
||||
js_input_hash: Optional[str],
|
||||
css_input_hash: Optional[str],
|
||||
) -> str:
|
||||
"""
|
||||
Given some textual content, prepend it with a short string that
|
||||
|
@ -200,14 +314,14 @@ def _insert_component_comment(
|
|||
declared JS / CSS scripts.
|
||||
"""
|
||||
# Add components to the cache
|
||||
comp_cls_hash = _hash_comp_cls(deps.component_cls)
|
||||
comp_hash_mapping[comp_cls_hash] = deps.component_cls
|
||||
comp_cls_hash = _hash_comp_cls(component_cls)
|
||||
comp_hash_mapping[comp_cls_hash] = component_cls
|
||||
|
||||
data = f"{comp_cls_hash},{deps.component_id}"
|
||||
data = f"{comp_cls_hash},{component_id},{js_input_hash or ''},{css_input_hash or ''}"
|
||||
|
||||
# NOTE: It's important that we put the comment BEFORE the content, so we can
|
||||
# use the order of comments to evaluate components' instance JS code in the correct order.
|
||||
output = mark_safe(COMPONENT_DEPS_COMMENT.format(data=data)) + content
|
||||
output = mark_safe(COMPONENT_DEPS_COMMENT.format(data=data) + content)
|
||||
return output
|
||||
|
||||
|
||||
|
@ -217,9 +331,18 @@ def postprocess_component_html(
|
|||
component_cls: Type["Component"],
|
||||
component_id: str,
|
||||
html_content: str,
|
||||
css_input_hash: Optional[str],
|
||||
js_input_hash: Optional[str],
|
||||
type: RenderType,
|
||||
render_dependencies: bool,
|
||||
) -> str:
|
||||
# Make the HTML work with JS and CSS dependencies
|
||||
html_content = _link_dependencies_with_component_html(
|
||||
component_id=component_id,
|
||||
html_content=html_content,
|
||||
css_input_hash=css_input_hash,
|
||||
)
|
||||
|
||||
# NOTE: To better understand the next section, consider this:
|
||||
#
|
||||
# We define and cache the component's JS and CSS at the same time as
|
||||
|
@ -242,10 +365,10 @@ def postprocess_component_html(
|
|||
# scripts are associated with it.
|
||||
output = _insert_component_comment(
|
||||
html_content,
|
||||
Dependencies(
|
||||
component_cls=component_cls,
|
||||
component_id=component_id,
|
||||
),
|
||||
component_cls=component_cls,
|
||||
component_id=component_id,
|
||||
js_input_hash=js_input_hash,
|
||||
css_input_hash=css_input_hash,
|
||||
)
|
||||
|
||||
if render_dependencies:
|
||||
|
@ -266,21 +389,34 @@ def postprocess_component_html(
|
|||
TContent = TypeVar("TContent", bound=Union[bytes, str])
|
||||
|
||||
|
||||
CSS_DEPENDENCY_PLACEHOLDER = '<link name="CSS_PLACEHOLDER">'
|
||||
JS_DEPENDENCY_PLACEHOLDER = '<script name="JS_PLACEHOLDER"></script>'
|
||||
|
||||
CSS_PLACEHOLDER_BYTES = bytes(CSS_DEPENDENCY_PLACEHOLDER, encoding="utf-8")
|
||||
JS_PLACEHOLDER_BYTES = bytes(JS_DEPENDENCY_PLACEHOLDER, encoding="utf-8")
|
||||
CSS_PLACEHOLDER_NAME = "CSS_PLACEHOLDER"
|
||||
CSS_PLACEHOLDER_NAME_B = CSS_PLACEHOLDER_NAME.encode()
|
||||
JS_PLACEHOLDER_NAME = "JS_PLACEHOLDER"
|
||||
JS_PLACEHOLDER_NAME_B = JS_PLACEHOLDER_NAME.encode()
|
||||
|
||||
CSS_DEPENDENCY_PLACEHOLDER = f'<link name="{CSS_PLACEHOLDER_NAME}"/>'
|
||||
JS_DEPENDENCY_PLACEHOLDER = f'<script name="{JS_PLACEHOLDER_NAME}"></script>'
|
||||
COMPONENT_DEPS_COMMENT = "<!-- _RENDERED {data} -->"
|
||||
# E.g. `<!-- _RENDERED table,123 -->`
|
||||
COMPONENT_COMMENT_REGEX = re.compile(rb"<!-- _RENDERED (?P<data>[\w\-,/]+?) -->")
|
||||
# E.g. `table,123`
|
||||
SCRIPT_NAME_REGEX = re.compile(rb"^(?P<comp_cls_hash>[\w\-\./]+?),(?P<id>[\w]+?)$")
|
||||
|
||||
# E.g. `<!-- _RENDERED table,123,a92ef298,bd002c3 -->`
|
||||
COMPONENT_COMMENT_REGEX = re.compile(rb"<!--\s+_RENDERED\s+(?P<data>[\w\-,/]+?)\s+-->")
|
||||
# E.g. `table,123,a92ef298,bd002c3`
|
||||
# - comp_cls_hash - Cache key of the component class that was rendered
|
||||
# - id - Component render ID
|
||||
# - js - Cache key for the JS data from `get_js_data()`
|
||||
# - css - Cache key for the CSS data from `get_css_data()`
|
||||
SCRIPT_NAME_REGEX = re.compile(
|
||||
rb"^(?P<comp_cls_hash>[\w\-\./]+?),(?P<id>[\w]+?),(?P<js>[0-9a-f]*?),(?P<css>[0-9a-f]*?)$"
|
||||
)
|
||||
# E.g. `data-djc-id-a1b2c3`
|
||||
MAYBE_COMP_ID = r"(?: data-djc-id-\w{6})?"
|
||||
# E.g. `data-djc-css-99914b`
|
||||
MAYBE_COMP_CSS_ID = r"(?: data-djc-css-\w{6})?"
|
||||
|
||||
PLACEHOLDER_REGEX = re.compile(
|
||||
r"{css_placeholder}|{js_placeholder}".format(
|
||||
css_placeholder=CSS_DEPENDENCY_PLACEHOLDER,
|
||||
js_placeholder=JS_DEPENDENCY_PLACEHOLDER,
|
||||
css_placeholder=f'<link{MAYBE_COMP_CSS_ID}{MAYBE_COMP_ID} name="{CSS_PLACEHOLDER_NAME}"/>',
|
||||
js_placeholder=f'<script{MAYBE_COMP_CSS_ID}{MAYBE_COMP_ID} name="{JS_PLACEHOLDER_NAME}"></script>',
|
||||
).encode()
|
||||
)
|
||||
|
||||
|
@ -350,10 +486,10 @@ def render_dependencies(content: TContent, type: RenderType = "document") -> TCo
|
|||
nonlocal did_find_css_placeholder
|
||||
nonlocal did_find_js_placeholder
|
||||
|
||||
if match[0] == CSS_PLACEHOLDER_BYTES:
|
||||
if CSS_PLACEHOLDER_NAME_B in match[0]:
|
||||
replacement = css_replacement
|
||||
did_find_css_placeholder = True
|
||||
elif match[0] == JS_PLACEHOLDER_BYTES:
|
||||
elif JS_PLACEHOLDER_NAME_B in match[0]:
|
||||
replacement = js_replacement
|
||||
did_find_js_placeholder = True
|
||||
else:
|
||||
|
@ -418,11 +554,11 @@ def _process_dep_declarations(content: bytes, type: RenderType) -> Tuple[bytes,
|
|||
Process a textual content that may include metadata on rendered components.
|
||||
The metadata has format like this
|
||||
|
||||
`<!-- _RENDERED component_name,component_id -->`
|
||||
`<!-- _RENDERED component_name,component_id,js_hash,css_hash;... -->`
|
||||
|
||||
E.g.
|
||||
|
||||
`<!-- _RENDERED table_10bac31,123 -->`
|
||||
`<!-- _RENDERED table_10bac31,123,a92ef298,bd002c3 -->`
|
||||
"""
|
||||
# Extract all matched instances of `<!-- _RENDERED ... -->` while also removing them from the text
|
||||
all_parts: List[bytes] = list()
|
||||
|
@ -436,23 +572,49 @@ def _process_dep_declarations(content: bytes, type: RenderType) -> Tuple[bytes,
|
|||
# NOTE: Python's set does NOT preserve order
|
||||
seen_comp_hashes: Set[str] = set()
|
||||
comp_hashes: List[str] = []
|
||||
# Used for passing Python vars to JS/CSS
|
||||
inputs_data: List[Tuple[str, ScriptType, Optional[str]]] = []
|
||||
comp_data: List[Tuple[str, ScriptType, Optional[str]]] = []
|
||||
|
||||
# Process individual parts. Each part is like a CSV row of `name,id`.
|
||||
# Process individual parts. Each part is like a CSV row of `name,id,js,css`.
|
||||
# E.g. something like this:
|
||||
# `table_10bac31,1234`
|
||||
# `table_10bac31,1234,a92ef298,a92ef298`
|
||||
for part in all_parts:
|
||||
part_match = SCRIPT_NAME_REGEX.match(part)
|
||||
|
||||
if not part_match:
|
||||
raise RuntimeError("Malformed dependencies data")
|
||||
|
||||
comp_cls_hash = part_match.group("comp_cls_hash").decode("utf-8")
|
||||
comp_cls_hash: str = part_match.group("comp_cls_hash").decode("utf-8")
|
||||
js_input_hash: Optional[str] = part_match.group("js").decode("utf-8") or None
|
||||
css_input_hash: Optional[str] = part_match.group("css").decode("utf-8") or None
|
||||
|
||||
if comp_cls_hash in seen_comp_hashes:
|
||||
continue
|
||||
|
||||
comp_hashes.append(comp_cls_hash)
|
||||
seen_comp_hashes.add(comp_cls_hash)
|
||||
|
||||
# Schedule to load the `<script>` / `<link>` tags for the JS / CSS from `Component.js/css`.
|
||||
comp_data.append((comp_cls_hash, "js", None))
|
||||
comp_data.append((comp_cls_hash, "css", None))
|
||||
|
||||
# Schedule to load the `<script>` / `<link>` tags for the JS / CSS variables.
|
||||
# Skip if no variables are defined.
|
||||
if js_input_hash is not None:
|
||||
inputs_data.append((comp_cls_hash, "js", js_input_hash))
|
||||
if css_input_hash is not None:
|
||||
inputs_data.append((comp_cls_hash, "css", css_input_hash))
|
||||
|
||||
(
|
||||
to_load_input_js_urls,
|
||||
to_load_input_css_urls,
|
||||
inlined_input_js_tags,
|
||||
inlined_input_css_tags,
|
||||
loaded_input_js_urls,
|
||||
loaded_input_css_urls,
|
||||
) = _prepare_tags_and_urls(inputs_data, type)
|
||||
|
||||
(
|
||||
to_load_component_js_urls,
|
||||
to_load_component_css_urls,
|
||||
|
@ -460,7 +622,7 @@ def _process_dep_declarations(content: bytes, type: RenderType) -> Tuple[bytes,
|
|||
inlined_component_css_tags,
|
||||
loaded_component_js_urls,
|
||||
loaded_component_css_urls,
|
||||
) = _prepare_tags_and_urls(comp_hashes, type)
|
||||
) = _prepare_tags_and_urls(comp_data, type)
|
||||
|
||||
def get_component_media(comp_cls_hash: str) -> Media:
|
||||
comp_cls = comp_hash_mapping[comp_cls_hash]
|
||||
|
@ -473,8 +635,8 @@ def _process_dep_declarations(content: bytes, type: RenderType) -> Tuple[bytes,
|
|||
*[get_component_media(comp_cls_hash) for comp_cls_hash in comp_hashes],
|
||||
# All the inlined scripts that we plan to fetch / load
|
||||
Media(
|
||||
js=to_load_component_js_urls,
|
||||
css={"all": to_load_component_css_urls},
|
||||
js=[*to_load_component_js_urls, *to_load_input_js_urls],
|
||||
css={"all": [*to_load_component_css_urls, *to_load_input_css_urls]},
|
||||
),
|
||||
]
|
||||
|
||||
|
@ -492,6 +654,7 @@ def _process_dep_declarations(content: bytes, type: RenderType) -> Tuple[bytes,
|
|||
loaded_css_urls = sorted(
|
||||
[
|
||||
*loaded_component_css_urls,
|
||||
*loaded_input_css_urls,
|
||||
# NOTE: When rendering a document, the initial CSS is inserted directly into the HTML
|
||||
# to avoid a flash of unstyled content. In the dependency manager, we only mark those
|
||||
# scripts as loaded.
|
||||
|
@ -501,6 +664,7 @@ def _process_dep_declarations(content: bytes, type: RenderType) -> Tuple[bytes,
|
|||
loaded_js_urls = sorted(
|
||||
[
|
||||
*loaded_component_js_urls,
|
||||
*loaded_input_js_urls,
|
||||
# NOTE: When rendering a document, the initial JS is inserted directly into the HTML
|
||||
# so the scripts are executed at proper order. In the dependency manager, we only mark those
|
||||
# scripts as loaded.
|
||||
|
@ -533,6 +697,8 @@ def _process_dep_declarations(content: bytes, type: RenderType) -> Tuple[bytes,
|
|||
# so the scripts are executed at proper order. In the dependency manager, we only mark those
|
||||
# scripts as loaded.
|
||||
*(to_load_js_tags if type == "document" else []),
|
||||
# JS variables
|
||||
*[tag for tag in inlined_input_js_tags],
|
||||
# JS from `Component.js` (if not fragment)
|
||||
*[tag for tag in inlined_component_js_tags],
|
||||
]
|
||||
|
@ -544,6 +710,8 @@ def _process_dep_declarations(content: bytes, type: RenderType) -> Tuple[bytes,
|
|||
# <NONE>
|
||||
# CSS from `Component.css` (if not fragment)
|
||||
*[tag for tag in inlined_component_css_tags],
|
||||
# CSS variables
|
||||
*[tag for tag in inlined_input_css_tags],
|
||||
# CSS from `Media.css` (plus from `Component.css` if fragment)
|
||||
# NOTE: Similarly to JS, the initial CSS is loaded outside of the dependency
|
||||
# manager, and only marked as loaded, to avoid a flash of unstyled content.
|
||||
|
@ -554,10 +722,6 @@ def _process_dep_declarations(content: bytes, type: RenderType) -> Tuple[bytes,
|
|||
return (content, final_script_tags.encode("utf-8"), final_css_tags.encode("utf-8"))
|
||||
|
||||
|
||||
def _is_nonempty_str(txt: Optional[str]) -> bool:
|
||||
return txt is not None and bool(txt.strip())
|
||||
|
||||
|
||||
# Detect duplicates by URLs, extract URLs, and sort by URLs
|
||||
def _postprocess_media_tags(
|
||||
script_type: ScriptType,
|
||||
|
@ -572,7 +736,7 @@ def _postprocess_media_tags(
|
|||
attr = "src" if script_type == "js" else "href"
|
||||
maybe_url = node.get_attr(attr, None)
|
||||
|
||||
if not _is_nonempty_str(maybe_url):
|
||||
if not is_nonempty_str(maybe_url):
|
||||
raise RuntimeError(
|
||||
f"One of entries for `Component.Media.{script_type}` media is missing a "
|
||||
f"value for attribute '{attr}'. If there is content inlined inside the `<{node.name()}>` tags, "
|
||||
|
@ -595,7 +759,7 @@ def _postprocess_media_tags(
|
|||
|
||||
|
||||
def _prepare_tags_and_urls(
|
||||
data: Iterable[str],
|
||||
data: List[Tuple[str, ScriptType, Optional[str]]],
|
||||
type: RenderType,
|
||||
) -> Tuple[List[str], List[str], List[str], List[str], List[str], List[str]]:
|
||||
to_load_js_urls: List[str] = []
|
||||
|
@ -609,7 +773,7 @@ def _prepare_tags_and_urls(
|
|||
# But even in that case we still need to call `Components.manager.markScriptLoaded`,
|
||||
# so the client knows NOT to fetch them again.
|
||||
# So in that case we populate both `inlined` and `loaded` lists
|
||||
for comp_cls_hash in data:
|
||||
for comp_cls_hash, script_type, input_hash in data:
|
||||
# NOTE: When CSS is scoped, then EVERY component instance will have different
|
||||
# copy of the style, because each copy will have component's ID embedded.
|
||||
# So, in that case we inline the style into the HTML (See `_link_dependencies_with_component_html`),
|
||||
|
@ -618,22 +782,27 @@ def _prepare_tags_and_urls(
|
|||
|
||||
if type == "document":
|
||||
# NOTE: Skip fetching of inlined JS/CSS if it's not defined or empty for given component
|
||||
if _is_nonempty_str(comp_cls.js):
|
||||
inlined_js_tags.append(_get_script_tag("js", comp_cls))
|
||||
loaded_js_urls.append(get_script_url("js", comp_cls))
|
||||
#
|
||||
# NOTE: If `input_hash` is `None`, then we get the component's JS/CSS
|
||||
# (e.g. `/components/cache/table.js`).
|
||||
# And if `input_hash` is given, we get the component's JS/CSS variables
|
||||
# (e.g. `/components/cache/table.0ab2c3.js`).
|
||||
if script_type == "js" and is_nonempty_str(comp_cls.js):
|
||||
inlined_js_tags.append(get_script_tag("js", comp_cls, input_hash))
|
||||
loaded_js_urls.append(get_script_url("js", comp_cls, input_hash))
|
||||
|
||||
if _is_nonempty_str(comp_cls.css):
|
||||
inlined_css_tags.append(_get_script_tag("css", comp_cls))
|
||||
loaded_css_urls.append(get_script_url("css", comp_cls))
|
||||
if script_type == "css" and is_nonempty_str(comp_cls.css):
|
||||
inlined_css_tags.append(get_script_tag("css", comp_cls, input_hash))
|
||||
loaded_css_urls.append(get_script_url("css", comp_cls, input_hash))
|
||||
|
||||
# When NOT a document (AKA is a fragment), then scripts are NOT inserted into
|
||||
# the HTML, and instead we fetch and load them all via our JS dependency manager.
|
||||
else:
|
||||
if _is_nonempty_str(comp_cls.js):
|
||||
to_load_js_urls.append(get_script_url("js", comp_cls))
|
||||
if script_type == "js" and is_nonempty_str(comp_cls.js):
|
||||
to_load_js_urls.append(get_script_url("js", comp_cls, input_hash))
|
||||
|
||||
if _is_nonempty_str(comp_cls.css):
|
||||
to_load_css_urls.append(get_script_url("css", comp_cls))
|
||||
if script_type == "css" and is_nonempty_str(comp_cls.css):
|
||||
to_load_css_urls.append(get_script_url("css", comp_cls, input_hash))
|
||||
|
||||
return (
|
||||
to_load_js_urls,
|
||||
|
@ -648,43 +817,36 @@ def _prepare_tags_and_urls(
|
|||
def get_script_content(
|
||||
script_type: ScriptType,
|
||||
comp_cls: Type["Component"],
|
||||
input_hash: Optional[str],
|
||||
) -> SafeString:
|
||||
comp_cls_hash = _hash_comp_cls(comp_cls)
|
||||
cache_key = _gen_cache_key(comp_cls_hash, script_type)
|
||||
cache_key = _gen_cache_key(comp_cls_hash, script_type, input_hash)
|
||||
script = comp_media_cache.get(cache_key)
|
||||
|
||||
return script
|
||||
|
||||
|
||||
def _get_script_tag(
|
||||
def get_script_tag(
|
||||
script_type: ScriptType,
|
||||
comp_cls: Type["Component"],
|
||||
input_hash: Optional[str],
|
||||
) -> SafeString:
|
||||
script = get_script_content(script_type, comp_cls)
|
||||
content = get_script_content(script_type, comp_cls, input_hash)
|
||||
|
||||
if script_type == "js":
|
||||
if "</script" in script:
|
||||
raise RuntimeError(
|
||||
f"Content of `Component.js` for component '{comp_cls.__name__}' contains '</script>' end tag. "
|
||||
"This is not allowed, as it would break the HTML."
|
||||
)
|
||||
return f"<script>{script}</script>"
|
||||
|
||||
content = wrap_component_js(comp_cls, content)
|
||||
elif script_type == "css":
|
||||
if "</style" in script:
|
||||
raise RuntimeError(
|
||||
f"Content of `Component.css` for component '{comp_cls.__name__}' contains '</style>' end tag. "
|
||||
"This is not allowed, as it would break the HTML."
|
||||
)
|
||||
content = wrap_component_css(comp_cls, content)
|
||||
else:
|
||||
raise ValueError(f"Unexpected script_type '{script_type}'")
|
||||
|
||||
return f"<style>{script}</style>"
|
||||
|
||||
return script
|
||||
return content
|
||||
|
||||
|
||||
def get_script_url(
|
||||
script_type: ScriptType,
|
||||
comp_cls: Type["Component"],
|
||||
input_hash: Optional[str],
|
||||
) -> str:
|
||||
comp_cls_hash = _hash_comp_cls(comp_cls)
|
||||
|
||||
|
@ -693,6 +855,7 @@ def get_script_url(
|
|||
kwargs={
|
||||
"comp_cls_hash": comp_cls_hash,
|
||||
"script_type": script_type,
|
||||
**({"input_hash": input_hash} if input_hash is not None else {}),
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -703,10 +866,11 @@ def _gen_exec_script(
|
|||
loaded_js_urls: List[str],
|
||||
loaded_css_urls: List[str],
|
||||
) -> Optional[str]:
|
||||
if not to_load_js_tags and not to_load_css_tags and not loaded_css_urls and not loaded_js_urls:
|
||||
# Return None if all lists are empty
|
||||
if not any([to_load_js_tags, to_load_css_tags, loaded_css_urls, loaded_js_urls]):
|
||||
return None
|
||||
|
||||
def map_to_base64(lst: List[str]) -> List[str]:
|
||||
def map_to_base64(lst: Sequence[str]) -> List[str]:
|
||||
return [base64.b64encode(tag.encode()).decode() for tag in lst]
|
||||
|
||||
# Generate JSON that will tell the JS dependency manager which JS and CSS to load
|
||||
|
@ -797,14 +961,16 @@ def cached_script_view(
|
|||
req: HttpRequest,
|
||||
comp_cls_hash: str,
|
||||
script_type: ScriptType,
|
||||
input_hash: Optional[str] = None,
|
||||
) -> HttpResponse:
|
||||
if req.method != "GET":
|
||||
return HttpResponseNotAllowed(["GET"])
|
||||
|
||||
# Otherwise check if the file is among the dynamically generated files in the cache
|
||||
cache_key = _gen_cache_key(comp_cls_hash, script_type)
|
||||
script = comp_media_cache.get(cache_key)
|
||||
comp_cls = comp_hash_mapping.get(comp_cls_hash)
|
||||
if comp_cls is None:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
script = get_script_content(script_type, comp_cls, input_hash)
|
||||
if script is None:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
|
@ -813,7 +979,8 @@ def cached_script_view(
|
|||
|
||||
|
||||
urlpatterns = [
|
||||
# E.g. `/components/cache/table.js`
|
||||
# E.g. `/components/cache/table.js` or `/components/cache/table.0ab2c3.js`
|
||||
path("cache/<str:comp_cls_hash>.<str:input_hash>.<str:script_type>", cached_script_view, name=CACHE_ENDPOINT_NAME),
|
||||
path("cache/<str:comp_cls_hash>.<str:script_type>", cached_script_view, name=CACHE_ENDPOINT_NAME),
|
||||
]
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ from django_components.context import set_provided_context_var
|
|||
from django_components.expression import RuntimeKwargs
|
||||
from django_components.node import BaseNode
|
||||
from django_components.util.logger import trace_msg
|
||||
from django_components.util.misc import gen_id
|
||||
|
||||
PROVIDE_NAME_KWARG = "name"
|
||||
|
||||
|
@ -28,10 +27,7 @@ class ProvideNode(BaseNode):
|
|||
):
|
||||
super().__init__(nodelist=nodelist, args=None, kwargs=kwargs, node_id=node_id)
|
||||
|
||||
self.nodelist = nodelist
|
||||
self.node_id = node_id or gen_id()
|
||||
self.trace_id = trace_id
|
||||
self.kwargs = kwargs or RuntimeKwargs({})
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Provide Node: {self.node_id}. Contents: {repr(self.nodelist)}>"
|
||||
|
|
|
@ -1 +1 @@
|
|||
(()=>{var x=o=>new DOMParser().parseFromString(o,"text/html").documentElement.textContent,E=Array.isArray,m=o=>typeof o=="function",H=o=>o!==null&&typeof o=="object",S=o=>(H(o)||m(o))&&m(o.then)&&m(o.catch);function N(o,i){try{return i?o.apply(null,i):o()}catch(s){L(s)}}function g(o,i){if(m(o)){let s=N(o,i);return s&&S(s)&&s.catch(c=>{L(c)}),[s]}if(E(o)){let s=[];for(let c=0;c<o.length;c++)s.push(g(o[c],i));return s}else console.warn(`[Components] Invalid value type passed to callWithAsyncErrorHandling(): ${typeof o}`)}function L(o){console.error(o)}var M=o=>{let i=new MutationObserver(s=>{for(let c of s)c.type==="childList"&&c.addedNodes.forEach(p=>{p.nodeName==="SCRIPT"&&p.hasAttribute("data-djc")&&o(p)})});return i.observe(document,{childList:!0,subtree:!0}),i};var y=()=>{let o=new Set,i=new Set,s={},c={},p=t=>{let e=new DOMParser().parseFromString(t,"text/html").querySelector("script");if(!e)throw Error("[Components] Failed to extract <script> tag. Make sure that the string contains <script><\/script> and is a valid HTML");return e},F=t=>{let e=new DOMParser().parseFromString(t,"text/html").querySelector("link");if(!e)throw Error("[Components] Failed to extract <link> tag. Make sure that the string contains <link></link> and is a valid HTML");return e},T=t=>{let e=document.createElement(t.tagName);e.innerHTML=t.innerHTML;for(let r of t.attributes)e.setAttributeNode(r.cloneNode());return e},f=t=>{let e=p(t),r=e.getAttribute("src");if(!r||C("js",r))return;d("js",r);let a=T(e),l=e.getAttribute("async")!=null||e.getAttribute("defer")!=null||e.getAttribute("type")==="module";a.async=l;let u=new Promise((n,b)=>{a.onload=()=>{n()},globalThis.document.body.append(a)});return{el:a,promise:u}},h=t=>{let e=F(t),r=e.getAttribute("href");if(!r||C("css",r))return;let a=T(e);return globalThis.document.head.append(a),d("css",r),{el:a,promise:Promise.resolve()}},d=(t,e)=>{if(t!=="js"&&t!=="css")throw Error(`[Components] markScriptLoaded received invalid script type '${t}'. Must be one of 'js', 'css'`);(t==="js"?o:i).add(e)},C=(t,e)=>{if(t!=="js"&&t!=="css")throw Error(`[Components] isScriptLoaded received invalid script type '${t}'. Must be one of 'js', 'css'`);return(t==="js"?o:i).has(e)},w=(t,e)=>{s[t]=e},j=(t,e,r)=>{let a=`${t}:${e}`;c[a]=r},A=(t,e,r)=>{let a=s[t];if(!a)throw Error(`[Components] '${t}': No component registered for that name`);let l=Array.from(document.querySelectorAll(`[data-comp-id-${e}]`));if(!l.length)throw Error(`[Components] '${t}': No elements with component ID '${e}' found`);let u=`${t}:${r}`,n=c[u];if(!n)throw Error(`[Components] '${t}': Cannot find input for hash '${r}'`);let b=n(),v={name:t,id:e,els:l},[P]=g(a,[b,v]);return P},k=async t=>{let e=t.loadedCssUrls.map(n=>atob(n)),r=t.loadedJsUrls.map(n=>atob(n)),a=t.toLoadCssTags.map(n=>atob(n)),l=t.toLoadJsTags.map(n=>atob(n));e.forEach(n=>d("css",n)),r.forEach(n=>d("js",n)),Promise.all(a.map(n=>h(n))).catch(console.error);let u=Promise.all(l.map(n=>f(n))).catch(console.error)};return M(t=>{let e=JSON.parse(t.text);k(e)}),{callComponent:A,registerComponent:w,registerComponentData:j,loadJs:f,loadCss:h,markScriptLoaded:d}};var $={manager:y(),createComponentsManager:y,unescapeJs:x};globalThis.Components=$;})();
|
||||
(()=>{var x=o=>new DOMParser().parseFromString(o,"text/html").documentElement.textContent,E=Array.isArray,m=o=>typeof o=="function",H=o=>o!==null&&typeof o=="object",S=o=>(H(o)||m(o))&&m(o.then)&&m(o.catch);function N(o,i){try{return i?o.apply(null,i):o()}catch(s){L(s)}}function g(o,i){if(m(o)){let s=N(o,i);return s&&S(s)&&s.catch(c=>{L(c)}),[s]}if(E(o)){let s=[];for(let c=0;c<o.length;c++)s.push(g(o[c],i));return s}else console.warn(`[Components] Invalid value type passed to callWithAsyncErrorHandling(): ${typeof o}`)}function L(o){console.error(o)}var M=o=>{let i=new MutationObserver(s=>{for(let c of s)c.type==="childList"&&c.addedNodes.forEach(d=>{d.nodeName==="SCRIPT"&&d.hasAttribute("data-djc")&&o(d)})});return i.observe(document,{childList:!0,subtree:!0}),i};var y=()=>{let o=new Set,i=new Set,s={},c={},d=t=>{let e=new DOMParser().parseFromString(t,"text/html").querySelector("script");if(!e)throw Error("[Components] Failed to extract <script> tag. Make sure that the string contains <script><\/script> and is a valid HTML");return e},F=t=>{let e=new DOMParser().parseFromString(t,"text/html").querySelector("link");if(!e)throw Error("[Components] Failed to extract <link> tag. Make sure that the string contains <link></link> and is a valid HTML");return e},T=t=>{let e=document.createElement(t.tagName);e.innerHTML=t.innerHTML;for(let r of t.attributes)e.setAttributeNode(r.cloneNode());return e},f=t=>{let e=d(t),r=e.getAttribute("src");if(!r||C("js",r))return;p("js",r);let a=T(e),l=e.getAttribute("async")!=null||e.getAttribute("defer")!=null||e.getAttribute("type")==="module";a.async=l;let u=new Promise((n,b)=>{a.onload=()=>{n()},globalThis.document.body.append(a)});return{el:a,promise:u}},h=t=>{let e=F(t),r=e.getAttribute("href");if(!r||C("css",r))return;let a=T(e);return globalThis.document.head.append(a),p("css",r),{el:a,promise:Promise.resolve()}},p=(t,e)=>{if(t!=="js"&&t!=="css")throw Error(`[Components] markScriptLoaded received invalid script type '${t}'. Must be one of 'js', 'css'`);(t==="js"?o:i).add(e)},C=(t,e)=>{if(t!=="js"&&t!=="css")throw Error(`[Components] isScriptLoaded received invalid script type '${t}'. Must be one of 'js', 'css'`);return(t==="js"?o:i).has(e)},w=(t,e)=>{s[t]=e},j=(t,e,r)=>{let a=`${t}:${e}`;c[a]=r},A=(t,e,r)=>{let a=s[t];if(!a)throw Error(`[Components] '${t}': No component registered for that name`);let l=Array.from(document.querySelectorAll(`[data-djc-id-${e}]`));if(!l.length)throw Error(`[Components] '${t}': No elements with component ID '${e}' found`);let u=`${t}:${r}`,n=c[u];if(!n)throw Error(`[Components] '${t}': Cannot find input for hash '${r}'`);let b=n(),v={name:t,id:e,els:l},[P]=g(a,[b,v]);return P},k=async t=>{let e=t.loadedCssUrls.map(n=>atob(n)),r=t.loadedJsUrls.map(n=>atob(n)),a=t.toLoadCssTags.map(n=>atob(n)),l=t.toLoadJsTags.map(n=>atob(n));e.forEach(n=>p("css",n)),r.forEach(n=>p("js",n)),Promise.all(a.map(n=>h(n))).catch(console.error);let u=Promise.all(l.map(n=>f(n))).catch(console.error)};return M(t=>{let e=JSON.parse(t.text);k(e)}),{callComponent:A,registerComponent:w,registerComponentData:j,loadJs:f,loadCss:h,markScriptLoaded:p}};var $={manager:y(),createComponentsManager:y,unescapeJs:x};globalThis.Components=$;})();
|
||||
|
|
|
@ -59,3 +59,7 @@ def get_last_index(lst: List, key: Callable[[Any], bool]) -> Optional[int]:
|
|||
if key(item):
|
||||
return len(lst) - 1 - index
|
||||
return None
|
||||
|
||||
|
||||
def is_nonempty_str(txt: Optional[str]) -> bool:
|
||||
return txt is not None and bool(txt.strip())
|
||||
|
|
|
@ -26,7 +26,7 @@ Components.registerComponentData(
|
|||
Components.callComponent(
|
||||
"table", // Component name
|
||||
12345, // Component ID - An HTML element with corresponding
|
||||
// attribute (`data-comp-id-12345`) MUST
|
||||
// attribute (`data-djc-id-12345`) MUST
|
||||
// be present in the DOM.
|
||||
"3d09cf", // Input ID
|
||||
);
|
||||
|
|
|
@ -209,7 +209,7 @@ export const createComponentsManager = () => {
|
|||
const initFn = components[name];
|
||||
if (!initFn) throw Error(`[Components] '${name}': No component registered for that name`);
|
||||
|
||||
const elems = Array.from(document.querySelectorAll<HTMLElement>(`[data-comp-id-${compId}]`));
|
||||
const elems = Array.from(document.querySelectorAll<HTMLElement>(`[data-djc-id-${compId}]`));
|
||||
if (!elems.length) throw Error(`[Components] '${name}': No elements with component ID '${compId}' found`);
|
||||
|
||||
const dataKey = `${name}:${inputHash}`;
|
||||
|
|
|
@ -101,7 +101,7 @@ class HtmlAttrsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div @click.stop="dispatch('click_event')" x-data="{hello: 'world'}" class="padding-top-8 added_class another-class" data-id=123>
|
||||
<div @click.stop="dispatch('click_event')" x-data="{hello: 'world'}" class="padding-top-8 added_class another-class" data-djc-id-a1bc3f data-id=123>
|
||||
content
|
||||
</div>
|
||||
""", # noqa: E501
|
||||
|
@ -153,7 +153,7 @@ class HtmlAttrsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div @click.stop="dispatch('click_event')" class="added_class another-class padding-top-8" data-id="123" x-data="{hello: 'world'}">
|
||||
<div @click.stop="dispatch('click_event')" class="added_class another-class padding-top-8" data-djc-id-a1bc3f data-id="123" x-data="{hello: 'world'}">
|
||||
content
|
||||
</div>
|
||||
""", # noqa: E501
|
||||
|
@ -181,7 +181,7 @@ class HtmlAttrsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div @click.stop="dispatch('click_event')" x-data="{hello: 'world'}" class="padding-top-8 added_class another-class" data-id=123>
|
||||
<div @click.stop="dispatch('click_event')" x-data="{hello: 'world'}" class="padding-top-8 added_class another-class" data-djc-id-a1bc3f data-id=123>
|
||||
content
|
||||
</div>
|
||||
""", # noqa: E501
|
||||
|
@ -213,7 +213,7 @@ class HtmlAttrsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div @click.stop="dispatch('click_event')" class="added_class another-class padding-top-8" data-id="123" x-data="{hello: 'world'}">
|
||||
<div @click.stop="dispatch('click_event')" class="added_class another-class padding-top-8" data-djc-id-a1bc3f data-id="123" x-data="{hello: 'world'}">
|
||||
content
|
||||
</div>
|
||||
""", # noqa: E501
|
||||
|
@ -240,7 +240,7 @@ class HtmlAttrsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div class="added_class another-class from_agg_key" data-id="123" type="submit">
|
||||
<div class="added_class another-class from_agg_key" data-djc-id-a1bc3f data-id="123" type="submit">
|
||||
content
|
||||
</div>
|
||||
""", # noqa: E501
|
||||
|
@ -304,7 +304,7 @@ class HtmlAttrsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div class="added_class another-class override-me" data-id=123>
|
||||
<div class="added_class another-class override-me" data-djc-id-a1bc3f data-id=123>
|
||||
content
|
||||
</div>
|
||||
""",
|
||||
|
@ -333,7 +333,7 @@ class HtmlAttrsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div @click.stop="dispatch('click_event')" x-data="{hello: 'world'}" class="padding-top-8 added_class another-class" data-id=123>
|
||||
<div @click.stop="dispatch('click_event')" x-data="{hello: 'world'}" class="padding-top-8 added_class another-class" data-djc-id-a1bc3f data-id=123>
|
||||
content
|
||||
</div>
|
||||
""", # noqa: E501
|
||||
|
@ -358,7 +358,7 @@ class HtmlAttrsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div class="added_class another-class" data-id="123">
|
||||
<div class="added_class another-class" data-djc-id-a1bc3f data-id="123">
|
||||
content
|
||||
</div>
|
||||
""",
|
||||
|
@ -386,7 +386,7 @@ class HtmlAttrsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div >
|
||||
<div data-djc-id-a1bc3f>
|
||||
content
|
||||
</div>
|
||||
""",
|
||||
|
@ -414,7 +414,7 @@ class HtmlAttrsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div >
|
||||
<div data-djc-id-a1bc3f>
|
||||
content
|
||||
</div>
|
||||
""",
|
||||
|
|
|
@ -101,7 +101,7 @@ class ComponentOldTemplateApiTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>test</strong>
|
||||
Variable: <strong data-djc-id-a1bc3e>test</strong>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -175,7 +175,7 @@ class ComponentTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>test</strong>
|
||||
Variable: <strong data-djc-id-a1bc3e>test</strong>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -201,7 +201,7 @@ class ComponentTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>test</strong>
|
||||
Variable: <strong data-djc-id-a1bc3e>test</strong>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -223,7 +223,7 @@ class ComponentTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>test</strong>
|
||||
Variable: <strong data-djc-id-a1bc3e>test</strong>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -244,13 +244,13 @@ class ComponentTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
SvgComponent.render(kwargs={"name": "svg1"}),
|
||||
"""
|
||||
<svg>Dynamic1</svg>
|
||||
<svg data-djc-id-a1bc3e>Dynamic1</svg>
|
||||
""",
|
||||
)
|
||||
self.assertHTMLEqual(
|
||||
SvgComponent.render(kwargs={"name": "svg2"}),
|
||||
"""
|
||||
<svg>Dynamic2</svg>
|
||||
<svg data-djc-id-a1bc3f>Dynamic2</svg>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -270,7 +270,7 @@ class ComponentTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>test</strong>
|
||||
Variable: <strong data-djc-id-a1bc3e>test</strong>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -314,7 +314,7 @@ class ComponentTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>test</strong> MY_SLOT
|
||||
Variable: <strong data-djc-id-a1bc3e>test</strong> MY_SLOT
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -407,7 +407,7 @@ class ComponentValidationTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>test</strong>
|
||||
Variable: <strong data-djc-id-a1bc3e>test</strong>
|
||||
Slot 1: MY_SLOT
|
||||
Slot 2: abc
|
||||
""",
|
||||
|
@ -536,7 +536,7 @@ class ComponentValidationTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>test</strong>
|
||||
Variable: <strong data-djc-id-a1bc3e>test</strong>
|
||||
Slot 1: 123.5
|
||||
Slot 2: abc
|
||||
""",
|
||||
|
@ -568,7 +568,7 @@ class ComponentValidationTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>test</strong>
|
||||
Variable: <strong data-djc-id-a1bc3e>test</strong>
|
||||
Slot 1: MY_SLOT
|
||||
Slot 2: abc
|
||||
""",
|
||||
|
@ -655,7 +655,7 @@ class ComponentValidationTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Name: <strong>TestComponent</strong>
|
||||
Name: <strong data-djc-id-a1bc3e>TestComponent</strong>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -960,7 +960,7 @@ class ComponentRenderTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc3e>
|
||||
<header>Default header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -982,7 +982,7 @@ class ComponentRenderTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc3e>
|
||||
<header>Default header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -1005,7 +1005,7 @@ class ComponentRenderTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc3e>
|
||||
<header>Default header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -1057,7 +1057,7 @@ class ComponentRenderTest(BaseTestCase):
|
|||
# """
|
||||
self.assertInHTML(
|
||||
"""
|
||||
<kbd>
|
||||
<kbd data-djc-id-a1bc3e>
|
||||
Rendered via GET request
|
||||
</kbd>
|
||||
""",
|
||||
|
@ -1150,7 +1150,7 @@ class ComponentRenderTest(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-djc-id-a1bc3e lang="en">
|
||||
<body>
|
||||
<main role="main">
|
||||
<div class='container main-container'>
|
||||
|
@ -1169,13 +1169,13 @@ class ComponentRenderTest(BaseTestCase):
|
|||
|
||||
def get_context_data(self, **attrs):
|
||||
return {
|
||||
"id": self.component_id,
|
||||
"id": self.id,
|
||||
}
|
||||
|
||||
rendered = TestComponent(component_id="123").render()
|
||||
rendered = TestComponent.render()
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"Variable: <strong>123</strong>",
|
||||
"Variable: <strong data-djc-id-a1bc3e>a1bc3e</strong>",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
@ -1185,13 +1185,13 @@ class ComponentRenderTest(BaseTestCase):
|
|||
|
||||
def get_context_data(self, **attrs):
|
||||
return {
|
||||
"id": self.component_id,
|
||||
"id": self.id,
|
||||
}
|
||||
|
||||
rendered_resp = TestComponent(component_id="123").render_to_response()
|
||||
rendered_resp = TestComponent.render_to_response()
|
||||
self.assertHTMLEqual(
|
||||
rendered_resp.content.decode("utf-8"),
|
||||
"Variable: <strong>123</strong>",
|
||||
"Variable: <strong data-djc-id-a1bc3e>a1bc3e</strong>",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -56,9 +56,9 @@ class TestComponentAsView(BaseTestCase):
|
|||
client = CustomClient(urlpatterns=[path("test_template/", render_template_view)])
|
||||
response = client.get("/test_template/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(
|
||||
b'<input type="text" name="variable" value="TEMPLATE">',
|
||||
response.content,
|
||||
self.assertInHTML(
|
||||
'<input type="text" name="variable" value="TEMPLATE">',
|
||||
response.content.decode(),
|
||||
)
|
||||
|
||||
def test_get_request(self):
|
||||
|
@ -81,9 +81,9 @@ class TestComponentAsView(BaseTestCase):
|
|||
client = CustomClient(urlpatterns=[path("test/", MockComponentRequest.as_view())])
|
||||
response = client.get("/test/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(
|
||||
b'<input type="text" name="variable" value="GET">',
|
||||
response.content,
|
||||
self.assertInHTML(
|
||||
'<input type="text" name="variable" value="GET">',
|
||||
response.content.decode(),
|
||||
)
|
||||
|
||||
def test_get_request_shortcut(self):
|
||||
|
@ -105,9 +105,9 @@ class TestComponentAsView(BaseTestCase):
|
|||
client = CustomClient(urlpatterns=[path("test/", MockComponentRequest.as_view())])
|
||||
response = client.get("/test/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(
|
||||
b'<input type="text" name="variable" value="GET">',
|
||||
response.content,
|
||||
self.assertInHTML(
|
||||
'<input type="text" name="variable" value="GET">',
|
||||
response.content.decode(),
|
||||
)
|
||||
|
||||
def test_post_request(self):
|
||||
|
@ -131,9 +131,9 @@ class TestComponentAsView(BaseTestCase):
|
|||
client = CustomClient(urlpatterns=[path("test/", MockComponentRequest.as_view())])
|
||||
response = client.post("/test/", {"variable": "POST"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(
|
||||
b'<input type="text" name="variable" value="POST">',
|
||||
response.content,
|
||||
self.assertInHTML(
|
||||
'<input type="text" name="variable" value="POST">',
|
||||
response.content.decode(),
|
||||
)
|
||||
|
||||
def test_post_request_shortcut(self):
|
||||
|
@ -156,9 +156,9 @@ class TestComponentAsView(BaseTestCase):
|
|||
client = CustomClient(urlpatterns=[path("test/", MockComponentRequest.as_view())])
|
||||
response = client.post("/test/", {"variable": "POST"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(
|
||||
b'<input type="text" name="variable" value="POST">',
|
||||
response.content,
|
||||
self.assertInHTML(
|
||||
'<input type="text" name="variable" value="POST">',
|
||||
response.content.decode(),
|
||||
)
|
||||
|
||||
def test_instantiate_component(self):
|
||||
|
@ -178,9 +178,9 @@ class TestComponentAsView(BaseTestCase):
|
|||
client = CustomClient(urlpatterns=[path("test/", MockComponentRequest("my_comp").as_view())])
|
||||
response = client.get("/test/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(
|
||||
b'<input type="text" name="variable" value="my_comp">',
|
||||
response.content,
|
||||
self.assertInHTML(
|
||||
'<input type="text" name="variable" value="my_comp">',
|
||||
response.content.decode(),
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
|
|
@ -24,7 +24,7 @@ class InlineComponentTest(BaseTestCase):
|
|||
|
||||
self.assertHTMLEqual(
|
||||
InlineHTMLComponent.render(),
|
||||
"<div class='inline'>Hello Inline</div>",
|
||||
'<div class="inline" data-djc-id-a1bc3e>Hello Inline</div>',
|
||||
)
|
||||
|
||||
def test_inlined_js_and_css(self):
|
||||
|
@ -41,7 +41,7 @@ class InlineComponentTest(BaseTestCase):
|
|||
rendered = TestComponent.render()
|
||||
|
||||
self.assertInHTML(
|
||||
"<div class='html-css-only'>Content</div>",
|
||||
'<div class="html-css-only" data-djc-id-a1bc3e>Content</div>',
|
||||
rendered,
|
||||
)
|
||||
self.assertInHTML(
|
||||
|
@ -62,7 +62,7 @@ class InlineComponentTest(BaseTestCase):
|
|||
context = Context({"variable": "Dynamic Content"})
|
||||
self.assertHTMLEqual(
|
||||
comp.render(context),
|
||||
"<div class='variable-html'>Dynamic Content</div>",
|
||||
'<div class="variable-html" data-djc-id-a1bc3e>Dynamic Content</div>',
|
||||
)
|
||||
|
||||
def test_html_variable_filtered(self):
|
||||
|
@ -82,8 +82,8 @@ class InlineComponentTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Var1: <strong>test1</strong>
|
||||
Var2 (uppercased): <strong>TEST2</strong>
|
||||
Var1: <strong data-djc-id-a1bc3e>test1</strong>
|
||||
Var2 (uppercased): <strong data-djc-id-a1bc3e>TEST2</strong>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -691,7 +691,7 @@ class MediaRelativePathTests(BaseTestCase):
|
|||
|
||||
self.assertInHTML(
|
||||
"""
|
||||
<form method="post">
|
||||
<form data-djc-id-a1bc41 method="post">
|
||||
<input type="text" name="variable" value="test">
|
||||
<input type="submit">
|
||||
</form>
|
||||
|
@ -730,7 +730,7 @@ class MediaRelativePathTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertIn('<input type="text" name="variable" value="hello">', rendered, rendered)
|
||||
self.assertInHTML('<input type="text" name="variable" value="hello">', rendered)
|
||||
|
||||
# Settings required for autodiscover to work
|
||||
@override_settings(
|
||||
|
|
|
@ -100,13 +100,9 @@ class ContextTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
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.assertInHTML("<h1 data-djc-id-a1bc43>Shadowing variable = override</h1>", rendered)
|
||||
self.assertInHTML("<h1 data-djc-id-a1bc44>Shadowing variable = slot_default_override</h1>", rendered)
|
||||
self.assertNotIn("Shadowing variable = NOT SHADOWED", rendered)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_nested_component_instances_have_unique_context_with_unfilled_slots_and_component_tag(
|
||||
|
@ -119,10 +115,9 @@ class ContextTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
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,
|
||||
self.assertInHTML("<h1 data-djc-id-a1bc43>Uniquely named variable = unique_val</h1>", rendered)
|
||||
self.assertInHTML(
|
||||
"<h1 data-djc-id-a1bc44>Uniquely named variable = slot_default_unique</h1>",
|
||||
rendered,
|
||||
)
|
||||
|
||||
|
@ -140,13 +135,9 @@ class ContextTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
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.assertInHTML("<h1 data-djc-id-a1bc45>Shadowing variable = override</h1>", rendered)
|
||||
self.assertInHTML("<h1 data-djc-id-a1bc46>Shadowing variable = shadow_from_slot</h1>", rendered)
|
||||
self.assertNotIn("Shadowing variable = NOT SHADOWED", rendered)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_nested_component_instances_have_unique_context_with_filled_slots(self):
|
||||
|
@ -162,12 +153,8 @@ class ContextTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
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.assertInHTML('<h1 data-djc-id-a1bc45="">Uniquely named variable = unique_val</h1>', rendered)
|
||||
self.assertInHTML('<h1 data-djc-id-a1bc46="">Uniquely named variable = unique_from_slot</h1>', rendered)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_nested_component_context_shadows_outer_context_with_unfilled_slots_and_component_tag(
|
||||
|
@ -180,13 +167,9 @@ class ContextTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
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.assertInHTML("<h1 data-djc-id-a1bc43>Shadowing variable = override</h1>", rendered)
|
||||
self.assertInHTML("<h1 data-djc-id-a1bc44>Shadowing variable = slot_default_override</h1>", rendered)
|
||||
self.assertNotIn("Shadowing variable = NOT SHADOWED", rendered)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_nested_component_context_shadows_outer_context_with_filled_slots(
|
||||
|
@ -204,13 +187,9 @@ class ContextTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
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.assertInHTML("<h1 data-djc-id-a1bc45>Shadowing variable = override</h1>", rendered)
|
||||
self.assertInHTML("<h1 data-djc-id-a1bc46>Shadowing variable = shadow_from_slot</h1>", rendered)
|
||||
self.assertNotIn("Shadowing variable = NOT SHADOWED", rendered)
|
||||
|
||||
|
||||
class ParentArgsTests(BaseTestCase):
|
||||
|
@ -253,15 +232,15 @@ class ParentArgsTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
<h1>Parent content</h1>
|
||||
<h1>Shadowing variable = passed_in</h1>
|
||||
<h1>Uniquely named variable = unique_val</h1>
|
||||
<h1 data-djc-id-a1bc43>Shadowing variable = passed_in</h1>
|
||||
<h1 data-djc-id-a1bc43>Uniquely named variable = unique_val</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
<h2>Slot content</h2>
|
||||
<h1>Shadowing variable = slot_default_override</h1>
|
||||
<h1>Uniquely named variable = passed_in</h1>
|
||||
<h1 data-djc-id-a1bc44>Shadowing variable = slot_default_override</h1>
|
||||
<h1 data-djc-id-a1bc44>Uniquely named variable = passed_in</h1>
|
||||
</div>
|
||||
""",
|
||||
)
|
||||
|
@ -275,9 +254,9 @@ class ParentArgsTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
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.assertInHTML("<h1 data-djc-id-a1bc43>Shadowing variable = passed_in</h1>", rendered)
|
||||
self.assertInHTML("<h1 data-djc-id-a1bc44>Uniquely named variable = passed_in</h1>", rendered)
|
||||
self.assertNotIn("Shadowing variable = NOT SHADOWED", rendered)
|
||||
|
||||
# NOTE: Second arg in tuple are expected values passed through components.
|
||||
@parametrize_context_behavior(
|
||||
|
@ -300,17 +279,18 @@ class ParentArgsTests(BaseTestCase):
|
|||
""" # noqa: E501
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context())
|
||||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
f"""
|
||||
<div>
|
||||
<div data-djc-id-a1bc41>
|
||||
<h1>Parent content</h1>
|
||||
<h1>Shadowing variable = {first_val}</h1>
|
||||
<h1>Uniquely named variable = unique_val</h1>
|
||||
<h1 data-djc-id-a1bc45>Shadowing variable = {first_val}</h1>
|
||||
<h1 data-djc-id-a1bc45>Uniquely named variable = unique_val</h1>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Shadowing variable = value_from_slot</h1>
|
||||
<h1>Uniquely named variable = {second_val}</h1>
|
||||
<div data-djc-id-a1bc41>
|
||||
<h1 data-djc-id-a1bc46>Shadowing variable = value_from_slot</h1>
|
||||
<h1 data-djc-id-a1bc46>Uniquely named variable = {second_val}</h1>
|
||||
</div>
|
||||
""",
|
||||
)
|
||||
|
@ -331,7 +311,7 @@ class ContextCalledOnceTests(BaseTestCase):
|
|||
rendered = template.render(Context()).strip().replace("\n", "")
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
'<p class="incrementer">value=1;calls=1</p>',
|
||||
'<p class="incrementer" data-djc-id-a1bc3f>value=1;calls=1</p>',
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
@ -343,7 +323,12 @@ class ContextCalledOnceTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
rendered = template.render(Context()).strip()
|
||||
|
||||
self.assertHTMLEqual(rendered, '<p class="incrementer">value=3;calls=1</p>', rendered)
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<p class="incrementer" data-djc-id-a1bc3f>value=3;calls=1</p>
|
||||
""",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_one_context_call_with_component(self):
|
||||
|
@ -354,7 +339,7 @@ class ContextCalledOnceTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
rendered = template.render(Context()).strip()
|
||||
|
||||
self.assertHTMLEqual(rendered, '<p class="incrementer">value=1;calls=1</p>', rendered)
|
||||
self.assertHTMLEqual(rendered, '<p class="incrementer" data-djc-id-a1bc3f>value=1;calls=1</p>')
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_one_context_call_with_component_and_arg(self):
|
||||
|
@ -365,7 +350,7 @@ class ContextCalledOnceTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
rendered = template.render(Context()).strip()
|
||||
|
||||
self.assertHTMLEqual(rendered, '<p class="incrementer">value=4;calls=1</p>', rendered)
|
||||
self.assertHTMLEqual(rendered, '<p class="incrementer" data-djc-id-a1bc3f>value=4;calls=1</p>')
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_one_context_call_with_slot(self):
|
||||
|
@ -382,7 +367,10 @@ class ContextCalledOnceTests(BaseTestCase):
|
|||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
'<p class="incrementer">value=1;calls=1</p>\n<p>slot</p>',
|
||||
"""
|
||||
<p class="incrementer" data-djc-id-a1bc40>value=1;calls=1</p>
|
||||
<p data-djc-id-a1bc40>slot</p>
|
||||
""",
|
||||
rendered,
|
||||
)
|
||||
|
||||
|
@ -401,7 +389,10 @@ class ContextCalledOnceTests(BaseTestCase):
|
|||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
'<p class="incrementer">value=4;calls=1</p>\n<p>slot</p>',
|
||||
"""
|
||||
<p class="incrementer" data-djc-id-a1bc40>value=4;calls=1</p>
|
||||
<p data-djc-id-a1bc40>slot</p>
|
||||
""",
|
||||
rendered,
|
||||
)
|
||||
|
||||
|
@ -428,7 +419,7 @@ class ComponentsCanAccessOuterContext(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
f"""
|
||||
Variable: <strong> {context_behavior_data} </strong>
|
||||
Variable: <strong data-djc-id-a1bc3f> {context_behavior_data} </strong>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -613,7 +604,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
|
|||
rendered = Template(template).render(Context())
|
||||
|
||||
expected = """
|
||||
<div class="frontmatter-component">
|
||||
<div class="frontmatter-component" data-djc-id-a1bc42>
|
||||
title: True
|
||||
my_title: False
|
||||
my_title_1: False
|
||||
|
@ -635,7 +626,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<div class="frontmatter-component">
|
||||
<div class="frontmatter-component" data-djc-id-a1bc3f>
|
||||
bla bla
|
||||
title: False
|
||||
my_title: False
|
||||
|
@ -653,7 +644,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
|
|||
{% component "conditional_slots" %}{% endcomponent %}
|
||||
"""
|
||||
expected = """
|
||||
<div class="frontmatter-component">
|
||||
<div class="frontmatter-component" data-djc-id-a1bc3f>
|
||||
<div class="title">
|
||||
Title
|
||||
</div>
|
||||
|
@ -671,7 +662,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
|
|||
{% endcomponent %}
|
||||
"""
|
||||
expected = """
|
||||
<div class="frontmatter-component">
|
||||
<div class="frontmatter-component" data-djc-id-a1bc40>
|
||||
<div class="title">
|
||||
Title
|
||||
</div>
|
||||
|
@ -692,7 +683,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
|
|||
{% endcomponent %}
|
||||
"""
|
||||
expected = """
|
||||
<div class="frontmatter-component">
|
||||
<div class="frontmatter-component" data-djc-id-a1bc40>
|
||||
<div class="title">
|
||||
Title
|
||||
</div>
|
||||
|
@ -712,7 +703,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
|
|||
{% endcomponent %}
|
||||
"""
|
||||
expected = """
|
||||
<div class="frontmatter-component">
|
||||
<div class="frontmatter-component" data-djc-id-a1bc3f>
|
||||
<div class="title">
|
||||
Title
|
||||
</div>
|
||||
|
@ -746,7 +737,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
|
|||
{% endcomponent %}
|
||||
"""
|
||||
expected = """
|
||||
<div class="frontmatter-component">
|
||||
<div class="frontmatter-component" data-djc-id-a1bc3f>
|
||||
<div class="title">
|
||||
Title
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
"""
|
||||
These tests check the public API side of managing dependencies - We check
|
||||
if calling `Component.render()` or `render_dependencies()` behave as expected.
|
||||
|
||||
For checking the OUTPUT of the dependencies, see `test_dependency_rendering.py`.
|
||||
"""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
from django.http import HttpResponseNotModified
|
||||
|
@ -54,7 +61,7 @@ class RenderDependenciesTests(BaseTestCase):
|
|||
rendered_raw = template.render(Context({}))
|
||||
|
||||
# Placeholders
|
||||
self.assertEqual(rendered_raw.count('<link name="CSS_PLACEHOLDER">'), 1)
|
||||
self.assertEqual(rendered_raw.count('<link name="CSS_PLACEHOLDER"/>'), 1)
|
||||
self.assertEqual(rendered_raw.count('<script name="JS_PLACEHOLDER"></script>'), 1)
|
||||
|
||||
self.assertEqual(rendered_raw.count("<script"), 1)
|
||||
|
@ -258,7 +265,7 @@ class RenderDependenciesTests(BaseTestCase):
|
|||
self.assertInHTML(
|
||||
"""
|
||||
<body>
|
||||
Variable: <strong>foo</strong>
|
||||
Variable: <strong data-djc-id-a1bc41>foo</strong>
|
||||
|
||||
<style>.xyz { color: red; }</style>
|
||||
<link href="style.css" media="all" rel="stylesheet">
|
||||
|
@ -415,7 +422,7 @@ class RenderDependenciesTests(BaseTestCase):
|
|||
<td class="whitespace-nowrap w-fit text-center px-4 w-px"
|
||||
aria-colindex="1">
|
||||
1
|
||||
Variable: <strong>hi</strong>
|
||||
Variable: <strong data-djc-id-a1bc3f>hi</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -471,37 +478,59 @@ class MiddlewareTests(BaseTestCase):
|
|||
request = Mock()
|
||||
self.assertEqual(response, middleware(request=request))
|
||||
|
||||
def test_middleware_response_with_components_with_slash_dash_and_underscore(
|
||||
self,
|
||||
):
|
||||
def test_middleware_response_with_components_with_slash_dash_and_underscore(self):
|
||||
registry.register("dynamic", DynamicComponent)
|
||||
registry.register("test-component", component=SimpleComponent)
|
||||
registry.register("test/component", component=SimpleComponent)
|
||||
registry.register("test_component", component=SimpleComponent)
|
||||
|
||||
component_names = [
|
||||
"test-component",
|
||||
"test/component",
|
||||
"test_component",
|
||||
]
|
||||
for component_name in component_names:
|
||||
registry.register(name=component_name, component=SimpleComponent)
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component_css_dependencies %}
|
||||
{% component_js_dependencies %}
|
||||
{% component "dynamic" is=component_name variable='value' / %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
rendered = create_and_process_template_response(
|
||||
template, context=Context({"component_name": component_name})
|
||||
)
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component_css_dependencies %}
|
||||
{% component_js_dependencies %}
|
||||
{% component "dynamic" is=component_name variable='value' / %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
|
||||
def assert_dependencies(content: str):
|
||||
# Dependency manager script (empty)
|
||||
self.assertInHTML('<script src="django_components/django_components.min.js"></script>', rendered, count=1)
|
||||
self.assertInHTML('<script src="django_components/django_components.min.js"></script>', content, count=1)
|
||||
|
||||
# Inlined JS
|
||||
self.assertInHTML('<script>console.log("xyz");</script>', rendered, count=1)
|
||||
self.assertInHTML('<script>console.log("xyz");</script>', content, count=1)
|
||||
# Inlined CSS
|
||||
self.assertInHTML("<style>.xyz { color: red; }</style>", rendered, count=1)
|
||||
self.assertInHTML("<style>.xyz { color: red; }</style>", content, count=1)
|
||||
# Media.css
|
||||
self.assertInHTML('<link href="style.css" media="all" rel="stylesheet">', rendered, count=1)
|
||||
self.assertInHTML('<link href="style.css" media="all" rel="stylesheet">', content, count=1)
|
||||
|
||||
self.assertEqual(rendered.count("Variable: <strong>value</strong>"), 1)
|
||||
rendered1 = create_and_process_template_response(
|
||||
template,
|
||||
context=Context({"component_name": "test-component"}),
|
||||
)
|
||||
|
||||
assert_dependencies(rendered1)
|
||||
self.assertEqual(
|
||||
rendered1.count('Variable: <strong data-djc-id-a1bc41 data-djc-id-a1bc42="">value</strong>'),
|
||||
1,
|
||||
)
|
||||
|
||||
rendered2 = create_and_process_template_response(
|
||||
template,
|
||||
context=Context({"component_name": "test-component"}),
|
||||
)
|
||||
assert_dependencies(rendered2)
|
||||
self.assertEqual(
|
||||
rendered2.count('Variable: <strong data-djc-id-a1bc43 data-djc-id-a1bc44="">value</strong>'),
|
||||
1,
|
||||
)
|
||||
|
||||
rendered3 = create_and_process_template_response(
|
||||
template,
|
||||
context=Context({"component_name": "test_component"}),
|
||||
)
|
||||
|
||||
assert_dependencies(rendered3)
|
||||
self.assertEqual(
|
||||
rendered3.count('Variable: <strong data-djc-id-a1bc45 data-djc-id-a1bc46="">value</strong>'),
|
||||
1,
|
||||
)
|
||||
|
|
|
@ -215,7 +215,7 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
const inputHash = 'input-abc';
|
||||
|
||||
// Pretend that this HTML belongs to our component
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-comp-id-12345> abc </div>');
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-djc-id-12345> abc </div>');
|
||||
|
||||
let captured = null;
|
||||
manager.registerComponent(compName, (data, ctx) => {
|
||||
|
@ -248,7 +248,7 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
"hello": "world",
|
||||
},
|
||||
"ctx": {
|
||||
"els": ['<div data-comp-id-12345=""> abc </div>'],
|
||||
"els": ['<div data-djc-id-12345=""> abc </div>'],
|
||||
"id": "12345",
|
||||
"name": "my_comp",
|
||||
},
|
||||
|
@ -269,7 +269,7 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
const inputHash = 'input-abc';
|
||||
|
||||
// Pretend that this HTML belongs to our component
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-comp-id-12345> abc </div>');
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-djc-id-12345> abc </div>');
|
||||
|
||||
manager.registerComponent(compName, (data, ctx) => {
|
||||
return Promise.resolve(123);
|
||||
|
@ -309,7 +309,7 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
const inputHash = 'input-abc';
|
||||
|
||||
// Pretend that this HTML belongs to our component
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-comp-id-12345> abc </div>');
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-djc-id-12345> abc </div>');
|
||||
|
||||
manager.registerComponent(compName, (data, ctx) => {
|
||||
throw Error('Oops!');
|
||||
|
@ -343,7 +343,7 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
const inputHash = 'input-abc';
|
||||
|
||||
// Pretend that this HTML belongs to our component
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-comp-id-12345> abc </div>');
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-djc-id-12345> abc </div>');
|
||||
|
||||
manager.registerComponent(compName, async (data, ctx) => {
|
||||
throw Error('Oops!');
|
||||
|
@ -408,7 +408,7 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
const compId = '12345';
|
||||
const inputHash = 'input-abc';
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-comp-id-12345> abc </div>');
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-djc-id-12345> abc </div>');
|
||||
|
||||
manager.registerComponent(compName, (data, ctx) => {
|
||||
return Promise.resolve(123);
|
||||
|
@ -434,7 +434,7 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
const compId = '12345';
|
||||
const inputHash = 'input-abc';
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-comp-id-12345> abc </div>');
|
||||
document.body.insertAdjacentHTML('beforeend', '<div data-djc-id-12345> abc </div>');
|
||||
|
||||
manager.registerComponentData(compName, inputHash, () => {
|
||||
return { hello: 'world' };
|
||||
|
|
|
@ -5,7 +5,7 @@ During actual rendering, the HTML is then picked up by the JS-side dependency ma
|
|||
|
||||
import re
|
||||
|
||||
from django.template import Template
|
||||
from django.template import Context, Template
|
||||
|
||||
from django_components import Component, registry, types
|
||||
|
||||
|
@ -456,8 +456,8 @@ class DependencyRenderingTests(BaseTestCase):
|
|||
# `c3R5bGUyLmNzcw==` -> `style2.css`
|
||||
# `eHl6MS5jc3M=` -> `xyz1.css`
|
||||
# `L2NvbXBvbmVudHMvY2FjaGUvT3RoZXJDb21wb25lbnRfNjMyOWFlLmNzcw==` -> `/components/cache/OtherComponent_6329ae.css`
|
||||
# `L2NvbXBvbmVudHMvY2FjaGUvU2ltcGxlQ29tcG9uZW50TmVzdGVkX2YwMmQzMi5jc3M=` -> `/components/cache/SimpleComponentNested_f02d32.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`
|
||||
|
@ -465,8 +465,16 @@ class DependencyRenderingTests(BaseTestCase):
|
|||
self.assertInHTML(
|
||||
"""
|
||||
<script type="application/json" data-djc>
|
||||
{"loadedCssUrls": ["L2NvbXBvbmVudHMvY2FjaGUvT3RoZXJDb21wb25lbnRfNjMyOWFlLmNzcw==", "L2NvbXBvbmVudHMvY2FjaGUvU2ltcGxlQ29tcG9uZW50TmVzdGVkX2YwMmQzMi5jc3M=", "c3R5bGUuY3Nz", "c3R5bGUyLmNzcw==", "eHl6MS5jc3M="],
|
||||
"loadedJsUrls": ["L2NvbXBvbmVudHMvY2FjaGUvT3RoZXJDb21wb25lbnRfNjMyOWFlLmpz", "L2NvbXBvbmVudHMvY2FjaGUvU2ltcGxlQ29tcG9uZW50TmVzdGVkX2YwMmQzMi5qcw==", "c2NyaXB0Lmpz", "c2NyaXB0Mi5qcw==", "eHl6MS5qcw=="],
|
||||
{"loadedCssUrls": ["L2NvbXBvbmVudHMvY2FjaGUvT3RoZXJDb21wb25lbnRfNjMyOWFlLmNzcw==",
|
||||
"L2NvbXBvbmVudHMvY2FjaGUvU2ltcGxlQ29tcG9uZW50TmVzdGVkX2YwMmQzMi5jc3M=",
|
||||
"c3R5bGUuY3Nz",
|
||||
"c3R5bGUyLmNzcw==",
|
||||
"eHl6MS5jc3M="],
|
||||
"loadedJsUrls": ["L2NvbXBvbmVudHMvY2FjaGUvT3RoZXJDb21wb25lbnRfNjMyOWFlLmpz",
|
||||
"L2NvbXBvbmVudHMvY2FjaGUvU2ltcGxlQ29tcG9uZW50TmVzdGVkX2YwMmQzMi5qcw==",
|
||||
"c2NyaXB0Lmpz",
|
||||
"c2NyaXB0Mi5qcw==",
|
||||
"eHl6MS5qcw=="],
|
||||
"toLoadCssTags": [],
|
||||
"toLoadJsTags": []}
|
||||
</script>
|
||||
|
@ -491,3 +499,131 @@ class DependencyRenderingTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
rendered = create_and_process_template_response(template)
|
||||
self.assertNotIn("_RENDERED", 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 = create_and_process_template_response(template)
|
||||
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong data-djc-id-a1bc3f>foo</strong>")
|
||||
|
||||
def test_adds_component_id_html_attr_single_multiroot(self):
|
||||
class SimpleMultiroot(SimpleComponent):
|
||||
template: types.django_html = """
|
||||
Variable: <strong>{{ variable }}</strong>
|
||||
Variable2: <div>{{ variable }}</div>
|
||||
Variable3: <span>{{ variable }}</span>
|
||||
"""
|
||||
|
||||
registry.register(name="test", component=SimpleMultiroot)
|
||||
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component 'test' variable='foo' / %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
rendered = create_and_process_template_response(template)
|
||||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong data-djc-id-a1bc3f>foo</strong>
|
||||
Variable2: <div data-djc-id-a1bc3f>foo</div>
|
||||
Variable3: <span data-djc-id-a1bc3f>foo</span>
|
||||
""",
|
||||
)
|
||||
|
||||
# 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: <strong>{{ variable }}</strong>
|
||||
Variable2: <div>{{ variable }}</div>
|
||||
Variable3: <span>{{ variable }}</span>
|
||||
"""
|
||||
|
||||
class SimpleOuter(SimpleComponent):
|
||||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component 'multiroot' variable='foo' / %}
|
||||
<div>Another</div>
|
||||
"""
|
||||
|
||||
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 = create_and_process_template_response(template)
|
||||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong data-djc-id-a1bc3f data-djc-id-a1bc41>foo</strong>
|
||||
Variable2: <div data-djc-id-a1bc3f data-djc-id-a1bc41>foo</div>
|
||||
Variable3: <span data-djc-id-a1bc3f data-djc-id-a1bc41>foo</span>
|
||||
<div data-djc-id-a1bc3f>Another</div>
|
||||
""",
|
||||
)
|
||||
|
||||
# `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: <strong>{{ variable }}</strong>
|
||||
Variable2: <div>{{ variable }}</div>
|
||||
Variable3: <span>{{ variable }}</span>
|
||||
"""
|
||||
|
||||
class SimpleOuter(SimpleComponent):
|
||||
template: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component 'multiroot' variable='foo' / %}
|
||||
<div>Another</div>
|
||||
"""
|
||||
|
||||
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 = create_and_process_template_response(
|
||||
template,
|
||||
context=Context({"lst": range(3)}),
|
||||
)
|
||||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong data-djc-id-a1bc3f data-djc-id-a1bc41>foo</strong>
|
||||
Variable2: <div data-djc-id-a1bc3f data-djc-id-a1bc41>foo</div>
|
||||
Variable3: <span data-djc-id-a1bc3f data-djc-id-a1bc41>foo</span>
|
||||
<div data-djc-id-a1bc3f>Another</div>
|
||||
|
||||
Variable: <strong data-djc-id-a1bc42 data-djc-id-a1bc43>foo</strong>
|
||||
Variable2: <div data-djc-id-a1bc42 data-djc-id-a1bc43>foo</div>
|
||||
Variable3: <span data-djc-id-a1bc42 data-djc-id-a1bc43>foo</span>
|
||||
<div data-djc-id-a1bc42>Another</div>
|
||||
|
||||
Variable: <strong data-djc-id-a1bc44 data-djc-id-a1bc45>foo</strong>
|
||||
Variable2: <div data-djc-id-a1bc44 data-djc-id-a1bc45>foo</div>
|
||||
Variable3: <span data-djc-id-a1bc44 data-djc-id-a1bc45>foo</span>
|
||||
<div data-djc-id-a1bc44>Another</div>
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -3,6 +3,8 @@ Here we check that all parts of managing JS and CSS dependencies work together
|
|||
in an actual browser.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from playwright.async_api import Page
|
||||
|
||||
from django_components import types
|
||||
|
@ -44,7 +46,10 @@ class E2eDependencyRenderingTests(BaseTestCase):
|
|||
data = await page.evaluate(test_js)
|
||||
|
||||
# Check that the actual HTML content was loaded
|
||||
self.assertIn('Variable: <strong class="inner">foo</strong>', data["bodyHTML"])
|
||||
self.assertRegex(
|
||||
data["bodyHTML"],
|
||||
re.compile(r'Variable: <strong class="inner" data-djc-id-\w{6}="">foo</strong>'),
|
||||
)
|
||||
self.assertInHTML('<div class="my-style"> 123 </div>', data["bodyHTML"], count=1)
|
||||
self.assertInHTML('<div class="my-style2"> xyz </div>', data["bodyHTML"], count=1)
|
||||
|
||||
|
@ -106,17 +111,34 @@ class E2eDependencyRenderingTests(BaseTestCase):
|
|||
data = await page.evaluate(test_js)
|
||||
|
||||
# Check that the actual HTML content was loaded
|
||||
self.assertInHTML(
|
||||
"""
|
||||
<div class="outer">
|
||||
Variable: <strong class="inner">variable</strong>
|
||||
XYZ: <strong class="other">variable_inner</strong>
|
||||
</div>
|
||||
<div class="my-style">123</div>
|
||||
<div class="my-style2">xyz</div>
|
||||
""",
|
||||
self.assertRegex(
|
||||
data["bodyHTML"],
|
||||
count=1,
|
||||
# <div class="outer" data-djc-id-10uLMD>
|
||||
# Variable:
|
||||
# <strong class="inner" data-djc-id-DZEnUC>
|
||||
# variable
|
||||
# </strong>
|
||||
# XYZ:
|
||||
# <strong class="other" data-djc-id-IYirHK>
|
||||
# variable_inner
|
||||
# </strong>
|
||||
# </div>
|
||||
# <div class="my-style">123</div>
|
||||
# <div class="my-style2">xyz</div>
|
||||
re.compile(
|
||||
r'<div class="outer" data-djc-id-\w{6}="">\s*'
|
||||
r"Variable:\s*"
|
||||
r'<strong class="inner" data-djc-id-\w{6}="">\s*'
|
||||
r"variable\s*"
|
||||
r"<\/strong>\s*"
|
||||
r"XYZ:\s*"
|
||||
r'<strong class="other" data-djc-id-\w{6}="">\s*'
|
||||
r"variable_inner\s*"
|
||||
r"<\/strong>\s*"
|
||||
r"<\/div>\s*"
|
||||
r'<div class="my-style">123<\/div>\s*'
|
||||
r'<div class="my-style2">xyz<\/div>\s*'
|
||||
),
|
||||
)
|
||||
|
||||
# Check components' inlined JS got loaded
|
||||
|
@ -183,17 +205,34 @@ class E2eDependencyRenderingTests(BaseTestCase):
|
|||
data = await page.evaluate(test_js)
|
||||
|
||||
# Check that the actual HTML content was loaded
|
||||
self.assertInHTML(
|
||||
"""
|
||||
<div class="outer">
|
||||
Variable: <strong class="inner">variable</strong>
|
||||
XYZ: <strong class="other">variable_inner</strong>
|
||||
</div>
|
||||
<div class="my-style">123</div>
|
||||
<div class="my-style2">xyz</div>
|
||||
""",
|
||||
self.assertRegex(
|
||||
data["bodyHTML"],
|
||||
count=1,
|
||||
# <div class="outer" data-djc-id-10uLMD>
|
||||
# Variable:
|
||||
# <strong class="inner" data-djc-id-DZEnUC>
|
||||
# variable
|
||||
# </strong>
|
||||
# XYZ:
|
||||
# <strong data-djc-id-IYirHK class="other">
|
||||
# variable_inner
|
||||
# </strong>
|
||||
# </div>
|
||||
# <div class="my-style">123</div>
|
||||
# <div class="my-style2">xyz</div>
|
||||
re.compile(
|
||||
r'<div class="outer" data-djc-id-\w{6}="">\s*'
|
||||
r"Variable:\s*"
|
||||
r'<strong class="inner" data-djc-id-\w{6}="">\s*'
|
||||
r"variable\s*"
|
||||
r"<\/strong>\s*"
|
||||
r"XYZ:\s*"
|
||||
r'<strong class="other" data-djc-id-\w{6}="">\s*'
|
||||
r"variable_inner\s*"
|
||||
r"<\/strong>\s*"
|
||||
r"<\/div>\s*"
|
||||
r'<div class="my-style">123<\/div>\s*'
|
||||
r'<div class="my-style2">xyz<\/div>\s*'
|
||||
),
|
||||
)
|
||||
|
||||
# Check components' inlined JS did NOT get loaded
|
||||
|
@ -342,7 +381,12 @@ class E2eDependencyRenderingTests(BaseTestCase):
|
|||
data = await page.evaluate(test_js)
|
||||
|
||||
self.assertEqual(data["targetHtml"], None)
|
||||
self.assertHTMLEqual('<div class="frag"> 123 <span id="frag-text">xxx</span></div>', data["fragHtml"])
|
||||
self.assertRegex(
|
||||
data["fragHtml"],
|
||||
re.compile(
|
||||
r'<div class="frag" data-djc-id-\w{6}="">\s*' r"123\s*" r'<span id="frag-text">xxx</span>\s*' r"</div>"
|
||||
),
|
||||
)
|
||||
self.assertIn("rgb(0, 0, 255)", data["fragBg"]) # AKA 'background: blue'
|
||||
|
||||
await page.close()
|
||||
|
@ -390,7 +434,12 @@ class E2eDependencyRenderingTests(BaseTestCase):
|
|||
data = await page.evaluate(test_js)
|
||||
|
||||
self.assertEqual(data["targetHtml"], None)
|
||||
self.assertHTMLEqual('<div class="frag"> 123 <span id="frag-text">xxx</span></div>', data["fragHtml"])
|
||||
self.assertRegex(
|
||||
data["fragHtml"],
|
||||
re.compile(
|
||||
r'<div class="frag" data-djc-id-\w{6}="">\s*' r"123\s*" r'<span id="frag-text">xxx</span>\s*' r"</div>"
|
||||
),
|
||||
)
|
||||
self.assertIn("rgb(0, 0, 255)", data["fragBg"]) # AKA 'background: blue'
|
||||
|
||||
await page.close()
|
||||
|
@ -441,11 +490,12 @@ class E2eDependencyRenderingTests(BaseTestCase):
|
|||
|
||||
# NOTE: Unlike the vanilla JS tests, for the Alpine test we don't remove the targetHtml,
|
||||
# but only change its contents.
|
||||
self.assertInHTML(
|
||||
'<div class="frag"> 123 <span id="frag-text">xxx</span></div>',
|
||||
self.assertRegex(
|
||||
data["targetHtml"],
|
||||
re.compile(
|
||||
r'<div class="frag" data-djc-id-\w{6}="">\s*' r"123\s*" r'<span id="frag-text">xxx</span>\s*' r"</div>"
|
||||
),
|
||||
)
|
||||
self.assertHTMLEqual(data["fragHtml"], '<div class="frag"> 123 <span id="frag-text">xxx</span></div>')
|
||||
self.assertIn("rgb(0, 0, 255)", data["fragBg"]) # AKA 'background: blue'
|
||||
|
||||
await page.close()
|
||||
|
@ -497,9 +547,9 @@ class E2eDependencyRenderingTests(BaseTestCase):
|
|||
self.assertEqual(data["targetHtml"], None)
|
||||
# NOTE: We test only the inner HTML, because the element itself may or may not have
|
||||
# extra CSS classes added by HTMX, which results in flaky tests.
|
||||
self.assertHTMLEqual(
|
||||
self.assertRegex(
|
||||
data["fragInnerHtml"],
|
||||
'123 <span id="frag-text">xxx</span>',
|
||||
re.compile(r'123\s*<span id="frag-text">xxx</span>'),
|
||||
)
|
||||
self.assertIn("rgb(0, 0, 255)", data["fragBg"]) # AKA 'background: blue'
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ class DynamicExprTests(BaseTestCase):
|
|||
|
||||
self.assertEqual(
|
||||
rendered.strip(),
|
||||
"<!-- _RENDERED SimpleComponent_5b8d97,a1bc3e -->\n <div>lorem</div>\n <div>True</div>\n <div>[{'a': 1}, {'a': 2}]</div>", # noqa: E501
|
||||
"<!-- _RENDERED SimpleComponent_5b8d97,a1bc3f,, -->\n<div data-djc-id-a1bc3f>lorem</div>\n<div data-djc-id-a1bc3f>True</div>\n<div data-djc-id-a1bc3f>[{'a': 1}, {'a': 2}]</div>", # noqa: E501
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
@ -220,7 +220,13 @@ class DynamicExprTests(BaseTestCase):
|
|||
|
||||
self.assertEqual(
|
||||
rendered.strip(),
|
||||
"<!-- _RENDERED SimpleComponent_743413,a1bc3e -->\n <div>lorem ipsum dolor</div>\n <div>True</div>\n <div>[{'a': 1}, {'a': 2}]</div>\n <div>{'a': 3}</div>", # noqa E501
|
||||
(
|
||||
"<!-- _RENDERED SimpleComponent_743413,a1bc3f,, -->\n"
|
||||
"<div data-djc-id-a1bc3f>lorem ipsum dolor</div>\n"
|
||||
"<div data-djc-id-a1bc3f>True</div>\n"
|
||||
"<div data-djc-id-a1bc3f>[{'a': 1}, {'a': 2}]</div>\n"
|
||||
"<div data-djc-id-a1bc3f>{'a': 3}</div>"
|
||||
),
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
@ -290,7 +296,13 @@ class DynamicExprTests(BaseTestCase):
|
|||
|
||||
self.assertEqual(
|
||||
rendered.strip(),
|
||||
"<!-- _RENDERED SimpleComponent_e258c0,a1bc3e -->\n <div></div>\n <div> abc</div>\n <div></div>\n <div> </div>", # noqa E501
|
||||
(
|
||||
"<!-- _RENDERED SimpleComponent_e258c0,a1bc3f,, -->\n"
|
||||
"<div data-djc-id-a1bc3f></div>\n"
|
||||
"<div data-djc-id-a1bc3f> abc</div>\n"
|
||||
"<div data-djc-id-a1bc3f></div>\n"
|
||||
"<div data-djc-id-a1bc3f> </div>"
|
||||
),
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
@ -364,7 +376,14 @@ class DynamicExprTests(BaseTestCase):
|
|||
|
||||
self.assertEqual(
|
||||
rendered.strip(),
|
||||
"<!-- _RENDERED SimpleComponent_6c8e94,a1bc3e -->\n <div> lorem ipsum dolor </div>\n <div> lorem ipsum dolor [{'a': 1}] </div>\n <div> True </div>\n <div> [{'a': 1}, {'a': 2}] </div>\n <div> {'a': 3} </div>", # noqa E501
|
||||
(
|
||||
"<!-- _RENDERED SimpleComponent_6c8e94,a1bc3f,, -->\n"
|
||||
"<div data-djc-id-a1bc3f> lorem ipsum dolor </div>\n"
|
||||
"<div data-djc-id-a1bc3f> lorem ipsum dolor [{'a': 1}] </div>\n"
|
||||
"<div data-djc-id-a1bc3f> True </div>\n"
|
||||
"<div data-djc-id-a1bc3f> [{'a': 1}, {'a': 2}] </div>\n"
|
||||
"<div data-djc-id-a1bc3f> {'a': 3} </div>"
|
||||
),
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
@ -408,7 +427,12 @@ class DynamicExprTests(BaseTestCase):
|
|||
|
||||
self.assertEqual(
|
||||
rendered.strip(),
|
||||
'<!-- _RENDERED SimpleComponent_c7a5c3,a1bc3e -->\n <div>"</div>\n <div>{%}</div>\n <div>True</div>', # noqa: E501
|
||||
(
|
||||
"<!-- _RENDERED SimpleComponent_c7a5c3,a1bc3f,, -->\n"
|
||||
'<div data-djc-id-a1bc3f>"</div>\n'
|
||||
"<div data-djc-id-a1bc3f>{%}</div>\n"
|
||||
"<div data-djc-id-a1bc3f>True</div>"
|
||||
),
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
@ -457,7 +481,14 @@ class DynamicExprTests(BaseTestCase):
|
|||
|
||||
self.assertEqual(
|
||||
rendered.strip(),
|
||||
"<!-- _RENDERED SimpleComponent_5c8766,a1bc3e -->\n <div><!-- _RENDERED SimpleComponent_5c8766,a1bc3f -->\n <div>3</div>\n <div>True</div>\n </div>\n <div>True</div>", # noqa E501
|
||||
(
|
||||
"<!-- _RENDERED SimpleComponent_5c8766,a1bc41,, -->\n"
|
||||
"<div data-djc-id-a1bc41><!-- _RENDERED SimpleComponent_5c8766,a1bc40,, -->\n"
|
||||
'<div data-djc-id-a1bc40="">3</div>\n'
|
||||
'<div data-djc-id-a1bc40="">True</div>\n'
|
||||
"</div>\n"
|
||||
"<div data-djc-id-a1bc41>True</div>"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
@ -528,11 +559,11 @@ class SpreadOperatorTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>LoREM</div>
|
||||
<div>{'@click': '() => {}', 'style': 'height: 20px'}</div>
|
||||
<div>[1, 2, 3]</div>
|
||||
<div>1</div>
|
||||
<div>123</div>
|
||||
<div data-djc-id-a1bc3f>LoREM</div>
|
||||
<div data-djc-id-a1bc3f>{'@click': '() => {}', 'style': 'height: 20px'}</div>
|
||||
<div data-djc-id-a1bc3f>[1, 2, 3]</div>
|
||||
<div data-djc-id-a1bc3f>1</div>
|
||||
<div data-djc-id-a1bc3f>123</div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -665,9 +696,9 @@ class SpreadOperatorTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>{'@click': '() => {}', 'style': 'height: 20px'}</div>
|
||||
<div>[1, 2, 3]</div>
|
||||
<div>1</div>
|
||||
<div data-djc-id-a1bc40>{'@click': '() => {}', 'style': 'height: 20px'}</div>
|
||||
<div data-djc-id-a1bc40>[1, 2, 3]</div>
|
||||
<div data-djc-id-a1bc40>1</div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -748,10 +779,10 @@ class SpreadOperatorTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>{'@click': '() => {}', 'style': 'OVERWRITTEN'}</div>
|
||||
<div>[1, 2, 3]</div>
|
||||
<div>1</div>
|
||||
<div>OVERWRITTEN_X</div>
|
||||
<div data-djc-id-a1bc3f>{'@click': '() => {}', 'style': 'OVERWRITTEN'}</div>
|
||||
<div data-djc-id-a1bc3f>[1, 2, 3]</div>
|
||||
<div data-djc-id-a1bc3f>1</div>
|
||||
<div data-djc-id-a1bc3f>OVERWRITTEN_X</div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
|
|
@ -203,11 +203,11 @@ class MultipleComponentRegistriesTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>123</strong>
|
||||
Variable: <strong data-djc-id-a1bc40>123</strong>
|
||||
Slot:
|
||||
SLOT 123
|
||||
|
||||
Variable: <strong>123</strong>
|
||||
Variable: <strong data-djc-id-a1bc42>123</strong>
|
||||
Slot:
|
||||
SLOT ABC
|
||||
""",
|
||||
|
|
|
@ -68,7 +68,7 @@ class ComponentTagTests(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
hello1
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
SLOT_DEFAULT
|
||||
</div>
|
||||
hello2
|
||||
|
@ -101,7 +101,7 @@ class ComponentTagTests(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
hello1
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
OVERRIDEN!
|
||||
</div>
|
||||
hello2
|
||||
|
@ -139,7 +139,7 @@ class ComponentTagTests(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
hello1
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
SLOT_DEFAULT
|
||||
</div>
|
||||
hello2
|
||||
|
@ -179,7 +179,7 @@ class ComponentTagTests(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
hello1
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
OVERRIDEN!
|
||||
</div>
|
||||
hello2
|
||||
|
@ -217,7 +217,7 @@ class ComponentTagTests(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
hello1
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
SLOT_DEFAULT
|
||||
</div>
|
||||
hello2
|
||||
|
@ -257,7 +257,7 @@ class ComponentTagTests(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
hello1
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
OVERRIDEN!
|
||||
</div>
|
||||
hello2
|
||||
|
@ -297,7 +297,7 @@ class ComponentTagTests(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
hello1
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
OVERRIDEN!
|
||||
</div>
|
||||
hello2
|
||||
|
@ -334,7 +334,7 @@ class ComponentTagTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
OVERRIDEN!
|
||||
</div>
|
||||
""",
|
||||
|
@ -419,7 +419,7 @@ class ComponentTagTests(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
hello1
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
SLOT_DEFAULT
|
||||
</div>
|
||||
hello2
|
||||
|
@ -439,7 +439,7 @@ class ComponentTagTests(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
hello1
|
||||
<div>
|
||||
<div data-djc-id-a1bc42>
|
||||
OVERRIDEN!
|
||||
</div>
|
||||
hello2
|
||||
|
|
|
@ -120,7 +120,7 @@ class MultilineTagsTests(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
Variable: <strong>123</strong>
|
||||
Variable: <strong data-djc-id-a1bc3f>123</strong>
|
||||
"""
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
|
@ -147,7 +147,7 @@ class NestedTagsTests(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
Variable: <strong>lorem</strong>
|
||||
Variable: <strong data-djc-id-a1bc3f>lorem</strong>
|
||||
"""
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
|
@ -161,7 +161,7 @@ class NestedTagsTests(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
Variable: <strong>organisation's</strong>
|
||||
Variable: <strong data-djc-id-a1bc3f>organisation's</strong>
|
||||
"""
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
|
@ -175,7 +175,7 @@ class NestedTagsTests(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
Variable: <strong>organisation's</strong>
|
||||
Variable: <strong data-djc-id-a1bc3f>organisation's</strong>
|
||||
"""
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
|
@ -189,7 +189,7 @@ class NestedTagsTests(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
Variable: <strong>organisation"s</strong>
|
||||
Variable: <strong data-djc-id-a1bc3f>organisation"s</strong>
|
||||
"""
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
|
@ -203,6 +203,6 @@ class NestedTagsTests(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
Variable: <strong>organisation"s</strong>
|
||||
Variable: <strong data-djc-id-a1bc3f>organisation"s</strong>
|
||||
"""
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import textwrap
|
||||
|
||||
from django.template import Context, Template, TemplateSyntaxError
|
||||
|
||||
from django_components import AlreadyRegistered, Component, NotRegistered, register, registry, types
|
||||
|
@ -60,7 +58,7 @@ class ComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
template = Template(simple_tag_template)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong data-djc-id-a1bc3f>variable</strong>\n")
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_single_component_self_closing(self):
|
||||
|
@ -73,7 +71,7 @@ class ComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
template = Template(simple_tag_template)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong data-djc-id-a1bc3f>variable</strong>\n")
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_call_with_invalid_name(self):
|
||||
|
@ -99,7 +97,7 @@ class ComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
template = Template(simple_tag_template)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong data-djc-id-a1bc3f>variable</strong>\n")
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_call_component_with_two_variables(self):
|
||||
|
@ -129,8 +127,13 @@ class ComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
template = Template(simple_tag_template)
|
||||
rendered = template.render(Context({}))
|
||||
expected_outcome = """Variable: <strong>variable</strong>\n""" """Variable2: <strong>hej</strong>"""
|
||||
self.assertHTMLEqual(rendered, textwrap.dedent(expected_outcome))
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong data-djc-id-a1bc3f>variable</strong>
|
||||
Variable2: <strong data-djc-id-a1bc3f>hej</strong>
|
||||
""",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_component_called_with_singlequoted_name(self):
|
||||
|
@ -143,7 +146,7 @@ class ComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
template = Template(simple_tag_template)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong data-djc-id-a1bc3f>variable</strong>\n")
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_raises_on_component_called_with_variable_as_name(self):
|
||||
|
@ -183,7 +186,10 @@ class ComponentTemplateTagTest(BaseTestCase):
|
|||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"Provided variable: <strong>provided value</strong>\nDefault: <p>default text</p>",
|
||||
"""
|
||||
Provided variable: <strong data-djc-id-a1bc3f>provided value</strong>
|
||||
Default: <p data-djc-id-a1bc3f>default text</p>
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
|
@ -222,7 +228,10 @@ class DynamicComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
template = Template(simple_tag_template)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"Variable: <strong data-djc-id-a1bc3f data-djc-id-a1bc40>variable</strong>",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_call_with_invalid_name(self):
|
||||
|
@ -250,7 +259,10 @@ class DynamicComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
template = Template(simple_tag_template)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"Variable: <strong data-djc-id-a1bc3f data-djc-id-a1bc40>variable</strong>",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_component_called_with_variable_as_spread(self):
|
||||
|
@ -272,7 +284,10 @@ class DynamicComponentTemplateTagTest(BaseTestCase):
|
|||
}
|
||||
)
|
||||
)
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"Variable: <strong data-djc-id-a1bc3f data-djc-id-a1bc40>variable</strong>",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_component_as_class(self):
|
||||
|
@ -291,7 +306,10 @@ class DynamicComponentTemplateTagTest(BaseTestCase):
|
|||
}
|
||||
)
|
||||
)
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"Variable: <strong data-djc-id-a1bc3f data-djc-id-a1bc40>variable</strong>",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(
|
||||
["django", "isolated"],
|
||||
|
@ -316,7 +334,7 @@ class DynamicComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
template = Template(simple_tag_template)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong data-djc-id-a1bc3f data-djc-id-a1bc40>variable</strong>\n")
|
||||
|
||||
@parametrize_context_behavior(
|
||||
["django", "isolated"],
|
||||
|
@ -342,7 +360,10 @@ class DynamicComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
template = Template(simple_tag_template)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, "Variable: <strong>variable</strong>\n")
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"Variable: <strong data-djc-id-a1bc3f data-djc-id-a1bc40>variable</strong>",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_raises_already_registered_on_name_conflict(self):
|
||||
|
@ -380,7 +401,7 @@ class DynamicComponentTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>variable</strong>
|
||||
Variable: <strong data-djc-id-a1bc3f data-djc-id-a1bc40>variable</strong>
|
||||
Slot: HELLO_FROM_SLOT
|
||||
""",
|
||||
)
|
||||
|
@ -422,7 +443,7 @@ class DynamicComponentTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>variable</strong>
|
||||
Variable: <strong data-djc-id-a1bc41 data-djc-id-a1bc42>variable</strong>
|
||||
Slot 1: HELLO_FROM_SLOT_1
|
||||
Slot 2: HELLO_FROM_SLOT_2
|
||||
""",
|
||||
|
@ -465,7 +486,7 @@ class DynamicComponentTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
Variable: <strong>variable</strong>
|
||||
Variable: <strong data-djc-id-a1bc41 data-djc-id-a1bc42>variable</strong>
|
||||
Slot 1: HELLO_FROM_SLOT_1
|
||||
Slot 2:
|
||||
""",
|
||||
|
@ -488,75 +509,149 @@ class DynamicComponentTemplateTagTest(BaseTestCase):
|
|||
|
||||
|
||||
class MultiComponentTests(BaseTestCase):
|
||||
def register_components(self):
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_both_components_render_correctly_with_no_slots(self):
|
||||
registry.register("first_component", SlottedComponent)
|
||||
registry.register("second_component", SlottedComponentWithContext)
|
||||
|
||||
def make_template(self, first_slot: str = "", second_slot: str = "") -> Template:
|
||||
template_str: types.django_html = f"""
|
||||
{{% load component_tags %}}
|
||||
{{% component 'first_component' %}}
|
||||
{first_slot}
|
||||
{{% endcomponent %}}
|
||||
{{% component 'second_component' variable='xyz' %}}
|
||||
{second_slot}
|
||||
{{% endcomponent %}}
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component 'first_component' %}
|
||||
{% endcomponent %}
|
||||
{% component 'second_component' variable='xyz' %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
return Template(template_str)
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context())
|
||||
|
||||
def expected_result(self, first_slot: str = "", second_slot: str = "") -> str:
|
||||
first_slot = first_slot or "Default header"
|
||||
second_slot = second_slot or "Default header"
|
||||
return f"""
|
||||
<custom-template>
|
||||
<header>{first_slot}</header>
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template data-djc-id-a1bc40>
|
||||
<header>
|
||||
Default header
|
||||
</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
<custom-template>
|
||||
<header>{second_slot}</header>
|
||||
<custom-template data-djc-id-a1bc44>
|
||||
<header>
|
||||
Default header
|
||||
</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
"""
|
||||
|
||||
def wrap_with_slot_tags(self, s):
|
||||
return '{% fill "header" %}' + s + "{% endfill %}"
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_both_components_render_correctly_with_no_slots(self):
|
||||
self.register_components()
|
||||
rendered = self.make_template().render(Context({}))
|
||||
self.assertHTMLEqual(rendered, self.expected_result())
|
||||
""",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
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 = 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({}))
|
||||
registry.register("first_component", SlottedComponent)
|
||||
registry.register("second_component", SlottedComponentWithContext)
|
||||
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component 'first_component' %}
|
||||
{% fill "header" %}<p>Slot #1</p>{% endfill %}
|
||||
{% endcomponent %}
|
||||
{% component 'second_component' variable='xyz' %}
|
||||
{% fill "header" %}<div>Slot #2</div>{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context())
|
||||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
self.expected_result(first_slot_content, second_slot_content),
|
||||
"""
|
||||
<custom-template data-djc-id-a1bc42>
|
||||
<header>
|
||||
<p>Slot #1</p>
|
||||
</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
<custom-template data-djc-id-a1bc46>
|
||||
<header>
|
||||
<div>Slot #2</div>
|
||||
</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
""",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_both_components_render_correctly_when_only_first_has_slots(self):
|
||||
self.register_components()
|
||||
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))
|
||||
registry.register("first_component", SlottedComponent)
|
||||
registry.register("second_component", SlottedComponentWithContext)
|
||||
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component 'first_component' %}
|
||||
{% fill "header" %}<p>Slot #1</p>{% endfill %}
|
||||
{% endcomponent %}
|
||||
{% component 'second_component' variable='xyz' %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template data-djc-id-a1bc41>
|
||||
<header>
|
||||
<p>Slot #1</p>
|
||||
</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header>
|
||||
Default header
|
||||
</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
""",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_both_components_render_correctly_when_only_second_has_slots(self):
|
||||
self.register_components()
|
||||
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))
|
||||
registry.register("first_component", SlottedComponent)
|
||||
registry.register("second_component", SlottedComponentWithContext)
|
||||
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component 'first_component' %}
|
||||
{% endcomponent %}
|
||||
{% component 'second_component' variable='xyz' %}
|
||||
{% fill "header" %}<div>Slot #2</div>{% endfill %}
|
||||
{% endcomponent %}
|
||||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
|
||||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template data-djc-id-a1bc41>
|
||||
<header>
|
||||
Default header
|
||||
</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header>
|
||||
<div>Slot #2</div>
|
||||
</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
class ComponentIsolationTests(BaseTestCase):
|
||||
|
@ -596,17 +691,17 @@ class ComponentIsolationTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc4a>
|
||||
<header>Override header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc4b>
|
||||
<header>Default header</header>
|
||||
<main>Override main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc4c>
|
||||
<header>Default header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Override footer</footer>
|
||||
|
@ -641,7 +736,7 @@ class AggregateInputTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
attrs: {'@click.stop': "dispatch('click_event')", 'x-data': "{hello: 'world'}", 'class': 'padding-top-8'}
|
||||
my_dict: {'one': 2}
|
||||
</div>
|
||||
|
|
|
@ -49,14 +49,15 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
{% endblock %}
|
||||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
|
||||
expected = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<main role="main">
|
||||
<div class='container main-container'>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc40>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc40>
|
||||
<header>SLOT OVERRIDEN</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -105,14 +106,14 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
<body>
|
||||
<main role="main">
|
||||
<div class='container main-container'>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc42>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc42>
|
||||
<header>SLOT OVERRIDEN</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc46>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc46>
|
||||
<header>SLOT OVERRIDEN 2</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -171,14 +172,14 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
<body>
|
||||
<main role="main">
|
||||
<div class='container main-container'>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc42>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc42>
|
||||
<header>SLOT OVERRIDEN</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc46>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc46>
|
||||
<header>SLOT OVERRIDEN 2</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -230,20 +231,21 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
{% endblock %}
|
||||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
|
||||
expected = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<main role="main">
|
||||
<div class='container main-container'>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc42>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc42>
|
||||
<header>SLOT OVERRIDEN</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc46>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc46>
|
||||
<header>SLOT OVERRIDEN 2</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -287,8 +289,8 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc40>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc40>
|
||||
<header>SLOT OVERRIDEN</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -335,14 +337,14 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc42>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc42>
|
||||
<header>SLOT OVERRIDEN</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc46>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc46>
|
||||
<header>SLOT OVERRIDEN 2</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -389,11 +391,11 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
<body>
|
||||
<main role="main">
|
||||
<div class='container main-container'>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc42>
|
||||
<header>Default header</header>
|
||||
<main>
|
||||
<div>BLOCK OVERRIDEN</div>
|
||||
<custom-template>
|
||||
<div data-djc-id-a1bc46>BLOCK OVERRIDEN</div>
|
||||
<custom-template data-djc-id-a1bc46>
|
||||
<header>SLOT OVERRIDEN</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -435,7 +437,7 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
<main role="main">
|
||||
<div class='container main-container'>
|
||||
Variable: <strong></strong>
|
||||
Variable: <strong></strong>
|
||||
Variable: <strong data-djc-id-a1bc3f></strong>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
@ -469,9 +471,9 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-djc-id-a1bc40 lang="en">
|
||||
<body>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header></header>
|
||||
<main>BODY_FROM_FILL</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -502,9 +504,9 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-djc-id-a1bc40 lang="en">
|
||||
<body>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header></header>
|
||||
<main>BODY_FROM_FILL</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -537,7 +539,7 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
<body>
|
||||
<main role="main">
|
||||
<div class='container main-container'>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc42>
|
||||
<header></header>
|
||||
<main>TEST</main>
|
||||
<footer></footer>
|
||||
|
@ -567,7 +569,7 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc41>
|
||||
<header></header>
|
||||
<main>
|
||||
<div> 58 giraffes and 2 pantaloons </div>
|
||||
|
@ -594,9 +596,9 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-djc-id-a1bc3f lang="en">
|
||||
<body>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc43>
|
||||
<header></header>
|
||||
<main>
|
||||
<div> 58 giraffes and 2 pantaloons </div>
|
||||
|
@ -634,9 +636,9 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-djc-id-a1bc40 lang="en">
|
||||
<body>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header></header>
|
||||
<main>BODY_FROM_FILL</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -664,9 +666,9 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-djc-id-a1bc3f lang="en">
|
||||
<body>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc44>
|
||||
<header></header>
|
||||
<main>
|
||||
Helloodiddoo
|
||||
|
@ -700,9 +702,9 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-djc-id-a1bc3f lang="en">
|
||||
<body>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc44>
|
||||
<header></header>
|
||||
<main>
|
||||
Helloodiddoo
|
||||
|
@ -736,9 +738,9 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-djc-id-a1bc40 lang="en">
|
||||
<body>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header></header>
|
||||
<main>
|
||||
Helloodiddoo
|
||||
|
@ -782,9 +784,9 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-djc-id-a1bc41 lang="en">
|
||||
<body>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc47>
|
||||
<header></header>
|
||||
<main>
|
||||
Helloodiddoo
|
||||
|
@ -824,10 +826,10 @@ class ExtendsCompatTests(BaseTestCase):
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc43>
|
||||
<header></header>
|
||||
<main>
|
||||
<div> injected: DepInject(hello='from_block') </div>
|
||||
<div data-djc-id-a1bc47> injected: DepInject(hello='from_block') </div>
|
||||
</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
|
|
|
@ -36,7 +36,7 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> injected: DepInject(key='hi', another=123) </div>
|
||||
<div data-djc-id-a1bc40> injected: DepInject(key='hi', another=123) </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -87,8 +87,8 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> key: hi </div>
|
||||
<div> another: 123 </div>
|
||||
<div data-djc-id-a1bc40> key: hi </div>
|
||||
<div data-djc-id-a1bc40> another: 123 </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -120,8 +120,8 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> key: hi </div>
|
||||
<div> another: 123 </div>
|
||||
<div data-djc-id-a1bc40> key: hi </div>
|
||||
<div data-djc-id-a1bc40> another: 123 </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -150,7 +150,7 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> injected: default </div>
|
||||
<div data-djc-id-a1bc40> injected: default </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -183,8 +183,8 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> injected: DepInject() </div>
|
||||
<div> injected: default </div>
|
||||
<div data-djc-id-a1bc41> injected: DepInject() </div>
|
||||
<div data-djc-id-a1bc42> injected: default </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -216,8 +216,8 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div data-djc-id-a1bc41></div>
|
||||
<div data-djc-id-a1bc42></div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -248,8 +248,8 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> injected: DepInject(key='hi', another=123) </div>
|
||||
<div> injected: default </div>
|
||||
<div data-djc-id-a1bc41> injected: DepInject(key='hi', another=123) </div>
|
||||
<div data-djc-id-a1bc42> injected: default </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -286,8 +286,8 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> injected: DepInject(key='hi', another=123) </div>
|
||||
<div> injected: default </div>
|
||||
<div data-djc-id-a1bc41> injected: DepInject(key='hi', another=123) </div>
|
||||
<div data-djc-id-a1bc42> injected: default </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -328,8 +328,8 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> injected: DepInject(key='hi', another=123) </div>
|
||||
<div> injected: default </div>
|
||||
<div data-djc-id-a1bc41> injected: DepInject(key='hi', another=123) </div>
|
||||
<div data-djc-id-a1bc42> injected: default </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -431,7 +431,7 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> injected: DepInject(var1={'key': 'hi', 'another': 123}, var2={'x': 'y'}) </div>
|
||||
<div data-djc-id-a1bc40> injected: DepInject(var1={'key': 'hi', 'another': 123}, var2={'x': 'y'}) </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -505,9 +505,9 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> injected: DepInject(key='hi1', another=1231, new=3) </div>
|
||||
<div> injected: DepInject(key='hi', another=123, lost=0) </div>
|
||||
<div> injected: default </div>
|
||||
<div data-djc-id-a1bc43> injected: DepInject(key='hi1', another=1231, new=3) </div>
|
||||
<div data-djc-id-a1bc44> injected: DepInject(key='hi', another=123, lost=0) </div>
|
||||
<div data-djc-id-a1bc45> injected: default </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -545,8 +545,8 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> first_provide: DepInject(key='hi', another=123, lost=0) </div>
|
||||
<div> second_provide: DepInject(key='hi1', another=1231, new=3) </div>
|
||||
<div data-djc-id-a1bc41> first_provide: DepInject(key='hi', another=123, lost=0) </div>
|
||||
<div data-djc-id-a1bc41> second_provide: DepInject(key='hi1', another=1231, new=3) </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -575,7 +575,7 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
<div>
|
||||
<div> injected: DepInject(key='hi', another=123) </div>
|
||||
<div data-djc-id-a1bc40> injected: DepInject(key='hi', another=123) </div>
|
||||
</div>
|
||||
""",
|
||||
)
|
||||
|
@ -613,7 +613,7 @@ class ProvideTemplateTagTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>
|
||||
<div data-djc-id-a1bc40 data-djc-id-a1bc43>
|
||||
injected: DepInject(key='hi', another=123)
|
||||
</div>
|
||||
""",
|
||||
|
@ -646,7 +646,7 @@ class InjectTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> injected: DepInject(key='hi', another=123) </div>
|
||||
<div data-djc-id-a1bc40> injected: DepInject(key='hi', another=123) </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -694,7 +694,7 @@ class InjectTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div> injected: default </div>
|
||||
<div data-djc-id-a1bc3f> injected: default </div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -799,10 +799,12 @@ class InjectTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>
|
||||
<div data-djc-id-a1bc3e data-djc-id-a1bc41 data-djc-id-a1bc45 data-djc-id-a1bc48>
|
||||
injected: DepInject(key='hi', data=123)
|
||||
</div>
|
||||
<main>456</main>
|
||||
<main data-djc-id-a1bc3e data-djc-id-a1bc41 data-djc-id-a1bc45 data-djc-id-a1bc48>
|
||||
456
|
||||
</main>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -861,9 +863,10 @@ class InjectTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>
|
||||
<div data-djc-id-a1bc3e data-djc-id-a1bc41 data-djc-id-a1bc44 data-djc-id-a1bc47>
|
||||
injected: DepInject(key='hi', data=123)
|
||||
</div>
|
||||
<main></main>
|
||||
<main data-djc-id-a1bc3e data-djc-id-a1bc41 data-djc-id-a1bc44 data-djc-id-a1bc47>
|
||||
</main>
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -67,9 +67,11 @@ class ComponentSlotTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc42>
|
||||
<header>Custom header</header>
|
||||
<main>Variable: <strong>variable</strong></main>
|
||||
<main>
|
||||
Variable: <strong data-djc-id-a1bc46>variable</strong>
|
||||
</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
""",
|
||||
|
@ -117,8 +119,10 @@ class ComponentSlotTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<header> Variable: <strong>variable</strong> </header>
|
||||
<custom-template data-djc-id-a1bc43>
|
||||
<header>
|
||||
Variable: <strong data-djc-id-a1bc47>variable</strong>
|
||||
</header>
|
||||
<main></main>
|
||||
<footer></footer>
|
||||
</custom-template>
|
||||
|
@ -149,7 +153,7 @@ class ComponentSlotTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
f"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc41>
|
||||
<header>Default header</header>
|
||||
<main>test123 - {context_behavior_data} </main>
|
||||
<footer>test321</footer>
|
||||
|
@ -171,7 +175,7 @@ class ComponentSlotTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc3f>
|
||||
<header>Default header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -194,7 +198,7 @@ class ComponentSlotTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
|
||||
self.assertHTMLEqual(rendered, "<custom-template></custom-template>")
|
||||
self.assertHTMLEqual(rendered, "<custom-template data-djc-id-a1bc3f></custom-template>")
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_slotted_template_without_slots_and_single_quotes(self):
|
||||
|
@ -211,7 +215,7 @@ class ComponentSlotTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
|
||||
self.assertHTMLEqual(rendered, "<custom-template></custom-template>")
|
||||
self.assertHTMLEqual(rendered, "<custom-template data-djc-id-a1bc3f></custom-template>")
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_variable_fill_name(self):
|
||||
|
@ -227,7 +231,7 @@ class ComponentSlotTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
expected = """
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc40>
|
||||
<header>Hi there!</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -296,7 +300,7 @@ class ComponentSlotTests(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
<body>
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
<main> ABC: carl var </main>
|
||||
</div>
|
||||
</body>
|
||||
|
@ -327,7 +331,7 @@ class ComponentSlotTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
<h1> Custom title </h1>
|
||||
<h2> Default subtitle </h2>
|
||||
</div>
|
||||
|
@ -451,8 +455,8 @@ class ComponentSlotTests(BaseTestCase):
|
|||
render_dependencies=False,
|
||||
)
|
||||
|
||||
self.assertInHTML(rendered1, "<div><div>MAIN</div></div>")
|
||||
self.assertInHTML(rendered2, "<div><div>MAIN</div></div>")
|
||||
self.assertHTMLEqual(rendered1, "<div data-djc-id-a1bc3e><div>MAIN</div></div>")
|
||||
self.assertHTMLEqual(rendered2, "<div data-djc-id-a1bc41><div>MAIN</div></div>")
|
||||
|
||||
# 3. Specify the required slot by its name
|
||||
rendered3 = TestComp.render(
|
||||
|
@ -462,7 +466,7 @@ class ComponentSlotTests(BaseTestCase):
|
|||
},
|
||||
render_dependencies=False,
|
||||
)
|
||||
self.assertInHTML(rendered3, "<div><main>MAIN</main><div>MAIN</div></div>")
|
||||
self.assertHTMLEqual(rendered3, "<div data-djc-id-a1bc42><main>MAIN</main><div>MAIN</div></div>")
|
||||
|
||||
# 4. RAISES: Specify the required slot by the "default" name
|
||||
# This raises because the slot that is marked as 'required' is NOT marked as 'default'.
|
||||
|
@ -499,7 +503,7 @@ class ComponentSlotTests(BaseTestCase):
|
|||
rendered = Template(template_str).render(Context({}))
|
||||
|
||||
expected = """
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc42>
|
||||
<header>Custom header</header>
|
||||
<main>Custom main</main>
|
||||
<footer>Custom footer</footer>
|
||||
|
@ -553,8 +557,10 @@ class ComponentSlotDefaultTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
|
||||
expected = """
|
||||
<div>
|
||||
<main><p>This fills the 'main' slot.</p></main>
|
||||
<div data-djc-id-a1bc3f>
|
||||
<main>
|
||||
<p>This fills the 'main' slot.</p>
|
||||
</main>
|
||||
</div>
|
||||
"""
|
||||
rendered = template.render(Context({}))
|
||||
|
@ -579,8 +585,10 @@ class ComponentSlotDefaultTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
expected = """
|
||||
<div>
|
||||
<main><p>This fills the 'main' slot.</p></main>
|
||||
<div data-djc-id-a1bc40>
|
||||
<main>
|
||||
<p>This fills the 'main' slot.</p>
|
||||
</main>
|
||||
</div>
|
||||
"""
|
||||
rendered = template.render(Context({}))
|
||||
|
@ -606,9 +614,9 @@ class ComponentSlotDefaultTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
expected = """
|
||||
<div>
|
||||
<main><p>This fills the 'main' slot.</p></main>
|
||||
<div><p>This fills the 'main' slot.</p></div>
|
||||
<div data-djc-id-a1bc40>
|
||||
<main><p>This fills the 'main' slot.</p></main>
|
||||
<div><p>This fills the 'main' slot.</p></div>
|
||||
</div>
|
||||
"""
|
||||
rendered = template.render(Context({}))
|
||||
|
@ -686,14 +694,14 @@ class ComponentSlotDefaultTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
expected = """
|
||||
<div>
|
||||
<main>
|
||||
<custom-template>
|
||||
<header>This Is Allowed</header>
|
||||
<main></main>
|
||||
<footer></footer>
|
||||
</custom-template>
|
||||
</main>
|
||||
<div data-djc-id-a1bc43>
|
||||
<main>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header>This Is Allowed</header>
|
||||
<main></main>
|
||||
<footer></footer>
|
||||
</custom-template>
|
||||
</main>
|
||||
</div>
|
||||
"""
|
||||
rendered = template.render(Context({}))
|
||||
|
@ -761,7 +769,7 @@ class ComponentSlotDefaultTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc3f>
|
||||
<header>Default header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -799,7 +807,7 @@ class ComponentSlotDefaultTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered_truthy,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc3f>
|
||||
<header>123</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -811,7 +819,7 @@ class ComponentSlotDefaultTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered_falsy,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc43>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
|
@ -862,7 +870,7 @@ class PassthroughSlotsTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc41>
|
||||
<header>
|
||||
OVERRIDEN_SLOT "header" - INDEX 0 - ORIGINAL "Default header"
|
||||
</header>
|
||||
|
@ -910,7 +918,7 @@ class PassthroughSlotsTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc40>
|
||||
<header>
|
||||
OVERRIDEN_SLOT "header" - ORIGINAL "Default header"
|
||||
</header>
|
||||
|
@ -997,9 +1005,9 @@ class PassthroughSlotsTest(BaseTestCase):
|
|||
rendered = template.render(Context())
|
||||
|
||||
expected = """
|
||||
<div>CUSTOM HEADER</div>
|
||||
<div>CUSTOM MAIN</div>
|
||||
<div>footer</div>
|
||||
<div data-djc-id-a1bc41>CUSTOM HEADER</div>
|
||||
<div data-djc-id-a1bc41>CUSTOM MAIN</div>
|
||||
<div data-djc-id-a1bc41>footer</div>
|
||||
"""
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
|
@ -1042,11 +1050,11 @@ class PassthroughSlotsTest(BaseTestCase):
|
|||
rendered = template.render(Context())
|
||||
|
||||
expected = """
|
||||
<div>
|
||||
<custom-template>
|
||||
<header>CUSTOM HEADER</header>
|
||||
<main>CUSTOM MAIN</main>
|
||||
<footer>Default footer</footer>
|
||||
<div data-djc-id-a1bc41>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header>CUSTOM HEADER</header>
|
||||
<main>CUSTOM MAIN</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
</div>
|
||||
"""
|
||||
|
@ -1093,11 +1101,11 @@ class PassthroughSlotsTest(BaseTestCase):
|
|||
rendered = template.render(Context())
|
||||
|
||||
expected = """
|
||||
<div>
|
||||
<custom-template>
|
||||
<header>Default header</header>
|
||||
<main>CUSTOM MAIN</main>
|
||||
<footer>Default footer</footer>
|
||||
<div data-djc-id-a1bc41>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header>Default header</header>
|
||||
<main>CUSTOM MAIN</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
</div>
|
||||
"""
|
||||
|
@ -1145,7 +1153,7 @@ class NestedSlotsTests(BaseTestCase):
|
|||
|
||||
rendered = Template(template_str).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc3f>
|
||||
Wrapper Default
|
||||
<div>
|
||||
Parent1 Default
|
||||
|
@ -1175,7 +1183,7 @@ class NestedSlotsTests(BaseTestCase):
|
|||
|
||||
rendered = Template(template_str).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
Entire Wrapper Replaced
|
||||
</div>
|
||||
"""
|
||||
|
@ -1196,7 +1204,7 @@ class NestedSlotsTests(BaseTestCase):
|
|||
|
||||
rendered = Template(template_str).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
Wrapper Default
|
||||
<div>
|
||||
Parent1 Replaced
|
||||
|
@ -1223,7 +1231,7 @@ class NestedSlotsTests(BaseTestCase):
|
|||
|
||||
rendered = Template(template_str).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
Wrapper Default
|
||||
<div>
|
||||
Parent1 Default
|
||||
|
@ -1263,7 +1271,7 @@ class NestedSlotsTests(BaseTestCase):
|
|||
|
||||
rendered = Template(template_str).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc42>
|
||||
Entire Wrapper Replaced
|
||||
</div>
|
||||
"""
|
||||
|
@ -1295,7 +1303,7 @@ class SlottedTemplateRegressionTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc3f>
|
||||
<header>Default header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -1325,7 +1333,7 @@ class SlotDefaultTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc42>
|
||||
<header>Before: Default header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer, after</footer>
|
||||
|
@ -1350,7 +1358,7 @@ class SlotDefaultTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc40>
|
||||
<header>First: Default header; Second: Default header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -1380,7 +1388,7 @@ class SlotDefaultTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc40>
|
||||
<header>First Default header Later Default header Later Default header</header>
|
||||
<main>Default main</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -1414,10 +1422,10 @@ class SlotDefaultTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc43>
|
||||
<header>
|
||||
header1_in_header1: Default header
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc47>
|
||||
<header>
|
||||
header1_in_header2: Default header
|
||||
header2_in_header2: Default header
|
||||
|
@ -1465,7 +1473,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
def
|
||||
456
|
||||
</div>
|
||||
|
@ -1500,7 +1508,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
def
|
||||
456
|
||||
</div>
|
||||
|
@ -1536,7 +1544,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
Default text
|
||||
def
|
||||
456
|
||||
|
@ -1573,7 +1581,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
def
|
||||
456
|
||||
</div>
|
||||
|
@ -1611,7 +1619,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
def
|
||||
456
|
||||
</div>
|
||||
|
@ -1648,7 +1656,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
<b>Default text A</b>
|
||||
<b>xyz Default text B 456</b>
|
||||
</div>
|
||||
|
@ -1712,7 +1720,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
{% endcomponent %}
|
||||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = "<div> overriden </div>"
|
||||
expected = "<div data-djc-id-a1bc40> overriden </div>"
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
@ -1735,7 +1743,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
{% endcomponent %}
|
||||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = "<div> {} </div>"
|
||||
expected = "<div data-djc-id-a1bc40> {} </div>"
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
@ -1761,7 +1769,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
{% endcomponent %}
|
||||
"""
|
||||
rendered = Template(template).render(Context())
|
||||
expected = "<div> Default text </div>"
|
||||
expected = "<div data-djc-id-a1bc3f> Default text </div>"
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
|
@ -1800,7 +1808,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
)
|
||||
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
def
|
||||
456
|
||||
</div>
|
||||
|
@ -1845,7 +1853,7 @@ class ScopedSlotTest(BaseTestCase):
|
|||
)
|
||||
|
||||
expected = """
|
||||
<div>
|
||||
<div data-djc-id-a1bc40>
|
||||
def
|
||||
456
|
||||
</div>
|
||||
|
@ -1889,9 +1897,9 @@ class ScopedSlotTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<div>
|
||||
<div data-djc-id-a1bc42>
|
||||
data1_in_slot1: {'abc': 'def', 'input': 1}
|
||||
<div>
|
||||
<div data-djc-id-a1bc44>
|
||||
data1_in_slot2: {'abc': 'def', 'input': 1}
|
||||
data2_in_slot2: {'abc': 'def', 'input': 2}
|
||||
</div>
|
||||
|
@ -1990,9 +1998,9 @@ class DuplicateSlotTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<header>Name: Jannete</header>
|
||||
<main>Name: Jannete</main>
|
||||
<footer>Hello</footer>
|
||||
<header data-djc-id-a1bc41>Name: Jannete</header>
|
||||
<main data-djc-id-a1bc41>Name: Jannete</main>
|
||||
<footer data-djc-id-a1bc41>Hello</footer>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -2010,9 +2018,9 @@ class DuplicateSlotTest(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<header>Default header</header>
|
||||
<main>Default main header</main>
|
||||
<footer>Default footer</footer>
|
||||
<header data-djc-id-a1bc3f>Default header</header>
|
||||
<main data-djc-id-a1bc3f>Default main header</main>
|
||||
<footer data-djc-id-a1bc3f>Default footer</footer>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -2034,8 +2042,8 @@ class DuplicateSlotTest(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
OVERRIDDEN!
|
||||
<div class="dashboard-component">
|
||||
<div class="calendar-component">
|
||||
<div class="dashboard-component" data-djc-id-a1bc40>
|
||||
<div class="calendar-component" data-djc-id-a1bc47>
|
||||
<h1>
|
||||
OVERRIDDEN!
|
||||
</h1>
|
||||
|
@ -2071,8 +2079,8 @@ class DuplicateSlotTest(BaseTestCase):
|
|||
rendered,
|
||||
"""
|
||||
START
|
||||
<div class="dashboard-component">
|
||||
<div class="calendar-component">
|
||||
<div class="dashboard-component" data-djc-id-a1bc3f>
|
||||
<div class="calendar-component" data-djc-id-a1bc46>
|
||||
<h1>
|
||||
NESTED
|
||||
</h1>
|
||||
|
@ -2198,11 +2206,11 @@ class SlotBehaviorTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header>Name: Igor</header>
|
||||
<main>Day: Monday</main>
|
||||
<footer>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc49>
|
||||
<header>Name2: Joe2</header>
|
||||
<main>Day2: Monday</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -2214,7 +2222,22 @@ class SlotBehaviorTests(BaseTestCase):
|
|||
|
||||
# {{ name }} should be effectively the same as before, because overriden everywhere
|
||||
rendered2 = template.render(Context({"day": "Monday"}))
|
||||
self.assertHTMLEqual(rendered2, rendered)
|
||||
self.assertHTMLEqual(
|
||||
rendered2,
|
||||
"""
|
||||
<custom-template data-djc-id-a1bc4a>
|
||||
<header>Name: Igor</header>
|
||||
<main>Day: Monday</main>
|
||||
<footer>
|
||||
<custom-template data-djc-id-a1bc4b>
|
||||
<header>Name2: Joe2</header>
|
||||
<main>Day2: Monday</main>
|
||||
<footer>Default footer</footer>
|
||||
</custom-template>
|
||||
</footer>
|
||||
</custom-template>
|
||||
""",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["isolated"])
|
||||
def test_slot_context__isolated(self):
|
||||
|
@ -2224,11 +2247,11 @@ class SlotBehaviorTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header>Name: Jannete</header>
|
||||
<main>Day: Monday</main>
|
||||
<footer>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc49>
|
||||
<header>Name2: Jannete</header>
|
||||
<main>Day2: Monday</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -2243,11 +2266,11 @@ class SlotBehaviorTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered2,
|
||||
"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc4a>
|
||||
<header>Name: </header>
|
||||
<main>Day: Monday</main>
|
||||
<footer>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc4b>
|
||||
<header>Name2: </header>
|
||||
<main>Day2: Monday</main>
|
||||
<footer>Default footer</footer>
|
||||
|
|
|
@ -52,7 +52,7 @@ class NestedSlotTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, '<div id="outer">Default</div>')
|
||||
self.assertHTMLEqual(rendered, '<div id="outer" data-djc-id-a1bc3f>Default</div>')
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_inner_slot_overriden(self):
|
||||
|
@ -66,7 +66,7 @@ class NestedSlotTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, '<div id="outer">Override</div>')
|
||||
self.assertHTMLEqual(rendered, '<div id="outer" data-djc-id-a1bc40>Override</div>')
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_outer_slot_overriden(self):
|
||||
|
@ -78,7 +78,7 @@ class NestedSlotTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, "<p>Override</p>")
|
||||
self.assertHTMLEqual(rendered, "<p data-djc-id-a1bc40>Override</p>")
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_both_overriden_and_inner_removed(self):
|
||||
|
@ -93,7 +93,7 @@ class NestedSlotTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
self.assertHTMLEqual(rendered, "<p>Override</p>")
|
||||
self.assertHTMLEqual(rendered, "<p data-djc-id-a1bc41>Override</p>")
|
||||
|
||||
# NOTE: Second arg in tuple is expected name in nested fill. In "django" mode,
|
||||
# the value should be overridden by the component, while in "isolated" it should
|
||||
|
@ -145,9 +145,9 @@ class NestedSlotTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
f"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc49>
|
||||
<header>Name2: {context_behavior_data}</header>
|
||||
<main>Day2: Monday</main>
|
||||
<footer>XYZ</footer>
|
||||
|
@ -202,7 +202,13 @@ class ConditionalSlotTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
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" data-djc-id-a1bc40>Default A</p>
|
||||
<p id="b" data-djc-id-a1bc43>Default B</p>
|
||||
""",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_one_slot_overridden(self):
|
||||
|
@ -217,7 +223,13 @@ class ConditionalSlotTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
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" data-djc-id-a1bc42>Default A</p>
|
||||
<p id="b" data-djc-id-a1bc45>Override B</p>
|
||||
""",
|
||||
)
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_both_slots_overridden(self):
|
||||
|
@ -234,7 +246,13 @@ class ConditionalSlotTests(BaseTestCase):
|
|||
"""
|
||||
template = Template(template_str)
|
||||
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" data-djc-id-a1bc44>Override A</p>
|
||||
<p id="b" data-djc-id-a1bc47>Override B</p>
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
class SlotIterationTest(BaseTestCase):
|
||||
|
@ -670,11 +688,11 @@ class ComponentNestingTests(BaseTestCase):
|
|||
self.assertHTMLEqual(
|
||||
rendered,
|
||||
f"""
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc45>
|
||||
<header>Name: {first_name}</header>
|
||||
<main>Day: Monday</main>
|
||||
<footer>
|
||||
<custom-template>
|
||||
<custom-template data-djc-id-a1bc49>
|
||||
<header>Name2: {second_name}</header>
|
||||
<main>Day2: Monday</main>
|
||||
<footer>Default footer</footer>
|
||||
|
@ -699,8 +717,8 @@ class ComponentNestingTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
rendered = template.render(Context({"items": [1, 2, 3]}))
|
||||
expected = f"""
|
||||
<div class="dashboard-component">
|
||||
<div class="calendar-component">
|
||||
<div class="dashboard-component" data-djc-id-a1bc3f>
|
||||
<div class="calendar-component" data-djc-id-a1bc44>
|
||||
<h1>
|
||||
Welcome to your dashboard!
|
||||
</h1>
|
||||
|
@ -734,8 +752,8 @@ class ComponentNestingTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
rendered = template.render(Context({"items": [1, 2, 3]}))
|
||||
expected = f"""
|
||||
<div class="dashboard-component">
|
||||
<div class="calendar-component">
|
||||
<div class="dashboard-component" data-djc-id-a1bc40>
|
||||
<div class="calendar-component" data-djc-id-a1bc45>
|
||||
<h1>
|
||||
Whoa!
|
||||
</h1>
|
||||
|
@ -759,16 +777,17 @@ class ComponentNestingTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
items = [{"value": 1}, {"value": 2}, {"value": 3}]
|
||||
rendered = template.render(Context({"items": items}))
|
||||
|
||||
expected = """
|
||||
ITEMS: [{'value': 1}, {'value': 2}, {'value': 3}]
|
||||
<li>
|
||||
<div> 1 </div>
|
||||
<li data-djc-id-a1bc3f>
|
||||
<div data-djc-id-a1bc41> 1 </div>
|
||||
</li>
|
||||
<li>
|
||||
<div> 2 </div>
|
||||
<li data-djc-id-a1bc3f>
|
||||
<div data-djc-id-a1bc43> 2 </div>
|
||||
</li>
|
||||
<li>
|
||||
<div> 3 </div>
|
||||
<li data-djc-id-a1bc3f>
|
||||
<div data-djc-id-a1bc44> 3 </div>
|
||||
</li>
|
||||
"""
|
||||
self.assertHTMLEqual(rendered, expected)
|
||||
|
@ -790,8 +809,8 @@ class ComponentNestingTests(BaseTestCase):
|
|||
template = Template(template_str)
|
||||
rendered = template.render(Context({"items": [1, 2]}))
|
||||
expected = f"""
|
||||
<div class="dashboard-component">
|
||||
<div class="calendar-component">
|
||||
<div class="dashboard-component" data-djc-id-a1bc40>
|
||||
<div class="calendar-component" data-djc-id-a1bc45>
|
||||
<h1>
|
||||
Hello! Welcome to your dashboard!
|
||||
</h1>
|
||||
|
|
|
@ -176,6 +176,12 @@ def parametrize_context_behavior(cases: List[ContextBehParam], settings: Optiona
|
|||
self._stop_gen_id_patch()
|
||||
self._start_gen_id_patch()
|
||||
|
||||
# Reset template cache
|
||||
from django_components.template import template_cache
|
||||
|
||||
if template_cache: # May be None if the cache was not initialized
|
||||
template_cache.clear()
|
||||
|
||||
case_has_data = not isinstance(case, str)
|
||||
|
||||
if isinstance(case, str):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue