mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #2652 from davep/multiselect
This commit is contained in:
100
tests/selection_list/test_selection_list_create.py
Normal file
100
tests/selection_list/test_selection_list_create.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""Core selection list unit tests, aimed at testing basic list creation.
|
||||
|
||||
Note that the vast majority of the API *isn't* tested in here as
|
||||
`SelectionList` inherits from `OptionList` and so that would be duplicated
|
||||
effort. Instead these tests aim to just test the things that have been
|
||||
changed or wrapped in some way.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from rich.text import Text
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import SelectionList
|
||||
from textual.widgets.option_list import Option
|
||||
from textual.widgets.selection_list import Selection, SelectionError
|
||||
|
||||
|
||||
class SelectionListApp(App[None]):
|
||||
"""Test selection list application."""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield SelectionList[int](
|
||||
("0", 0),
|
||||
("1", 1, False),
|
||||
("2", 2, True),
|
||||
Selection("3", 3, id="3"),
|
||||
Selection("4", 4, True, id="4"),
|
||||
)
|
||||
|
||||
|
||||
async def test_all_parameters_become_selctions() -> None:
|
||||
"""All input parameters to a list should become selections."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
selections = pilot.app.query_one(SelectionList)
|
||||
assert selections.option_count == 5
|
||||
for n in range(5):
|
||||
assert isinstance(selections.get_option_at_index(n), Selection)
|
||||
|
||||
|
||||
async def test_get_selection_by_index() -> None:
|
||||
"""It should be possible to get a selection by index."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
option_list = pilot.app.query_one(SelectionList)
|
||||
for n in range(5):
|
||||
assert option_list.get_option_at_index(n).prompt == Text(str(n))
|
||||
assert option_list.get_option_at_index(-1).prompt == Text("4")
|
||||
|
||||
|
||||
async def test_get_selection_by_id() -> None:
|
||||
"""It should be possible to get a selection by ID."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
option_list = pilot.app.query_one(SelectionList)
|
||||
assert option_list.get_option("3").prompt == Text("3")
|
||||
assert option_list.get_option("4").prompt == Text("4")
|
||||
|
||||
|
||||
async def test_add_later() -> None:
|
||||
"""It should be possible to add more items to a selection list."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
selections = pilot.app.query_one(SelectionList)
|
||||
assert selections.option_count == 5
|
||||
selections.add_option(("5", 5))
|
||||
assert selections.option_count == 6
|
||||
selections.add_option(Selection("6", 6))
|
||||
assert selections.option_count == 7
|
||||
selections.add_options(
|
||||
[Selection("7", 7), Selection("8", 8, True), ("9", 9), ("10", 10, True)]
|
||||
)
|
||||
assert selections.option_count == 11
|
||||
selections.add_options([])
|
||||
assert selections.option_count == 11
|
||||
|
||||
|
||||
async def test_add_later_selcted_state() -> None:
|
||||
"""When adding selections later the selected collection should get updated."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
selections = pilot.app.query_one(SelectionList)
|
||||
assert selections.selected == [2, 4]
|
||||
selections.add_option(("5", 5, True))
|
||||
assert selections.selected == [2, 4, 5]
|
||||
selections.add_option(Selection("6", 6, True))
|
||||
assert selections.selected == [2, 4, 5, 6]
|
||||
|
||||
|
||||
async def test_add_non_selections() -> None:
|
||||
"""Adding options that aren't selections should result in errors."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
selections = pilot.app.query_one(SelectionList)
|
||||
with pytest.raises(SelectionError):
|
||||
selections.add_option(None)
|
||||
with pytest.raises(SelectionError):
|
||||
selections.add_option(Option("Nope"))
|
||||
with pytest.raises(SelectionError):
|
||||
selections.add_option("Nope")
|
||||
with pytest.raises(SelectionError):
|
||||
selections.add_option(("Nope",))
|
||||
with pytest.raises(SelectionError):
|
||||
selections.add_option(("Nope", 0, False, 23))
|
||||
210
tests/selection_list/test_selection_messages.py
Normal file
210
tests/selection_list/test_selection_messages.py
Normal file
@@ -0,0 +1,210 @@
|
||||
"""Unit tests aimed at testing the selection list messages.
|
||||
|
||||
Note that these tests only cover a subset of the public API of this widget.
|
||||
The bulk of the API is inherited from OptionList, and as such there are
|
||||
comprehensive tests for that. These tests simply cover the parts of the API
|
||||
that have been modified by the child class.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from textual import on
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.messages import Message
|
||||
from textual.widgets import OptionList, SelectionList
|
||||
|
||||
|
||||
class SelectionListApp(App[None]):
|
||||
"""Test selection list application."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.messages: list[tuple[str, int | None]] = []
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield SelectionList[int](*[(str(n), n) for n in range(10)])
|
||||
|
||||
@on(OptionList.OptionHighlighted)
|
||||
@on(OptionList.OptionSelected)
|
||||
@on(SelectionList.SelectionHighlighted)
|
||||
@on(SelectionList.SelectionToggled)
|
||||
@on(SelectionList.SelectedChanged)
|
||||
def _record(self, event: Message) -> None:
|
||||
assert event.control == self.query_one(SelectionList)
|
||||
self.messages.append(
|
||||
(
|
||||
event.__class__.__name__,
|
||||
event.selection_index
|
||||
if isinstance(event, SelectionList.SelectionMessage)
|
||||
else None,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def test_messages_on_startup() -> None:
|
||||
"""There should be a highlighted message when a non-empty selection list first starts up."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [("SelectionHighlighted", 0)]
|
||||
|
||||
|
||||
async def test_new_highlight() -> None:
|
||||
"""Setting the highlight to a new option should result in a message."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).highlighted = 2
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages[1:] == [("SelectionHighlighted", 2)]
|
||||
|
||||
|
||||
async def test_toggle() -> None:
|
||||
"""Toggling an option should result in messages."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).toggle(0)
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [
|
||||
("SelectionHighlighted", 0),
|
||||
("SelectedChanged", None),
|
||||
]
|
||||
|
||||
|
||||
async def test_toggle_via_user() -> None:
|
||||
"""Toggling via the user should result in the correct messages."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.press("space")
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [
|
||||
("SelectionHighlighted", 0),
|
||||
("SelectedChanged", None),
|
||||
("SelectionToggled", 0),
|
||||
]
|
||||
|
||||
|
||||
async def test_toggle_all() -> None:
|
||||
"""Toggling all options should result in messages."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).toggle_all()
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [
|
||||
("SelectionHighlighted", 0),
|
||||
("SelectedChanged", None),
|
||||
]
|
||||
|
||||
|
||||
async def test_select() -> None:
|
||||
"""Selecting all an option should result in a message."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).select(1)
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [
|
||||
("SelectionHighlighted", 0),
|
||||
("SelectedChanged", None),
|
||||
]
|
||||
|
||||
|
||||
async def test_select_selected() -> None:
|
||||
"""Selecting an option that is already selected should emit no extra message.."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).select(0)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).select(0)
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [
|
||||
("SelectionHighlighted", 0),
|
||||
("SelectedChanged", None),
|
||||
]
|
||||
|
||||
|
||||
async def test_select_all() -> None:
|
||||
"""Selecting all options should result in messages."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).select_all()
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [
|
||||
("SelectionHighlighted", 0),
|
||||
("SelectedChanged", None),
|
||||
]
|
||||
|
||||
|
||||
async def test_select_all_selected() -> None:
|
||||
"""Selecting all when all are selected should result in no extra messages."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).select_all()
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).select_all()
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [
|
||||
("SelectionHighlighted", 0),
|
||||
("SelectedChanged", None),
|
||||
]
|
||||
|
||||
|
||||
async def test_deselect() -> None:
|
||||
"""Deselecting an option should result in a message."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).select(1)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).deselect(1)
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [
|
||||
("SelectionHighlighted", 0),
|
||||
("SelectedChanged", None),
|
||||
("SelectedChanged", None),
|
||||
]
|
||||
|
||||
|
||||
async def test_deselect_deselected() -> None:
|
||||
"""Deselecting a deselected option should result in no extra messages."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).deselect(0)
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [("SelectionHighlighted", 0)]
|
||||
|
||||
|
||||
async def test_deselect_all() -> None:
|
||||
"""Deselecting all deselected options should result in no additional messages."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).deselect_all()
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [("SelectionHighlighted", 0)]
|
||||
|
||||
|
||||
async def test_select_then_deselect_all() -> None:
|
||||
"""Selecting and then deselecting all options should result in messages."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert isinstance(pilot.app, SelectionListApp)
|
||||
await pilot.pause()
|
||||
pilot.app.query_one(SelectionList).select_all()
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [
|
||||
("SelectionHighlighted", 0),
|
||||
("SelectedChanged", None),
|
||||
]
|
||||
pilot.app.query_one(SelectionList).deselect_all()
|
||||
await pilot.pause()
|
||||
assert pilot.app.messages == [
|
||||
("SelectionHighlighted", 0),
|
||||
("SelectedChanged", None),
|
||||
("SelectedChanged", None),
|
||||
]
|
||||
82
tests/selection_list/test_selection_values.py
Normal file
82
tests/selection_list/test_selection_values.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""Unit tests dealing with the tracking of selection list values."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import SelectionList
|
||||
|
||||
|
||||
class SelectionListApp(App[None]):
|
||||
def __init__(self, default_state: bool = False) -> None:
|
||||
super().__init__()
|
||||
self._default_state = default_state
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield SelectionList[int](*[(str(n), n, self._default_state) for n in range(50)])
|
||||
|
||||
|
||||
async def test_empty_selected() -> None:
|
||||
"""Selected should be empty when nothing is selected."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
assert pilot.app.query_one(SelectionList).selected == []
|
||||
|
||||
|
||||
async def test_programatic_select() -> None:
|
||||
"""Selected should contain a selected value."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
selection = pilot.app.query_one(SelectionList)
|
||||
selection.select(0)
|
||||
assert pilot.app.query_one(SelectionList).selected == [0]
|
||||
|
||||
|
||||
async def test_programatic_select_all() -> None:
|
||||
"""Selected should contain all selected values."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
selection = pilot.app.query_one(SelectionList)
|
||||
selection.select_all()
|
||||
assert pilot.app.query_one(SelectionList).selected == list(range(50))
|
||||
|
||||
|
||||
async def test_programatic_deselect() -> None:
|
||||
"""Selected should not contain a deselected value."""
|
||||
async with SelectionListApp(True).run_test() as pilot:
|
||||
selection = pilot.app.query_one(SelectionList)
|
||||
selection.deselect(0)
|
||||
assert pilot.app.query_one(SelectionList).selected == list(range(50)[1:])
|
||||
|
||||
|
||||
async def test_programatic_deselect_all() -> None:
|
||||
"""Selected should not contain anything after deselecting all values."""
|
||||
async with SelectionListApp(True).run_test() as pilot:
|
||||
selection = pilot.app.query_one(SelectionList)
|
||||
selection.deselect_all()
|
||||
assert pilot.app.query_one(SelectionList).selected == []
|
||||
|
||||
|
||||
async def test_programatic_toggle() -> None:
|
||||
"""Selected should reflect a toggle."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
selection = pilot.app.query_one(SelectionList)
|
||||
for n in range(25, 50):
|
||||
selection.select(n)
|
||||
for n in range(50):
|
||||
selection.toggle(n)
|
||||
assert pilot.app.query_one(SelectionList).selected == list(range(50)[:25])
|
||||
|
||||
|
||||
async def test_programatic_toggle_all() -> None:
|
||||
"""Selected should contain all values after toggling all on."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
selection = pilot.app.query_one(SelectionList)
|
||||
selection.toggle_all()
|
||||
assert pilot.app.query_one(SelectionList).selected == list(range(50))
|
||||
|
||||
|
||||
async def test_removal_of_selected_item() -> None:
|
||||
"""Removing a selected selection should remove its value from the selected set."""
|
||||
async with SelectionListApp().run_test() as pilot:
|
||||
selection = pilot.app.query_one(SelectionList)
|
||||
selection.toggle(0)
|
||||
assert pilot.app.query_one(SelectionList).selected == [0]
|
||||
selection.remove_option_at_index(0)
|
||||
assert pilot.app.query_one(SelectionList).selected == []
|
||||
File diff suppressed because one or more lines are too long
@@ -245,6 +245,14 @@ def test_progress_bar_completed_styled(snap_compare):
|
||||
def test_select(snap_compare):
|
||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "select_widget.py")
|
||||
|
||||
def test_selection_list_selected(snap_compare):
|
||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "selection_list_selected.py")
|
||||
|
||||
def test_selection_list_selections(snap_compare):
|
||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "selection_list_selections.py")
|
||||
|
||||
def test_selection_list_tuples(snap_compare):
|
||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "selection_list_tuples.py")
|
||||
|
||||
def test_select_expanded(snap_compare):
|
||||
assert snap_compare(
|
||||
|
||||
Reference in New Issue
Block a user