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