Skip to content

Commit ffffce0

Browse files
authored
Merge pull request #1026 from plotly/children-array-error
Children array error
2 parents b232b7a + 637042e commit ffffce0

File tree

4 files changed

+55
-25
lines changed

4 files changed

+55
-25
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
88
clientside JavaScript callbacks via inline strings.
99
- [#1020](https://github.com/plotly/dash/pull/1020) Allow `visit_and_snapshot` API in `dash.testing.browser` to stay on the page so you can run other checks.
1010

11+
### Changed
12+
- [#1026](https://github.com/plotly/dash/pull/1026) Better error message when you forget to wrap multiple `children` in an array, and they get passed to other props.
13+
1114
### Fixed
1215
- [#1018](https://github.com/plotly/dash/pull/1006) Fix the `dash.testing` **stop** API with process application runner in Python2. Use `kill()` instead of `communicate()` to avoid hanging.
1316
- [#1027](https://github.com/plotly/dash/pull/1027) Fix bug with renderer callback lock never resolving with non-rendered async component using the asyncDecorator

dash/development/base_component.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ def __init__(self, **kwargs):
9494
", ".join(sorted(self._prop_names))
9595
)
9696
)
97+
98+
if k != "children" and isinstance(v, Component):
99+
raise TypeError(
100+
"Component detected as a prop other than `children`\n" +
101+
"Did you forget to wrap multiple `children` in an array?\n" +
102+
"Prop {} has value {}\n".format(k, repr(v))
103+
)
104+
97105
setattr(self, k, v)
98106

99107
def to_plotly_json(self):

tests/integration/devtools/test_devtools_error_handling.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ def update_output(n_clicks):
186186
dev_tools_hot_reload=False,
187187
)
188188

189+
dash_duo.wait_for_element('.js-plotly-plot .main-svg')
190+
189191
dash_duo.find_element("#button").click()
190192
dash_duo.wait_for_text_to_equal(dash_duo.devtools_error_count_locator, "1")
191193
dash_duo.percy_snapshot("devtools - validation exception - closed")

tests/unit/development/test_base_component.py

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
from dash.development.base_component import Component
7+
import dash_html_components as html
78

89
Component._prop_names = ("id", "a", "children", "style")
910
Component._type = "TestComponent"
@@ -36,23 +37,23 @@ def nested_tree():
3637
return c, c1, c2, c3, c4, c5
3738

3839

39-
def test_init():
40+
def test_debc001_init():
4041
Component(a=3)
4142

4243

43-
def test_get_item_with_children():
44+
def test_debc002_get_item_with_children():
4445
c1 = Component(id="1")
4546
c2 = Component(children=[c1])
4647
assert c2["1"] == c1
4748

4849

49-
def test_get_item_with_children_as_component_instead_of_list():
50+
def test_debc003_get_item_with_children_as_component_instead_of_list():
5051
c1 = Component(id="1")
5152
c2 = Component(id="2", children=c1)
5253
assert c2["1"] == c1
5354

5455

55-
def test_get_item_with_nested_children_one_branch():
56+
def test_debc004_get_item_with_nested_children_one_branch():
5657
c1 = Component(id="1")
5758
c2 = Component(id="2", children=[c1])
5859
c3 = Component(children=[c2])
@@ -61,7 +62,7 @@ def test_get_item_with_nested_children_one_branch():
6162
assert c3["1"] == c1
6263

6364

64-
def test_get_item_with_nested_children_two_branches():
65+
def test_debc005_get_item_with_nested_children_two_branches():
6566
c1 = Component(id="1")
6667
c2 = Component(id="2", children=[c1])
6768
c3 = Component(id="3")
@@ -75,7 +76,7 @@ def test_get_item_with_nested_children_two_branches():
7576
assert c5["3"] == c3
7677

7778

78-
def test_get_item_with_nested_children_with_mixed_strings_and_without_lists():
79+
def test_debc006_get_item_with_full_tree():
7980
c, c1, c2, c3, c4, c5 = nested_tree()
8081
keys = [k for k in c]
8182

@@ -90,15 +91,15 @@ def test_get_item_with_nested_children_with_mixed_strings_and_without_lists():
9091
c["x"]
9192

9293

93-
def test_len_with_nested_children_with_mixed_strings_and_without_lists():
94+
def test_debc007_len_with_full_tree():
9495
c = nested_tree()[0]
9596
assert (
9697
len(c) == 5 + 5 + 1
9798
), "the length of the nested children should match the total of 5 \
9899
components, 2 strings + 2 numbers + none in c2, and 1 string in c1"
99100

100101

101-
def test_set_item_with_nested_children_with_mixed_strings_and_without_lists():
102+
def test_debc008_set_item_anywhere_in_tree():
102103
keys = ["0.0", "0.1", "0.1.x", "0.1.x.x", "0.1.x.x.0"]
103104
c = nested_tree()[0]
104105

@@ -110,7 +111,7 @@ def test_set_item_with_nested_children_with_mixed_strings_and_without_lists():
110111
assert c[new_id] == new_component
111112

112113

113-
def test_del_item_with_nested_children_with_mixed_strings_and_without_lists():
114+
def test_debc009_del_item_full_tree():
114115
c = nested_tree()[0]
115116
keys = reversed([k for k in c])
116117
for key in keys:
@@ -120,21 +121,21 @@ def test_del_item_with_nested_children_with_mixed_strings_and_without_lists():
120121
c[key]
121122

122123

123-
def test_traverse_with_nested_children_with_mixed_strings_and_without_lists():
124+
def test_debc010_traverse_full_tree():
124125
c, c1, c2, c3, c4, c5 = nested_tree()
125126
elements = [i for i in c._traverse()]
126127
assert elements == c.children + [c3] + [c2] + c2.children
127128

128129

129-
def test_traverse_with_tuples():
130+
def test_debc011_traverse_with_tuples():
130131
c, c1, c2, c3, c4, c5 = nested_tree()
131132
c2.children = tuple(c2.children)
132133
c.children = tuple(c.children)
133134
elements = [i for i in c._traverse()]
134135
assert elements == list(c.children) + [c3] + [c2] + list(c2.children)
135136

136137

137-
def test_to_plotly_json_with_nested_children_with_mixed_strings_and_without_lists():
138+
def test_debc012_to_plotly_json_full_tree():
138139
c = nested_tree()[0]
139140
Component._namespace
140141
Component._type
@@ -194,7 +195,7 @@ def test_to_plotly_json_with_nested_children_with_mixed_strings_and_without_list
194195
assert res == expected
195196

196197

197-
def test_get_item_raises_key_if_id_doesnt_exist():
198+
def test_debc013_get_item_raises_key_if_id_doesnt_exist():
198199
c = Component()
199200
with pytest.raises(KeyError):
200201
c["1"]
@@ -212,7 +213,7 @@ def test_get_item_raises_key_if_id_doesnt_exist():
212213
c3["0"]
213214

214215

215-
def test_set_item():
216+
def test_debc014_set_item():
216217
c1a = Component(id="1", children="Hello world")
217218
c2 = Component(id="2", children=c1a)
218219
assert c2["1"] == c1a
@@ -222,7 +223,7 @@ def test_set_item():
222223
assert c2["1"] == c1b
223224

224225

225-
def test_set_item_with_children_as_list():
226+
def test_debc015_set_item_with_children_as_list():
226227
c1 = Component(id="1")
227228
c2 = Component(id="2", children=[c1])
228229
assert c2["1"] == c1
@@ -231,7 +232,7 @@ def test_set_item_with_children_as_list():
231232
assert c2["3"] == c3
232233

233234

234-
def test_set_item_with_nested_children():
235+
def test_debc016_set_item_with_nested_children():
235236
c1 = Component(id="1")
236237
c2 = Component(id="2", children=[c1])
237238
c3 = Component(id="3")
@@ -256,14 +257,14 @@ def test_set_item_with_nested_children():
256257
c5["1"]
257258

258259

259-
def test_set_item_raises_key_error():
260+
def test_debc017_set_item_raises_key_error():
260261
c1 = Component(id="1")
261262
c2 = Component(id="2", children=[c1])
262263
with pytest.raises(KeyError):
263264
c2["3"] = Component(id="3")
264265

265266

266-
def test_del_item_from_list():
267+
def test_debc018_del_item_from_list():
267268
c1 = Component(id="1")
268269
c2 = Component(id="2")
269270
c3 = Component(id="3", children=[c1, c2])
@@ -280,7 +281,7 @@ def test_del_item_from_list():
280281
assert c3.children == []
281282

282283

283-
def test_del_item_from_class():
284+
def test_debc019_del_item_from_class():
284285
c1 = Component(id="1")
285286
c2 = Component(id="2", children=c1)
286287
assert c2["1"] == c1
@@ -291,7 +292,7 @@ def test_del_item_from_class():
291292
assert c2.children is None
292293

293294

294-
def test_to_plotly_json_without_children():
295+
def test_debc020_to_plotly_json_without_children():
295296
c = Component(id="a")
296297
c._prop_names = ("id",)
297298
c._type = "MyComponent"
@@ -303,7 +304,7 @@ def test_to_plotly_json_without_children():
303304
}
304305

