This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Input component updates - #356

Merged
valentijnnieman merged 13 commits into
masterfrom
input_update
Nov 5, 2018
Merged

Input component updates#356
valentijnnieman merged 13 commits into
masterfrom
input_update

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 31, 2018

Copy link
Copy Markdown
Contributor

edit:
This PR is becoming much bigger than expected, apologies in advance if the commit history or comments are not super clear.

This updates the Input component with a new debounce prop that determines when setProps is called, should fix#169. It also adds a bunch of unit tests, and fixes an issue where props coming from dash-renderer would overwrite the current state of the input. That last bug is more common than we think - components that use state as well as setProps get out of sync often.

Here's an example app with the new debounce prop:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.scripts.config.serve_locally = True
app.layout = html.Div([
dcc.Input(placeholder='Enter a value...',
type='number',
value=0,
step=0.01,
debounce=True,
id='input_id',
style={'float': 'left'}),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input(component_id='input_id', component_property='value')]
)
def update_output_div(input_value):
return 'You\'ve entered (+1) {}'.format(input_value+1)
if __name__ == '__main__':
app.run_server()

It also fixes#292 by fixing the step prop and #173 by not updating value if < min or > max.

Also renamed a couple of props that weren't being picked up by React, similarly to #348.

args = {k: _locals[k] for k in _explicit_args if k != 'children'}

for k in [u'id']:
for k in ['id']:

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is happening to be honest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That the dash generate component, I think I generated from py3 and it added that unicode, I regened from py2 and they were gone.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok! That's probably fine then!

Comment threadtest/unit/Input.test.js Outdated
expect(input.props().value).toBeDefined();
expect(input.props().value).toEqual(defaultProps.value);

// test if id is in the actual HTML string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value, not id

Comment threadtest/unit/Input.test.js Outdated
// props, if dash-renderer is not informed of prop updates
input.setProps({value: 'initial value'});

expect(input.find('input').getNode().value).toEqual('new value');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what is being tested here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In some cases, dash-renderer will need to re-render the entire layout. It will, at that point, push the props in the components it wants to render. If, Dash isn't informed of any prop updates (in the case were setProps() is not being used, but the component's internal state is) then it will push in the old prop, or the initial prop.

@Marc-Andre-RivetMarc-Andre-Rivet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm. There's one outstanding comment left where it's written 'id' instead of 'value'. Otherwise looks fine.

@valentijnnieman

valentijnnieman commented Nov 1, 2018

Copy link
Copy Markdown
ContributorAuthor

There are still some open issues on the Input component, figured I'd get some fixes for those in. This should close #173 now (when merge conflicts are resolved). I intend to look into #169 as well, while I'm at it. I'll change the status of this to WIP!

@valentijnniemanvalentijnnieman changed the title Unit test for Input component and some prop renames[WIP] Input component updatesNov 1, 2018
@T4rk1n

Copy link
Copy Markdown
Contributor

@valentijnnieman this fix #362, can you add this test to test_integration:

deftest_input_lose_focus(self):
app=dash.Dash(__name__)
app.layout=html.Div([
dcc.Input(
id='input',
value='initial value'
),
dcc.Input(id='input-2'),
html.Div(
html.Div([
1.5,
None,
'string',
html.Div(id='output-1')
])
)
])
@app.callback(Output('output-1', 'children'), [Input('input', 'value')])defupdate_output(value):
returnvalueself.startServer(app)
self.wait_for_text_to_equal('#output-1', 'initial value')
input1=self.wait_for_element_by_css_selector('#input')
input1.clear()
clicker=self.wait_for_element_by_css_selector('#input-2')
clicker.send_keys('bad')
input1.send_keys('hello world')
time.sleep(1)
self.assertEqual('hello world', input1.get_attribute('value'))

💃

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Sorry, I'm not sure what this test is testing, could you explain? I ask because I want to try to split up tests into (1) Unit tests that go into test/unit/{componentName}.test.js and (2) Integration tests that use an actual Dash app that go into test/test_integration.py. I'm not sure if your test should be a unit test or an integration test.

@T4rk1n

Copy link
Copy Markdown
Contributor

It's an integration test for a bug introduced by n_blur and setState in componentWillReceiveProps, when the component lost focus it would prepend the value with the initial value.

So I wrote this test that change the focus to another input and assert the first input is still the same value, it failed with the dcc version on master. wait_for_text_to_equal is actually wait_for_text_to_be_present, so the previous tests in dash would pass with those percy diffs. With your changes to props handling this test passed.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n In that case, the behaviour of new props and setState in componentWillReceiveProps is already being tested in the unit test, I feel. I'm kind of wary of adding (slow) integration tests that test behaviour that is (or should be) already tested in that component's unit tests. If you want you can make a PR later that adds this test, or better yet, update the new unit tests!

@T4rk1n

Copy link
Copy Markdown
Contributor

Testing time used to be not so slow, I investigated and found a few issues. Dropped the test time from ~8:30 avg to ~4:30..

The test is already written and proven to fail, it only adds at most a couple seconds. I've been hunting this bug for two weeks, finally found the culprit and wrote the test with failing behavior with the intention to fix it, was out of idea and noticed you changed things here so I tried and it passed. I'd prefer not having to rewrite it, so I'll merge master after this PR on the failing branch and add it in another PR.

Have yet to take a good look at the new unit tests, I don't know much about jest/enzyme. They look good at first glance with the mocked setProps.

@valentijnniemanvalentijnnieman changed the title [WIP] Input component updatesInput component updatesNov 5, 2018
@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Yeah the time isn't really that much of an issue for me, it's more that I think that what you're testing in your test is already covered in the unit tests now. I want to really start dividing tests between unit and integration, testing the behaviour of the component in the unit tests and the integration with Dash in the integration tests. That way, we can more easily do test driven development, writing unit tests that fail and make them pass, then assert that the behaviour in Dash stays the same later in the integration tests. It's just a lot easier to do that as opposed to make a change to the code, do a build:js and build:py, and then fire up the Selenium tests each time to make sure it works. With Jest you're testing the src code, not the build. Having said all that, I don't think there are a lot of Input integration tests, so that's something that we should change.

Comment threadCHANGELOG.md Outdated

## [0.36.0] - 2018-11-01
### Fixed
### Fixe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The d is gone!

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the d gone?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unno!

