Skip to content

Commit 22072e2

Browse files
committed
Merge pull request plotly#384 from plotly/fix-tests
Add more Input tests, remove duplicate value callbacks.
1 parent 3bad5f9 commit 22072e2

File tree

10 files changed

+1274
-656
lines changed

10 files changed

+1274
-656
lines changed

packages/dash-core-components/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [0.40.2] - 2018-12-04
6+
### Fixed
7+
- Put Input value set in onBlur/onSubmit under a debounce check [#384](https://github.com/plotly/dash-core-components/pull/384)
8+
59
## [0.40.1] - 2018-12-04
610
### Fixed
711
- Fixed issue [#390](https://github.com/plotly/dash-core-components/issues/390) by providing better styles for vertical Tabs.
12+
813
## [0.40.0] - 2018-11-28
914
### Added
1015
- Add Logout button (dash-deployment-server authentication integration) [#388](https://github.com/plotly/dash-core-components/pull/388)

packages/dash-core-components/dash_core_components/dash_core_components.dev.js

+989-524
Large diffs are not rendered by default.

packages/dash-core-components/dash_core_components/dash_core_components.min.js

+12-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/dash-core-components/dash_core_components/metadata.json

+106-106
Large diffs are not rendered by default.

packages/dash-core-components/dash_core_components/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dash-core-components",
3-
"version": "0.40.1",
3+
"version": "0.40.2",
44
"description": "Core component suite for Dash",
55
"repository": {
66
"type": "git",
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.40.1'
1+
__version__ = '0.40.2'

packages/dash-core-components/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dash-core-components",
3-
"version": "0.40.1",
3+
"version": "0.40.2",
44
"description": "Core component suite for Dash",
55
"repository": {
66
"type": "git",

packages/dash-core-components/src/components/Input.react.js

+14-10
Original file line numberDiff line numberDiff line change
@@ -63,24 +63,28 @@ export default class Input extends Component {
6363
fireEvent({event: 'blur'});
6464
}
6565
if (setProps) {
66-
const castValue =
67-
type === 'number' ? Number(value) : value;
68-
setProps({
66+
const payload = {
6967
n_blur: this.props.n_blur + 1,
7068
n_blur_timestamp: new Date(),
71-
value: castValue,
72-
});
69+
};
70+
if (debounce) {
71+
payload.value =
72+
type === 'number' ? Number(value) : value;
73+
}
74+
setProps(payload);
7375
}
7476
}}
7577
onKeyPress={e => {
7678
if (setProps && e.key === 'Enter') {
77-
const castValue =
78-
type === 'number' ? Number(value) : value;
79-
setProps({
79+
const payload = {
8080
n_submit: this.props.n_submit + 1,
8181
n_submit_timestamp: new Date(),
82-
value: castValue,
83-
});
82+
};
83+
if (debounce) {
84+
payload.value =
85+
type === 'number' ? Number(value) : value;
86+
}
87+
setProps(payload);
8488
}
8589
}}
8690
value={value}

packages/dash-core-components/test/test_integration.py

+95-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from selenium.webdriver.common.by import By
2323
from selenium.webdriver.support.ui import WebDriverWait
2424
from selenium.webdriver.support import expected_conditions as EC
25+
from selenium.webdriver.common.keys import Keys
2526

2627
from textwrap import dedent
2728
try:
@@ -30,6 +31,7 @@
3031
from urllib.parse import urlparse
3132

3233
from .IntegrationTests import IntegrationTests
34+
from .utils import wait_for
3335

3436
from multiprocessing import Value
3537

@@ -54,10 +56,14 @@ def wait_for_element_by_css_selector(self, selector):
5456
)
5557

5658
def wait_for_text_to_equal(self, selector, assertion_text):
57-
return WebDriverWait(self.driver, TIMEOUT).until(
59+
WebDriverWait(self.driver, TIMEOUT).until(
5860
EC.text_to_be_present_in_element((By.CSS_SELECTOR, selector),
5961
assertion_text)
6062
)
63+
self.assertEqual(
64+
assertion_text,
65+
self.driver.find_element_by_css_selector(selector).text
66+
)
6167

6268
def snapshot(self, name):
6369
if 'PERCY_PROJECT' in os.environ and 'PERCY_TOKEN' in os.environ:
@@ -1468,3 +1474,91 @@ def _insert_cookie(rep):
14681474
self.wait_for_text_to_equal('#content', 'Logged out')
14691475

14701476
self.assertFalse(self.driver.get_cookie('logout-cookie'))
1477+
1478+
def test_state_and_inputs(self):
1479+
app = dash.Dash(__name__)
1480+
app.layout = html.Div([
1481+
dcc.Input(value='Initial Input', id='input'),
1482+
dcc.Input(value='Initial State', id='state'),
1483+
html.Div(id='output')
1484+
])
1485+
1486+
call_count = Value('i', 0)
1487+
1488+
@app.callback(Output('output', 'children'),
1489+
inputs=[Input('input', 'value')],
1490+
state=[State('state', 'value')])
1491+
def update_output(input, state):
1492+
call_count.value += 1
1493+
return 'input="{}", state="{}"'.format(input, state)
1494+
1495+
self.startServer(app)
1496+
output = lambda: self.driver.find_element_by_id('output')
1497+
input = lambda: self.driver.find_element_by_id('input')
1498+
state = lambda: self.driver.find_element_by_id('state')
1499+
1500+
# callback gets called with initial input
1501+
wait_for(lambda: call_count.value == 1)
1502+
self.assertEqual(
1503+
output().text,
1504+
'input="Initial Input", state="Initial State"'
1505+
)
1506+
1507+
input().send_keys('x')
1508+
wait_for(lambda: call_count.value == 2)
1509+
self.assertEqual(
1510+
output().text,
1511+
'input="Initial Inputx", state="Initial State"')
1512+
1513+
state().send_keys('x')
1514+
time.sleep(0.75)
1515+
self.assertEqual(call_count.value, 2)
1516+
self.assertEqual(
1517+
output().text,
1518+
'input="Initial Inputx", state="Initial State"')
1519+
1520+
input().send_keys('y')
1521+
wait_for(lambda: call_count.value == 3)
1522+
self.assertEqual(
1523+
output().text,
1524+
'input="Initial Inputxy", state="Initial Statex"')
1525+
1526+
def test_simple_callback(self):
1527+
app = dash.Dash(__name__)
1528+
app.layout = html.Div([
1529+
dcc.Input(
1530+
id='input',
1531+
),
1532+
html.Div(
1533+
html.Div([
1534+
1.5,
1535+
None,
1536+
'string',
1537+
html.Div(id='output-1')
1538+
])
1539+
)
1540+
])
1541+
1542+
call_count = Value('i', 0)
1543+
1544+
@app.callback(Output('output-1', 'children'), [Input('input', 'value')])
1545+
def update_output(value):
1546+
call_count.value = call_count.value + 1
1547+
return value
1548+
1549+
self.startServer(app)
1550+
1551+
input1 = self.wait_for_element_by_css_selector('#input')
1552+
input1.send_keys('hello world')
1553+
output1 = self.wait_for_element_by_css_selector('#output-1')
1554+
self.wait_for_text_to_equal('#output-1', 'hello world')
1555+
output1.click() # Lose focus, no callback sent for value.
1556+
1557+
self.assertEqual(
1558+
call_count.value,
1559+
# an initial call to retrieve the first value
1560+
1 +
1561+
# one for each hello world character
1562+
len('hello world')
1563+
)
1564+

packages/dash-core-components/test/utils.py

+50
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import time
22

3+
TIMEOUT = 20
4+
5+
6+
class WaitForTimeout(Exception):
7+
"""This should only be raised inside the `wait_for` function."""
8+
pass
9+
310

411
def assert_clean_console(TestClass):
512
def assert_no_console_errors(TestClass):
@@ -20,3 +27,46 @@ def assert_no_console_warnings(TestClass):
2027

2128
assert_no_console_warnings(TestClass)
2229
assert_no_console_errors(TestClass)
30+
31+
32+
def wait_for(condition_function, get_message=lambda: '', *args, **kwargs):
33+
"""
34+
Waits for condition_function to return True or raises WaitForTimeout.
35+
:param (function) condition_function: Should return True on success.
36+
:param args: Optional args to pass to condition_function.
37+
:param kwargs: Optional kwargs to pass to condition_function.
38+
if `timeout` is in kwargs, it will be used to override TIMEOUT
39+
:raises: WaitForTimeout If condition_function doesn't return True in time.
40+
Usage:
41+
def get_element(selector):
42+
# some code to get some element or return a `False`-y value.
43+
selector = '.js-plotly-plot'
44+
try:
45+
wait_for(get_element, selector)
46+
except WaitForTimeout:
47+
self.fail('element never appeared...')
48+
plot = get_element(selector) # we know it exists.
49+
"""
50+
def wrapped_condition_function():
51+
"""We wrap this to alter the call base on the closure."""
52+
if args and kwargs:
53+
return condition_function(*args, **kwargs)
54+
if args:
55+
return condition_function(*args)
56+
if kwargs:
57+
return condition_function(**kwargs)
58+
return condition_function()
59+
60+
if 'timeout' in kwargs:
61+
timeout = kwargs['timeout']
62+
del kwargs['timeout']
63+
else:
64+
timeout = TIMEOUT
65+
66+
start_time = time.time()
67+
while time.time() < start_time + timeout:
68+
if wrapped_condition_function():
69+
return True
70+
time.sleep(0.5)
71+
72+
raise WaitForTimeout(get_message())

0 commit comments

Comments
 (0)