Răsfoiți Sursa

Document use of user fixture with ui.select (#4757)

This PR addresses #4737 by documenting how the `user` fixture supports
the `ui.select` control.

---------

Co-authored-by: Barry Hart <barry.hart@zoro.com>
Co-authored-by: Rodja Trappe <rodja@zauberzeug.com>
Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Barry Hart 3 zile în urmă
părinte
comite
f5797c800f

+ 6 - 2
nicegui/testing/user_interaction.py

@@ -84,11 +84,15 @@ class UserInteraction(Generic[T]):
                         if element.multiple:
                             if target_value in element.value:
                                 element.value = [v for v in element.value if v != target_value]
-                            else:
+                            elif target_value in element._values:  # pylint: disable=protected-access
                                 element.value = [*element.value, target_value]
+                            else:
+                                element._is_showing_popup = False  # pylint: disable=protected-access
                         else:
                             element.value = target_value
-                    element._is_showing_popup = not element.is_showing_popup  # pylint: disable=protected-access
+                            element._is_showing_popup = False  # pylint: disable=protected-access
+                    else:
+                        element._is_showing_popup = True  # pylint: disable=protected-access
                     return self
 
                 for listener in element._event_listeners.values():  # pylint: disable=protected-access

+ 57 - 1
tests/test_select.py

@@ -4,7 +4,7 @@ import pytest
 from selenium.webdriver import Keys
 
 from nicegui import ui
-from nicegui.testing import Screen
+from nicegui.testing import Screen, User
 
 
 def test_select(screen: Screen):
@@ -205,3 +205,59 @@ def test_invalid_value(screen: Screen):
         ui.select(['A', 'B', 'C'], value='X')
 
     screen.open('/')
+
+
+@pytest.mark.parametrize('multiple', [False, True])
+def test_opening_and_closing_popup_with_screen(multiple: bool, screen: Screen):
+    select = ui.select(options=['Apple', 'Banana', 'Cherry'], label='Fruits', multiple=multiple).classes('w-24')
+    ui.label().bind_text_from(select, 'is_showing_popup', lambda v: 'open' if v else 'closed')
+    ui.label().bind_text_from(select, 'value', lambda v: f'value = {v}')
+
+    screen.open('/')
+    fruits = screen.find_element(select)
+    screen.should_contain('closed')
+
+    fruits.click()
+    screen.should_contain('open')
+    fruits.click()
+    screen.should_contain('closed')
+
+    fruits.click()
+    screen.click('Apple')
+    if multiple:
+        screen.click('Banana')
+        screen.should_contain("value = ['Apple', 'Banana']")
+        screen.should_contain('open')
+    else:
+        fruits.click()
+        screen.click('Banana')
+        screen.should_contain('value = Banana')
+        screen.should_contain('closed')
+
+
+@pytest.mark.parametrize('multiple', [False, True])
+async def test_opening_and_closing_popup_with_user(multiple: bool, user: User):
+    select = ui.select(options=['Apple', 'Banana', 'Cherry'], label='Fruits', multiple=multiple)
+    ui.label().bind_text_from(select, 'is_showing_popup', lambda v: 'open' if v else 'closed')
+    ui.label().bind_text_from(select, 'value', lambda v: f'value = {v}')
+
+    await user.open('/')
+    fruits = user.find('Fruits')
+    await user.should_see('closed')
+
+    fruits.click()
+    await user.should_see('open')
+    fruits.click()
+    await user.should_see('closed')
+
+    fruits.click()
+    user.find('Apple').click()
+    if multiple:
+        user.find('Banana').click()
+        await user.should_see("value = ['Apple', 'Banana']")
+        await user.should_see('open')
+    else:
+        fruits.click()
+        user.find('Banana').click()
+        await user.should_see('value = Banana')
+        await user.should_see('closed')

+ 0 - 2
tests/test_user_simulation.py

@@ -397,7 +397,6 @@ async def test_select_multiple_from_dict(user: User) -> None:
     user.find('label A').click()
     await user.should_see("Notify: ['value A']")
 
-    user.find(ui.select).click()
     user.find('label B').click()
     await user.should_see("Notify: ['value A', 'value B']")
 
@@ -416,7 +415,6 @@ async def test_select_multiple_values(user: User):
     await user.should_see("value = ['A', 'B']")
     assert select.value == ['A', 'B']
 
-    user.find(ui.select).click()
     user.find('A').click()
     await user.should_see("Notify: ['B']")
     await user.should_see("value = ['B']")

+ 67 - 28
website/documentation/content/user_documentation.py

@@ -105,6 +105,73 @@ def querying():
             ''')
 
 
+doc.text('User Interaction', '''
+    `user.find(...)` returns a `UserInteraction` object which provides methods to type text,
+    clear inputs, click buttons and trigger events on the found elements.
+    This demo shows how to trigger a "keydown.tab" event to autocomplete an input field after typing the first letter.
+
+    *Added in version 2.7.0: triggering events*
+''')
+
+
+@doc.ui
+def trigger_events():
+    with ui.row().classes('gap-4 items-stretch'):
+        with python_window(classes='w-[500px]', title='some UI code'):
+            ui.markdown('''
+                ```python
+                fruits = ['apple', 'banana', 'cherry']
+                ui.input(label='fruit', autocomplete=fruits)
+                ```
+            ''')
+        with python_window(classes='w-[500px]', title='user assertions'):
+            ui.markdown('''
+                ```python
+                await user.open('/')
+                user.find('fruit').type('a').trigger('keydown.tab')
+                await user.should_see('apple')
+                ```
+            ''')
+
+
+doc.text('Selecting options', '''
+    To choose items in a `ui.select` simply
+
+    - locate the `ui.select` element using `user.find()`,
+    - use `click()` to open the dropdown,
+    - locate the specific _option_ you want to select, again using `user.find()`, and
+    - use `click()` a second time to select the desired option.
+
+    For a multi-select element, repeat the click-and-choose steps for each item.
+''')
+
+
+@doc.ui
+def selecting_options_in_a_select():
+    with ui.row().classes('gap-4 items-stretch'):
+        with python_window(classes='w-[500px]', title='UI code'):
+            ui.markdown('''
+                ```python
+                ui.select(
+                    ['Apple', 'Banana', 'Cherry'],
+                    label='Fruits',
+                    multiple=True,
+                    on_change=lambda e: ui.notify(', '.join(e.value)),
+                )
+                ```
+            ''')
+
+        with python_window(classes='w-[500px]', title='user assertions'):
+            ui.markdown('''
+                ```python
+                user.find('Fruits').click()
+                user.find('Apple').click()
+                user.find('Banana').click()
+                await user.should_see('Apple, Banana')
+                ```
+            ''')
+
+
 doc.text('Using an ElementFilter', '''
     It may be desirable to use an [`ElementFilter`](/documentation/element_filter) to
 
@@ -192,34 +259,6 @@ def upload_table():
             ''')
 
 
-doc.text('Autocomplete', '''
-    The `UserInteraction` object returned by `user.find(...)` provides methods to trigger events on the found elements.
-    This demo shows how to trigger a "keydown.tab" event to autocomplete an input field.
-
-    *Added in version 2.7.0*
-''')
-
-
-@doc.ui
-def trigger_events():
-    with ui.row().classes('gap-4 items-stretch'):
-        with python_window(classes='w-[500px]', title='some UI code'):
-            ui.markdown('''
-                ```python
-                fruits = ['apple', 'banana', 'cherry']
-                ui.input(label='fruit', autocomplete=fruits)
-                ```
-            ''')
-        with python_window(classes='w-[500px]', title='user assertions'):
-            ui.markdown('''
-                ```python
-                await user.open('/')
-                user.find('fruit').type('a').trigger('keydown.tab')
-                await user.should_see('apple')
-                ```
-            ''')
-
-
 doc.text('Test Downloads', '''
     You can verify that a download was triggered by checking `user.downloads.http_responses`.
     By awaiting `user.downloads.next()` you can get the next download response.