test_page.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import asyncio
  2. from uuid import uuid4
  3. from fastapi.responses import PlainTextResponse
  4. from nicegui import Client, background_tasks, ui
  5. from .screen import Screen
  6. def test_page(screen: Screen):
  7. @ui.page('/')
  8. def page():
  9. ui.label('Hello, world!')
  10. screen.open('/')
  11. screen.should_contain('NiceGUI')
  12. screen.should_contain('Hello, world!')
  13. def test_auto_index_page(screen: Screen):
  14. ui.label('Hello, world!')
  15. screen.open('/')
  16. screen.should_contain('NiceGUI')
  17. screen.should_contain('Hello, world!')
  18. def test_custom_title(screen: Screen):
  19. @ui.page('/', title='My Custom Title')
  20. def page():
  21. ui.label('Hello, world!')
  22. screen.open('/')
  23. screen.should_contain('My Custom Title')
  24. screen.should_contain('Hello, world!')
  25. def test_route_with_custom_path(screen: Screen):
  26. @ui.page('/test_route')
  27. def page():
  28. ui.label('page with custom path')
  29. screen.open('/test_route')
  30. screen.should_contain('page with custom path')
  31. def test_auto_index_page_with_link_to_subpage(screen: Screen):
  32. ui.link('link to subpage', '/subpage')
  33. @ui.page('/subpage')
  34. def page():
  35. ui.label('the subpage')
  36. screen.open('/')
  37. screen.click('link to subpage')
  38. screen.should_contain('the subpage')
  39. def test_link_to_page_by_passing_function(screen: Screen):
  40. @ui.page('/subpage')
  41. def page():
  42. ui.label('the subpage')
  43. ui.link('link to subpage', page)
  44. screen.open('/')
  45. screen.click('link to subpage')
  46. screen.should_contain('the subpage')
  47. def test_creating_new_page_after_startup(screen: Screen):
  48. screen.start_server()
  49. @ui.page('/late_page')
  50. def page():
  51. ui.label('page created after startup')
  52. screen.open('/late_page')
  53. screen.should_contain('page created after startup')
  54. def test_shared_and_private_pages(screen: Screen):
  55. @ui.page('/private_page')
  56. def private_page():
  57. ui.label(f'private page with uuid {uuid4()}')
  58. ui.label(f'shared page with uuid {uuid4()}')
  59. screen.open('/private_page')
  60. uuid1 = screen.find('private page').text.split()[-1]
  61. screen.open('/private_page')
  62. uuid2 = screen.find('private page').text.split()[-1]
  63. assert uuid1 != uuid2
  64. screen.open('/')
  65. uuid1 = screen.find('shared page').text.split()[-1]
  66. screen.open('/')
  67. uuid2 = screen.find('shared page').text.split()[-1]
  68. assert uuid1 == uuid2
  69. def test_wait_for_connected(screen: Screen):
  70. label: ui.label
  71. async def load() -> None:
  72. label.text = 'loading...'
  73. # NOTE we can not use asyncio.create_task() here because we are on a different thread than the NiceGUI event loop
  74. background_tasks.create(takes_a_while())
  75. async def takes_a_while() -> None:
  76. await asyncio.sleep(0.1)
  77. label.text = 'delayed data has been loaded'
  78. @ui.page('/')
  79. async def page(client: Client):
  80. nonlocal label
  81. label = ui.label()
  82. await client.connected()
  83. await load()
  84. screen.open('/')
  85. screen.should_contain('delayed data has been loaded')
  86. def test_wait_for_disconnect(screen: Screen):
  87. events = []
  88. @ui.page('/')
  89. async def page(client: Client):
  90. await client.connected()
  91. events.append('connected')
  92. await client.disconnected()
  93. events.append('disconnected')
  94. screen.open('/')
  95. screen.wait(0.5)
  96. screen.open('/')
  97. screen.wait(0.5)
  98. assert events == ['connected', 'disconnected', 'connected']
  99. def test_wait_for_disconnect_without_awaiting_connected(screen: Screen):
  100. events = []
  101. @ui.page('/')
  102. async def page(client: Client):
  103. await client.disconnected()
  104. events.append('disconnected')
  105. screen.open('/')
  106. screen.wait(0.5)
  107. screen.open('/')
  108. screen.wait(0.5)
  109. assert events == ['disconnected']
  110. def test_adding_elements_after_connected(screen: Screen):
  111. @ui.page('/')
  112. async def page(client: Client):
  113. ui.label('before')
  114. await client.connected()
  115. ui.label('after')
  116. screen.open('/')
  117. screen.should_contain('before')
  118. screen.should_contain('after')
  119. def test_exception(screen: Screen):
  120. @ui.page('/')
  121. def page():
  122. raise RuntimeError('some exception')
  123. screen.open('/')
  124. screen.should_contain('500')
  125. screen.should_contain('Server error')
  126. screen.assert_py_logger('ERROR', 'some exception')
  127. def test_exception_after_connected(screen: Screen):
  128. @ui.page('/')
  129. async def page(client: Client):
  130. await client.connected()
  131. ui.label('this is shown')
  132. raise RuntimeError('some exception')
  133. screen.open('/')
  134. screen.should_contain('this is shown')
  135. screen.assert_py_logger('ERROR', 'some exception')
  136. def test_page_with_args(screen: Screen):
  137. @ui.page('/page/{id_}')
  138. def page(id_: int):
  139. ui.label(f'Page {id_}')
  140. screen.open('/page/42')
  141. screen.should_contain('Page 42')
  142. def test_adding_elements_during_onconnect(screen: Screen):
  143. @ui.page('/')
  144. def page(client: Client):
  145. ui.label('Label 1')
  146. client.on_connect(lambda: ui.label('Label 2'))
  147. screen.open('/')
  148. screen.should_contain('Label 2')
  149. def test_async_connect_handler(screen: Screen):
  150. @ui.page('/')
  151. def page(client: Client):
  152. async def run_js():
  153. result.text = await ui.run_javascript('41 + 1')
  154. result = ui.label()
  155. client.on_connect(run_js)
  156. screen.open('/')
  157. screen.should_contain('42')
  158. def test_dark_mode(screen: Screen):
  159. @ui.page('/auto', dark=None)
  160. def page():
  161. ui.label('A').classes('text-blue-400 dark:text-red-400')
  162. @ui.page('/light', dark=False)
  163. def light_page():
  164. ui.label('B').classes('text-blue-400 dark:text-red-400')
  165. @ui.page('/dark', dark=True)
  166. def dark_page():
  167. ui.label('C').classes('text-blue-400 dark:text-red-400')
  168. blue = 'rgba(96, 165, 250, 1)'
  169. red = 'rgba(248, 113, 113, 1)'
  170. white = 'rgba(0, 0, 0, 0)'
  171. black = 'rgba(18, 18, 18, 1)'
  172. screen.open('/auto')
  173. assert screen.find('A').value_of_css_property('color') == blue
  174. assert screen.find_by_tag('body').value_of_css_property('background-color') == white
  175. screen.open('/light')
  176. assert screen.find('B').value_of_css_property('color') == blue
  177. assert screen.find_by_tag('body').value_of_css_property('background-color') == white
  178. screen.open('/dark')
  179. assert screen.find('C').value_of_css_property('color') == red
  180. assert screen.find_by_tag('body').value_of_css_property('background-color') == black
  181. def test_returning_custom_response(screen: Screen):
  182. @ui.page('/')
  183. def page(plain: bool = False):
  184. if plain:
  185. return PlainTextResponse('custom response')
  186. else:
  187. ui.label('normal NiceGUI page')
  188. screen.open('/')
  189. screen.should_contain('normal NiceGUI page')
  190. screen.should_not_contain('custom response')
  191. screen.open('/?plain=true')
  192. screen.should_contain('custom response')
  193. screen.should_not_contain('normal NiceGUI page')
  194. def test_returning_custom_response_async(screen: Screen):
  195. @ui.page('/')
  196. async def page(plain: bool = False):
  197. await asyncio.sleep(0.01) # simulates a db request or similar
  198. if plain:
  199. return PlainTextResponse('custom response')
  200. else:
  201. ui.label('normal NiceGUI page')
  202. screen.open('/')
  203. screen.should_contain('normal NiceGUI page')
  204. screen.should_not_contain('custom response')
  205. screen.open('/?plain=true')
  206. screen.should_contain('custom response')
  207. screen.should_not_contain('normal NiceGUI page')