Skip to content

Commit

Permalink
Merge pull request #2985 from nekeal/feature/extend-optionlist
Browse files Browse the repository at this point in the history
Adds an interface for replacing prompt of an individual option in an `OptionList`
  • Loading branch information
rodrigogiraoserrao authored Aug 8, 2023
2 parents 5a662e6 + def2628 commit 49612e3
Show file tree
Hide file tree
Showing 6 changed files with 673 additions and 20 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixed relative units not always expanding auto containers https://github.com/Textualize/textual/pull/3059
- Fixed background refresh https://github.com/Textualize/textual/issues/3055

### Added
- Added an interface for replacing prompt of an individual option in an `OptionList` https://github.com/Textualize/textual/issues/2603

## [0.32.0] - 2023-08-03

Expand Down
92 changes: 72 additions & 20 deletions src/textual/widgets/_option_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ def prompt(self) -> RenderableType:
"""The prompt for the option."""
return self.__prompt

def set_prompt(self, prompt: RenderableType) -> None:
"""Set the prompt for the option.
Args:
prompt: The new prompt for the option.
"""
self.__prompt = prompt

@property
def id(self) -> str | None:
"""The optional ID for the option."""
Expand Down Expand Up @@ -619,12 +627,7 @@ def remove_option(self, option_id: str) -> Self:
Raises:
OptionDoesNotExist: If no option has the given ID.
"""
try:
self._remove_option(self._option_ids[option_id])
except KeyError:
raise OptionDoesNotExist(
f"There is no option with an ID of '{option_id}'"
) from None
self._remove_option(self.get_option_index(option_id))
return self

def remove_option_at_index(self, index: int) -> Self:
Expand All @@ -647,6 +650,54 @@ def remove_option_at_index(self, index: int) -> Self:
) from None
return self

def _replace_option_prompt(self, index: int, prompt: RenderableType) -> None:
"""Replace the prompt of an option in the list.
Args:
index: The index of the option to replace the prompt of.
prompt: The new prompt for the option.
Raises:
OptionDoesNotExist: If there is no option with the given index.
"""
self.get_option_at_index(index).set_prompt(prompt)
self._refresh_content_tracking(force=True)
self.refresh()

def replace_option_prompt(self, option_id: str, prompt: RenderableType) -> Self:
"""Replace the prompt of the option with the given ID.
Args:
option_id: The ID of the option to replace the prompt of.
prompt: The new prompt for the option.
Returns:
The `OptionList` instance.
Raises:
OptionDoesNotExist: If no option has the given ID.
"""
self._replace_option_prompt(self.get_option_index(option_id), prompt)
return self

def replace_option_prompt_at_index(
self, index: int, prompt: RenderableType
) -> Self:
"""Replace the prompt of the option at the given index.
Args:
index: The index of the option to replace the prompt of.
prompt: The new prompt for the option.
Returns:
The `OptionList` instance.
Raises:
OptionDoesNotExist: If there is no option with the given index.
"""
self._replace_option_prompt(index, prompt)
return self

def clear_options(self) -> Self:
"""Clear the content of the option list.
Expand Down Expand Up @@ -720,12 +771,7 @@ def enable_option(self, option_id: str) -> Self:
Raises:
OptionDoesNotExist: If no option has the given ID.
"""
try:
return self.enable_option_at_index(self._option_ids[option_id])
except KeyError:
raise OptionDoesNotExist(
f"There is no option with an ID of '{option_id}'"
) from None
return self.enable_option_at_index(self.get_option_index(option_id))

def disable_option(self, option_id: str) -> Self:
"""Disable the option with the given ID.
Expand All @@ -739,12 +785,7 @@ def disable_option(self, option_id: str) -> Self:
Raises:
OptionDoesNotExist: If no option has the given ID.
"""
try:
return self.disable_option_at_index(self._option_ids[option_id])
except KeyError:
raise OptionDoesNotExist(
f"There is no option with an ID of '{option_id}'"
) from None
return self.disable_option_at_index(self.get_option_index(option_id))

@property
def option_count(self) -> int:
Expand All @@ -761,7 +802,7 @@ def get_option_at_index(self, index: int) -> Option:
The option at that index.
Raises:
OptionDoesNotExist: If there is no option with the index.
OptionDoesNotExist: If there is no option with the given index.
"""
try:
return self._options[index]
Expand All @@ -779,11 +820,22 @@ def get_option(self, option_id: str) -> Option:
Returns:
The option with the ID.
Raises:
OptionDoesNotExist: If no option has the given ID.
"""
return self.get_option_at_index(self.get_option_index(option_id))

def get_option_index(self, option_id):
"""Get the index of the option with the given ID.
Args:
option_id: The ID of the option to get the index of.
Raises:
OptionDoesNotExist: If no option has the given ID.
"""
try:
return self.get_option_at_index(self._option_ids[option_id])
return self._option_ids[option_id]
except KeyError:
raise OptionDoesNotExist(
f"There is no option with an ID of '{option_id}'"
Expand Down
72 changes: 72 additions & 0 deletions tests/option_list/test_option_prompt_replacement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Test replacing options prompt from an option list."""
import pytest

