Skip to content

Commit bbda8a8

Browse files
Fix select value being reset when toggling popup via User fixture (#5670)
### Motivation Fixes #4894 When using the `User` fixture to test select elements, clicking a select found by its label (e.g., `user.find('Fruit').click()`) to toggle the popup would incorrectly reset the select's value to `None`. The bug occurred because the click handler used `self.target` (which was the label like `'Fruit'`) as the value to set. Since the label is not a valid option in the select's options list, this triggered validation that set the value to `None`. ### Implementation Before setting the value, the code now checks if `self.target` is actually a valid option: - For list options: checks if `self.target in element._values` - For dict options: checks if any option label matches `self.target` If the target is not a valid option (meaning the user clicked the select itself, not an option), the code just toggles the popup state without modifying the value. ### Progress - [x] I chose a meaningful title that completes the sentence: "If applied, this PR will..." - [x] The implementation is complete. - [x] This is not a security issue. - [x] Pytests have been added. - [x] Documentation is not necessary.
1 parent 3db2617 commit bbda8a8

File tree

2 files changed

+33
-9
lines changed

2 files changed

+33
-9
lines changed

nicegui/testing/user_interaction.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def click(self) -> Self:
7272
"""Click the selected elements."""
7373
assert self.user.client
7474
with self.user.client:
75-
for element in self.elements:
75+
for element in self.elements: # pylint: disable=too-many-nested-blocks
7676
if isinstance(element, DisableableElement) and not element.enabled:
7777
continue
7878
if isinstance(element, ui.link):
@@ -83,18 +83,21 @@ def click(self) -> Self:
8383
if isinstance(element, ui.select):
8484
if element.is_showing_popup:
8585
if isinstance(element.options, dict):
86-
target_value = next((k for k, v in element.options.items() if v == self.target), '')
86+
target_value = next((k for k, v in element.options.items() if v == self.target), None)
8787
else:
88-
target_value = self.target
89-
if element.multiple:
90-
if target_value in element.value:
91-
element.value = [v for v in element.value if v != target_value]
92-
elif target_value in element._values: # pylint: disable=protected-access
93-
element.value = [*element.value, target_value]
88+
target_value = self.target if self.target in element._values else None # pylint: disable=protected-access
89+
if target_value is not None:
90+
# User clicked on a valid option: update the value and close the popup
91+
if element.multiple:
92+
if target_value in element.value:
93+
element.value = [v for v in element.value if v != target_value]
94+
else:
95+
element.value = [*element.value, target_value]
9496
else:
97+
element.value = target_value
9598
element._is_showing_popup = False # pylint: disable=protected-access
9699
else:
97-
element.value = target_value
100+
# User clicked the select itself (not a valid option): just close the popup
98101
element._is_showing_popup = False # pylint: disable=protected-access
99102
else:
100103
element._is_showing_popup = True # pylint: disable=protected-access

tests/test_user_simulation.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,27 @@ def page():
511511
assert select.value == ['B']
512512

513513

514+
async def test_select_keeps_value_when_toggling_popup(user: User):
515+
@ui.page('/')
516+
def page():
517+
s = ui.select(['Apple', 'Banana', 'Cherry'], label='Fruit', value='Apple')
518+
ui.label().bind_text_from(s, 'is_showing_popup', lambda v: 'open' if v else 'closed')
519+
ui.label().bind_text_from(s, 'value', lambda v: f'value = {v}')
520+
521+
await user.open('/')
522+
one = user.find('Fruit')
523+
await user.should_see('closed')
524+
await user.should_see('value = Apple')
525+
526+
one.click()
527+
await user.should_see('open')
528+
await user.should_see('value = Apple')
529+
530+
one.click()
531+
await user.should_see('closed')
532+
await user.should_see('value = Apple')
533+
534+
514535
async def test_upload_table(user: User) -> None:
515536
@ui.page('/')
516537
def page():

0 commit comments

Comments
 (0)