test_pages.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import asyncio
  2. from time import time
  3. from typing import Generator
  4. from uuid import uuid4
  5. import justpy.htmlcomponents
  6. from nicegui import task_logger, ui
  7. from nicegui.events import PageEvent
  8. from starlette.requests import Request
  9. from .screen import Screen
  10. def test_page(screen: Screen):
  11. @ui.page('/')
  12. def page():
  13. ui.label('Hello, world!')
  14. screen.open('/')
  15. screen.should_contain('NiceGUI')
  16. screen.should_contain('Hello, world!')
  17. def test_shared_page(screen: Screen):
  18. @ui.page('/', shared=True)
  19. def page():
  20. ui.label('Hello, world!')
  21. screen.open('/')
  22. screen.should_contain('NiceGUI')
  23. screen.should_contain('Hello, world!')
  24. def test_auto_index_page(screen: Screen):
  25. ui.label('Hello, world!')
  26. screen.open('/')
  27. screen.should_contain('NiceGUI')
  28. screen.should_contain('Hello, world!')
  29. def test_custom_title(screen: Screen):
  30. @ui.page('/', title='My Custom Title')
  31. def page():
  32. ui.label('Hello, world!')
  33. screen.open('/')
  34. screen.should_contain('My Custom Title')
  35. screen.should_contain('Hello, world!')
  36. def test_route_with_custom_path(screen: Screen):
  37. @ui.page('/test_route')
  38. def page():
  39. ui.label('page with custom path')
  40. screen.open('/test_route')
  41. screen.should_contain('page with custom path')
  42. def test_auto_index_page_with_link_to_subpage(screen: Screen):
  43. ui.link('link to subpage', '/subpage')
  44. @ui.page('/subpage')
  45. def page():
  46. ui.label('the subpage')
  47. screen.open('/')
  48. screen.click('link to subpage')
  49. screen.should_contain('the subpage')
  50. def test_link_to_page_by_passing_function(screen: Screen):
  51. @ui.page('/subpage')
  52. def page():
  53. ui.label('the subpage')
  54. ui.link('link to subpage', page)
  55. screen.open('/')
  56. screen.click('link to subpage')
  57. screen.should_contain('the subpage')
  58. def test_creating_new_page_after_startup(screen: Screen):
  59. screen.start_server()
  60. @ui.page('/late_page')
  61. def page():
  62. ui.label('page created after startup')
  63. screen.open('/late_page')
  64. screen.should_contain('page created after startup')
  65. def test_shared_and_individual_pages(screen: Screen):
  66. @ui.page('/individual_page')
  67. def individual_page():
  68. ui.label(f'individual page with uuid {uuid4()}')
  69. @ui.page('/shared_page', shared=True)
  70. def shared_page():
  71. ui.label(f'shared page with uuid {uuid4()}')
  72. screen.open('/shared_page')
  73. uuid1 = screen.find('shared page').text.split()[-1]
  74. screen.open('/shared_page')
  75. uuid2 = screen.find('shared page').text.split()[-1]
  76. assert uuid1 == uuid2
  77. screen.open('/individual_page')
  78. uuid1 = screen.find('individual page').text.split()[-1]
  79. screen.open('/individual_page')
  80. uuid2 = screen.find('individual page').text.split()[-1]
  81. assert uuid1 != uuid2
  82. def test_on_page_ready_event(screen: Screen):
  83. '''This feature was introduced to fix #50; see https://github.com/zauberzeug/nicegui/issues/50#issuecomment-1210962617.'''
  84. async def load() -> None:
  85. label.text = 'loading...'
  86. # NOTE we can not use asyncio.create_task() here because we are on a different thread than the nicegui event loop
  87. task_logger.create_task(takes_a_while())
  88. async def takes_a_while() -> None:
  89. await asyncio.sleep(0.1)
  90. label.text = 'delayed data has been loaded'
  91. @ui.page('/', on_page_ready=load)
  92. def page():
  93. global label
  94. label = ui.label()
  95. screen.open('/')
  96. screen.should_contain('delayed data has been loaded')
  97. def test_customized_page(screen: Screen):
  98. trace = []
  99. class custom_page(ui.page):
  100. def __init__(self, route: str, **kwargs):
  101. super().__init__(route, title='My Customized Page', **kwargs)
  102. trace.append('init')
  103. async def connected(self, request: Request) -> None:
  104. await super().connected(request)
  105. assert isinstance(request, Request)
  106. trace.append('connected')
  107. async def before_content(self) -> None:
  108. assert isinstance(self.page.view, justpy.htmlcomponents.Div), \
  109. 'we should be able to access the underlying JustPy view'
  110. await super().before_content()
  111. trace.append('before_content')
  112. async def after_content(self) -> None:
  113. await super().after_content()
  114. trace.append('after_content')
  115. @custom_page('/', dark=True)
  116. def mainpage():
  117. trace.append('content')
  118. ui.label('Hello, world!')
  119. screen.open('/')
  120. screen.should_contain('Hello, world!')
  121. screen.should_contain('My Customized Page')
  122. assert 'body--dark' in screen.get_tags('body')[0].get_attribute('class')
  123. assert trace == ['init', 'connected', 'before_content', 'content', 'after_content']
  124. def test_shared_page_with_request_parameter_raises_exception(screen: Screen):
  125. @ui.page('/', shared=True)
  126. def page(request: Request):
  127. ui.label('Hello, world!')
  128. screen.open('/')
  129. screen.should_contain('500')
  130. screen.should_contain('Server error')
  131. screen.assert_py_logger('ERROR', 'Cannot use `request` argument in shared page')
  132. def test_adding_elements_in_on_page_ready_event(screen: Screen):
  133. @ui.page('/', on_page_ready=lambda: ui.markdown('Hello, world!'))
  134. def page():
  135. pass
  136. screen.open('/')
  137. screen.should_contain('Hello, world!')
  138. def test_pageready_after_yield_on_async_page(screen: Screen):
  139. @ui.page('/')
  140. async def page() -> Generator[None, PageEvent, None]:
  141. ui.label('before')
  142. page_ready = yield
  143. await asyncio.sleep(1)
  144. ui.label('after')
  145. ui.label(page_ready.socket.base_url)
  146. screen.open('/')
  147. screen.should_contain('before')
  148. screen.should_not_contain('after')
  149. screen.wait(1)
  150. screen.should_contain('after')
  151. screen.should_contain('ws://localhost:3392/')
  152. def test_pageready_after_yield_on_non_async_page(screen: Screen):
  153. @ui.page('/')
  154. def page() -> Generator[None, PageEvent, None]:
  155. t = time()
  156. page_ready = yield
  157. ui.label(f'loading page took {time() - t:.2f} seconds')
  158. ui.label(page_ready.socket.base_url)
  159. screen.open('/')
  160. timing = screen.find('loading page took')
  161. assert 0 < float(timing.text.split()[-2]) < 3
  162. screen.should_contain('ws://localhost:3392/')
  163. def test_pageready_after_yield_on_shared_page_raises_exception(screen: Screen):
  164. @ui.page('/', shared=True)
  165. def page():
  166. yield
  167. screen.open('/')
  168. screen.should_contain('500')
  169. screen.should_contain('Server error')
  170. screen.assert_py_logger('ERROR', 'Yielding for page_ready is not supported on shared pages')
  171. def test_exception_before_yield_on_async_page(screen: Screen):
  172. @ui.page('/')
  173. async def page() -> Generator[None, PageEvent, None]:
  174. raise Exception('some exception')
  175. screen.open('/')
  176. screen.should_contain('500')
  177. screen.should_contain('Server error')
  178. screen.assert_py_logger('ERROR', 'some exception')
  179. def test_exception_after_yield_on_async_page(screen: Screen):
  180. @ui.page('/')
  181. async def page() -> Generator[None, PageEvent, None]:
  182. yield
  183. ui.label('this is shown')
  184. raise Exception('some exception')
  185. screen.open('/')
  186. screen.should_contain('this is shown')
  187. screen.assert_py_logger('ERROR', 'Failed to execute page-ready')
  188. def test_exception_in_on_page_ready_callback(screen: Screen):
  189. def ready():
  190. raise Exception('some exception')
  191. @ui.page('/', on_page_ready=ready)
  192. async def page() -> None:
  193. ui.label('this is shown')
  194. screen.open('/')
  195. screen.should_contain('this is shown')
  196. screen.assert_py_logger('ERROR', 'Failed to execute page-ready')