from textual.app import App, ComposeResult
from textual.widgets import OptionList
from textual.widgets.option_list import Option, OptionDoesNotExist


class OptionListApp(App[None]):
"""Test option list application."""

def compose(self) -> ComposeResult:
yield OptionList(
Option("0", id="0"),
Option("line1\nline2"),
)


async def test_replace_option_prompt_with_invalid_id() -> None:
"""Attempting to replace the prompt of an option ID that doesn't exist should raise an exception."""
async with OptionListApp().run_test() as pilot:
with pytest.raises(OptionDoesNotExist):
pilot.app.query_one(OptionList).replace_option_prompt("does-not-exist", "new-prompt")


async def test_replace_option_prompt_with_invalid_index() -> None:
"""Attempting to replace the prompt of an option index that doesn't exist should raise an exception."""
async with OptionListApp().run_test() as pilot:
with pytest.raises(OptionDoesNotExist):
pilot.app.query_one(OptionList).replace_option_prompt_at_index(23, "new-prompt")


async def test_replace_option_prompt_with_valid_id() -> None:
"""It should be possible to replace the prompt of an option ID that does exist."""
async with OptionListApp().run_test() as pilot:
option_list = pilot.app.query_one(OptionList)
option_list.replace_option_prompt("0", "new-prompt")
assert option_list.get_option("0").prompt == "new-prompt"


async def test_replace_option_prompt_with_valid_index() -> None:
"""It should be possible to replace the prompt of an option index that does exist."""
async with OptionListApp().run_test() as pilot:
option_list = pilot.app.query_one(OptionList).replace_option_prompt_at_index(1, "new-prompt")
assert option_list.get_option_at_index(1).prompt == "new-prompt"


async def test_replace_single_line_option_prompt_with_multiple() -> None:
"""It should be possible to replace single line prompt with multiple lines """
new_prompt = "new-prompt\nsecond line"
async with OptionListApp().run_test() as pilot:
option_list = pilot.app.query_one(OptionList)
option_list.replace_option_prompt("0", new_prompt)
assert option_list.get_option("0").prompt == new_prompt


async def test_replace_multiple_line_option_prompt_with_single() -> None:
"""It should be possible to replace multiple line prompt with a single line"""
new_prompt = "new-prompt"
async with OptionListApp().run_test() as pilot:
option_list = pilot.app.query_one(OptionList)
option_list.replace_option_prompt("0", new_prompt)
assert option_list.get_option("0").prompt == new_prompt


async def test_replace_multiple_line_option_prompt_with_multiple() -> None:
"""It should be possible to replace multiple line prompt with multiple lines"""
new_prompt = "new-prompt\nsecond line"
async with OptionListApp().run_test() as pilot:
option_list = pilot.app.query_one(OptionList)
option_list.replace_option_prompt_at_index(1, new_prompt)
assert option_list.get_option_at_index(1).prompt == new_prompt
Loading

0 comments on commit 49612e3

Please sign in to comment.