Skip to content

Commit 540d3f9

Browse files
committed
Merge pull request plotly#140 from plotly/fix-ie11
Fix Link and Location for IE
1 parent b721680 commit 540d3f9

File tree

6 files changed

+96
-152
lines changed

6 files changed

+96
-152
lines changed

packages/dash-core-components/CHANGELOG.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
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.15.5] - 2017-01-08
6+
### Fixed
7+
- The `dash_core_components.Location` and `dash_core_components.Link` properties
8+
should now work on Internet Explorer.
9+
Thanks to @nedned for suggesting a solution.
10+
Fixes https://github.com/plotly/dash-core-components/pull/113
11+
512
## [0.15.4] - 2017-12-21
613
### Changed
714
- The `dash_core_components.Location` component now supports `hash`,
815
`href`, and `search` in addition to the already supported `pathname`
916
(mimicking the `window.location` API). `href` can be used to handle
1017
`pathname`, `hash`, and `search` in aggregate, or each can be manipulated
1118
independently.
12-
- The `children` property of `dash_core_components.Markdown` and
13-
`dash_core_components.SyntaxHighlighter` now accepts an
19+
- The `children` property of `dash_core_components.Markdown` and
20+
`dash_core_components.SyntaxHighlighter` now accepts an
1421
array of strings (previously it *had* to be a string). Now,
15-
if an array is provided, it is collapsed into a string with line
22+
if an array is provided, it is collapsed into a string with line
1623
breaks (see #134).
1724

1825
## [0.15.3] - 2017-12-11
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.15.4'
1+
__version__ = '0.15.5'

packages/dash-core-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dash-core-components",
3-
"version": "0.15.4",
3+
"version": "0.15.5",
44
"description": "Core component suite for Dash",
55
"repository": {
66
"type": "git",

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
import React, {Component, PropTypes} from 'react';
44

5+
/*
6+
* event polyfill for IE
7+
* https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
8+
*/
9+
function CustomEvent (event, params) {
10+
params = params || {
11+
bubbles: false,
12+
cancelable: false,
13+
detail: undefined
14+
};
15+
var evt = document.createEvent('CustomEvent');
16+
evt.initCustomEvent(
17+
event, params.bubbles, params.cancelable, params.detail);
18+
return evt;
19+
}
20+
CustomEvent.prototype = window.Event.prototype;
21+
522
export default class Link extends Component {
623
constructor(props) {
724
super(props);
@@ -14,7 +31,7 @@ export default class Link extends Component {
1431
window.location.pathname = href;
1532
} else {
1633
window.history.pushState({}, '', href);
17-
window.dispatchEvent(new Event('onpushstate'));
34+
window.dispatchEvent(new CustomEvent('onpushstate'));
1835
}
1936
}
2037

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

Lines changed: 66 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
import dash_table_experiments as dt
1515
from selenium import webdriver
1616
from selenium.webdriver.common.keys import Keys
17+
import time
1718

1819
try:
1920
from urlparse import urlparse
2021
except ImportError:
2122
from urllib.parse import urlparse
2223

2324
from .IntegrationTests import IntegrationTests
24-
from .utils import invincible, wait_for, waiter
2525

2626
# Download geckodriver: https://github.com/mozilla/geckodriver/releases
2727
# And add to path:
@@ -34,21 +34,28 @@
3434

3535
class Tests(IntegrationTests):
3636
def setUp(self):
37-
def wait_for_element_by_id(id):
38-
wait_for(lambda: None is not invincible(
39-
lambda: self.driver.find_element_by_id(id)
40-
))
41-
return self.driver.find_element_by_id(id)
42-
self.wait_for_element_by_id = wait_for_element_by_id
43-
44-
def wait_for_element_by_css_selector(css_selector):
45-
wait_for(lambda: None is not invincible(
46-
lambda: self.driver.find_element_by_css_selector(css_selector)
47-
))
48-
return self.driver.find_element_by_css_selector(css_selector)
49-
self.wait_for_element_by_css_selector = (
50-
wait_for_element_by_css_selector
51-
)
37+
pass
38+
39+
def wait_for_element_by_css_selector(self, selector):
40+
start_time = time.time()
41+
while time.time() < start_time + 20:
42+
try:
43+
return self.driver.find_element_by_css_selector(selector)
44+
except Exception as e:
45+
pass
46+
time.sleep(0.25)
47+
raise e
48+
49+
def wait_for_text_to_equal(self, selector, assertion_text):
50+
start_time = time.time()
51+
while time.time() < start_time + 20:
52+
el = self.wait_for_element_by_css_selector(selector)
53+
try:
54+
return self.assertEqual(el.text, assertion_text)
55+
except Exception as e:
56+
pass
57+
time.sleep(0.25)
58+
raise e
5259

5360
def snapshot(self, name):
5461
if 'PERCY_PROJECT' in os.environ and 'PERCY_TOKEN' in os.environ:
@@ -100,7 +107,9 @@ def update_output(contents):
100107
df = pd.read_csv(io.StringIO(base64.b64decode(
101108
content_string).decode('utf-8')))
102109
return html.Div([
103-
dt.DataTable(rows=df.to_dict('records')),
110+
dt.DataTable(
111+
rows=df.to_dict('records'),
112+
columns=['city', 'country']),
104113
html.Hr(),
105114
html.Div('Raw Content'),
106115
html.Pre(contents, style=pre_style)
@@ -109,7 +118,9 @@ def update_output(contents):
109118
df = pd.read_excel(io.BytesIO(base64.b64decode(
110119
content_string)))
111120
return html.Div([
112-
dt.DataTable(rows=df.to_dict('records')),
121+
dt.DataTable(
122+
rows=df.to_dict('records'),
123+
columns=['city', 'country']),
113124
html.Hr(),
114125
html.Div('Raw Content'),
115126
html.Pre(contents, style=pre_style)
@@ -131,10 +142,10 @@ def update_output(contents):
131142
self.startServer(app)
132143

133144
try:
134-
self.wait_for_element_by_id('waitfor')
145+
self.wait_for_element_by_css_selector('#waitfor')
135146
except Exception as e:
136-
print(self.wait_for_element_by_id(
137-
'_dash-app-content').get_attribute('innerHTML'))
147+
print(self.wait_for_element_by_css_selector(
148+
'#_dash-app-content').get_attribute('innerHTML'))
138149
raise e
139150

140151
upload_div = self.wait_for_element_by_css_selector(
@@ -189,10 +200,10 @@ def test_upload_gallery(self):
189200
self.startServer(app)
190201

191202
try:
192-
self.wait_for_element_by_id('waitfor')
203+
self.wait_for_element_by_css_selector('#waitfor')
193204
except Exception as e:
194-
print(self.wait_for_element_by_id(
195-
'_dash-app-content').get_attribute('innerHTML'))
205+
print(self.wait_for_element_by_css_selector(
206+
'#_dash-app-content').get_attribute('innerHTML'))
196207
raise e
197208

198209
self.snapshot('test_upload_gallery')
@@ -311,12 +322,7 @@ def test_gallery(self):
311322
])
312323
self.startServer(app)
313324

314-
try:
315-
self.wait_for_element_by_id('waitfor')
316-
except Exception as e:
317-
print(self.wait_for_element_by_id(
318-
'_dash-app-content').get_attribute('innerHTML'))
319-
raise e
325+
self.wait_for_element_by_css_selector('#waitfor')
320326

321327
self.snapshot('gallery')
322328

@@ -393,60 +399,55 @@ def update_pathname(n_clicks, current_pathname):
393399
self.snapshot('link -- location')
394400

395401
# Check that link updates pathname
396-
self.driver.find_element_by_id('test-link').click()
397-
398-
self.snapshot('link --- /test/pathname')
402+
self.wait_for_element_by_css_selector('#test-link').click()
399403
self.assertEqual(
400404
self.driver.current_url.replace('http://localhost:8050', ''),
401405
'/test/pathname')
402-
self.assertEqual(
403-
self.driver.find_element_by_id('test-pathname').text,
404-
'/test/pathname')
406+
self.wait_for_text_to_equal('#test-pathname', '/test/pathname')
405407

406408
# Check that hash is updated in the Location
407-
self.driver.find_element_by_id('test-link-hash').click()
408-
409+
self.wait_for_element_by_css_selector('#test-link-hash').click()
410+
self.wait_for_text_to_equal('#test-pathname', '/test/pathname')
411+
self.wait_for_text_to_equal('#test-hash', '#test')
409412
self.snapshot('link -- /test/pathname#test')
410-
self.assertEqual(self.driver.find_element_by_id('test-pathname').text, '/test/pathname')
411-
self.assertEqual(self.driver.find_element_by_id('test-hash').text, '#test')
412413

413414
# Check that search is updated in the Location -- note that this goes through href and therefore wipes the hash
414-
self.driver.find_element_by_id('test-link-search').click()
415-
415+
self.wait_for_element_by_css_selector('#test-link-search').click()
416+
self.wait_for_text_to_equal('#test-search', '?testQuery=testValue')
417+
self.wait_for_text_to_equal('#test-hash', '')
416418
self.snapshot('link -- /test/pathname?testQuery=testValue')
417-
self.assertEqual(self.driver.find_element_by_id('test-search').text, '?testQuery=testValue')
418-
self.assertEqual(self.driver.find_element_by_id('test-hash').text, '')
419419

420420
# Check that pathname is updated through a Button click via props
421-
self.driver.find_element_by_id('test-button').click()
422-
time.sleep(1) # Need to wait for the callback to fire TODO is there a better way to wait?
423-
421+
self.wait_for_element_by_css_selector('#test-button').click()
422+
self.wait_for_text_to_equal('#test-pathname', '/new/pathname')
423+
self.wait_for_text_to_equal('#test-search', '?testQuery=testValue')
424424
self.snapshot('link -- /new/pathname?testQuery=testValue')
425-
self.assertEqual(self.driver.find_element_by_id('test-pathname').text, '/new/pathname')
426-
self.assertEqual(self.driver.find_element_by_id('test-search').text, '?testQuery=testValue')
427425

428426
# Check that pathname is updated through an a tag click via props
429-
self.driver.find_element_by_id('test-a').click()
430-
waiter(self.wait_for_element_by_id)
427+
self.wait_for_element_by_css_selector('#test-a').click()
428+
try:
429+
self.wait_for_element_by_css_selector('#waitfor')
430+
except Exception as e:
431+
print(self.wait_for_element_by_css_selector(
432+
'#_dash-app-content').get_attribute('innerHTML'))
433+
raise e
431434

435+
self.wait_for_text_to_equal('#test-pathname', '/test/pathname/a')
436+
self.wait_for_text_to_equal('#test-search', '')
437+
self.wait_for_text_to_equal('#test-hash', '')
432438
self.snapshot('link -- /test/pathname/a')
433-
self.assertEqual(self.driver.find_element_by_id('test-pathname').text, '/test/pathname/a')
434-
self.assertEqual(self.driver.find_element_by_id('test-search').text, '')
435-
self.assertEqual(self.driver.find_element_by_id('test-hash').text, '')
436439

437440
# Check that hash is updated through an a tag click via props
438-
self.driver.find_element_by_id('test-a-hash').click()
439-
441+
self.wait_for_element_by_css_selector('#test-a-hash').click()
442+
self.wait_for_text_to_equal('#test-pathname', '/test/pathname/a')
443+
self.wait_for_text_to_equal('#test-search', '')
444+
self.wait_for_text_to_equal('#test-hash', '#test-hash')
440445
self.snapshot('link -- /test/pathname/a#test-hash')
441-
self.assertEqual(self.driver.find_element_by_id('test-pathname').text, '/test/pathname/a')
442-
self.assertEqual(self.driver.find_element_by_id('test-search').text, '')
443-
self.assertEqual(self.driver.find_element_by_id('test-hash').text, '#test-hash')
444446

445447
# Check that hash is updated through an a tag click via props
446-
self.driver.find_element_by_id('test-a-query').click()
447-
waiter(self.wait_for_element_by_id)
448-
448+
self.wait_for_element_by_css_selector('#test-a-query').click()
449+
self.wait_for_element_by_css_selector('#waitfor')
450+
self.wait_for_text_to_equal('#test-pathname', '/test/pathname/a')
451+
self.wait_for_text_to_equal('#test-search', '?queryA=valueA')
452+
self.wait_for_text_to_equal('#test-hash', '')
449453
self.snapshot('link -- /test/pathname/a?queryA=valueA')
450-
self.assertEqual(self.driver.find_element_by_id('test-pathname').text, '/test/pathname/a')
451-
self.assertEqual(self.driver.find_element_by_id('test-search').text, '?queryA=valueA')
452-
self.assertEqual(self.driver.find_element_by_id('test-hash').text, '')

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

Lines changed: 0 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,6 @@
11
import time
22

33

4-
TIMEOUT = 20 # Seconds
5-
6-
7-
def invincible(func):
8-
def wrap():
9-
try:
10-
return func()
11-
except:
12-
pass
13-
return wrap
14-
15-
16-
class WaitForTimeout(Exception):
17-
"""This should only be raised inside the `wait_for` function."""
18-
pass
19-
20-
21-
def wait_for(condition_function, get_message=lambda: '', *args, **kwargs):
22-
"""
23-
Waits for condition_function to return True or raises WaitForTimeout.
24-
:param (function) condition_function: Should return True on success.
25-
:param args: Optional args to pass to condition_function.
26-
:param kwargs: Optional kwargs to pass to condition_function.
27-
if `timeout` is in kwargs, it will be used to override TIMEOUT
28-
:raises: WaitForTimeout If condition_function doesn't return True in time.
29-
Usage:
30-
def get_element(selector):
31-
# some code to get some element or return a `False`-y value.
32-
selector = '.js-plotly-plot'
33-
try:
34-
wait_for(get_element, selector)
35-
except WaitForTimeout:
36-
self.fail('element never appeared...')
37-
plot = get_element(selector) # we know it exists.
38-
"""
39-
def wrapped_condition_function():
40-
"""We wrap this to alter the call base on the closure."""
41-
if args and kwargs:
42-
return condition_function(*args, **kwargs)
43-
if args:
44-
return condition_function(*args)
45-
if kwargs:
46-
return condition_function(**kwargs)
47-
return condition_function()
48-
49-
if 'timeout' in kwargs:
50-
timeout = kwargs['timeout']
51-
del kwargs['timeout']
52-
else:
53-
timeout = TIMEOUT
54-
55-
start_time = time.time()
56-
while time.time() < start_time + timeout:
57-
if wrapped_condition_function():
58-
return True
59-
time.sleep(0.5)
60-
61-
raise WaitForTimeout(get_message())
62-
63-
64-
def waiter(waiter_func, waitfor_string='waitfor'):
65-
"""
66-
Wait for an
67-
:param waiter_func: Function that takes a string and waits
68-
:param waitfor_string: String to wait for (an id or class name, e.g., dependeing on waiter_func).
69-
Defaults to 'waitfor'
70-
Usage:
71-
def get_element(selector):
72-
# some code to get some element or return a `False`-y value.
73-
selector = '.js-plotly-plot'
74-
waiter(my_waiting_function, selector) # Throws an error if the selector is not found
75-
plot = get_element(selector) # we know it exists.
76-
"""
77-
time.sleep(1)
78-
try:
79-
waiter_func(waitfor_string)
80-
except Exception as e:
81-
print(waiter_func('_dash-app-content').get_attribute('innerHTML'))
82-
raise e
83-
84-
854
def assert_clean_console(TestClass):
865
def assert_no_console_errors(TestClass):
876
TestClass.assertEqual(

0 commit comments

Comments
 (0)