Comment threadsrc/components/Input.react.js Outdated
setProps({value: e.target.value});
}
const newValue = e.target.value;
if (((min || max) && newValue < min) || newValue > max) {

@Marc-Andre-RivetMarc-Andre-RivetNov 5, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check is wrong. 0 is falsy and max is compared always

Comment threadtest/unit/Input.test.js Outdated
test('Input can not be updated lower than props.min', () => {
input
.find('input')
.simulate('change', {target: {value: props.min - 1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, these tests are incorrect in so far as they are sending a number to the component when an actual onchange event would send a string

Comment threadtest/unit/Input.test.js Outdated
input = mount(<Input type="number" value={0} />);
});
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Comment threadtest/unit/Input.test.js Outdated
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});
expect(Number(input.find('input').getNode().value)).toEqual(-1);
input.find('input').simulate('change', {target: {value: 100}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input type “number” increment bug dcc.Input bug with decimal values

3 participants

@valentijnnieman@T4rk1n@Marc-Andre-Rivet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Input component updates - #356

Merged
valentijnnieman merged 13 commits into
masterfrom
input_update
Nov 5, 2018
Merged

Input component updates#356
valentijnnieman merged 13 commits into
masterfrom
input_update

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 31, 2018

Copy link
Copy Markdown
Contributor

edit:
This PR is becoming much bigger than expected, apologies in advance if the commit history or comments are not super clear.

This updates the Input component with a new debounce prop that determines when setProps is called, should fix#169. It also adds a bunch of unit tests, and fixes an issue where props coming from dash-renderer would overwrite the current state of the input. That last bug is more common than we think - components that use state as well as setProps get out of sync often.

Here's an example app with the new debounce prop:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.scripts.config.serve_locally = True
app.layout = html.Div([
dcc.Input(placeholder='Enter a value...',
type='number',
value=0,
step=0.01,
debounce=True,
id='input_id',
style={'float': 'left'}),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input(component_id='input_id', component_property='value')]
)
def update_output_div(input_value):
return 'You\'ve entered (+1) {}'.format(input_value+1)
if __name__ == '__main__':
app.run_server()

It also fixes#292 by fixing the step prop and #173 by not updating value if < min or > max.

Also renamed a couple of props that weren't being picked up by React, similarly to #348.

args = {k: _locals[k] for k in _explicit_args if k != 'children'}

for k in [u'id']:
for k in ['id']:

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is happening to be honest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That the dash generate component, I think I generated from py3 and it added that unicode, I regened from py2 and they were gone.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok! That's probably fine then!

Comment threadtest/unit/Input.test.js Outdated
expect(input.props().value).toBeDefined();
expect(input.props().value).toEqual(defaultProps.value);

// test if id is in the actual HTML string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value, not id

Comment threadtest/unit/Input.test.js Outdated
// props, if dash-renderer is not informed of prop updates
input.setProps({value: 'initial value'});

expect(input.find('input').getNode().value).toEqual('new value');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what is being tested here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In some cases, dash-renderer will need to re-render the entire layout. It will, at that point, push the props in the components it wants to render. If, Dash isn't informed of any prop updates (in the case were setProps() is not being used, but the component's internal state is) then it will push in the old prop, or the initial prop.

@Marc-Andre-RivetMarc-Andre-Rivet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm. There's one outstanding comment left where it's written 'id' instead of 'value'. Otherwise looks fine.

@valentijnnieman

valentijnnieman commented Nov 1, 2018

Copy link
Copy Markdown
ContributorAuthor

There are still some open issues on the Input component, figured I'd get some fixes for those in. This should close #173 now (when merge conflicts are resolved). I intend to look into #169 as well, while I'm at it. I'll change the status of this to WIP!

@valentijnniemanvalentijnnieman changed the title Unit test for Input component and some prop renames[WIP] Input component updatesNov 1, 2018
@T4rk1n

Copy link
Copy Markdown
Contributor

@valentijnnieman this fix #362, can you add this test to test_integration:

deftest_input_lose_focus(self):
app=dash.Dash(__name__)
app.layout=html.Div([
dcc.Input(
id='input',
value='initial value'
),
dcc.Input(id='input-2'),
html.Div(
html.Div([
1.5,
None,
'string',
html.Div(id='output-1')
])
)
])
@app.callback(Output('output-1', 'children'), [Input('input', 'value')])defupdate_output(value):
returnvalueself.startServer(app)
self.wait_for_text_to_equal('#output-1', 'initial value')
input1=self.wait_for_element_by_css_selector('#input')
input1.clear()
clicker=self.wait_for_element_by_css_selector('#input-2')
clicker.send_keys('bad')
input1.send_keys('hello world')
time.sleep(1)
self.assertEqual('hello world', input1.get_attribute('value'))

💃

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Sorry, I'm not sure what this test is testing, could you explain? I ask because I want to try to split up tests into (1) Unit tests that go into test/unit/{componentName}.test.js and (2) Integration tests that use an actual Dash app that go into test/test_integration.py. I'm not sure if your test should be a unit test or an integration test.

@T4rk1n

Copy link
Copy Markdown
Contributor

It's an integration test for a bug introduced by n_blur and setState in componentWillReceiveProps, when the component lost focus it would prepend the value with the initial value.

So I wrote this test that change the focus to another input and assert the first input is still the same value, it failed with the dcc version on master. wait_for_text_to_equal is actually wait_for_text_to_be_present, so the previous tests in dash would pass with those percy diffs. With your changes to props handling this test passed.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n In that case, the behaviour of new props and setState in componentWillReceiveProps is already being tested in the unit test, I feel. I'm kind of wary of adding (slow) integration tests that test behaviour that is (or should be) already tested in that component's unit tests. If you want you can make a PR later that adds this test, or better yet, update the new unit tests!

@T4rk1n

Copy link
Copy Markdown
Contributor

Testing time used to be not so slow, I investigated and found a few issues. Dropped the test time from ~8:30 avg to ~4:30..

The test is already written and proven to fail, it only adds at most a couple seconds. I've been hunting this bug for two weeks, finally found the culprit and wrote the test with failing behavior with the intention to fix it, was out of idea and noticed you changed things here so I tried and it passed. I'd prefer not having to rewrite it, so I'll merge master after this PR on the failing branch and add it in another PR.

Have yet to take a good look at the new unit tests, I don't know much about jest/enzyme. They look good at first glance with the mocked setProps.

@valentijnniemanvalentijnnieman changed the title [WIP] Input component updatesInput component updatesNov 5, 2018
@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Yeah the time isn't really that much of an issue for me, it's more that I think that what you're testing in your test is already covered in the unit tests now. I want to really start dividing tests between unit and integration, testing the behaviour of the component in the unit tests and the integration with Dash in the integration tests. That way, we can more easily do test driven development, writing unit tests that fail and make them pass, then assert that the behaviour in Dash stays the same later in the integration tests. It's just a lot easier to do that as opposed to make a change to the code, do a build:js and build:py, and then fire up the Selenium tests each time to make sure it works. With Jest you're testing the src code, not the build. Having said all that, I don't think there are a lot of Input integration tests, so that's something that we should change.

Comment threadCHANGELOG.md Outdated

## [0.36.0] - 2018-11-01
### Fixed
### Fixe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The d is gone!

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the d gone?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unno!

Comment threadsrc/components/Input.react.js Outdated
setProps({value: e.target.value});
}
const newValue = e.target.value;
if (((min || max) && newValue < min) || newValue > max) {

@Marc-Andre-RivetMarc-Andre-RivetNov 5, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check is wrong. 0 is falsy and max is compared always

Comment threadtest/unit/Input.test.js Outdated
test('Input can not be updated lower than props.min', () => {
input
.find('input')
.simulate('change', {target: {value: props.min - 1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, these tests are incorrect in so far as they are sending a number to the component when an actual onchange event would send a string

Comment threadtest/unit/Input.test.js Outdated
input = mount(<Input type="number" value={0} />);
});
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Comment threadtest/unit/Input.test.js Outdated
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});
expect(Number(input.find('input').getNode().value)).toEqual(-1);
input.find('input').simulate('change', {target: {value: 100}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input type “number” increment bug dcc.Input bug with decimal values

3 participants

@valentijnnieman@T4rk1n@Marc-Andre-Rivet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Input component updates - #356

Merged
valentijnnieman merged 13 commits into
masterfrom
input_update
Nov 5, 2018
Merged

Input component updates#356
valentijnnieman merged 13 commits into
masterfrom
input_update

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 31, 2018

Copy link
Copy Markdown
Contributor

edit:
This PR is becoming much bigger than expected, apologies in advance if the commit history or comments are not super clear.

This updates the Input component with a new debounce prop that determines when setProps is called, should fix#169. It also adds a bunch of unit tests, and fixes an issue where props coming from dash-renderer would overwrite the current state of the input. That last bug is more common than we think - components that use state as well as setProps get out of sync often.

Here's an example app with the new debounce prop:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.scripts.config.serve_locally = True
app.layout = html.Div([
dcc.Input(placeholder='Enter a value...',
type='number',
value=0,
step=0.01,
debounce=True,
id='input_id',
style={'float': 'left'}),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input(component_id='input_id', component_property='value')]
)
def update_output_div(input_value):
return 'You\'ve entered (+1) {}'.format(input_value+1)
if __name__ == '__main__':
app.run_server()

It also fixes#292 by fixing the step prop and #173 by not updating value if < min or > max.

Also renamed a couple of props that weren't being picked up by React, similarly to #348.

args = {k: _locals[k] for k in _explicit_args if k != 'children'}

for k in [u'id']:
for k in ['id']:

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is happening to be honest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That the dash generate component, I think I generated from py3 and it added that unicode, I regened from py2 and they were gone.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok! That's probably fine then!

Comment threadtest/unit/Input.test.js Outdated
expect(input.props().value).toBeDefined();
expect(input.props().value).toEqual(defaultProps.value);

// test if id is in the actual HTML string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value, not id

Comment threadtest/unit/Input.test.js Outdated
// props, if dash-renderer is not informed of prop updates
input.setProps({value: 'initial value'});

expect(input.find('input').getNode().value).toEqual('new value');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what is being tested here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In some cases, dash-renderer will need to re-render the entire layout. It will, at that point, push the props in the components it wants to render. If, Dash isn't informed of any prop updates (in the case were setProps() is not being used, but the component's internal state is) then it will push in the old prop, or the initial prop.

@Marc-Andre-RivetMarc-Andre-Rivet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm. There's one outstanding comment left where it's written 'id' instead of 'value'. Otherwise looks fine.

@valentijnnieman

valentijnnieman commented Nov 1, 2018

Copy link
Copy Markdown
ContributorAuthor

There are still some open issues on the Input component, figured I'd get some fixes for those in. This should close #173 now (when merge conflicts are resolved). I intend to look into #169 as well, while I'm at it. I'll change the status of this to WIP!

@valentijnniemanvalentijnnieman changed the title Unit test for Input component and some prop renames[WIP] Input component updatesNov 1, 2018
@T4rk1n

Copy link
Copy Markdown
Contributor

@valentijnnieman this fix #362, can you add this test to test_integration:

deftest_input_lose_focus(self):
app=dash.Dash(__name__)
app.layout=html.Div([
dcc.Input(
id='input',
value='initial value'
),
dcc.Input(id='input-2'),
html.Div(
html.Div([
1.5,
None,
'string',
html.Div(id='output-1')
])
)
])
@app.callback(Output('output-1', 'children'), [Input('input', 'value')])defupdate_output(value):
returnvalueself.startServer(app)
self.wait_for_text_to_equal('#output-1', 'initial value')
input1=self.wait_for_element_by_css_selector('#input')
input1.clear()
clicker=self.wait_for_element_by_css_selector('#input-2')
clicker.send_keys('bad')
input1.send_keys('hello world')
time.sleep(1)
self.assertEqual('hello world', input1.get_attribute('value'))

💃

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Sorry, I'm not sure what this test is testing, could you explain? I ask because I want to try to split up tests into (1) Unit tests that go into test/unit/{componentName}.test.js and (2) Integration tests that use an actual Dash app that go into test/test_integration.py. I'm not sure if your test should be a unit test or an integration test.

@T4rk1n

Copy link
Copy Markdown
Contributor

It's an integration test for a bug introduced by n_blur and setState in componentWillReceiveProps, when the component lost focus it would prepend the value with the initial value.

So I wrote this test that change the focus to another input and assert the first input is still the same value, it failed with the dcc version on master. wait_for_text_to_equal is actually wait_for_text_to_be_present, so the previous tests in dash would pass with those percy diffs. With your changes to props handling this test passed.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n In that case, the behaviour of new props and setState in componentWillReceiveProps is already being tested in the unit test, I feel. I'm kind of wary of adding (slow) integration tests that test behaviour that is (or should be) already tested in that component's unit tests. If you want you can make a PR later that adds this test, or better yet, update the new unit tests!

@T4rk1n

Copy link
Copy Markdown
Contributor

Testing time used to be not so slow, I investigated and found a few issues. Dropped the test time from ~8:30 avg to ~4:30..

The test is already written and proven to fail, it only adds at most a couple seconds. I've been hunting this bug for two weeks, finally found the culprit and wrote the test with failing behavior with the intention to fix it, was out of idea and noticed you changed things here so I tried and it passed. I'd prefer not having to rewrite it, so I'll merge master after this PR on the failing branch and add it in another PR.

Have yet to take a good look at the new unit tests, I don't know much about jest/enzyme. They look good at first glance with the mocked setProps.

@valentijnniemanvalentijnnieman changed the title [WIP] Input component updatesInput component updatesNov 5, 2018
@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Yeah the time isn't really that much of an issue for me, it's more that I think that what you're testing in your test is already covered in the unit tests now. I want to really start dividing tests between unit and integration, testing the behaviour of the component in the unit tests and the integration with Dash in the integration tests. That way, we can more easily do test driven development, writing unit tests that fail and make them pass, then assert that the behaviour in Dash stays the same later in the integration tests. It's just a lot easier to do that as opposed to make a change to the code, do a build:js and build:py, and then fire up the Selenium tests each time to make sure it works. With Jest you're testing the src code, not the build. Having said all that, I don't think there are a lot of Input integration tests, so that's something that we should change.

Comment threadCHANGELOG.md Outdated

## [0.36.0] - 2018-11-01
### Fixed
### Fixe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The d is gone!

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the d gone?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unno!

Comment threadsrc/components/Input.react.js Outdated
setProps({value: e.target.value});
}
const newValue = e.target.value;
if (((min || max) && newValue < min) || newValue > max) {

@Marc-Andre-RivetMarc-Andre-RivetNov 5, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check is wrong. 0 is falsy and max is compared always

Comment threadtest/unit/Input.test.js Outdated
test('Input can not be updated lower than props.min', () => {
input
.find('input')
.simulate('change', {target: {value: props.min - 1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, these tests are incorrect in so far as they are sending a number to the component when an actual onchange event would send a string

Comment threadtest/unit/Input.test.js Outdated
input = mount(<Input type="number" value={0} />);
});
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Comment threadtest/unit/Input.test.js Outdated
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});
expect(Number(input.find('input').getNode().value)).toEqual(-1);
input.find('input').simulate('change', {target: {value: 100}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input type “number” increment bug dcc.Input bug with decimal values

3 participants

@valentijnnieman@T4rk1n@Marc-Andre-Rivet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Input component updates - #356

Merged
valentijnnieman merged 13 commits into
masterfrom
input_update
Nov 5, 2018
Merged

Input component updates#356
valentijnnieman merged 13 commits into
masterfrom
input_update

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 31, 2018

Copy link
Copy Markdown
Contributor

edit:
This PR is becoming much bigger than expected, apologies in advance if the commit history or comments are not super clear.

This updates the Input component with a new debounce prop that determines when setProps is called, should fix#169. It also adds a bunch of unit tests, and fixes an issue where props coming from dash-renderer would overwrite the current state of the input. That last bug is more common than we think - components that use state as well as setProps get out of sync often.

Here's an example app with the new debounce prop:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.scripts.config.serve_locally = True
app.layout = html.Div([
dcc.Input(placeholder='Enter a value...',
type='number',
value=0,
step=0.01,
debounce=True,
id='input_id',
style={'float': 'left'}),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input(component_id='input_id', component_property='value')]
)
def update_output_div(input_value):
return 'You\'ve entered (+1) {}'.format(input_value+1)
if __name__ == '__main__':
app.run_server()

It also fixes#292 by fixing the step prop and #173 by not updating value if < min or > max.

Also renamed a couple of props that weren't being picked up by React, similarly to #348.

args = {k: _locals[k] for k in _explicit_args if k != 'children'}

for k in [u'id']:
for k in ['id']:

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is happening to be honest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That the dash generate component, I think I generated from py3 and it added that unicode, I regened from py2 and they were gone.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok! That's probably fine then!

Comment threadtest/unit/Input.test.js Outdated
expect(input.props().value).toBeDefined();
expect(input.props().value).toEqual(defaultProps.value);

// test if id is in the actual HTML string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value, not id

Comment threadtest/unit/Input.test.js Outdated
// props, if dash-renderer is not informed of prop updates
input.setProps({value: 'initial value'});

expect(input.find('input').getNode().value).toEqual('new value');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what is being tested here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In some cases, dash-renderer will need to re-render the entire layout. It will, at that point, push the props in the components it wants to render. If, Dash isn't informed of any prop updates (in the case were setProps() is not being used, but the component's internal state is) then it will push in the old prop, or the initial prop.

@Marc-Andre-RivetMarc-Andre-Rivet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm. There's one outstanding comment left where it's written 'id' instead of 'value'. Otherwise looks fine.

@valentijnnieman

valentijnnieman commented Nov 1, 2018

Copy link
Copy Markdown
ContributorAuthor

There are still some open issues on the Input component, figured I'd get some fixes for those in. This should close #173 now (when merge conflicts are resolved). I intend to look into #169 as well, while I'm at it. I'll change the status of this to WIP!

@valentijnniemanvalentijnnieman changed the title Unit test for Input component and some prop renames[WIP] Input component updatesNov 1, 2018
@T4rk1n

Copy link
Copy Markdown
Contributor

@valentijnnieman this fix #362, can you add this test to test_integration:

deftest_input_lose_focus(self):
app=dash.Dash(__name__)
app.layout=html.Div([
dcc.Input(
id='input',
value='initial value'
),
dcc.Input(id='input-2'),
html.Div(
html.Div([
1.5,
None,
'string',
html.Div(id='output-1')
])
)
])
@app.callback(Output('output-1', 'children'), [Input('input', 'value')])defupdate_output(value):
returnvalueself.startServer(app)
self.wait_for_text_to_equal('#output-1', 'initial value')
input1=self.wait_for_element_by_css_selector('#input')
input1.clear()
clicker=self.wait_for_element_by_css_selector('#input-2')
clicker.send_keys('bad')
input1.send_keys('hello world')
time.sleep(1)
self.assertEqual('hello world', input1.get_attribute('value'))

💃

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Sorry, I'm not sure what this test is testing, could you explain? I ask because I want to try to split up tests into (1) Unit tests that go into test/unit/{componentName}.test.js and (2) Integration tests that use an actual Dash app that go into test/test_integration.py. I'm not sure if your test should be a unit test or an integration test.

@T4rk1n

Copy link
Copy Markdown
Contributor

It's an integration test for a bug introduced by n_blur and setState in componentWillReceiveProps, when the component lost focus it would prepend the value with the initial value.

So I wrote this test that change the focus to another input and assert the first input is still the same value, it failed with the dcc version on master. wait_for_text_to_equal is actually wait_for_text_to_be_present, so the previous tests in dash would pass with those percy diffs. With your changes to props handling this test passed.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n In that case, the behaviour of new props and setState in componentWillReceiveProps is already being tested in the unit test, I feel. I'm kind of wary of adding (slow) integration tests that test behaviour that is (or should be) already tested in that component's unit tests. If you want you can make a PR later that adds this test, or better yet, update the new unit tests!

@T4rk1n

Copy link
Copy Markdown
Contributor

Testing time used to be not so slow, I investigated and found a few issues. Dropped the test time from ~8:30 avg to ~4:30..

The test is already written and proven to fail, it only adds at most a couple seconds. I've been hunting this bug for two weeks, finally found the culprit and wrote the test with failing behavior with the intention to fix it, was out of idea and noticed you changed things here so I tried and it passed. I'd prefer not having to rewrite it, so I'll merge master after this PR on the failing branch and add it in another PR.

Have yet to take a good look at the new unit tests, I don't know much about jest/enzyme. They look good at first glance with the mocked setProps.

@valentijnniemanvalentijnnieman changed the title [WIP] Input component updatesInput component updatesNov 5, 2018
@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Yeah the time isn't really that much of an issue for me, it's more that I think that what you're testing in your test is already covered in the unit tests now. I want to really start dividing tests between unit and integration, testing the behaviour of the component in the unit tests and the integration with Dash in the integration tests. That way, we can more easily do test driven development, writing unit tests that fail and make them pass, then assert that the behaviour in Dash stays the same later in the integration tests. It's just a lot easier to do that as opposed to make a change to the code, do a build:js and build:py, and then fire up the Selenium tests each time to make sure it works. With Jest you're testing the src code, not the build. Having said all that, I don't think there are a lot of Input integration tests, so that's something that we should change.

Comment threadCHANGELOG.md Outdated

## [0.36.0] - 2018-11-01
### Fixed
### Fixe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The d is gone!

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the d gone?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unno!

Comment threadsrc/components/Input.react.js Outdated
setProps({value: e.target.value});
}
const newValue = e.target.value;
if (((min || max) && newValue < min) || newValue > max) {

@Marc-Andre-RivetMarc-Andre-RivetNov 5, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check is wrong. 0 is falsy and max is compared always

Comment threadtest/unit/Input.test.js Outdated
test('Input can not be updated lower than props.min', () => {
input
.find('input')
.simulate('change', {target: {value: props.min - 1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, these tests are incorrect in so far as they are sending a number to the component when an actual onchange event would send a string

Comment threadtest/unit/Input.test.js Outdated
input = mount(<Input type="number" value={0} />);
});
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Comment threadtest/unit/Input.test.js Outdated
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});
expect(Number(input.find('input').getNode().value)).toEqual(-1);
input.find('input').simulate('change', {target: {value: 100}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input type “number” increment bug dcc.Input bug with decimal values

3 participants

@valentijnnieman@T4rk1n@Marc-Andre-Rivet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Input component updates - #356

Merged
valentijnnieman merged 13 commits into
masterfrom
input_update
Nov 5, 2018
Merged

Input component updates#356
valentijnnieman merged 13 commits into
masterfrom
input_update

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 31, 2018

Copy link
Copy Markdown
Contributor

edit:
This PR is becoming much bigger than expected, apologies in advance if the commit history or comments are not super clear.

This updates the Input component with a new debounce prop that determines when setProps is called, should fix#169. It also adds a bunch of unit tests, and fixes an issue where props coming from dash-renderer would overwrite the current state of the input. That last bug is more common than we think - components that use state as well as setProps get out of sync often.

Here's an example app with the new debounce prop:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.scripts.config.serve_locally = True
app.layout = html.Div([
dcc.Input(placeholder='Enter a value...',
type='number',
value=0,
step=0.01,
debounce=True,
id='input_id',
style={'float': 'left'}),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input(component_id='input_id', component_property='value')]
)
def update_output_div(input_value):
return 'You\'ve entered (+1) {}'.format(input_value+1)
if __name__ == '__main__':
app.run_server()

It also fixes#292 by fixing the step prop and #173 by not updating value if < min or > max.

Also renamed a couple of props that weren't being picked up by React, similarly to #348.

args = {k: _locals[k] for k in _explicit_args if k != 'children'}

for k in [u'id']:
for k in ['id']:

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is happening to be honest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That the dash generate component, I think I generated from py3 and it added that unicode, I regened from py2 and they were gone.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok! That's probably fine then!

Comment threadtest/unit/Input.test.js Outdated
expect(input.props().value).toBeDefined();
expect(input.props().value).toEqual(defaultProps.value);

// test if id is in the actual HTML string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value, not id

Comment threadtest/unit/Input.test.js Outdated
// props, if dash-renderer is not informed of prop updates
input.setProps({value: 'initial value'});

expect(input.find('input').getNode().value).toEqual('new value');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what is being tested here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In some cases, dash-renderer will need to re-render the entire layout. It will, at that point, push the props in the components it wants to render. If, Dash isn't informed of any prop updates (in the case were setProps() is not being used, but the component's internal state is) then it will push in the old prop, or the initial prop.

@Marc-Andre-RivetMarc-Andre-Rivet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm. There's one outstanding comment left where it's written 'id' instead of 'value'. Otherwise looks fine.

@valentijnnieman

valentijnnieman commented Nov 1, 2018

Copy link
Copy Markdown
ContributorAuthor

There are still some open issues on the Input component, figured I'd get some fixes for those in. This should close #173 now (when merge conflicts are resolved). I intend to look into #169 as well, while I'm at it. I'll change the status of this to WIP!

@valentijnniemanvalentijnnieman changed the title Unit test for Input component and some prop renames[WIP] Input component updatesNov 1, 2018
@T4rk1n

Copy link
Copy Markdown
Contributor

@valentijnnieman this fix #362, can you add this test to test_integration:

deftest_input_lose_focus(self):
app=dash.Dash(__name__)
app.layout=html.Div([
dcc.Input(
id='input',
value='initial value'
),
dcc.Input(id='input-2'),
html.Div(
html.Div([
1.5,
None,
'string',
html.Div(id='output-1')
])
)
])
@app.callback(Output('output-1', 'children'), [Input('input', 'value')])defupdate_output(value):
returnvalueself.startServer(app)
self.wait_for_text_to_equal('#output-1', 'initial value')
input1=self.wait_for_element_by_css_selector('#input')
input1.clear()
clicker=self.wait_for_element_by_css_selector('#input-2')
clicker.send_keys('bad')
input1.send_keys('hello world')
time.sleep(1)
self.assertEqual('hello world', input1.get_attribute('value'))

💃

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Sorry, I'm not sure what this test is testing, could you explain? I ask because I want to try to split up tests into (1) Unit tests that go into test/unit/{componentName}.test.js and (2) Integration tests that use an actual Dash app that go into test/test_integration.py. I'm not sure if your test should be a unit test or an integration test.

@T4rk1n

Copy link
Copy Markdown
Contributor

It's an integration test for a bug introduced by n_blur and setState in componentWillReceiveProps, when the component lost focus it would prepend the value with the initial value.

So I wrote this test that change the focus to another input and assert the first input is still the same value, it failed with the dcc version on master. wait_for_text_to_equal is actually wait_for_text_to_be_present, so the previous tests in dash would pass with those percy diffs. With your changes to props handling this test passed.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n In that case, the behaviour of new props and setState in componentWillReceiveProps is already being tested in the unit test, I feel. I'm kind of wary of adding (slow) integration tests that test behaviour that is (or should be) already tested in that component's unit tests. If you want you can make a PR later that adds this test, or better yet, update the new unit tests!

@T4rk1n

Copy link
Copy Markdown
Contributor

Testing time used to be not so slow, I investigated and found a few issues. Dropped the test time from ~8:30 avg to ~4:30..

The test is already written and proven to fail, it only adds at most a couple seconds. I've been hunting this bug for two weeks, finally found the culprit and wrote the test with failing behavior with the intention to fix it, was out of idea and noticed you changed things here so I tried and it passed. I'd prefer not having to rewrite it, so I'll merge master after this PR on the failing branch and add it in another PR.

Have yet to take a good look at the new unit tests, I don't know much about jest/enzyme. They look good at first glance with the mocked setProps.

@valentijnniemanvalentijnnieman changed the title [WIP] Input component updatesInput component updatesNov 5, 2018
@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Yeah the time isn't really that much of an issue for me, it's more that I think that what you're testing in your test is already covered in the unit tests now. I want to really start dividing tests between unit and integration, testing the behaviour of the component in the unit tests and the integration with Dash in the integration tests. That way, we can more easily do test driven development, writing unit tests that fail and make them pass, then assert that the behaviour in Dash stays the same later in the integration tests. It's just a lot easier to do that as opposed to make a change to the code, do a build:js and build:py, and then fire up the Selenium tests each time to make sure it works. With Jest you're testing the src code, not the build. Having said all that, I don't think there are a lot of Input integration tests, so that's something that we should change.

Comment threadCHANGELOG.md Outdated

## [0.36.0] - 2018-11-01
### Fixed
### Fixe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The d is gone!

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the d gone?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unno!

Comment threadsrc/components/Input.react.js Outdated
setProps({value: e.target.value});
}
const newValue = e.target.value;
if (((min || max) && newValue < min) || newValue > max) {

@Marc-Andre-RivetMarc-Andre-RivetNov 5, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check is wrong. 0 is falsy and max is compared always

Comment threadtest/unit/Input.test.js Outdated
test('Input can not be updated lower than props.min', () => {
input
.find('input')
.simulate('change', {target: {value: props.min - 1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, these tests are incorrect in so far as they are sending a number to the component when an actual onchange event would send a string

Comment threadtest/unit/Input.test.js Outdated
input = mount(<Input type="number" value={0} />);
});
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Comment threadtest/unit/Input.test.js Outdated
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});
expect(Number(input.find('input').getNode().value)).toEqual(-1);
input.find('input').simulate('change', {target: {value: 100}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input type “number” increment bug dcc.Input bug with decimal values

3 participants

@valentijnnieman@T4rk1n@Marc-Andre-Rivet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Input component updates - #356

Merged
valentijnnieman merged 13 commits into
masterfrom
input_update
Nov 5, 2018
Merged

Input component updates#356
valentijnnieman merged 13 commits into
masterfrom
input_update

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 31, 2018

Copy link
Copy Markdown
Contributor

edit:
This PR is becoming much bigger than expected, apologies in advance if the commit history or comments are not super clear.

This updates the Input component with a new debounce prop that determines when setProps is called, should fix#169. It also adds a bunch of unit tests, and fixes an issue where props coming from dash-renderer would overwrite the current state of the input. That last bug is more common than we think - components that use state as well as setProps get out of sync often.

Here's an example app with the new debounce prop:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.scripts.config.serve_locally = True
app.layout = html.Div([
dcc.Input(placeholder='Enter a value...',
type='number',
value=0,
step=0.01,
debounce=True,
id='input_id',
style={'float': 'left'}),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input(component_id='input_id', component_property='value')]
)
def update_output_div(input_value):
return 'You\'ve entered (+1) {}'.format(input_value+1)
if __name__ == '__main__':
app.run_server()

It also fixes#292 by fixing the step prop and #173 by not updating value if < min or > max.

Also renamed a couple of props that weren't being picked up by React, similarly to #348.

args = {k: _locals[k] for k in _explicit_args if k != 'children'}

for k in [u'id']:
for k in ['id']:

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is happening to be honest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That the dash generate component, I think I generated from py3 and it added that unicode, I regened from py2 and they were gone.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok! That's probably fine then!

Comment threadtest/unit/Input.test.js Outdated
expect(input.props().value).toBeDefined();
expect(input.props().value).toEqual(defaultProps.value);

// test if id is in the actual HTML string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value, not id

Comment threadtest/unit/Input.test.js Outdated
// props, if dash-renderer is not informed of prop updates
input.setProps({value: 'initial value'});

expect(input.find('input').getNode().value).toEqual('new value');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what is being tested here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In some cases, dash-renderer will need to re-render the entire layout. It will, at that point, push the props in the components it wants to render. If, Dash isn't informed of any prop updates (in the case were setProps() is not being used, but the component's internal state is) then it will push in the old prop, or the initial prop.

@Marc-Andre-RivetMarc-Andre-Rivet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm. There's one outstanding comment left where it's written 'id' instead of 'value'. Otherwise looks fine.

@valentijnnieman

valentijnnieman commented Nov 1, 2018

Copy link
Copy Markdown
ContributorAuthor

There are still some open issues on the Input component, figured I'd get some fixes for those in. This should close #173 now (when merge conflicts are resolved). I intend to look into #169 as well, while I'm at it. I'll change the status of this to WIP!

@valentijnniemanvalentijnnieman changed the title Unit test for Input component and some prop renames[WIP] Input component updatesNov 1, 2018
@T4rk1n

Copy link
Copy Markdown
Contributor

@valentijnnieman this fix #362, can you add this test to test_integration:

deftest_input_lose_focus(self):
app=dash.Dash(__name__)
app.layout=html.Div([
dcc.Input(
id='input',
value='initial value'
),
dcc.Input(id='input-2'),
html.Div(
html.Div([
1.5,
None,
'string',
html.Div(id='output-1')
])
)
])
@app.callback(Output('output-1', 'children'), [Input('input', 'value')])defupdate_output(value):
returnvalueself.startServer(app)
self.wait_for_text_to_equal('#output-1', 'initial value')
input1=self.wait_for_element_by_css_selector('#input')
input1.clear()
clicker=self.wait_for_element_by_css_selector('#input-2')
clicker.send_keys('bad')
input1.send_keys('hello world')
time.sleep(1)
self.assertEqual('hello world', input1.get_attribute('value'))

💃

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Sorry, I'm not sure what this test is testing, could you explain? I ask because I want to try to split up tests into (1) Unit tests that go into test/unit/{componentName}.test.js and (2) Integration tests that use an actual Dash app that go into test/test_integration.py. I'm not sure if your test should be a unit test or an integration test.

@T4rk1n

Copy link
Copy Markdown
Contributor

It's an integration test for a bug introduced by n_blur and setState in componentWillReceiveProps, when the component lost focus it would prepend the value with the initial value.

So I wrote this test that change the focus to another input and assert the first input is still the same value, it failed with the dcc version on master. wait_for_text_to_equal is actually wait_for_text_to_be_present, so the previous tests in dash would pass with those percy diffs. With your changes to props handling this test passed.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n In that case, the behaviour of new props and setState in componentWillReceiveProps is already being tested in the unit test, I feel. I'm kind of wary of adding (slow) integration tests that test behaviour that is (or should be) already tested in that component's unit tests. If you want you can make a PR later that adds this test, or better yet, update the new unit tests!

@T4rk1n

Copy link
Copy Markdown
Contributor

Testing time used to be not so slow, I investigated and found a few issues. Dropped the test time from ~8:30 avg to ~4:30..

The test is already written and proven to fail, it only adds at most a couple seconds. I've been hunting this bug for two weeks, finally found the culprit and wrote the test with failing behavior with the intention to fix it, was out of idea and noticed you changed things here so I tried and it passed. I'd prefer not having to rewrite it, so I'll merge master after this PR on the failing branch and add it in another PR.

Have yet to take a good look at the new unit tests, I don't know much about jest/enzyme. They look good at first glance with the mocked setProps.

@valentijnniemanvalentijnnieman changed the title [WIP] Input component updatesInput component updatesNov 5, 2018
@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Yeah the time isn't really that much of an issue for me, it's more that I think that what you're testing in your test is already covered in the unit tests now. I want to really start dividing tests between unit and integration, testing the behaviour of the component in the unit tests and the integration with Dash in the integration tests. That way, we can more easily do test driven development, writing unit tests that fail and make them pass, then assert that the behaviour in Dash stays the same later in the integration tests. It's just a lot easier to do that as opposed to make a change to the code, do a build:js and build:py, and then fire up the Selenium tests each time to make sure it works. With Jest you're testing the src code, not the build. Having said all that, I don't think there are a lot of Input integration tests, so that's something that we should change.

Comment threadCHANGELOG.md Outdated

## [0.36.0] - 2018-11-01
### Fixed
### Fixe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The d is gone!

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the d gone?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unno!

Comment threadsrc/components/Input.react.js Outdated
setProps({value: e.target.value});
}
const newValue = e.target.value;
if (((min || max) && newValue < min) || newValue > max) {

@Marc-Andre-RivetMarc-Andre-RivetNov 5, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check is wrong. 0 is falsy and max is compared always

Comment threadtest/unit/Input.test.js Outdated
test('Input can not be updated lower than props.min', () => {
input
.find('input')
.simulate('change', {target: {value: props.min - 1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, these tests are incorrect in so far as they are sending a number to the component when an actual onchange event would send a string

Comment threadtest/unit/Input.test.js Outdated
input = mount(<Input type="number" value={0} />);
});
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Comment threadtest/unit/Input.test.js Outdated
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});
expect(Number(input.find('input').getNode().value)).toEqual(-1);
input.find('input').simulate('change', {target: {value: 100}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input type “number” increment bug dcc.Input bug with decimal values

3 participants

@valentijnnieman@T4rk1n@Marc-Andre-Rivet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Input component updates - #356

Merged
valentijnnieman merged 13 commits into
masterfrom
input_update
Nov 5, 2018
Merged

Input component updates#356
valentijnnieman merged 13 commits into
masterfrom
input_update

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 31, 2018

Copy link
Copy Markdown
Contributor

edit:
This PR is becoming much bigger than expected, apologies in advance if the commit history or comments are not super clear.

This updates the Input component with a new debounce prop that determines when setProps is called, should fix#169. It also adds a bunch of unit tests, and fixes an issue where props coming from dash-renderer would overwrite the current state of the input. That last bug is more common than we think - components that use state as well as setProps get out of sync often.

Here's an example app with the new debounce prop:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.scripts.config.serve_locally = True
app.layout = html.Div([
dcc.Input(placeholder='Enter a value...',
type='number',
value=0,
step=0.01,
debounce=True,
id='input_id',
style={'float': 'left'}),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input(component_id='input_id', component_property='value')]
)
def update_output_div(input_value):
return 'You\'ve entered (+1) {}'.format(input_value+1)
if __name__ == '__main__':
app.run_server()

It also fixes#292 by fixing the step prop and #173 by not updating value if < min or > max.

Also renamed a couple of props that weren't being picked up by React, similarly to #348.

args = {k: _locals[k] for k in _explicit_args if k != 'children'}

for k in [u'id']:
for k in ['id']:

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is happening to be honest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That the dash generate component, I think I generated from py3 and it added that unicode, I regened from py2 and they were gone.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok! That's probably fine then!

Comment threadtest/unit/Input.test.js Outdated
expect(input.props().value).toBeDefined();
expect(input.props().value).toEqual(defaultProps.value);

// test if id is in the actual HTML string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value, not id

Comment threadtest/unit/Input.test.js Outdated
// props, if dash-renderer is not informed of prop updates
input.setProps({value: 'initial value'});

expect(input.find('input').getNode().value).toEqual('new value');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what is being tested here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In some cases, dash-renderer will need to re-render the entire layout. It will, at that point, push the props in the components it wants to render. If, Dash isn't informed of any prop updates (in the case were setProps() is not being used, but the component's internal state is) then it will push in the old prop, or the initial prop.

@Marc-Andre-RivetMarc-Andre-Rivet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm. There's one outstanding comment left where it's written 'id' instead of 'value'. Otherwise looks fine.

@valentijnnieman

valentijnnieman commented Nov 1, 2018

Copy link
Copy Markdown
ContributorAuthor

There are still some open issues on the Input component, figured I'd get some fixes for those in. This should close #173 now (when merge conflicts are resolved). I intend to look into #169 as well, while I'm at it. I'll change the status of this to WIP!

@valentijnniemanvalentijnnieman changed the title Unit test for Input component and some prop renames[WIP] Input component updatesNov 1, 2018
@T4rk1n

Copy link
Copy Markdown
Contributor

@valentijnnieman this fix #362, can you add this test to test_integration:

deftest_input_lose_focus(self):
app=dash.Dash(__name__)
app.layout=html.Div([
dcc.Input(
id='input',
value='initial value'
),
dcc.Input(id='input-2'),
html.Div(
html.Div([
1.5,
None,
'string',
html.Div(id='output-1')
])
)
])
@app.callback(Output('output-1', 'children'), [Input('input', 'value')])defupdate_output(value):
returnvalueself.startServer(app)
self.wait_for_text_to_equal('#output-1', 'initial value')
input1=self.wait_for_element_by_css_selector('#input')
input1.clear()
clicker=self.wait_for_element_by_css_selector('#input-2')
clicker.send_keys('bad')
input1.send_keys('hello world')
time.sleep(1)
self.assertEqual('hello world', input1.get_attribute('value'))

💃

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Sorry, I'm not sure what this test is testing, could you explain? I ask because I want to try to split up tests into (1) Unit tests that go into test/unit/{componentName}.test.js and (2) Integration tests that use an actual Dash app that go into test/test_integration.py. I'm not sure if your test should be a unit test or an integration test.

@T4rk1n

Copy link
Copy Markdown
Contributor

It's an integration test for a bug introduced by n_blur and setState in componentWillReceiveProps, when the component lost focus it would prepend the value with the initial value.

So I wrote this test that change the focus to another input and assert the first input is still the same value, it failed with the dcc version on master. wait_for_text_to_equal is actually wait_for_text_to_be_present, so the previous tests in dash would pass with those percy diffs. With your changes to props handling this test passed.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n In that case, the behaviour of new props and setState in componentWillReceiveProps is already being tested in the unit test, I feel. I'm kind of wary of adding (slow) integration tests that test behaviour that is (or should be) already tested in that component's unit tests. If you want you can make a PR later that adds this test, or better yet, update the new unit tests!

@T4rk1n

Copy link
Copy Markdown
Contributor

Testing time used to be not so slow, I investigated and found a few issues. Dropped the test time from ~8:30 avg to ~4:30..

The test is already written and proven to fail, it only adds at most a couple seconds. I've been hunting this bug for two weeks, finally found the culprit and wrote the test with failing behavior with the intention to fix it, was out of idea and noticed you changed things here so I tried and it passed. I'd prefer not having to rewrite it, so I'll merge master after this PR on the failing branch and add it in another PR.

Have yet to take a good look at the new unit tests, I don't know much about jest/enzyme. They look good at first glance with the mocked setProps.

@valentijnniemanvalentijnnieman changed the title [WIP] Input component updatesInput component updatesNov 5, 2018
@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Yeah the time isn't really that much of an issue for me, it's more that I think that what you're testing in your test is already covered in the unit tests now. I want to really start dividing tests between unit and integration, testing the behaviour of the component in the unit tests and the integration with Dash in the integration tests. That way, we can more easily do test driven development, writing unit tests that fail and make them pass, then assert that the behaviour in Dash stays the same later in the integration tests. It's just a lot easier to do that as opposed to make a change to the code, do a build:js and build:py, and then fire up the Selenium tests each time to make sure it works. With Jest you're testing the src code, not the build. Having said all that, I don't think there are a lot of Input integration tests, so that's something that we should change.

Comment threadCHANGELOG.md Outdated

## [0.36.0] - 2018-11-01
### Fixed
### Fixe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The d is gone!

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the d gone?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unno!

Comment threadsrc/components/Input.react.js Outdated
setProps({value: e.target.value});
}
const newValue = e.target.value;
if (((min || max) && newValue < min) || newValue > max) {

@Marc-Andre-RivetMarc-Andre-RivetNov 5, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check is wrong. 0 is falsy and max is compared always

Comment threadtest/unit/Input.test.js Outdated
test('Input can not be updated lower than props.min', () => {
input
.find('input')
.simulate('change', {target: {value: props.min - 1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, these tests are incorrect in so far as they are sending a number to the component when an actual onchange event would send a string

Comment threadtest/unit/Input.test.js Outdated
input = mount(<Input type="number" value={0} />);
});
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Comment threadtest/unit/Input.test.js Outdated
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});
expect(Number(input.find('input').getNode().value)).toEqual(-1);
input.find('input').simulate('change', {target: {value: 100}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input type “number” increment bug dcc.Input bug with decimal values

3 participants

@valentijnnieman@T4rk1n@Marc-Andre-Rivet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Input component updates - #356

Merged
valentijnnieman merged 13 commits into
masterfrom
input_update
Nov 5, 2018
Merged

Input component updates#356
valentijnnieman merged 13 commits into
masterfrom
input_update

Conversation

@valentijnnieman

@valentijnniemanvalentijnnieman commented Oct 31, 2018

Copy link
Copy Markdown
Contributor

edit:
This PR is becoming much bigger than expected, apologies in advance if the commit history or comments are not super clear.

This updates the Input component with a new debounce prop that determines when setProps is called, should fix#169. It also adds a bunch of unit tests, and fixes an issue where props coming from dash-renderer would overwrite the current state of the input. That last bug is more common than we think - components that use state as well as setProps get out of sync often.

Here's an example app with the new debounce prop:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.scripts.config.serve_locally = True
app.layout = html.Div([
dcc.Input(placeholder='Enter a value...',
type='number',
value=0,
step=0.01,
debounce=True,
id='input_id',
style={'float': 'left'}),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input(component_id='input_id', component_property='value')]
)
def update_output_div(input_value):
return 'You\'ve entered (+1) {}'.format(input_value+1)
if __name__ == '__main__':
app.run_server()

It also fixes#292 by fixing the step prop and #173 by not updating value if < min or > max.

Also renamed a couple of props that weren't being picked up by React, similarly to #348.

args = {k: _locals[k] for k in _explicit_args if k != 'children'}

for k in [u'id']:
for k in ['id']:

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is happening to be honest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That the dash generate component, I think I generated from py3 and it added that unicode, I regened from py2 and they were gone.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok! That's probably fine then!

Comment threadtest/unit/Input.test.js Outdated
expect(input.props().value).toBeDefined();
expect(input.props().value).toEqual(defaultProps.value);

// test if id is in the actual HTML string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value, not id

Comment threadtest/unit/Input.test.js Outdated
// props, if dash-renderer is not informed of prop updates
input.setProps({value: 'initial value'});

expect(input.find('input').getNode().value).toEqual('new value');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what is being tested here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In some cases, dash-renderer will need to re-render the entire layout. It will, at that point, push the props in the components it wants to render. If, Dash isn't informed of any prop updates (in the case were setProps() is not being used, but the component's internal state is) then it will push in the old prop, or the initial prop.

@Marc-Andre-RivetMarc-Andre-Rivet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm. There's one outstanding comment left where it's written 'id' instead of 'value'. Otherwise looks fine.

@valentijnnieman

valentijnnieman commented Nov 1, 2018

Copy link
Copy Markdown
ContributorAuthor

There are still some open issues on the Input component, figured I'd get some fixes for those in. This should close #173 now (when merge conflicts are resolved). I intend to look into #169 as well, while I'm at it. I'll change the status of this to WIP!

@valentijnniemanvalentijnnieman changed the title Unit test for Input component and some prop renames[WIP] Input component updatesNov 1, 2018
@T4rk1n

Copy link
Copy Markdown
Contributor

@valentijnnieman this fix #362, can you add this test to test_integration:

deftest_input_lose_focus(self):
app=dash.Dash(__name__)
app.layout=html.Div([
dcc.Input(
id='input',
value='initial value'
),
dcc.Input(id='input-2'),
html.Div(
html.Div([
1.5,
None,
'string',
html.Div(id='output-1')
])
)
])
@app.callback(Output('output-1', 'children'), [Input('input', 'value')])defupdate_output(value):
returnvalueself.startServer(app)
self.wait_for_text_to_equal('#output-1', 'initial value')
input1=self.wait_for_element_by_css_selector('#input')
input1.clear()
clicker=self.wait_for_element_by_css_selector('#input-2')
clicker.send_keys('bad')
input1.send_keys('hello world')
time.sleep(1)
self.assertEqual('hello world', input1.get_attribute('value'))

💃

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Sorry, I'm not sure what this test is testing, could you explain? I ask because I want to try to split up tests into (1) Unit tests that go into test/unit/{componentName}.test.js and (2) Integration tests that use an actual Dash app that go into test/test_integration.py. I'm not sure if your test should be a unit test or an integration test.

@T4rk1n

Copy link
Copy Markdown
Contributor

It's an integration test for a bug introduced by n_blur and setState in componentWillReceiveProps, when the component lost focus it would prepend the value with the initial value.

So I wrote this test that change the focus to another input and assert the first input is still the same value, it failed with the dcc version on master. wait_for_text_to_equal is actually wait_for_text_to_be_present, so the previous tests in dash would pass with those percy diffs. With your changes to props handling this test passed.

@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n In that case, the behaviour of new props and setState in componentWillReceiveProps is already being tested in the unit test, I feel. I'm kind of wary of adding (slow) integration tests that test behaviour that is (or should be) already tested in that component's unit tests. If you want you can make a PR later that adds this test, or better yet, update the new unit tests!

@T4rk1n

Copy link
Copy Markdown
Contributor

Testing time used to be not so slow, I investigated and found a few issues. Dropped the test time from ~8:30 avg to ~4:30..

The test is already written and proven to fail, it only adds at most a couple seconds. I've been hunting this bug for two weeks, finally found the culprit and wrote the test with failing behavior with the intention to fix it, was out of idea and noticed you changed things here so I tried and it passed. I'd prefer not having to rewrite it, so I'll merge master after this PR on the failing branch and add it in another PR.

Have yet to take a good look at the new unit tests, I don't know much about jest/enzyme. They look good at first glance with the mocked setProps.

@valentijnniemanvalentijnnieman changed the title [WIP] Input component updatesInput component updatesNov 5, 2018
@valentijnnieman

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n Yeah the time isn't really that much of an issue for me, it's more that I think that what you're testing in your test is already covered in the unit tests now. I want to really start dividing tests between unit and integration, testing the behaviour of the component in the unit tests and the integration with Dash in the integration tests. That way, we can more easily do test driven development, writing unit tests that fail and make them pass, then assert that the behaviour in Dash stays the same later in the integration tests. It's just a lot easier to do that as opposed to make a change to the code, do a build:js and build:py, and then fire up the Selenium tests each time to make sure it works. With Jest you're testing the src code, not the build. Having said all that, I don't think there are a lot of Input integration tests, so that's something that we should change.

Comment threadCHANGELOG.md Outdated

## [0.36.0] - 2018-11-01
### Fixed
### Fixe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The d is gone!

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the d gone?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unno!

Comment threadsrc/components/Input.react.js Outdated
setProps({value: e.target.value});
}
const newValue = e.target.value;
if (((min || max) && newValue < min) || newValue > max) {

@Marc-Andre-RivetMarc-Andre-RivetNov 5, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check is wrong. 0 is falsy and max is compared always

Comment threadtest/unit/Input.test.js Outdated
test('Input can not be updated lower than props.min', () => {
input
.find('input')
.simulate('change', {target: {value: props.min - 1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, these tests are incorrect in so far as they are sending a number to the component when an actual onchange event would send a string

Comment threadtest/unit/Input.test.js Outdated
input = mount(<Input type="number" value={0} />);
});
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Comment threadtest/unit/Input.test.js Outdated
test('Input can be updated', () => {
input.find('input').simulate('change', {target: {value: -1}});
expect(Number(input.find('input').getNode().value)).toEqual(-1);
input.find('input').simulate('change', {target: {value: 100}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a string

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input type “number” increment bug dcc.Input bug with decimal values

3 participants

@valentijnnieman@T4rk1n@Marc-Andre-Rivet