diff --git a/CHANGELOG.md b/CHANGELOG.md index b1c5572cc0..47199666c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Loading indicators and app notifications overlapped in the wrong order https://github.com/Textualize/textual/issues/3677 - Widgets being loaded are disabled and have their scrolling explicitly disabled too https://github.com/Textualize/textual/issues/3677 - Method render on a widget could be called before mounting said widget https://github.com/Textualize/textual/issues/2914 +- Fixed `Select` not updating after changing the `prompt` reactive https://github.com/Textualize/textual/issues/2983 ### Added diff --git a/src/textual/widgets/_select.py b/src/textual/widgets/_select.py index 33fb70d505..cbd35cd138 100644 --- a/src/textual/widgets/_select.py +++ b/src/textual/widgets/_select.py @@ -492,3 +492,15 @@ def clear(self) -> None: raise InvalidSelectValueError( "Can't clear selection if allow_blank is set to False." ) from None + + def _watch_prompt(self, prompt: str) -> None: + if not self.is_mounted: + return + select_current = self.query_one(SelectCurrent) + select_current.placeholder = prompt + if not self._allow_blank: + return + if self.value == self.BLANK: + select_current.update(self.BLANK) + option_list = self.query_one(SelectOverlay) + option_list.replace_option_prompt_at_index(0, Text(prompt, style="dim")) diff --git a/tests/select/test_prompt.py b/tests/select/test_prompt.py new file mode 100644 index 0000000000..8c66df1af8 --- /dev/null +++ b/tests/select/test_prompt.py @@ -0,0 +1,54 @@ +from rich.text import Text + +from textual.app import App +from textual.widgets import Select, Static +from textual.widgets._select import SelectCurrent, SelectOverlay + + +async def test_reactive_prompt_change(): + """Regression test for https://github.com/Textualize/textual/issues/2983""" + + class SelectApp(App): + def compose(self): + yield Select[int]( + [(str(n), n) for n in range(3)], + prompt="Old prompt", + ) + + app = SelectApp() + async with app.run_test() as pilot: + select_widget = pilot.app.query_one(Select) + select_current = select_widget.query_one(SelectCurrent) + select_current_label = select_current.query_one("#label", Static) + select_overlay = select_widget.query_one(SelectOverlay) + + assert select_current_label.renderable == Text("Old prompt") + assert select_overlay._options[0].prompt == Text("Old prompt") + + select_widget.prompt = "New prompt" + assert select_current_label.renderable == Text("New prompt") + assert select_overlay._options[0].prompt == Text("New prompt") + + +async def test_reactive_prompt_change_when_allow_blank_is_false(): + class SelectApp(App): + def compose(self): + yield Select[int]( + [(str(n), n) for n in range(3)], + prompt="Old prompt", + allow_blank=False, + ) + + app = SelectApp() + async with app.run_test() as pilot: + select_widget = pilot.app.query_one(Select) + select_current = select_widget.query_one(SelectCurrent) + select_current_label = select_current.query_one("#label", Static) + select_overlay = select_widget.query_one(SelectOverlay) + + assert select_current_label.renderable == Text("0") + assert select_overlay._options[0].prompt == "0" + + select_widget.prompt = "New prompt" + assert select_current_label.renderable == Text("0") + assert select_overlay._options[0].prompt == "0"