From 26cfd5a4e6821a1b870b85f942541dcde315dc8e Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Fri, 22 Nov 2019 13:45:59 -0500 Subject: [PATCH 1/5] give test_base_component tests standard ids --- tests/unit/development/test_base_component.py | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/unit/development/test_base_component.py b/tests/unit/development/test_base_component.py index a2984b65d4..ac15f4ccc2 100644 --- a/tests/unit/development/test_base_component.py +++ b/tests/unit/development/test_base_component.py @@ -36,23 +36,23 @@ def nested_tree(): return c, c1, c2, c3, c4, c5 -def test_init(): +def test_debc001_init(): Component(a=3) -def test_get_item_with_children(): +def test_debc002_get_item_with_children(): c1 = Component(id="1") c2 = Component(children=[c1]) assert c2["1"] == c1 -def test_get_item_with_children_as_component_instead_of_list(): +def test_debc003_get_item_with_children_as_component_instead_of_list(): c1 = Component(id="1") c2 = Component(id="2", children=c1) assert c2["1"] == c1 -def test_get_item_with_nested_children_one_branch(): +def test_debc004_get_item_with_nested_children_one_branch(): c1 = Component(id="1") c2 = Component(id="2", children=[c1]) c3 = Component(children=[c2]) @@ -61,7 +61,7 @@ def test_get_item_with_nested_children_one_branch(): assert c3["1"] == c1 -def test_get_item_with_nested_children_two_branches(): +def test_debc005_get_item_with_nested_children_two_branches(): c1 = Component(id="1") c2 = Component(id="2", children=[c1]) c3 = Component(id="3") @@ -75,7 +75,7 @@ def test_get_item_with_nested_children_two_branches(): assert c5["3"] == c3 -def test_get_item_with_nested_children_with_mixed_strings_and_without_lists(): +def test_debc006_get_item_with_full_tree(): c, c1, c2, c3, c4, c5 = nested_tree() keys = [k for k in c] @@ -90,7 +90,7 @@ def test_get_item_with_nested_children_with_mixed_strings_and_without_lists(): c["x"] -def test_len_with_nested_children_with_mixed_strings_and_without_lists(): +def test_debc007_len_with_full_tree(): c = nested_tree()[0] assert ( len(c) == 5 + 5 + 1 @@ -98,7 +98,7 @@ def test_len_with_nested_children_with_mixed_strings_and_without_lists(): components, 2 strings + 2 numbers + none in c2, and 1 string in c1" -def test_set_item_with_nested_children_with_mixed_strings_and_without_lists(): +def test_debc008_set_item_anywhere_in_tree(): keys = ["0.0", "0.1", "0.1.x", "0.1.x.x", "0.1.x.x.0"] c = nested_tree()[0] @@ -110,7 +110,7 @@ def test_set_item_with_nested_children_with_mixed_strings_and_without_lists(): assert c[new_id] == new_component -def test_del_item_with_nested_children_with_mixed_strings_and_without_lists(): +def test_debc009_del_item_full_tree(): c = nested_tree()[0] keys = reversed([k for k in c]) for key in keys: @@ -120,13 +120,13 @@ def test_del_item_with_nested_children_with_mixed_strings_and_without_lists(): c[key] -def test_traverse_with_nested_children_with_mixed_strings_and_without_lists(): +def test_debc010_traverse_full_tree(): c, c1, c2, c3, c4, c5 = nested_tree() elements = [i for i in c._traverse()] assert elements == c.children + [c3] + [c2] + c2.children -def test_traverse_with_tuples(): +def test_debc011_traverse_with_tuples(): c, c1, c2, c3, c4, c5 = nested_tree() c2.children = tuple(c2.children) c.children = tuple(c.children) @@ -134,7 +134,7 @@ def test_traverse_with_tuples(): assert elements == list(c.children) + [c3] + [c2] + list(c2.children) -def test_to_plotly_json_with_nested_children_with_mixed_strings_and_without_lists(): +def test_debc012_to_plotly_json_full_tree(): c = nested_tree()[0] Component._namespace Component._type @@ -194,7 +194,7 @@ def test_to_plotly_json_with_nested_children_with_mixed_strings_and_without_list assert res == expected -def test_get_item_raises_key_if_id_doesnt_exist(): +def test_debc013_get_item_raises_key_if_id_doesnt_exist(): c = Component() with pytest.raises(KeyError): c["1"] @@ -212,7 +212,7 @@ def test_get_item_raises_key_if_id_doesnt_exist(): c3["0"] -def test_set_item(): +def test_debc014_set_item(): c1a = Component(id="1", children="Hello world") c2 = Component(id="2", children=c1a) assert c2["1"] == c1a @@ -222,7 +222,7 @@ def test_set_item(): assert c2["1"] == c1b -def test_set_item_with_children_as_list(): +def test_debc015_set_item_with_children_as_list(): c1 = Component(id="1") c2 = Component(id="2", children=[c1]) assert c2["1"] == c1 @@ -231,7 +231,7 @@ def test_set_item_with_children_as_list(): assert c2["3"] == c3 -def test_set_item_with_nested_children(): +def test_debc016_set_item_with_nested_children(): c1 = Component(id="1") c2 = Component(id="2", children=[c1]) c3 = Component(id="3") @@ -256,14 +256,14 @@ def test_set_item_with_nested_children(): c5["1"] -def test_set_item_raises_key_error(): +def test_debc017_set_item_raises_key_error(): c1 = Component(id="1") c2 = Component(id="2", children=[c1]) with pytest.raises(KeyError): c2["3"] = Component(id="3") -def test_del_item_from_list(): +def test_debc018_del_item_from_list(): c1 = Component(id="1") c2 = Component(id="2") c3 = Component(id="3", children=[c1, c2]) @@ -280,7 +280,7 @@ def test_del_item_from_list(): assert c3.children == [] -def test_del_item_from_class(): +def test_debc019_del_item_from_class(): c1 = Component(id="1") c2 = Component(id="2", children=c1) assert c2["1"] == c1 @@ -291,7 +291,7 @@ def test_del_item_from_class(): assert c2.children is None -def test_to_plotly_json_without_children(): +def test_debc020_to_plotly_json_without_children(): c = Component(id="a") c._prop_names = ("id",) c._type = "MyComponent" @@ -303,7 +303,7 @@ def test_to_plotly_json_without_children(): } -def test_to_plotly_json_with_null_arguments(): +def test_debc021_to_plotly_json_with_null_arguments(): c = Component(id="a") c._prop_names = ("id", "style") c._type = "MyComponent" @@ -325,7 +325,7 @@ def test_to_plotly_json_with_null_arguments(): } -def test_to_plotly_json_with_children(): +def test_debc022_to_plotly_json_with_children(): c = Component(id="a", children="Hello World") c._prop_names = ("id", "children") c._type = "MyComponent" @@ -341,7 +341,7 @@ def test_to_plotly_json_with_children(): } -def test_to_plotly_json_with_wildcards(): +def test_debc023_to_plotly_json_with_wildcards(): c = Component( id="a", **{"aria-expanded": "true", "data-toggle": "toggled", "data-none": None} ) @@ -360,7 +360,7 @@ def test_to_plotly_json_with_wildcards(): } -def test_len(): +def test_debc024_len(): assert len(Component()) == 0 assert len(Component(children="Hello World")) == 1 assert len(Component(children=Component())) == 1 @@ -368,7 +368,7 @@ def test_len(): assert len(Component(children=[Component(children=Component()), Component()])) == 3 -def test_iter(): +def test_debc025_iter(): # The mixin methods from MutableMapping were cute but probably never # used - at least not by us. Test that they're gone From 3d0d06ece64e4f6e1d714e722074dd32e2826e84 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Fri, 22 Nov 2019 15:24:03 -0500 Subject: [PATCH 2/5] check for components as args not in children ie if you forget to make children an array, and just put it flat in component args --- dash/development/base_component.py | 8 ++++++++ tests/unit/development/test_base_component.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/dash/development/base_component.py b/dash/development/base_component.py index 070c4adc90..4eb57810d9 100644 --- a/dash/development/base_component.py +++ b/dash/development/base_component.py @@ -94,6 +94,14 @@ def __init__(self, **kwargs): ", ".join(sorted(self._prop_names)) ) ) + + if k != "children" and isinstance(v, Component): + raise TypeError( + "Component detected as a prop other than `children`\n" + + "Did you forget to wrap multiple `children` in an array?" + + "prop {} has value {}\n".format(k, repr(v)) + ) + setattr(self, k, v) def to_plotly_json(self): diff --git a/tests/unit/development/test_base_component.py b/tests/unit/development/test_base_component.py index ac15f4ccc2..6da20789e3 100644 --- a/tests/unit/development/test_base_component.py +++ b/tests/unit/development/test_base_component.py @@ -4,6 +4,7 @@ import pytest from dash.development.base_component import Component +import dash_html_components as html Component._prop_names = ("id", "a", "children", "style") Component._type = "TestComponent" @@ -418,3 +419,19 @@ def test_debc025_iter(): assert k in keys, "iteration produces key " + k assert len(keys) == len(keys2), "iteration produces no extra keys" + + +def test_debc026_component_not_children(): + children = [Component(id='a'), html.Div(id='b'), 'c', 1] + for i in range(len(children)): + # cycle through each component in each position + children = children[1:] + [children[0]] + + # use html.Div because only real components accept positional args + html.Div(children) + # the first arg is children, and a single component works there + html.Div(children[0], id='x') + + with pytest.raises(TypeError): + # If you forget the `[]` around children you get this: + html.Div(children[0], children[1], children[2], children[3]) From 53a82b8eefa2670823ea22f099753dde9561efeb Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Fri, 22 Nov 2019 15:54:20 -0500 Subject: [PATCH 3/5] changelog for children array error message --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c01162d74e..30d55a8fcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). clientside JavaScript callbacks via inline strings. - [#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. +### Changed +- [#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. + ### Fixed - [#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. From 4d96e0dbf3c96ec19683c4e68c9e350302cb6da8 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 26 Nov 2019 12:02:33 -0500 Subject: [PATCH 4/5] new line, new caps in children array error message --- dash/development/base_component.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dash/development/base_component.py b/dash/development/base_component.py index 4eb57810d9..3318a76d86 100644 --- a/dash/development/base_component.py +++ b/dash/development/base_component.py @@ -98,8 +98,8 @@ def __init__(self, **kwargs): if k != "children" and isinstance(v, Component): raise TypeError( "Component detected as a prop other than `children`\n" + - "Did you forget to wrap multiple `children` in an array?" + - "prop {} has value {}\n".format(k, repr(v)) + "Did you forget to wrap multiple `children` in an array?\n" + + "Prop {} has value {}\n".format(k, repr(v)) ) setattr(self, k, v) From 637042e45a381b536316660beca78b15ec10265f Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 26 Nov 2019 21:18:08 -0500 Subject: [PATCH 5/5] wait for graph in devtools test --- tests/integration/devtools/test_devtools_error_handling.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/devtools/test_devtools_error_handling.py b/tests/integration/devtools/test_devtools_error_handling.py index e62fe31077..167ae4ee5b 100644 --- a/tests/integration/devtools/test_devtools_error_handling.py +++ b/tests/integration/devtools/test_devtools_error_handling.py @@ -186,6 +186,8 @@ def update_output(n_clicks): dev_tools_hot_reload=False, ) + dash_duo.wait_for_element('.js-plotly-plot .main-svg') + dash_duo.find_element("#button").click() dash_duo.wait_for_text_to_equal(dash_duo.devtools_error_count_locator, "1") dash_duo.percy_snapshot("devtools - validation exception - closed")