Skip to content

Commit 781c6f6

Browse files
committed
implement element fragment + try to fix tests
1 parent 99219f3 commit 781c6f6

File tree

10 files changed

+115
-53
lines changed

10 files changed

+115
-53
lines changed

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ testpaths = tests
2424
xfail_strict = True
2525
markers =
2626
slow: marks tests as slow (deselect with '-m "not slow"')
27+
python_files = assert_*.py test_*.py
2728

2829
[coverage:report]
2930
fail_under = 100

src/client/packages/idom-client-react/src/components.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export function Layout({ saveUpdateHook, sendEvent, loadImportSource }) {
2020

2121
React.useEffect(() => saveUpdateHook(patchModel), [patchModel]);
2222

23+
if (!Object.keys(model).length) {
24+
return html`<${React.Fragment} />`;
25+
}
26+
2327
return html`
2428
<${LayoutContext.Provider} value=${{ sendEvent, loadImportSource }}>
2529
<${Element} model=${model} />
@@ -28,7 +32,7 @@ export function Layout({ saveUpdateHook, sendEvent, loadImportSource }) {
2832
}
2933

3034
export function Element({ model }) {
31-
if (!model.tagName) {
35+
if (model.error !== undefined) {
3236
if (model.error) {
3337
return html`<pre>${model.error}</pre>`;
3438
} else {
@@ -45,11 +49,19 @@ export function Element({ model }) {
4549

4650
function StandardElement({ model }) {
4751
const layoutContext = React.useContext(LayoutContext);
52+
53+
let type;
54+
if (model.tagName == "") {
55+
type = React.Fragment;
56+
} else {
57+
type = model.tagName;
58+
}
59+
4860
// Use createElement here to avoid warning about variable numbers of children not
4961
// having keys. Warning about this must now be the responsibility of the server
5062
// providing the models instead of the client rendering them.
5163
return React.createElement(
52-
model.tagName,
64+
type,
5365
createElementAttributes(model, layoutContext.sendEvent),
5466
...createElementChildren(
5567
model,

src/idom/core/component.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ def definition_id(self) -> int:
5656
return id(self._func)
5757

5858
def render(self) -> VdomDict:
59-
model = self._func(*self._args, **self._kwargs)
60-
return {"tagName": "div", "children": [model]}
59+
return self._func(*self._args, **self._kwargs)
6160

6261
def __repr__(self) -> str:
6362
sig = inspect.signature(self._func)

src/idom/core/layout.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ def _render_component(
199199
raw_model = component.render()
200200
finally:
201201
life_cycle_hook.unset_current()
202+
203+
# wrap the model in a fragment (i.e. tagName="") to ensure components have
204+
# a separate node in the model state tree. This could be removed if this
205+
# components are given a node in the tree some other way
206+
raw_model = {"tagName": "", "children": [raw_model]}
207+
202208
self._render_model(old_state, new_state, raw_model)
203209
except Exception as error:
204210
logger.exception(f"Failed to render {component}")

temp.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import idom
2+
3+
4+
@idom.component
5+
def Demo():
6+
return idom.vdom("", idom.html.h1("hello"))
7+
8+
9+
idom.run(Demo)

tests/assert_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def assert_same_items(left, right):
2+
"""Check that two unordered sequences are equal (only works if reprs are equal)"""
3+
sorted_left = list(sorted(left, key=repr))
4+
sorted_right = list(sorted(right, key=repr))
5+
assert sorted_left == sorted_right

tests/general_utils.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

tests/test_core/test_dispatcher.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,20 @@ async def recv():
5757
def make_events_and_expected_model():
5858
events = [LayoutEvent(STATIC_EVENT_HANDLER.target, [])] * 4
5959
expected_model = {
60-
"tagName": "div",
61-
"attributes": {"count": 4},
62-
"eventHandlers": {
63-
EVENT_NAME: {
64-
"target": STATIC_EVENT_HANDLER.target,
65-
"preventDefault": False,
66-
"stopPropagation": False,
60+
"tagName": "",
61+
"children": [
62+
{
63+
"tagName": "div",
64+
"attributes": {"count": 4},
65+
"eventHandlers": {
66+
EVENT_NAME: {
67+
"target": STATIC_EVENT_HANDLER.target,
68+
"preventDefault": False,
69+
"stopPropagation": False,
70+
}
71+
},
6772
}
68-
},
73+
],
6974
}
7075
return events, expected_model
7176

tests/test_core/test_hooks.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from idom.core.dispatcher import render_json_patch
88
from idom.core.hooks import LifeCycleHook
99
from idom.testing import HookCatcher, assert_idom_logged
10-
from tests.general_utils import assert_same_items
10+
from tests.assert_utils import assert_same_items
1111

1212

1313
async def test_must_be_rendering_in_layout_to_use_hooks():
@@ -38,21 +38,25 @@ def SimpleStatefulComponent():
3838
assert_same_items(
3939
patch_1.changes,
4040
[
41-
{"op": "add", "path": "/children", "value": ["0"]},
42-
{"op": "add", "path": "/tagName", "value": "div"},
41+
{"op": "add", "path": "/tagName", "value": ""},
42+
{
43+
"op": "add",
44+
"path": "/children",
45+
"value": [{"children": ["0"], "tagName": "div"}],
46+
},
4347
],
4448
)
4549

4650
patch_2 = await render_json_patch(layout)
4751
assert patch_2.path == ""
4852
assert patch_2.changes == [
49-
{"op": "replace", "path": "/children/0", "value": "1"}
53+
{"op": "replace", "path": "/children/0/children/0", "value": "1"}
5054
]
5155

5256
patch_3 = await render_json_patch(layout)
5357
assert patch_3.path == ""
5458
assert patch_3.changes == [
55-
{"op": "replace", "path": "/children/0", "value": "2"}
59+
{"op": "replace", "path": "/children/0/children/0", "value": "2"}
5660
]
5761

5862

tests/test_core/test_layout.py

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
assert_idom_logged,
2121
capture_idom_logs,
2222
)
23-
from tests.general_utils import assert_same_items
23+
from tests.assert_utils import assert_same_items
2424

2525

2626
@pytest.fixture(autouse=True)
@@ -79,13 +79,21 @@ def SimpleComponent():
7979
path, changes = await render_json_patch(layout)
8080

8181
assert path == ""
82-
assert changes == [{"op": "add", "path": "/tagName", "value": "div"}]
82+
assert_same_items(
83+
changes,
84+
[
85+
{"op": "add", "path": "/children", "value": [{"tagName": "div"}]},
86+
{"op": "add", "path": "/tagName", "value": ""},
87+
],
88+
)
8389

8490
set_state_hook.current("table")
8591
path, changes = await render_json_patch(layout)
8692

8793
assert path == ""
88-
assert changes == [{"op": "replace", "path": "/tagName", "value": "table"}]
94+
assert changes == [
95+
{"op": "replace", "path": "/children/0/tagName", "value": "table"}
96+
]
8997

9098

9199
async def test_nested_component_layout():
@@ -112,23 +120,38 @@ def Child():
112120
{
113121
"op": "add",
114122
"path": "/children",
115-
"value": ["0", {"tagName": "div", "children": ["0"]}],
123+
"value": [
124+
{
125+
"children": [
126+
"0",
127+
{
128+
"children": [{"children": ["0"], "tagName": "div"}],
129+
"tagName": "",
130+
},
131+
],
132+
"tagName": "div",
133+
}
134+
],
116135
},
117-
{"op": "add", "path": "/tagName", "value": "div"},
136+
{"op": "add", "path": "/tagName", "value": ""},
118137
],
119138
)
120139

121140
parent_set_state.current(1)
122141
path, changes = await render_json_patch(layout)
123142

124143
assert path == ""
125-
assert changes == [{"op": "replace", "path": "/children/0", "value": "1"}]
144+
assert changes == [
145+
{"op": "replace", "path": "/children/0/children/0", "value": "1"}
146+
]
126147

127148
child_set_state.current(1)
128149
path, changes = await render_json_patch(layout)
129150

130151
assert path == "/children/1"
131-
assert changes == [{"op": "replace", "path": "/children/0", "value": "1"}]
152+
assert changes == [
153+
{"op": "replace", "path": "/children/0/children/0", "value": "1"}
154+
]
132155

133156

134157
@pytest.mark.skipif(
@@ -202,16 +225,21 @@ def BadChild():
202225
assert_same_items(
203226
patch.changes,
204227
[
228+
{"op": "add", "path": "/tagName", "value": ""},
205229
{
206230
"op": "add",
207231
"path": "/children",
208232
"value": [
209-
{"tagName": "div", "children": ["hello"]},
210-
{"tagName": "", "error": ""},
211-
{"tagName": "div", "children": ["hello"]},
233+
{
234+
"children": [
235+
{"children": [...], "tagName": ""},
236+
{"error": "", "tagName": ""},
237+
{"children": [...], "tagName": ""},
238+
],
239+
"tagName": "div",
240+
}
212241
],
213242
},
214-
{"op": "add", "path": "/tagName", "value": "div"},
215243
],
216244
)
217245

@@ -233,9 +261,24 @@ def Child():
233261
{
234262
"op": "add",
235263
"path": "/children",
236-
"value": [{"tagName": "div", "children": [{"tagName": "h1"}]}],
264+
"value": [
265+
{
266+
"children": [
267+
{
268+
"children": [
269+
{
270+
"children": [{"tagName": "h1"}],
271+
"tagName": "div",
272+
}
273+
],
274+
"tagName": "",
275+
}
276+
],
277+
"tagName": "div",
278+
}
279+
],
237280
},
238-
{"op": "add", "path": "/tagName", "value": "div"},
281+
{"op": "add", "path": "/tagName", "value": ""},
239282
],
240283
)
241284

@@ -422,7 +465,7 @@ def Child():
422465
hook.latest.schedule_render()
423466

424467
update = await render_json_patch(layout)
425-
assert update.path == "/children/0/children/0"
468+
assert update.path == "/children/0/children/0/children/0"
426469

427470

428471
async def test_log_on_dispatch_to_missing_event_handler(caplog):

0 commit comments

Comments
 (0)