test_pages.py 6.7 KB

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