305306

306-
def test_to_plotly_json_with_null_arguments():
307+
def test_debc021_to_plotly_json_with_null_arguments():
307308
c = Component(id="a")
308309
c._prop_names = ("id", "style")
309310
c._type = "MyComponent"
@@ -325,7 +326,7 @@ def test_to_plotly_json_with_null_arguments():
325326
}
326327

327328

328-
def test_to_plotly_json_with_children():
329+
def test_debc022_to_plotly_json_with_children():
329330
c = Component(id="a", children="Hello World")
330331
c._prop_names = ("id", "children")
331332
c._type = "MyComponent"
@@ -341,7 +342,7 @@ def test_to_plotly_json_with_children():
341342
}
342343

343344

344-
def test_to_plotly_json_with_wildcards():
345+
def test_debc023_to_plotly_json_with_wildcards():
345346
c = Component(
346347
id="a", **{"aria-expanded": "true", "data-toggle": "toggled", "data-none": None}
347348
)
@@ -360,15 +361,15 @@ def test_to_plotly_json_with_wildcards():
360361
}
361362

362363

363-
def test_len():
364+
def test_debc024_len():
364365
assert len(Component()) == 0
365366
assert len(Component(children="Hello World")) == 1
366367
assert len(Component(children=Component())) == 1
367368
assert len(Component(children=[Component(), Component()])) == 2
368369
assert len(Component(children=[Component(children=Component()), Component()])) == 3
369370

370371

371-
def test_iter():
372+
def test_debc025_iter():
372373
# The mixin methods from MutableMapping were cute but probably never
373374
# used - at least not by us. Test that they're gone
374375

@@ -418,3 +419,19 @@ def test_iter():
418419
assert k in keys, "iteration produces key " + k
419420

420421
assert len(keys) == len(keys2), "iteration produces no extra keys"
422+
423+
424+
def test_debc026_component_not_children():
425+
children = [Component(id='a'), html.Div(id='b'), 'c', 1]
426+
for i in range(len(children)):
427+
# cycle through each component in each position
428+
children = children[1:] + [children[0]]
429+
430+
# use html.Div because only real components accept positional args
431+
html.Div(children)
432+
# the first arg is children, and a single component works there
433+
html.Div(children[0], id='x')
434+
435+
with pytest.raises(TypeError):
436+
# If you forget the `[]` around children you get this:
437+
html.Div(children[0], children[1], children[2], children[3])

0 commit comments

Comments
 (0)