From 3e7b2c53a8d6493bee2f1a13ccd6c82456d3fced Mon Sep 17 00:00:00 2001 From: darrenburns Date: Mon, 29 May 2023 18:03:42 +0100 Subject: [PATCH] Add Widget.remove_children method (#2657) --- CHANGELOG.md | 1 + src/textual/widget.py | 9 +++++++ tests/test_widget_removing.py | 48 ++++++++++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e2325091..7e9f3fd5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `work` decorator accepts `description` parameter to add debug string https://github.com/Textualize/textual/issues/2597 - Added `SelectionList` widget https://github.com/Textualize/textual/pull/2652 - `App.AUTO_FOCUS` to set auto focus on all screens https://github.com/Textualize/textual/issues/2594 +- Added `Widget.remove_children` https://github.com/Textualize/textual/pull/2657 ### Changed diff --git a/src/textual/widget.py b/src/textual/widget.py index a67c3fe27..b77f3dd7c 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -2919,6 +2919,15 @@ class Widget(DOMNode): await_remove = self.app._remove_nodes([self], self.parent) return await_remove + def remove_children(self) -> AwaitRemove: + """Remove all children of this Widget from the DOM. + + Returns: + An awaitable object that waits for the children to be removed. + """ + await_remove = self.app._remove_nodes(list(self.children), self) + return await_remove + def render(self) -> RenderableType: """Get renderable for widget. diff --git a/tests/test_widget_removing.py b/tests/test_widget_removing.py index 6c648d04c..32d411ea8 100644 --- a/tests/test_widget_removing.py +++ b/tests/test_widget_removing.py @@ -1,9 +1,9 @@ import asyncio -from textual.app import App -from textual.containers import Container +from textual.app import App, ComposeResult +from textual.containers import Container, Vertical from textual.widget import Widget -from textual.widgets import Button, Static +from textual.widgets import Button, Label, Static async def test_remove_single_widget(): @@ -31,7 +31,7 @@ async def test_many_remove_all_widgets(): async def test_many_remove_some_widgets(): """It should be possible to remove some widgets on a multi-widget screen.""" async with App().run_test() as pilot: - await pilot.app.mount(*[Static(classes=f"is-{n%2}") for n in range(10)]) + await pilot.app.mount(*[Static(classes=f"is-{n % 2}") for n in range(10)]) assert len(pilot.app.screen._nodes) == 10 await pilot.app.query(".is-0").remove() assert len(pilot.app.screen._nodes) == 5 @@ -117,3 +117,43 @@ async def test_query_remove_order(): await pilot.app.query(Removable).remove() assert len(pilot.app.screen.walk_children(with_self=False)) == 0 assert removals == ["grandchild", "child", "parent"] + + +class ExampleApp(App): + def compose(self) -> ComposeResult: + yield Button("ABC") + yield Label("Outside of vertical.") + with Vertical(): + for index in range(5): + yield Label(str(index)) + + +async def test_widget_remove_children_container(): + app = ExampleApp() + async with app.run_test(): + container = app.query_one(Vertical) + + # 6 labels in total, with 5 of them inside the container. + assert len(app.query(Label)) == 6 + assert len(container.children) == 5 + + await container.remove_children() + + # The labels inside the container are gone, and the 1 outside remains. + assert len(app.query(Label)) == 1 + assert len(container.children) == 0 + + +async def test_widget_remove_children_no_children(): + app = ExampleApp() + async with app.run_test(): + button = app.query_one(Button) + + count_before = len(app.query("*")) + await button.remove_children() + count_after = len(app.query("*")) + + assert len(app.query(Button)) == 1 # The button still remains. + assert ( + count_before == count_after + ) # No widgets have been removed, since Button has no children.