test_storage.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import asyncio
  2. from pathlib import Path
  3. import httpx
  4. import pytest
  5. from nicegui import Client, app, background_tasks, context, ui
  6. from nicegui import storage as storage_module
  7. from nicegui.testing import Screen
  8. def test_browser_data_is_stored_in_the_browser(screen: Screen):
  9. @ui.page('/')
  10. def page():
  11. app.storage.browser['count'] = app.storage.browser.get('count', 0) + 1
  12. ui.label().bind_text_from(app.storage.browser, 'count')
  13. @app.get('/count')
  14. def count():
  15. return 'count = ' + str(app.storage.browser['count'])
  16. screen.ui_run_kwargs['storage_secret'] = 'just a test'
  17. screen.open('/')
  18. screen.should_contain('1')
  19. screen.open('/')
  20. screen.should_contain('2')
  21. screen.open('/')
  22. screen.should_contain('3')
  23. screen.open('/count')
  24. screen.should_contain('count = 3') # also works with FastAPI endpoints
  25. def test_browser_storage_supports_asyncio(screen: Screen):
  26. @ui.page('/')
  27. async def page():
  28. app.storage.browser['count'] = app.storage.browser.get('count', 0) + 1
  29. await asyncio.sleep(0.5)
  30. ui.label(app.storage.browser['count'])
  31. screen.ui_run_kwargs['storage_secret'] = 'just a test'
  32. screen.open('/')
  33. screen.switch_to(1)
  34. screen.open('/')
  35. screen.should_contain('2')
  36. screen.switch_to(0)
  37. screen.open('/')
  38. screen.should_contain('3')
  39. def test_browser_storage_modifications_after_page_load_are_forbidden(screen: Screen):
  40. @ui.page('/')
  41. async def page(client: Client):
  42. await client.connected()
  43. try:
  44. app.storage.browser['test'] = 'data'
  45. except TypeError as e:
  46. ui.label(str(e))
  47. screen.ui_run_kwargs['storage_secret'] = 'just a test'
  48. screen.open('/')
  49. screen.should_contain('response to the browser has already been built')
  50. def test_user_storage_modifications(screen: Screen):
  51. @ui.page('/')
  52. async def page(client: Client, delayed: bool = False):
  53. if delayed:
  54. await client.connected()
  55. app.storage.user['count'] = app.storage.user.get('count', 0) + 1
  56. ui.label().bind_text_from(app.storage.user, 'count')
  57. screen.ui_run_kwargs['storage_secret'] = 'just a test'
  58. screen.open('/')
  59. screen.should_contain('1')
  60. screen.open('/?delayed=True')
  61. screen.should_contain('2')
  62. screen.open('/')
  63. screen.should_contain('3')
  64. async def test_access_user_storage_from_fastapi(screen: Screen):
  65. @app.get('/api')
  66. def api():
  67. app.storage.user['msg'] = 'yes'
  68. return 'OK'
  69. screen.ui_run_kwargs['storage_secret'] = 'just a test'
  70. screen.open('/')
  71. async with httpx.AsyncClient() as http_client:
  72. response = await http_client.get(f'http://localhost:{Screen.PORT}/api')
  73. assert response.status_code == 200
  74. assert response.text == '"OK"'
  75. await asyncio.sleep(0.5) # wait for storage to be written
  76. assert next(Path('.nicegui').glob('storage-user-*.json')).read_text('utf-8') == '{"msg":"yes"}'
  77. def test_access_user_storage_on_interaction(screen: Screen):
  78. @ui.page('/')
  79. async def page():
  80. if 'test_switch' not in app.storage.user:
  81. app.storage.user['test_switch'] = False
  82. ui.switch('switch').bind_value(app.storage.user, 'test_switch')
  83. screen.ui_run_kwargs['storage_secret'] = 'just a test'
  84. screen.open('/')
  85. screen.click('switch')
  86. screen.wait(0.5)
  87. assert next(Path('.nicegui').glob('storage-user-*.json')).read_text('utf-8') == '{"test_switch":true}'
  88. def test_access_user_storage_from_button_click_handler(screen: Screen):
  89. @ui.page('/')
  90. async def page():
  91. ui.button('test', on_click=app.storage.user.update(inner_function='works'))
  92. screen.ui_run_kwargs['storage_secret'] = 'just a test'
  93. screen.open('/')
  94. screen.click('test')
  95. screen.wait(1)
  96. assert next(Path('.nicegui').glob('storage-user-*.json')).read_text('utf-8') == '{"inner_function":"works"}'
  97. async def test_access_user_storage_from_background_task(screen: Screen):
  98. @ui.page('/')
  99. def page():
  100. async def subtask():
  101. await asyncio.sleep(0.1)
  102. app.storage.user['subtask'] = 'works'
  103. background_tasks.create(subtask())
  104. screen.ui_run_kwargs['storage_secret'] = 'just a test'
  105. screen.open('/')
  106. assert next(Path('.nicegui').glob('storage-user-*.json')).read_text('utf-8') == '{"subtask":"works"}'
  107. def test_user_and_general_storage_is_persisted(screen: Screen):
  108. @ui.page('/')
  109. def page():
  110. app.storage.user['count'] = app.storage.user.get('count', 0) + 1
  111. app.storage.general['count'] = app.storage.general.get('count', 0) + 1
  112. ui.label(f'user: {app.storage.user["count"]}')
  113. ui.label(f'general: {app.storage.general["count"]}')
  114. screen.ui_run_kwargs['storage_secret'] = 'just a test'
  115. screen.open('/')
  116. screen.open('/')
  117. screen.open('/')
  118. screen.should_contain('user: 3')
  119. screen.should_contain('general: 3')
  120. screen.selenium.delete_all_cookies()
  121. screen.open('/')
  122. screen.should_contain('user: 1')
  123. screen.should_contain('general: 4')
  124. def test_rapid_storage(screen: Screen):
  125. # https://github.com/zauberzeug/nicegui/issues/1099
  126. ui.button('test', on_click=lambda: (
  127. app.storage.general.update(one=1),
  128. app.storage.general.update(two=2),
  129. app.storage.general.update(three=3),
  130. ))
  131. screen.open('/')
  132. screen.click('test')
  133. screen.wait(0.5)
  134. assert Path('.nicegui', 'storage-general.json').read_text('utf-8') == '{"one":1,"two":2,"three":3}'
  135. def test_tab_storage_is_local(screen: Screen):
  136. @ui.page('/')
  137. async def page():
  138. await context.get_client().connected()
  139. app.storage.tab['count'] = app.storage.tab.get('count', 0) + 1
  140. ui.label().bind_text_from(app.storage.tab, 'count')
  141. screen.open('/')
  142. screen.should_contain('1')
  143. screen.open('/')
  144. screen.should_contain('2')
  145. screen.switch_to(1)
  146. screen.open('/')
  147. screen.should_contain('1')
  148. screen.switch_to(0)
  149. screen.open('/')
  150. screen.should_contain('3')
  151. def test_tab_storage_is_auto_removed(screen: Screen):
  152. storage_module.PURGE_INTERVAL = 0.1
  153. app.storage.max_tab_storage_age = 0.5
  154. @ui.page('/')
  155. async def page():
  156. await context.get_client().connected()
  157. app.storage.tab['count'] = app.storage.tab.get('count', 0) + 1
  158. ui.label().bind_text_from(app.storage.tab, 'count')
  159. screen.open('/')
  160. screen.should_contain('1')
  161. screen.open('/')
  162. screen.should_contain('2')
  163. screen.wait(1)
  164. screen.open('/')
  165. screen.should_contain('1')
  166. def test_clear_tab_storage(screen: Screen):
  167. storage_module.PURGE_INTERVAL = 60
  168. @ui.page('/')
  169. async def page():
  170. await context.get_client().connected()
  171. app.storage.tab['test'] = '123'
  172. ui.button('clear', on_click=app.storage.clear)
  173. screen.open('/')
  174. screen.should_contain('clear')
  175. tab_storages = app.storage._tabs # pylint: disable=protected-access
  176. assert len(tab_storages) == 1
  177. assert list(tab_storages.values())[0] == {'test': '123'}
  178. screen.click('clear')
  179. screen.wait(0.5)
  180. assert not tab_storages
  181. def test_client_storage(screen: Screen):
  182. def increment():
  183. app.storage.client['counter'] = app.storage.client['counter'] + 1
  184. @ui.page('/')
  185. def page():
  186. app.storage.client['counter'] = 123
  187. ui.button('Increment').on_click(increment)
  188. ui.label().bind_text(app.storage.client, 'counter')
  189. screen.open('/')
  190. screen.should_contain('123')
  191. screen.click('Increment')
  192. screen.wait_for('124')
  193. screen.switch_to(1)
  194. screen.open('/')
  195. screen.should_contain('123')
  196. screen.switch_to(0)
  197. screen.should_contain('124')
  198. def test_clear_client_storage(screen: Screen):
  199. with pytest.raises(RuntimeError): # no context (auto index)
  200. app.storage.client.clear()
  201. @ui.page('/')
  202. def page():
  203. app.storage.client['counter'] = 123
  204. app.storage.client.clear()
  205. assert app.storage.client == {}
  206. screen.open('/')