Skip to content

Commit 6d63e97

Browse files
committed
Add support for ComponentType children in vdom_to_html
1 parent a54ce4e commit 6d63e97

File tree

2 files changed

+43
-27
lines changed

2 files changed

+43
-27
lines changed

src/reactpy/utils.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from lxml import etree
99
from lxml.html import fromstring, tostring
1010

11-
from reactpy.core.types import VdomDict
11+
from reactpy.core.types import ComponentType, VdomDict
1212
from reactpy.core.vdom import vdom
1313

1414
_RefValue = TypeVar("_RefValue")
@@ -156,42 +156,42 @@ def _etree_to_vdom(
156156
return el
157157

158158

159-
def _add_vdom_to_etree(parent: etree._Element, vdom: VdomDict | dict[str, Any]) -> None:
159+
def _add_vdom_to_etree(parent: etree._Element, node: VdomDict | dict[str, Any]) -> None:
160160
try:
161-
tag = vdom["tagName"]
161+
tag = node["tagName"]
162162
except KeyError as e:
163-
msg = f"Expected a VDOM dict, not {vdom}"
163+
msg = f"Expected a VDOM dict, not {type(node)}"
164164
raise TypeError(msg) from e
165165
else:
166-
vdom = cast(VdomDict, vdom)
166+
node = cast(VdomDict, node)
167167

168168
if tag:
169169
element = etree.SubElement(parent, tag)
170170
element.attrib.update(
171-
_vdom_attr_to_html_str(k, v) for k, v in vdom.get("attributes", {}).items()
171+
_vdom_attr_to_html_str(k, v) for k, v in node.get("attributes", {}).items()
172172
)
173173
else:
174174
element = parent
175175

176-
for c in vdom.get("children", []):
176+
for c in node.get("children", []):
177+
if hasattr(c, "render"):
178+
c = _component_to_vdom(cast(ComponentType, c))
177179
if isinstance(c, dict):
178180
_add_vdom_to_etree(element, c)
179181
else:
180-
"""
181-
LXML handles string children by storing them under `text` and `tail`
182-
attributes of Element objects. The `text` attribute, if present, effectively
183-
becomes that element's first child. Then the `tail` attribute, if present,
184-
becomes a sibling that follows that element. For example, consider the
185-
following HTML:
186-
187-
<p><a>hello</a>world</p>
188-
189-
In this code sample, "hello" is the `text` attribute of the `<a>` element
190-
and "world" is the `tail` attribute of that same `<a>` element. It's for
191-
this reason that, depending on whether the element being constructed has
192-
non-string a child element, we need to assign a `text` vs `tail` attribute
193-
to that element or the last non-string child respectively.
194-
"""
182+
# LXML handles string children by storing them under `text` and `tail`
183+
# attributes of Element objects. The `text` attribute, if present, effectively
184+
# becomes that element's first child. Then the `tail` attribute, if present,
185+
# becomes a sibling that follows that element. For example, consider the
186+
# following HTML:
187+
188+
# <p><a>hello</a>world</p>
189+
190+
# In this code sample, "hello" is the `text` attribute of the `<a>` element
191+
# and "world" is the `tail` attribute of that same `<a>` element. It's for
192+
# this reason that, depending on whether the element being constructed has
193+
# non-string a child element, we need to assign a `text` vs `tail` attribute
194+
# to that element or the last non-string child respectively.
195195
if len(element):
196196
last_child = element[-1]
197197
last_child.tail = f"{last_child.tail or ''}{c}"
@@ -249,6 +249,14 @@ def _generate_vdom_children(
249249
)
250250

251251

252+
def _component_to_vdom(component: ComponentType) -> VdomDict | str | None:
253+
"""Convert a component to a VDOM dictionary"""
254+
result = component.render()
255+
if hasattr(result, "render"):
256+
result = _component_to_vdom(cast(ComponentType, result))
257+
return cast(VdomDict, result)
258+
259+
252260
def del_html_head_body_transform(vdom: VdomDict) -> VdomDict:
253261
"""Transform intended for use with `html_to_vdom`.
254262

tests/test_utils.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44

55
import reactpy
6-
from reactpy import html
6+
from reactpy import component, html
77
from reactpy.utils import (
88
HTMLParseError,
99
del_html_head_body_transform,
@@ -193,6 +193,16 @@ def test_del_html_body_transform():
193193
SOME_OBJECT = object()
194194

195195

196+
@component
197+
def example_parent():
198+
return html.div({"id": "sample", "style": {"padding": "15px"}}, example_child())
199+
200+
201+
@component
202+
def example_child():
203+
return html.h1("Sample Application")
204+
205+
196206
@pytest.mark.parametrize(
197207
"vdom_in, html_out",
198208
[
@@ -254,10 +264,8 @@ def test_del_html_body_transform():
254264
'<div data-something="1" data-something-else="2" dataisnotdashed="3"></div>',
255265
),
256266
(
257-
html.div(
258-
{"dataSomething": 1, "dataSomethingElse": 2, "dataisnotdashed": 3}
259-
),
260-
'<div data-something="1" data-something-else="2" dataisnotdashed="3"></div>',
267+
html.div(example_parent()),
268+
'<div><div id="sample" style="padding:15px"><h1>Sample Application</h1></div></div>',
261269
),
262270
],
263271
)

0 commit comments

Comments
 (0)