From 6f32bb8d50769958b88212400813dea60e49e611 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Fri, 17 May 2019 14:38:45 -0400 Subject: [PATCH 01/10] allow hiding undo/redo toolbar --- src/AppContainer.react.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AppContainer.react.js b/src/AppContainer.react.js index 1c65b35..3b436af 100644 --- a/src/AppContainer.react.js +++ b/src/AppContainer.react.js @@ -30,9 +30,10 @@ class UnconnectedAppContainer extends React.Component { if (type(config) === 'Null') { return
Loading...
; } + const {show_undo_redo} = config; return ( - + {show_undo_redo ? : null} From 253c828d2f710408c4055f5ddc0d356dd5b3f2ff Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 20 May 2019 15:36:08 -0400 Subject: [PATCH 02/10] :palm_tree: DRY undo/redo actions --- src/actions/index.js | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index c2fd5cd..b70ffa2 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -131,34 +131,20 @@ export function redo() { }; } +const UNDO = createAction('UNDO')(); export function undo() { - return function(dispatch, getState) { - const history = getState().history; - dispatch(createAction('UNDO')()); - const previous = history.past[history.past.length - 1]; - - // Update props - dispatch( - createAction('UNDO_PROP_CHANGE')({ - itempath: getState().paths[previous.id], - props: previous.props, - }) - ); - - // Notify observers - dispatch( - notifyObservers({ - id: previous.id, - props: previous.props, - }) - ); - }; + return undo_revert(UNDO); } +const REVERT = createAction('REVERT')(); export function revert() { + return undo_revert(REVERT); +} + +function undo_revert(undo_or_revert) { return function(dispatch, getState) { const history = getState().history; - dispatch(createAction('REVERT')()); + dispatch(undo_or_revert); const previous = history.past[history.past.length - 1]; // Update props From 2b8e8179b023b21ad9223133e6cb57f1ab527762 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 20 May 2019 17:38:46 -0400 Subject: [PATCH 03/10] update tests & cleaner pattern --- tests/test_assets/initial_state_dash_app_content.html | 2 +- tests/test_render.py | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/test_assets/initial_state_dash_app_content.html b/tests/test_assets/initial_state_dash_app_content.html index 43180f4..16ab434 100644 --- a/tests/test_assets/initial_state_dash_app_content.html +++ b/tests/test_assets/initial_state_dash_app_content.html @@ -1 +1 @@ -
Basic string3.14
Child div with basic string
Grandchild div
Great grandchild
3.14159another basic string
\ No newline at end of file +
Basic string3.14
Child div with basic string
Grandchild div
Great grandchild
3.14159another basic string
diff --git a/tests/test_render.py b/tests/test_render.py index ce6ba5a..17a4dd0 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -109,7 +109,6 @@ def request_queue_assertions( if expected_length is not None: self.assertEqual(len(request_queue), expected_length) - def test_initial_state(self): app = Dash(__name__) my_class_attrs = { @@ -158,7 +157,7 @@ def test_initial_state(self): os.path.dirname(__file__), 'test_assets', 'initial_state_dash_app_content.html') with open(_dash_app_content_html) as fp: - rendered_dom = BeautifulSoup(fp.read(), 'lxml') + rendered_dom = BeautifulSoup(fp.read().strip(), 'lxml') fetched_dom = BeautifulSoup(el.get_attribute('outerHTML'), 'lxml') self.assertEqual( @@ -328,10 +327,9 @@ def update_input(value): '#react-entry-point').get_attribute('innerHTML'), 'lxml').select_one('#output > div').contents - self.assertTrue( - pad_input.attrs == {'type': 'text', 'id': 'sub-input-1', 'value': 'sub input initial value'} - and pad_input.name == 'input', - "pad input is correctly rendered") + self.assertEqual(pad_input.attrs['value'], 'sub input initial value') + self.assertEqual(pad_input.attrs['id'], 'sub-input-1') + self.assertEqual(pad_input.name, 'input') self.assertTrue( pad_div.text == pad_input.attrs['value'] From 4236d8b9bdee45c65cad1419a04ba094b7df9903 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 20 May 2019 19:28:50 -0400 Subject: [PATCH 04/10] test that undo/redo shows up on command and behaves correctly --- tests/test_render.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_render.py b/tests/test_render.py index 17a4dd0..72cfae4 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -153,6 +153,7 @@ def test_initial_state(self): self.startServer(app) el = self.wait_for_element_by_css_selector('#react-entry-point') + # Note: this .html file shows there's no undo/redo button by default _dash_app_content_html = os.path.join( os.path.dirname(__file__), 'test_assets', 'initial_state_dash_app_content.html') @@ -210,6 +211,44 @@ def test_initial_state(self): self.assertTrue(self.is_console_clean()) + def click_undo(self): + undo_selector = '._dash-undo-redo span:first-child' + undo = self.wait_for_element_by_css_selector(undo_selector) + self.wait_for_text_to_equal(undo_selector, '↺\nundo') + undo.click() + + def click_redo(self): + redo_selector = '._dash-undo-redo span:last-child' + self.wait_for_text_to_equal(redo_selector, '↻\nredo') + redo = self.wait_for_element_by_css_selector(redo_selector) + redo.click() + + def test_undo_redo(self): + app = Dash(__name__, show_undo_redo=True) + app.layout = html.Div([dcc.Input(id='a'), html.Div(id='b')]) + + @app.callback(Output('b', 'children'), [Input('a', 'value')]) + def set_b(a): + return a + + self.startServer(app) + + a = self.wait_for_element_by_css_selector('#a') + a.send_keys('xyz') + + self.wait_for_text_to_equal('#b', 'xyz') + + self.click_undo() + self.wait_for_text_to_equal('#b', 'xy') + + self.click_undo() + self.wait_for_text_to_equal('#b', 'x') + + self.click_redo() + self.wait_for_text_to_equal('#b', 'xy') + + self.percy_snapshot(name='undo-redo') + def test_array_of_falsy_child(self): app = Dash(__name__) app.layout = html.Div(id='nully-wrapper', children=[0]) From 933cd51e7c0ebd475e6972aac28e7da80eabdbf1 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 20 May 2019 19:42:21 -0400 Subject: [PATCH 05/10] point to relevant dash branch --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f22d1d7..ec83ab9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,7 +38,7 @@ jobs: - run: name: Install dependencies (dash) command: | - git clone git@github.com:plotly/dash.git + git clone -b hide-undo-redo git@github.com:plotly/dash.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 From 448586d85bca54cea641cb3b326e067ad57d4c72 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 20 May 2019 20:06:19 -0400 Subject: [PATCH 06/10] give encoding for the loops in undo/redo tests --- tests/test_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_render.py b/tests/test_render.py index 72cfae4..c9d86d4 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -1,3 +1,4 @@ +# -*- coding: UTF-8 -*- import os import textwrap From e4c6b6ad4f51b56c23e73918aa4bf9d14e86b225 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 20 May 2019 20:45:04 -0400 Subject: [PATCH 07/10] simplify undo/redo test for py2 compatibility --- tests/test_render.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_render.py b/tests/test_render.py index c9d86d4..2096e6b 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -213,14 +213,14 @@ def test_initial_state(self): self.assertTrue(self.is_console_clean()) def click_undo(self): - undo_selector = '._dash-undo-redo span:first-child' + undo_selector = '._dash-undo-redo span:first-child div:last-child' undo = self.wait_for_element_by_css_selector(undo_selector) - self.wait_for_text_to_equal(undo_selector, '↺\nundo') + self.wait_for_text_to_equal(undo_selector, 'undo') undo.click() def click_redo(self): - redo_selector = '._dash-undo-redo span:last-child' - self.wait_for_text_to_equal(redo_selector, '↻\nredo') + redo_selector = '._dash-undo-redo span:last-child div:last-child' + self.wait_for_text_to_equal(redo_selector, 'redo') redo = self.wait_for_element_by_css_selector(redo_selector) redo.click() From e1941e1a468c9abb6d2d12c18c54a19324c6283e Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 21 May 2019 09:54:07 -0400 Subject: [PATCH 08/10] changelog for hide-undo-redo --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa7f20..0115265 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [UNRELEASED] +### Changed +- Undo/redo toolbar is removed by default, unless `config.show_undo_redo=true` is provided. The CSS hack `._dash-undo-redo:{display:none;}` is no longer needed [#175](https://github.com/plotly/dash-renderer/pull/175) + ## [0.24.0] - 2019-05-15 ### Fixed - Fix regression on handling PreventUpdate (204 NO CONTENT) [#170](https://github.com/plotly/dash-renderer/pull/170) From 56cfddc62807208eadb4ad18378bd0a37508081c Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 21 May 2019 10:36:06 -0400 Subject: [PATCH 09/10] test existence of undo/redo toolbar and buttons in all cases --- tests/test_render.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_render.py b/tests/test_render.py index 2096e6b..9c8eca6 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -224,6 +224,15 @@ def click_redo(self): redo = self.wait_for_element_by_css_selector(redo_selector) redo.click() + def check_undo_redo_exist(self, has_undo, has_redo): + selector = '._dash-undo-redo span div:last-child' + els = self.driver.find_elements_by_css_selector(selector) + texts = (['undo'] if has_undo else []) + (['redo'] if has_redo else []) + + self.assertEqual(len(els), len(texts)) + for el, text in zip(els, texts): + self.assertEqual(el.text, text) + def test_undo_redo(self): app = Dash(__name__, show_undo_redo=True) app.layout = html.Div([dcc.Input(id='a'), html.Div(id='b')]) @@ -238,18 +247,44 @@ def set_b(a): a.send_keys('xyz') self.wait_for_text_to_equal('#b', 'xyz') + self.check_undo_redo_exist(True, False) self.click_undo() self.wait_for_text_to_equal('#b', 'xy') + self.check_undo_redo_exist(True, True) self.click_undo() self.wait_for_text_to_equal('#b', 'x') + self.check_undo_redo_exist(True, True) self.click_redo() self.wait_for_text_to_equal('#b', 'xy') + self.check_undo_redo_exist(True, True) self.percy_snapshot(name='undo-redo') + self.click_undo() + self.click_undo() + self.wait_for_text_to_equal('#b', '') + self.check_undo_redo_exist(False, True) + + def test_no_undo_redo(self): + app = Dash(__name__) + app.layout = html.Div([dcc.Input(id='a'), html.Div(id='b')]) + + @app.callback(Output('b', 'children'), [Input('a', 'value')]) + def set_b(a): + return a + + self.startServer(app) + + a = self.wait_for_element_by_css_selector('#a') + a.send_keys('xyz') + + self.wait_for_text_to_equal('#b', 'xyz') + toolbar = self.driver.find_elements_by_css_selector('._dash-undo-redo') + self.assertEqual(len(toolbar), 0) + def test_array_of_falsy_child(self): app = Dash(__name__) app.layout = html.Div(id='nully-wrapper', children=[0]) From 55802efbc7b1ba6fbbc37af2abb28aa2af2fc340 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 21 May 2019 13:35:47 -0400 Subject: [PATCH 10/10] dash hide-undo-redo is in master --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ec83ab9..f22d1d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,7 +38,7 @@ jobs: - run: name: Install dependencies (dash) command: | - git clone -b hide-undo-redo git@github.com:plotly/dash.git + git clone git@github.com:plotly/dash.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