From 8a1d3f2014d75f210ca3067e75e9075fd6c94f3c Mon Sep 17 00:00:00 2001 From: Chris P Date: Fri, 19 Apr 2019 17:17:40 -0400 Subject: [PATCH 1/7] provide valid setProps for all components, even if they don't have an ID construct paths on-the-fly while recursing through the tree. --- src/APIController.react.js | 4 ++-- src/TreeContainer.js | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/APIController.react.js b/src/APIController.react.js index 165044a..89d622a 100644 --- a/src/APIController.react.js +++ b/src/APIController.react.js @@ -119,11 +119,11 @@ class UnconnectedContainer extends Component { ) { return ( - + ); } else if (appLifecycle === getAppState('HYDRATED')) { - return ; + return ; } return
{'Loading...'}
; diff --git a/src/TreeContainer.js b/src/TreeContainer.js index 900a761..b156cf9 100644 --- a/src/TreeContainer.js +++ b/src/TreeContainer.js @@ -4,7 +4,9 @@ import Registry from './registry'; import {propTypeErrorHandler} from './exceptions'; import {connect} from 'react-redux'; import { + addIndex, any, + concat, contains, filter, forEach, @@ -15,6 +17,7 @@ import { map, mergeAll, omit, + path, pick, propOr, type @@ -50,11 +53,12 @@ function validateComponent(componentDefinition) { } } -const createContainer = component => isSimpleComponent(component) ? +const createContainer = (component, path) => isSimpleComponent(component) ? component : (); function CheckedComponent(p) { @@ -87,14 +91,16 @@ CheckedComponent.propTypes = { id: PropTypes.string, }; class TreeContainer extends Component { - getChildren(components) { + getChildren(components, path) { if (isNil(components)) { return null; } return Array.isArray(components) ? - map(createContainer, components) : - createContainer(components); + addIndex(map)( + (component, i) => createContainer(component, concat(path, ['props', 'children', i])), + components + ) : createContainer(components, concat(path, ['props', 'children'])); } getComponent(_dashprivate_layout, children, loading_state, setProps) { @@ -148,7 +154,7 @@ class TreeContainer extends Component { const { _dashprivate_dependencies, _dashprivate_dispatch, - _dashprivate_paths + path } = this.props; const id = this.getLayoutProps().id; @@ -165,8 +171,7 @@ class TreeContainer extends Component { // Always update this component's props _dashprivate_dispatch(updateProps({ props: newProps, - id: id, - itempath: _dashprivate_paths[id] + itempath: path })); // Only dispatch changes to Dash if a watched prop changed @@ -194,12 +199,13 @@ class TreeContainer extends Component { const { _dashprivate_dispatch, _dashprivate_layout, - _dashprivate_loadingState + _dashprivate_loadingState, + path } = this.props; const layoutProps = this.getLayoutProps(); - const children = this.getChildren(layoutProps.children); + const children = this.getChildren(layoutProps.children, path); const setProps = this.getSetProps(_dashprivate_dispatch); return this.getComponent(_dashprivate_layout, children, _dashprivate_loadingState, setProps); @@ -295,6 +301,7 @@ export const AugmentedTreeContainer = connect( _dashprivate_dependencies: stateProps.dependencies, _dashprivate_dispatch: dispatchProps.dispatch, _dashprivate_layout: ownProps._dashprivate_layout, + path: ownProps.path, _dashprivate_loadingState: getLoadingState(ownProps._dashprivate_layout, stateProps.requestQueue), _dashprivate_paths: stateProps.paths, _dashprivate_requestQueue: stateProps.requestQueue, From e7d3fabff0d9c30818ccd55ba81f80649d72227b Mon Sep 17 00:00:00 2001 From: Chris P Date: Fri, 19 Apr 2019 17:30:34 -0400 Subject: [PATCH 2/7] keep `path` private & remove `store.paths` from component --- src/APIController.react.js | 12 ++++++++++-- src/TreeContainer.js | 17 +++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/APIController.react.js b/src/APIController.react.js index 89d622a..ceb4d87 100644 --- a/src/APIController.react.js +++ b/src/APIController.react.js @@ -119,11 +119,19 @@ class UnconnectedContainer extends Component { ) { return ( - + ); } else if (appLifecycle === getAppState('HYDRATED')) { - return ; + return ( + + ); } return
{'Loading...'}
; diff --git a/src/TreeContainer.js b/src/TreeContainer.js index b156cf9..7f6e44a 100644 --- a/src/TreeContainer.js +++ b/src/TreeContainer.js @@ -17,7 +17,6 @@ import { map, mergeAll, omit, - path, pick, propOr, type @@ -58,7 +57,7 @@ const createContainer = (component, path) => isSimpleComponent(component) ? (); function CheckedComponent(p) { @@ -154,7 +153,7 @@ class TreeContainer extends Component { const { _dashprivate_dependencies, _dashprivate_dispatch, - path + _dashprivate_path } = this.props; const id = this.getLayoutProps().id; @@ -171,7 +170,7 @@ class TreeContainer extends Component { // Always update this component's props _dashprivate_dispatch(updateProps({ props: newProps, - itempath: path + itempath: _dashprivate_path })); // Only dispatch changes to Dash if a watched prop changed @@ -200,12 +199,12 @@ class TreeContainer extends Component { _dashprivate_dispatch, _dashprivate_layout, _dashprivate_loadingState, - path + _dashprivate_path } = this.props; const layoutProps = this.getLayoutProps(); - const children = this.getChildren(layoutProps.children, path); + const children = this.getChildren(layoutProps.children, _dashprivate_path); const setProps = this.getSetProps(_dashprivate_dispatch); return this.getComponent(_dashprivate_layout, children, _dashprivate_loadingState, setProps); @@ -217,9 +216,9 @@ TreeContainer.propTypes = { _dashprivate_dispatch: PropTypes.func, _dashprivate_layout: PropTypes.object, _dashprivate_loadingState: PropTypes.object, - _dashprivate_paths: PropTypes.any, _dashprivate_requestQueue: PropTypes.any, _dashprivate_config: PropTypes.object, + _dashprivate_path: PropTypes.array, }; function isLoadingComponent(layout) { @@ -292,7 +291,6 @@ function getLoadingState(layout, requestQueue) { export const AugmentedTreeContainer = connect( state => ({ dependencies: state.dependenciesRequest.content, - paths: state.paths, requestQueue: state.requestQueue, config: state.config }), @@ -301,9 +299,8 @@ export const AugmentedTreeContainer = connect( _dashprivate_dependencies: stateProps.dependencies, _dashprivate_dispatch: dispatchProps.dispatch, _dashprivate_layout: ownProps._dashprivate_layout, - path: ownProps.path, + _dashprivate_path: ownProps._dashprivate_path, _dashprivate_loadingState: getLoadingState(ownProps._dashprivate_layout, stateProps.requestQueue), - _dashprivate_paths: stateProps.paths, _dashprivate_requestQueue: stateProps.requestQueue, _dashprivate_config: stateProps.config, }) From b63f4ef253a1113ccd405d5b0aa196b8bed8939d Mon Sep 17 00:00:00 2001 From: Chris P Date: Fri, 19 Apr 2019 17:58:30 -0400 Subject: [PATCH 3/7] first pass at a test --- .circleci/config.yml | 2 ++ tests/test_render.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 89932cb..84fc2bc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,11 +42,13 @@ jobs: git clone --branch fix-prop-types git@github.com:plotly/dash-core-components.git git clone git@github.com:plotly/dash-html-components.git git clone git@github.com:plotly/dash-table.git + git clone git@github.com:plotly/dash-renderer-test-components . venv/bin/activate pip install -e ./dash --quiet cd dash-core-components && npm install --ignore-scripts && npm run build && pip install -e . && cd .. cd dash-html-components && npm install --ignore-scripts && npm run build && pip install -e . && cd .. cd dash-table && npm install --ignore-scripts && npm run build && pip install -e . && cd .. + cd dash-renderer-test-components && npm install --ignore-scripts && npm run build:all && pip install -e . && cd .. - run: name: Build diff --git a/tests/test_render.py b/tests/test_render.py index 308308b..884efdf 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -2960,3 +2960,27 @@ def display_content(pathname): test_cases[test_case_id]['name'] ) ) + + def test_set_props_behavior(self): + app = dash.Dash(__name__) + app.layout = html.Div([ + dash_renderer_test_components.UncontrolledInput(id='id'), + html.Div( + id='container', + children=dash_renderer_test_components.UncontrolledInput(), + ) + ]) + + self.startServer( + app, + debug=True, + use_reloader=False, + use_debugger=True, + dev_tools_hot_reload=False, + ) + + self.wait_for_element_by_css_selector('#id').send_keys('hello input with ID') + + self.wait_for_element_by_css_selector('#container input').send_keys('hello input without ID') + + self.percy_snapshot('set props - inputs have values') From d594a8b1b2578c9983f9a3f7d5461f80849641fd Mon Sep 17 00:00:00 2001 From: Chris P Date: Fri, 19 Apr 2019 21:11:38 -0400 Subject: [PATCH 4/7] missing import --- tests/test_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_render.py b/tests/test_render.py index 884efdf..b090a8a 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -8,6 +8,7 @@ from dash.development.base_component import Component import dash_html_components as html import dash_core_components as dcc +import dash_renderer_test_components from bs4 import BeautifulSoup from selenium.webdriver.common.action_chains import ActionChains From 2da50d6fa316159d6a6c3ff408ba9578ccaf2582 Mon Sep 17 00:00:00 2001 From: Chris P Date: Sat, 20 Apr 2019 10:42:08 -0400 Subject: [PATCH 5/7] master --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 84fc2bc..f22d1d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,7 +39,7 @@ jobs: name: Install dependencies (dash) command: | git clone git@github.com:plotly/dash.git - git clone --branch fix-prop-types git@github.com:plotly/dash-core-components.git + git clone git@github.com:plotly/dash-core-components.git git clone git@github.com:plotly/dash-html-components.git git clone git@github.com:plotly/dash-table.git git clone git@github.com:plotly/dash-renderer-test-components From 28cc71b053f1f76b55939b34909da8c401933c0a Mon Sep 17 00:00:00 2001 From: Chris P Date: Sat, 20 Apr 2019 17:39:47 -0400 Subject: [PATCH 6/7] test value directly instead of via screenshots --- tests/test_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_render.py b/tests/test_render.py index b090a8a..217f2eb 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -2981,7 +2981,7 @@ def test_set_props_behavior(self): ) self.wait_for_element_by_css_selector('#id').send_keys('hello input with ID') + self.wait_for_text_to_equal('#id', 'hello input with ID') self.wait_for_element_by_css_selector('#container input').send_keys('hello input without ID') - - self.percy_snapshot('set props - inputs have values') + self.wait_for_text_to_equal('#container input', 'hello input without ID') From 9d95922f844bc12bbdc9ac29ddc3f75b3d127274 Mon Sep 17 00:00:00 2001 From: Chris P Date: Sat, 20 Apr 2019 17:49:12 -0400 Subject: [PATCH 7/7] set value of input so that the input is completely "controlled" --- tests/test_render.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_render.py b/tests/test_render.py index 217f2eb..8b51f5f 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -2965,10 +2965,15 @@ def display_content(pathname): def test_set_props_behavior(self): app = dash.Dash(__name__) app.layout = html.Div([ - dash_renderer_test_components.UncontrolledInput(id='id'), + dash_renderer_test_components.UncontrolledInput( + id='id', + value='' + ), html.Div( id='container', - children=dash_renderer_test_components.UncontrolledInput(), + children=dash_renderer_test_components.UncontrolledInput( + value='' + ), ) ])