Skip to content

Commit

Permalink
fix(listview): update index after pop
Browse files Browse the repository at this point in the history
  • Loading branch information
TomJGooding committed Oct 17, 2024
1 parent 4a822bc commit 4bd7edf
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 7 deletions.
25 changes: 19 additions & 6 deletions src/textual/widgets/_list_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing_extensions import TypeGuard

from textual import _widget_navigation
from textual.await_complete import AwaitComplete
from textual.await_remove import AwaitRemove
from textual.binding import Binding, BindingType
from textual.containers import VerticalScroll
Expand Down Expand Up @@ -231,7 +232,7 @@ def insert(self, index: int, items: Iterable[ListItem]) -> AwaitMount:
await_mount = self.mount(*items, before=index)
return await_mount

def pop(self, index: Optional[int] = None) -> AwaitRemove:
def pop(self, index: Optional[int] = None) -> AwaitComplete:
"""Remove last ListItem from ListView or
Remove ListItem from ListView by index
Expand All @@ -242,11 +243,23 @@ def pop(self, index: Optional[int] = None) -> AwaitRemove:
An awaitable that yields control to the event loop until
the DOM has been updated to reflect item being removed.
"""
if index is None:
await_remove = self.query("ListItem").last().remove()
else:
await_remove = self.query("ListItem")[index].remove()
return await_remove
if len(self) == 0:
raise IndexError("pop from empty list")

index = index if index is not None else -1
if index < 0:
index += len(self)
item_to_remove = self.query("ListItem")[index]

async def do_pop():
await item_to_remove.remove()
if self.index is not None:
if index == self.index:
self.index = self.index
elif index < self.index:
self.index = self.index - 1

return AwaitComplete(do_pop())

def remove_items(self, indices: Iterable[int]) -> AwaitRemove:
"""Remove ListItems from ListView by indices
Expand Down
56 changes: 55 additions & 1 deletion tests/listview/test_listview_remove_items.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import pytest

from textual.app import App, ComposeResult
from textual.widgets import ListView, ListItem, Label
from textual.widgets import Label, ListItem, ListView


class EmptyListViewApp(App[None]):
def compose(self) -> ComposeResult:
yield ListView()


async def test_listview_pop_empty_raises_index_error():
app = EmptyListViewApp()
async with app.run_test() as pilot:
listview = pilot.app.query_one(ListView)
with pytest.raises(IndexError) as excinfo:
listview.pop()
assert "pop from empty list" in str(excinfo.value)


class ListViewApp(App[None]):
def __init__(self, initial_index: int | None = None):
super().__init__()
self.initial_index = initial_index
self.highlighted = []

def compose(self) -> ComposeResult:
yield ListView(
ListItem(Label("0")),
Expand All @@ -14,8 +35,15 @@ def compose(self) -> ComposeResult:
ListItem(Label("6")),
ListItem(Label("7")),
ListItem(Label("8")),
initial_index=self.initial_index,
)

def _on_list_view_highlighted(self, message: ListView.Highlighted) -> None:
if message.item is None:
self.highlighted.append(None)
else:
self.highlighted.append(str(message.item.children[0].renderable))


async def test_listview_remove_items() -> None:
"""Regression test for https://github.com/Textualize/textual/issues/4735"""
Expand All @@ -27,6 +55,32 @@ async def test_listview_remove_items() -> None:
assert len(listview) == 4


@pytest.mark.parametrize(
"initial_index, pop_index, expected_new_index, expected_highlighted",
[
(2, 2, 2, ["2", "3"]), # Remove highlighted item
(0, 0, 0, ["0", "1"]), # Remove first item when highlighted
(8, None, 7, ["8", "7"]), # Remove last item when highlighted
(4, 2, 3, ["4", "4"]), # Remove item before the highlighted index
(4, -2, 4, ["4"]), # Remove item after the highlighted index
],
)
async def test_listview_pop_updates_index_and_highlighting(
initial_index, pop_index, expected_new_index, expected_highlighted
) -> None:
"""Regression test for https://github.com/Textualize/textual/issues/5114"""
app = ListViewApp(initial_index)
async with app.run_test() as pilot:
listview = pilot.app.query_one(ListView)

await listview.pop(pop_index)
await pilot.pause()

assert listview.index == expected_new_index
assert listview._nodes[expected_new_index].highlighted is True
assert app.highlighted == expected_highlighted


if __name__ == "__main__":
app = ListViewApp()
app.run()

0 comments on commit 4bd7edf

Please sign in to comment.