test_select.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. from typing import Optional
  2. import pytest
  3. from selenium.webdriver import Keys
  4. from nicegui import ui
  5. from nicegui.testing import Screen
  6. def test_select(screen: Screen):
  7. ui.select(['A', 'B', 'C'], value='A')
  8. screen.open('/')
  9. screen.should_contain('A')
  10. screen.should_not_contain('B')
  11. screen.should_not_contain('C')
  12. screen.click('A') # open the dropdown
  13. screen.click('B') # close the dropdown
  14. screen.wait(0.5)
  15. screen.should_not_contain('A')
  16. screen.should_contain('B')
  17. screen.should_not_contain('C')
  18. def test_select_with_input(screen: Screen):
  19. ui.select(['A', 'AB', 'XYZ'], with_input=True)
  20. screen.open('/')
  21. screen.find_by_tag('input').click()
  22. screen.should_contain('XYZ')
  23. screen.find_by_tag('input').send_keys('A')
  24. screen.wait(0.5)
  25. screen.should_contain('A')
  26. screen.should_contain('AB')
  27. screen.should_not_contain('XYZ')
  28. screen.find_by_tag('input').send_keys('ABC' + Keys.ENTER)
  29. screen.find_by_tag('input').click()
  30. screen.should_not_contain('ABC')
  31. def test_replace_select(screen: Screen):
  32. with ui.row() as container:
  33. ui.select(['A'], value='A')
  34. def replace():
  35. container.clear()
  36. with container:
  37. ui.select(['B'], value='B')
  38. ui.button('Replace', on_click=replace)
  39. screen.open('/')
  40. screen.should_contain('A')
  41. screen.click('Replace')
  42. screen.should_contain('B')
  43. screen.should_not_contain('A')
  44. def test_multi_select(screen: Screen):
  45. s = ui.select(['Alice', 'Bob', 'Carol'], value='Alice', multiple=True).props('use-chips')
  46. ui.label().bind_text_from(s, 'value', backward=str)
  47. screen.open('/')
  48. screen.should_contain("['Alice']")
  49. screen.click('Alice')
  50. screen.click('Bob')
  51. screen.should_contain("['Alice', 'Bob']")
  52. screen.click('cancel') # remove icon
  53. screen.should_contain("['Bob']")
  54. def test_changing_options(screen: Screen):
  55. s = ui.select([10, 20, 30], value=10)
  56. ui.label().bind_text_from(s, 'value', lambda v: f'value = {v}')
  57. ui.button('reverse', on_click=lambda: (s.options.reverse(), s.update()))
  58. ui.button('clear', on_click=lambda: (s.options.clear(), s.update()))
  59. screen.open('/')
  60. screen.click('reverse')
  61. screen.should_contain('value = 10')
  62. screen.click('clear')
  63. screen.should_contain('value = None')
  64. def test_set_options(screen: Screen):
  65. s = ui.select([1, 2, 3], value=1)
  66. ui.button('Set new options', on_click=lambda: s.set_options([4, 5, 6], value=4))
  67. screen.open('/')
  68. screen.click('Set new options')
  69. screen.click('4')
  70. screen.should_contain('5')
  71. screen.should_contain('6')
  72. @pytest.mark.parametrize('option_dict', [False, True])
  73. @pytest.mark.parametrize('multiple', [False, True])
  74. @pytest.mark.parametrize('new_value_mode', ['add', 'add-unique', 'toggle', None])
  75. def test_add_new_values(screen: Screen, option_dict: bool, multiple: bool, new_value_mode: Optional[str]):
  76. options = {'a': 'A', 'b': 'B', 'c': 'C'} if option_dict else ['a', 'b', 'c']
  77. if option_dict and new_value_mode == 'add':
  78. with pytest.raises(ValueError, match='new_value_mode "add" is not supported for dict options'):
  79. ui.select(options=options, multiple=multiple, new_value_mode=new_value_mode)
  80. return
  81. s = ui.select(options=options, multiple=multiple, new_value_mode=new_value_mode)
  82. ui.label().bind_text_from(s, 'value', lambda v: f'value = {v}')
  83. ui.label().bind_text_from(s, 'options', lambda v: f'options = {v}')
  84. screen.open('/')
  85. screen.should_contain('value = []' if multiple else 'value = None')
  86. screen.should_contain("options = {'a': 'A', 'b': 'B', 'c': 'C'}" if option_dict else "options = ['a', 'b', 'c']")
  87. screen.find_by_class('q-select').click()
  88. screen.wait(0.5)
  89. screen.find_all('A' if option_dict else 'a')[-1].click()
  90. screen.should_contain("value = ['a']" if multiple else 'value = a')
  91. if new_value_mode:
  92. for _ in range(2):
  93. screen.find_by_tag('input').send_keys(Keys.BACKSPACE + 'd')
  94. screen.wait(0.5)
  95. screen.find_by_tag('input').click()
  96. screen.wait(0.5)
  97. screen.find_by_tag('input').send_keys(Keys.ENTER)
  98. screen.wait(0.5)
  99. if new_value_mode == 'add':
  100. screen.should_contain("value = ['a', 'd', 'd']" if multiple else 'value = d')
  101. screen.should_contain("options = {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'd', 'd': 'd'}" if option_dict else
  102. "options = ['a', 'b', 'c', 'd', 'd']")
  103. elif new_value_mode == 'add-unique':
  104. screen.should_contain("value = ['a', 'd', 'd']" if multiple else 'value = d')
  105. screen.should_contain("options = {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'd'}" if option_dict else
  106. "options = ['a', 'b', 'c', 'd']")
  107. elif new_value_mode == 'toggle':
  108. screen.should_contain("value = ['a']" if multiple else 'value = None')
  109. screen.should_contain("options = {'a': 'A', 'b': 'B', 'c': 'C'}" if option_dict else
  110. "options = ['a', 'b', 'c']")
  111. def test_id_generator(screen: Screen):
  112. options = {'a': 'A', 'b': 'B', 'c': 'C'}
  113. select = ui.select(options, value='b', new_value_mode='add', key_generator=lambda _: len(options))
  114. ui.label().bind_text_from(select, 'options', lambda v: f'options = {v}')
  115. screen.open('/')
  116. screen.find_by_tag('input').send_keys(Keys.BACKSPACE + 'd')
  117. screen.wait(0.5)
  118. screen.find_by_tag('input').send_keys(Keys.ENTER)
  119. screen.should_contain("options = {'a': 'A', 'b': 'B', 'c': 'C', 3: 'd'}")
  120. @pytest.mark.parametrize('multiple', [False, True])
  121. def test_keep_filtered_options(multiple: bool, screen: Screen):
  122. ui.select(options=['A1', 'A2', 'B1', 'B2'], with_input=True, multiple=multiple)
  123. screen.open('/')
  124. screen.find_by_tag('input').click()
  125. screen.should_contain('A1')
  126. screen.should_contain('A2')
  127. screen.should_contain('B1')
  128. screen.should_contain('B2')
  129. screen.find_by_tag('input').send_keys('A')
  130. screen.wait(0.5)
  131. screen.should_contain('A1')
  132. screen.should_contain('A2')
  133. screen.should_not_contain('B1')
  134. screen.should_not_contain('B2')
  135. screen.click('A1')
  136. screen.wait(0.5)
  137. screen.find_by_tag('input').click()
  138. screen.should_contain('A1')
  139. screen.should_contain('A2')
  140. if multiple:
  141. screen.should_not_contain('B1')
  142. screen.should_not_contain('B2')
  143. else:
  144. screen.should_contain('B1')
  145. screen.should_contain('B2')
  146. @pytest.mark.parametrize('auto_validation', [True, False])
  147. def test_select_validation(auto_validation: bool, screen: Screen):
  148. select = ui.select(['A', 'BC', 'DEF'], value='A', validation={'Too long': lambda v: len(v) < 3})
  149. if not auto_validation:
  150. select.without_auto_validation()
  151. screen.open('/')
  152. screen.click('A')
  153. screen.click('DEF')
  154. screen.wait(0.5)
  155. if auto_validation:
  156. screen.should_contain('Too long')
  157. else:
  158. screen.should_not_contain('Too long')
  159. def test_invalid_value(screen: Screen):
  160. with pytest.raises(ValueError, match='Invalid value: X'):
  161. ui.select(['A', 'B', 'C'], value='X')
  162. screen.open('/')