|
1 | 1 | import json
|
2 |
| -import time |
| 2 | +import hashlib |
3 | 3 | import itertools
|
4 |
| -from pytest import approx |
5 | 4 | import dash
|
6 | 5 | from dash.dependencies import Input, Output, State
|
7 | 6 | from dash.exceptions import PreventUpdate
|
8 | 7 | import dash_core_components as dcc
|
9 | 8 | import dash_html_components as html
|
10 | 9 |
|
11 | 10 |
|
12 |
| -def test_stco001_storage_component_smoke(store_app, dash_duo): |
13 |
| - |
| 11 | +def test_stda001_data_types(dash_duo): |
14 | 12 | app = dash.Dash(__name__)
|
| 13 | + |
| 14 | + types = [ |
| 15 | + ("str", "hello"), |
| 16 | + ("number", 1), |
| 17 | + ("dict", {"data": [2, 3, None]}), |
| 18 | + ("list", [5, -6, 700000, 1e-12]), |
| 19 | + ("null", None), |
| 20 | + ("bool", True), |
| 21 | + ("bool", False), |
| 22 | + ("empty-dict", {}), |
| 23 | + ] |
| 24 | + data_types = list( |
| 25 | + itertools.chain(*itertools.combinations(types, 2)) |
| 26 | + ) + [ # No combinations as it add much test time. |
| 27 | + ("list-dict-1", [1, 2, {"data": [55, 66, 77], "dummy": "dum"}]), |
| 28 | + ("list-dict-2", [1, 2, {"data": [111, 99, 88]}]), |
| 29 | + ("dict-3", {"a": 1, "c": 1}), |
| 30 | + ("dict-2", {"a": 1, "b": None}), |
| 31 | + ] |
| 32 | + |
15 | 33 | app.layout = html.Div(
|
16 | 34 | [
|
17 |
| - dcc.Store(id="memory", storage_type="memory"), |
18 |
| - dcc.Store(id="storage", storage_type="local"), |
19 |
| - dcc.Store(id="session", storage_type="session", data=dummy_data), |
20 |
| - dcc.Store(id="initial-storage", storage_type="session"), |
21 |
| - html.Button("click me", id="btn"), |
22 |
| - html.Button("clear", id="clear-btn"), |
23 |
| - html.Button("set-init-storage", id="set-init-storage"), |
24 |
| - html.Div(id="memory-output"), |
25 |
| - html.Div(id="init-output"), |
| 35 | + html.Div(id="output"), |
| 36 | + html.Button("click", id="click"), |
| 37 | + dcc.Store(id="store"), |
26 | 38 | ]
|
27 | 39 | )
|
28 | 40 |
|
29 | 41 | @app.callback(
|
30 |
| - Output("storage", "data"), |
31 |
| - [Input("btn", "n_clicks")], |
32 |
| - [State("storage", "data")], |
33 |
| - ) |
34 |
| - def on_click(n_clicks, storage): |
35 |
| - if n_clicks is None: |
36 |
| - return |
37 |
| - storage = storage or {} |
38 |
| - return {"clicked": storage.get("clicked", 0) + 1} |
39 |
| - |
40 |
| - @app.callback( |
41 |
| - Output("storage", "clear_data"), [Input("clear-btn", "n_clicks")] |
42 |
| - ) |
43 |
| - def on_clear(n_clicks): |
44 |
| - if n_clicks is None: |
45 |
| - return |
46 |
| - return True |
47 |
| - |
48 |
| - @app.callback(Output("memory", "data"), [Input("storage", "data")]) |
49 |
| - def on_memory(data): |
50 |
| - return data |
51 |
| - |
52 |
| - @app.callback( |
53 |
| - Output("memory-output", "children"), [Input("memory", "data")] |
| 42 | + Output("output", "children"), |
| 43 | + [Input("store", "modified_timestamp")], |
| 44 | + [State("store", "data")], |
54 | 45 | )
|
55 |
| - def on_memory_json(data): |
56 |
| - if data is None: |
57 |
| - return "" |
| 46 | + def on_data(ts, data): |
| 47 | + if ts is None: |
| 48 | + raise PreventUpdate |
58 | 49 | return json.dumps(data)
|
59 | 50 |
|
60 |
| - @app.callback( |
61 |
| - Output("initial-storage", "data"), |
62 |
| - [Input("set-init-storage", "n_clicks")], |
63 |
| - ) |
64 |
| - def on_init(n_clicks): |
| 51 | + @app.callback(Output("store", "data"), [Input("click", "n_clicks")]) |
| 52 | + def on_click(n_clicks): |
65 | 53 | if n_clicks is None:
|
66 | 54 | raise PreventUpdate
|
67 |
| - |
68 |
| - return "initialized" |
69 |
| - |
70 |
| - @app.callback( |
71 |
| - Output("init-output", "children"), |
72 |
| - [Input("initial-storage", "modified_timestamp")], |
73 |
| - [State("initial-storage", "data")], |
74 |
| - ) |
75 |
| - def init_output(ts, data): |
76 |
| - return json.dumps({"data": data, "ts": ts}) |
| 55 | + return data_types[n_clicks - 1][1] |
77 | 56 |
|
78 | 57 | dash_duo.start_server(app)
|
79 | 58 |
|
80 |
| - getter = 'return JSON.parse(window.{}.getItem("{}"));' |
81 |
| - clicked_getter = getter.format("localStorage", "storage") |
82 |
| - |
83 |
| - session = dash_duo.driver.execute_script( |
84 |
| - getter.format("sessionStorage", "session") |
85 |
| - ) |
86 |
| - assert dummy_data == session |
87 |
| - |
88 |
| - for i in range(1, 11): |
89 |
| - dash_duo.find_element("#btn").click() |
90 |
| - click_data = dash_duo.driver.execute_script(clicked_getter) |
91 |
| - assert i == click_data.get("clicked") |
92 |
| - mem = dash_duo.wait_for_element("#memory-output") |
93 |
| - assert i == int(json.loads(mem.text).get("clicked")) |
94 |
| - |
95 |
| - |
96 |
| - # Test initial timestamp output |
97 |
| - dash_duo.find_element("#set-init-storage").click() |
98 |
| - # the python ts ends at seconds while javascript one ends at ms |
99 |
| - ts = float(time.time() * 1000) |
100 |
| - dash_duo.driver.refresh() |
101 |
| - init = json.loads(dash_duo.wait_for_element("#init-output").text) |
102 |
| - |
103 |
| - assert ts == approx(init.get("ts"), abs=10) |
104 |
| - assert init.get("data") == "initialized" |
| 59 | + button = dash_duo.wait_for_element("#click") |
| 60 | + for data_type in data_types: |
| 61 | + button.click() |
| 62 | + dash_duo.wait_for_text_to_equal("#output", json.dumps(data_type[1])) |
105 | 63 |
|
106 | 64 |
|
107 |
| -def test_store_nested_data(dash_duo): |
| 65 | +def test_stda002_nested_data(dash_duo): |
108 | 66 | app = dash.Dash(__name__)
|
109 | 67 |
|
110 | 68 | nested = {"nested": {"nest": "much"}}
|
@@ -148,68 +106,75 @@ def on_ts(ts, data):
|
148 | 106 |
|
149 | 107 | dash_duo.start_server(app)
|
150 | 108 |
|
151 |
| - obj_btn = dash_duo.wait_for_element_by_css_selector("#obj-btn") |
152 |
| - list_btn = dash_duo.wait_for_element_by_css_selector("#list-btn") |
| 109 | + obj_btn = dash_duo.wait_for_element("#obj-btn") |
| 110 | + list_btn = dash_duo.find_element("#list-btn") |
153 | 111 |
|
154 | 112 | obj_btn.click()
|
155 |
| - time.sleep(1) |
156 | 113 | dash_duo.wait_for_text_to_equal("#output", json.dumps(nested))
|
157 | 114 | # it would of crashed the app before adding the recursive check.
|
158 | 115 |
|
159 | 116 | list_btn.click()
|
160 |
| - time.sleep(1) |
161 | 117 | dash_duo.wait_for_text_to_equal("#output", json.dumps(nested_list))
|
162 | 118 |
|
163 | 119 |
|
164 |
| -def test_stco003_data_type_updates(dash_duo): |
165 |
| - app = dash.Dash(__name__) |
166 |
| - |
167 |
| - types = [ |
168 |
| - ("str", "hello"), |
169 |
| - ("number", 1), |
170 |
| - ("dict", {"data": [2, 3, None]}), |
171 |
| - ("list", [5, -6, 700000, 1e-12]), |
172 |
| - ("null", None), |
173 |
| - ("bool", True), |
174 |
| - ("bool", False), |
175 |
| - ("empty-dict", {}), |
176 |
| - ] |
177 |
| - types_changes = list( |
178 |
| - itertools.chain(*itertools.combinations(types, 2)) |
179 |
| - ) + [ # No combinations as it add much test time. |
180 |
| - ("list-dict-1", [1, 2, {"data": [55, 66, 77], "dummy": "dum"}]), |
181 |
| - ("list-dict-2", [1, 2, {"data": [111, 99, 88]}]), |
182 |
| - ("dict-3", {"a": 1, "c": 1}), |
183 |
| - ("dict-2", {"a": 1, "b": None}), |
184 |
| - ] |
| 120 | +def test_stda003_data_size_limit(fake_data, dash_duo): |
| 121 | + def fingerprint(data): |
| 122 | + return hashlib.sha1(data.encode("utf-8")).hexdigest() |
185 | 123 |
|
| 124 | + app = dash.Dash(__name__) |
186 | 125 | app.layout = html.Div(
|
187 | 126 | [
|
188 |
| - html.Div(id="output"), |
189 |
| - html.Button("click", id="click"), |
190 |
| - dcc.Store(id="store"), |
| 127 | + dcc.Store(id="memory", storage_type="memory"), |
| 128 | + dcc.Store(id="local", storage_type="local"), |
| 129 | + dcc.Store(id="session", storage_type="session"), |
| 130 | + html.Button("big data", id="btn"), |
| 131 | + html.Div(id="mout"), |
| 132 | + html.Div(id="sout"), |
| 133 | + html.Div(id="lout"), |
191 | 134 | ]
|
192 | 135 | )
|
193 | 136 |
|
194 | 137 | @app.callback(
|
195 |
| - Output("output", "children"), |
196 |
| - [Input("store", "modified_timestamp")], |
197 |
| - [State("store", "data")], |
| 138 | + [ |
| 139 | + Output("mout", "children"), |
| 140 | + Output("sout", "children"), |
| 141 | + Output("lout", "children"), |
| 142 | + ], |
| 143 | + [ |
| 144 | + Input("memory", "modified_timestamp"), |
| 145 | + Input("session", "modified_timestamp"), |
| 146 | + Input("local", "modified_timestamp"), |
| 147 | + ], |
| 148 | + [ |
| 149 | + State("memory", "data"), |
| 150 | + State("session", "data"), |
| 151 | + State("local", "data"), |
| 152 | + ], |
198 | 153 | )
|
199 |
| - def on_data(ts, data): |
200 |
| - if ts is None: |
201 |
| - raise PreventUpdate |
202 |
| - return json.dumps(data) |
| 154 | + def update_output(mts, sts, lts, mdata, sdata, ldata): |
| 155 | + if None in {mdata, sdata, ldata}: |
| 156 | + return ("nil",) * 3 |
| 157 | + return [fingerprint(data) for data in (mdata, sdata, ldata)] |
203 | 158 |
|
204 |
| - @app.callback(Output("store", "data"), [Input("click", "n_clicks")]) |
| 159 | + @app.callback( |
| 160 | + [ |
| 161 | + Output("memory", "data"), |
| 162 | + Output("local", "data"), |
| 163 | + Output("session", "data"), |
| 164 | + ], |
| 165 | + [Input("btn", "n_clicks")], |
| 166 | + ) |
205 | 167 | def on_click(n_clicks):
|
206 | 168 | if n_clicks is None:
|
207 | 169 | raise PreventUpdate
|
208 |
| - return types_changes[n_clicks - 1][1] |
| 170 | + return (fake_data,) * 3 |
209 | 171 |
|
210 | 172 | dash_duo.start_server(app)
|
| 173 | + outputs = ('#mout', '#lout', '#sout') |
| 174 | + for output in outputs: |
| 175 | + assert dash_duo.find_element(output).text == 'nil' |
| 176 | + |
| 177 | + dash_duo.find_element('#btn').click() |
| 178 | + for output in outputs: |
| 179 | + dash_duo.wait_for_text_to_equal(output, fingerprint(fake_data)) |
211 | 180 |
|
212 |
| - button = dash_duo.wait_for_element("#click") |
213 |
| - for type_change in types_changes: |
214 |
| - button.click() |
215 |
| - dash_duo.wait_for_text_to_equal("#output", json.dumps(type_change[1])) |
0 commit comments