test_events.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import asyncio
  2. from typing import Literal
  3. import pytest
  4. from selenium.webdriver.common.by import By
  5. from nicegui import ui
  6. from nicegui.events import ClickEventArguments
  7. from nicegui.testing import Screen
  8. def click_sync_no_args():
  9. ui.label('click_sync_no_args')
  10. def click_sync_with_args(_: ClickEventArguments):
  11. ui.label('click_sync_with_args')
  12. async def click_async_no_args():
  13. await asyncio.sleep(0.1)
  14. ui.label('click_async_no_args')
  15. async def click_async_with_args(_: ClickEventArguments):
  16. await asyncio.sleep(0.1)
  17. ui.label('click_async_with_args')
  18. async def click_lambda_with_async_and_parameters(msg: str):
  19. await asyncio.sleep(0.1)
  20. ui.label(f'click_lambda_with_async_and_parameters: {msg}')
  21. def test_click_events(screen: Screen):
  22. ui.button('click_sync_no_args', on_click=click_sync_no_args)
  23. ui.button('click_sync_with_args', on_click=click_sync_with_args)
  24. ui.button('click_async_no_args', on_click=click_async_no_args)
  25. ui.button('click_async_with_args', on_click=click_async_with_args)
  26. ui.button('click_lambda_with_async_and_parameters', on_click=lambda: click_lambda_with_async_and_parameters('works'))
  27. screen.open('/')
  28. screen.click('click_sync_no_args')
  29. screen.click('click_sync_with_args')
  30. screen.click('click_async_no_args')
  31. screen.click('click_async_with_args')
  32. screen.click('click_lambda_with_async_and_parameters')
  33. screen.should_contain('click_sync_no_args')
  34. screen.should_contain('click_sync_with_args')
  35. screen.should_contain('click_async_no_args')
  36. screen.should_contain('click_async_with_args')
  37. screen.should_contain('click_lambda_with_async_and_parameters: works')
  38. def test_generic_events(screen: Screen):
  39. ui.label('click_sync_no_args').on('click', click_sync_no_args, [])
  40. ui.label('click_sync_with_args').on('click', click_sync_with_args, [])
  41. ui.label('click_async_no_args').on('click', click_async_no_args, [])
  42. ui.label('click_async_with_args').on('click', click_async_with_args, [])
  43. screen.open('/')
  44. screen.click('click_sync_no_args')
  45. screen.click('click_sync_with_args')
  46. screen.click('click_async_no_args')
  47. screen.click('click_async_with_args')
  48. screen.should_contain('click_sync_no_args')
  49. screen.should_contain('click_sync_with_args')
  50. screen.should_contain('click_async_no_args')
  51. screen.should_contain('click_async_with_args')
  52. def test_event_with_update_before_await(screen: Screen):
  53. @ui.page('/')
  54. def page():
  55. async def update():
  56. ui.label('1')
  57. await asyncio.sleep(1.0)
  58. ui.label('2')
  59. ui.button('update', on_click=update)
  60. screen.open('/')
  61. screen.click('update')
  62. screen.should_contain('1')
  63. screen.should_not_contain('2')
  64. screen.should_contain('2')
  65. def test_event_modifiers(screen: Screen):
  66. events = []
  67. ui.input('A').on('keydown', lambda _: events.append('A'), [])
  68. ui.input('B').on('keydown.x', lambda _: events.append('B'), [])
  69. ui.input('C').on('keydown.once', lambda _: events.append('C'), [])
  70. ui.input('D').on('keydown.shift.x', lambda _: events.append('D'), [])
  71. screen.open('/')
  72. screen.selenium.find_element(By.XPATH, '//*[@aria-label="A"]').send_keys('x')
  73. screen.selenium.find_element(By.XPATH, '//*[@aria-label="B"]').send_keys('xy')
  74. screen.selenium.find_element(By.XPATH, '//*[@aria-label="C"]').send_keys('xx')
  75. screen.selenium.find_element(By.XPATH, '//*[@aria-label="D"]').send_keys('Xx')
  76. assert events == ['A', 'B', 'C', 'D']
  77. def test_throttling(screen: Screen):
  78. events = []
  79. ui.button('Test', on_click=lambda: events.append(1)).on('click', lambda: events.append(2), [], throttle=1)
  80. screen.open('/')
  81. screen.click('Test')
  82. screen.click('Test')
  83. screen.click('Test')
  84. assert events == [1, 2, 1, 1]
  85. screen.wait(1.1)
  86. assert events == [1, 2, 1, 1, 2]
  87. screen.click('Test')
  88. screen.click('Test')
  89. screen.click('Test')
  90. assert events == [1, 2, 1, 1, 2, 1, 2, 1, 1]
  91. def test_throttling_variants(screen: Screen):
  92. events = []
  93. value = 0
  94. ui.button('Both').on('click', lambda: events.append(value), [], throttle=1)
  95. ui.button('Leading').on('click', lambda: events.append(value), [], throttle=1, trailing_events=False)
  96. ui.button('Trailing').on('click', lambda: events.append(value), [], throttle=1, leading_events=False)
  97. screen.open('/')
  98. value = 1
  99. screen.click('Both')
  100. value = 2
  101. screen.click('Both')
  102. value = 3
  103. screen.click('Both')
  104. assert events == [1]
  105. screen.wait(1.1)
  106. assert events == [1, 3]
  107. events = []
  108. value = 1
  109. screen.click('Leading')
  110. value = 2
  111. screen.click('Leading')
  112. value = 3
  113. screen.click('Leading')
  114. assert events == [1]
  115. screen.wait(1.1)
  116. assert events == [1]
  117. events = []
  118. value = 1
  119. screen.click('Trailing')
  120. value = 2
  121. screen.click('Trailing')
  122. value = 3
  123. screen.click('Trailing')
  124. assert events == [] # pylint: disable=use-implicit-booleaness-not-comparison
  125. screen.wait(1.1)
  126. assert events == [3]
  127. @pytest.mark.parametrize('attribute', ['disabled', 'hidden'])
  128. def test_server_side_validation(screen: Screen, attribute: Literal['disabled', 'hidden']):
  129. b = ui.button('Button', on_click=lambda: ui.label('Button clicked'))
  130. n = ui.number('Number', on_change=lambda: ui.label('Number changed'))
  131. if attribute == 'disabled':
  132. b.disable()
  133. n.disable()
  134. else:
  135. b.set_visibility(False)
  136. n.set_visibility(False)
  137. ui.button('Forbidden', on_click=lambda: ui.run_javascript(f'''
  138. getElement({b.id}).$emit("click", {{"id": {b.id}, "listener_id": "{next(iter(b._event_listeners))}"}});
  139. ''')) # pylint: disable=protected-access
  140. ui.button('Allowed', on_click=lambda: n.set_value(42))
  141. screen.open('/')
  142. screen.click('Forbidden')
  143. screen.wait(0.5)
  144. screen.should_not_contain('Button clicked') # triggering the click event through JavaScript does not work
  145. screen.click('Allowed')
  146. screen.should_contain('Number changed') # triggering the change event through Python works
  147. def test_js_handler(screen: Screen) -> None:
  148. ui.button('Button').on('click', js_handler='() => document.body.appendChild(document.createTextNode("Click!"))')
  149. screen.open('/')
  150. screen.click('Button')
  151. screen.should_contain('Click!')