2727
2828'''
2929
30- import pytest
3130import json
3231from unittest .mock import patch
3332
34- #pylint: disable=bare-except
33+ import pytest
34+ # pylint: disable=bare-except
3535from dash .dependencies import Input
36+ from django .urls import reverse
3637
3738from django_plotly_dash import DjangoDash
39+ from django_plotly_dash .dash_wrapper import get_local_stateless_list , get_local_stateless_by_name
40+ from django_plotly_dash .models import DashApp , find_stateless_by_name
41+ from django_plotly_dash .tests_dash_contract import fill_in_test_app , dash_contract_data
3842
3943
4044def test_dash_app ():
@@ -48,6 +52,156 @@ def test_dash_app():
4852 assert str (stateless_a ) == stateless_a .app_name
4953
5054
55+ @pytest .mark .django_db
56+ def test_dash_stateful_app_client_contract (client ):
57+ 'Test the state management of a DashApp as well as the contract between the client and the Dash app'
58+
59+ from django_plotly_dash .models import StatelessApp
60+
61+ # create a DjangoDash, StatelessApp and DashApp
62+ ddash = DjangoDash (name = "DDash" )
63+ fill_in_test_app (ddash , write = False )
64+
65+ stateless_a = StatelessApp (app_name = "DDash" )
66+ stateless_a .save ()
67+ stateful_a = DashApp (stateless_app = stateless_a ,
68+ instance_name = "Some name" ,
69+ slug = "my-app" , save_on_change = True )
70+ stateful_a .save ()
71+
72+ # check app can be found back
73+ assert "DDash" in get_local_stateless_list ()
74+ assert get_local_stateless_by_name ("DDash" ) == ddash
75+ assert find_stateless_by_name ("DDash" ) == ddash
76+
77+ # check the current_state is empty
78+ assert stateful_a .current_state () == {}
79+
80+ # set the initial expected state
81+ expected_state = {'inp1' : {'n_clicks' : 0 , 'n_clicks_timestamp' : 1611733453854 },
82+ 'inp2' : {'n_clicks' : 5 , 'n_clicks_timestamp' : 1611733454354 },
83+ 'out1-0' : {'n_clicks' : 1 , 'n_clicks_timestamp' : 1611733453954 },
84+ 'out1-1' : {'n_clicks' : 2 , 'n_clicks_timestamp' : 1611733454054 },
85+ 'out1-2' : {'n_clicks' : 3 , 'n_clicks_timestamp' : 1611733454154 },
86+ 'out1-3' : {'n_clicks' : 4 , 'n_clicks_timestamp' : 1611733454254 },
87+ 'out2-0' : {'n_clicks' : 6 , 'n_clicks_timestamp' : 1611733454454 },
88+ 'out3' : {'n_clicks' : 10 , 'n_clicks_timestamp' : 1611733454854 },
89+ 'out4' : {'n_clicks' : 14 , 'n_clicks_timestamp' : 1611733455254 },
90+ 'out5' : {'n_clicks' : 18 , 'n_clicks_timestamp' : 1611733455654 },
91+ '{"_id":"inp-0","_type":"btn3"}' : {'n_clicks' : 7 ,
92+ 'n_clicks_timestamp' : 1611733454554 },
93+ '{"_id":"inp-0","_type":"btn4"}' : {'n_clicks' : 11 ,
94+ 'n_clicks_timestamp' : 1611733454954 },
95+ '{"_id":"inp-0","_type":"btn5"}' : {'n_clicks' : 15 ,
96+ 'n_clicks_timestamp' : 1611733455354 },
97+ '{"_id":"inp-1","_type":"btn3"}' : {'n_clicks' : 8 ,
98+ 'n_clicks_timestamp' : 1611733454654 },
99+ '{"_id":"inp-1","_type":"btn4"}' : {'n_clicks' : 12 ,
100+ 'n_clicks_timestamp' : 1611733455054 },
101+ '{"_id":"inp-1","_type":"btn5"}' : {'n_clicks' : 16 ,
102+ 'n_clicks_timestamp' : 1611733455454 },
103+ '{"_id":"inp-2","_type":"btn3"}' : {'n_clicks' : 9 ,
104+ 'n_clicks_timestamp' : 1611733454754 },
105+ '{"_id":"inp-2","_type":"btn4"}' : {'n_clicks' : 13 ,
106+ 'n_clicks_timestamp' : 1611733455154 },
107+ '{"_id":"inp-2","_type":"btn5"}' : {'n_clicks' : 17 ,
108+ 'n_clicks_timestamp' : 1611733455554 }}
109+
110+ ########## test state management of the app and conversion of components ids
111+ # search for state values in dash layout
112+ stateful_a .populate_values ()
113+ assert stateful_a .current_state () == expected_state
114+ assert stateful_a .have_current_state_entry ("inp1" , "n_clicks" )
115+ assert stateful_a .have_current_state_entry ({"_type" : "btn3" , "_id" : "inp-0" }, "n_clicks_timestamp" )
116+ assert stateful_a .have_current_state_entry ('{"_id":"inp-0","_type":"btn3"}' , "n_clicks_timestamp" )
117+ assert not stateful_a .have_current_state_entry ("checklist" , "other-prop" )
118+
119+ # update a non existent state => no effect on current_state
120+ stateful_a .update_current_state ("foo" , "value" , "random" )
121+ assert stateful_a .current_state () == expected_state
122+
123+ # update an existent state => update current_state
124+ stateful_a .update_current_state ('{"_id":"inp-2","_type":"btn5"}' , "n_clicks" , 100 )
125+ expected_state ['{"_id":"inp-2","_type":"btn5"}' ] = {'n_clicks' : 100 , 'n_clicks_timestamp' : 1611733455554 }
126+ assert stateful_a .current_state () == expected_state
127+
128+ assert DashApp .objects .get (instance_name = "Some name" ).current_state () == {}
129+
130+ stateful_a .handle_current_state ()
131+
132+ assert DashApp .objects .get (instance_name = "Some name" ).current_state () == expected_state
133+
134+ # check initial layout serve has the correct values injected
135+ dash_instance = stateful_a .as_dash_instance ()
136+ resp = dash_instance .serve_layout ()
137+
138+ # initialise layout with app state
139+ layout , mimetype = dash_instance .augment_initial_layout (resp , {})
140+ assert '"n_clicks": 100' in layout
141+
142+ # initialise layout with initial arguments
143+ layout , mimetype = dash_instance .augment_initial_layout (resp , {
144+ '{"_id":"inp-2","_type":"btn5"}' : {"n_clicks" : 200 }})
145+ assert '"n_clicks": 100' not in layout
146+ assert '"n_clicks": 200' in layout
147+
148+ ########### test contract between client and app by replaying interactions recorded in tests_dash_contract.json
149+ # get update component route
150+ url = reverse ('the_django_plotly_dash:update-component' , kwargs = {'ident' : 'my-app' })
151+
152+ # for all interactions in the tests_dash_contract.json
153+ for scenario in json .load (dash_contract_data .open ("r" )):
154+ body = scenario ["body" ]
155+
156+ response = client .post (url , json .dumps (body ), content_type = "application/json" )
157+
158+ assert response .status_code == 200
159+
160+ response = json .loads (response .content )
161+
162+ # compare first item in response with first result
163+ result = scenario ["result" ]
164+ if isinstance (result , list ):
165+ result = result [0 ]
166+ content = response ["response" ].popitem ()[1 ].popitem ()[1 ]
167+ assert content == result
168+
169+ # handle state
170+ stateful_a .handle_current_state ()
171+
172+ # check final state has been changed accordingly
173+ final_state = {'inp1' : {'n_clicks' : 1 , 'n_clicks_timestamp' : 1611736145932 },
174+ 'inp2' : {'n_clicks' : 6 , 'n_clicks_timestamp' : 1611736146875 },
175+ 'out1-0' : {'n_clicks' : 1 , 'n_clicks_timestamp' : 1611733453954 },
176+ 'out1-1' : {'n_clicks' : 2 , 'n_clicks_timestamp' : 1611733454054 },
177+ 'out1-2' : {'n_clicks' : 3 , 'n_clicks_timestamp' : 1611733454154 },
178+ 'out1-3' : {'n_clicks' : 4 , 'n_clicks_timestamp' : 1611733454254 },
179+ 'out2-0' : {'n_clicks' : 6 , 'n_clicks_timestamp' : 1611733454454 },
180+ 'out3' : {'n_clicks' : 10 , 'n_clicks_timestamp' : 1611733454854 },
181+ 'out4' : {'n_clicks' : 14 , 'n_clicks_timestamp' : 1611733455254 },
182+ 'out5' : {'n_clicks' : 18 , 'n_clicks_timestamp' : 1611733455654 },
183+ '{"_id":"inp-0","_type":"btn3"}' : {'n_clicks' : 8 ,
184+ 'n_clicks_timestamp' : 1611736147644 },
185+ '{"_id":"inp-0","_type":"btn4"}' : {'n_clicks' : 12 ,
186+ 'n_clicks_timestamp' : 1611733454954 },
187+ '{"_id":"inp-0","_type":"btn5"}' : {'n_clicks' : 16 ,
188+ 'n_clicks_timestamp' : 1611733455354 },
189+ '{"_id":"inp-1","_type":"btn3"}' : {'n_clicks' : 9 ,
190+ 'n_clicks_timestamp' : 1611736148172 },
191+ '{"_id":"inp-1","_type":"btn4"}' : {'n_clicks' : 13 ,
192+ 'n_clicks_timestamp' : 1611733455054 },
193+ '{"_id":"inp-1","_type":"btn5"}' : {'n_clicks' : 18 ,
194+ 'n_clicks_timestamp' : 1611733455454 },
195+ '{"_id":"inp-2","_type":"btn3"}' : {'n_clicks' : 10 ,
196+ 'n_clicks_timestamp' : 1611736149140 },
197+ '{"_id":"inp-2","_type":"btn4"}' : {'n_clicks' : 13 ,
198+ 'n_clicks_timestamp' : 1611733455154 },
199+ '{"_id":"inp-2","_type":"btn5"}' : {'n_clicks' : 19 ,
200+ 'n_clicks_timestamp' : 1611733455554 }}
201+
202+ assert DashApp .objects .get (instance_name = "Some name" ).current_state () == final_state
203+
204+
51205def test_util_error_cases (settings ):
52206 'Test handling of missing settings'
53207
@@ -258,6 +412,7 @@ def test_flexible_expanded_callbacks(client):
258412 resp = json .loads (response .content .decode ('utf-8' ))
259413 assert resp ["response" ]== {"output-three" : {"children" : "flexible_expanded_callbacks" }}
260414
415+
261416@pytest .mark .django_db
262417def test_injection_updating (client ):
263418 'Check updating of an app using demo test data'
0 commit comments