From 7563d4cb7e9af13e8f25878c73a8ea549838a53e Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 1 Feb 2023 12:33:36 +0000 Subject: [PATCH] Allow setting a new label when performing a clear on a Tree See #1437 for background. While it would be ideal to allow for the complete emptying of a Tree, the root node is required (and it's part of the construction of a Tree). So, here, when clearing the Tree we optionally allow for a new label to be given. Ideally we'll also allow for fresh data to be provided too; but there's a wrinkle there in knowing the difference between the data being None, and no data being provided (so the current root's data being carried over). Following the method of defaulting used in __init__ would cause problems. As such, rather than roll all of this into one commit, this goes with the basic requirement and the solution for data will follow. Note this also starts some unit tests for the clearing of a Tree. --- src/textual/widgets/_tree.py | 11 ++++-- tests/tree/test_tree_clearing.py | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 tests/tree/test_tree_clearing.py diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 2f3d073cf..8f2304ae8 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -560,12 +560,17 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): label = self.render_label(node, NULL_STYLE, NULL_STYLE) return label.cell_len - def clear(self) -> None: - """Clear all nodes under root.""" + def clear(self, label: TextType | None = None) -> None: + """Clear all nodes under root. + + Args: + label: An optional new label for the root node. If not provided + the current root node's label will be used. + """ self._line_cache.clear() self._tree_lines_cached = None self._current_id = 0 - root_label = self.root._label + root_label = self.root._label if label is None else label root_data = self.root.data self.root = TreeNode( self, diff --git a/tests/tree/test_tree_clearing.py b/tests/tree/test_tree_clearing.py new file mode 100644 index 000000000..56a4515d6 --- /dev/null +++ b/tests/tree/test_tree_clearing.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from textual.app import App, ComposeResult +from textual.widgets import Tree + + +class VerseBody: + pass + + +class VerseStar(VerseBody): + pass + + +class VersePlanet(VerseBody): + pass + + +class VerseMoon(VerseBody): + pass + + +class TestTree(Tree[VerseBody]): + pass + + +class TreeClearApp(App[None]): + """Tree clearing test app.""" + + def compose(self) -> ComposeResult: + yield TestTree("White Sun", data=VerseStar()) + + def on_mount(self) -> None: + tree = self.query_one(TestTree) + node = tree.root.add("Londinium", VersePlanet()) + node.add_leaf("Balkerne", VerseMoon()) + node.add_leaf("Colchester", VerseMoon()) + node = tree.root.add("Sihnon", VersePlanet()) + node.add_leaf("Airen", VerseMoon()) + node.add_leaf("Xiaojie", VerseMoon()) + + +async def test_tree_simple_clear() -> None: + """Clearing a tree should keep the old label and data.""" + async with TreeClearApp().run_test() as pilot: + tree = pilot.app.query_one(TestTree) + assert len(tree.root.children) > 1 + pilot.app.query_one(TestTree).clear() + assert len(tree.root.children) == 0 + assert str(tree.root.label) == "White Sun" + assert isinstance(tree.root.data, VerseStar) + + +async def test_tree_new_label_clear() -> None: + """Clearing a tree with a new label should use the new label and keep the old data.""" + async with TreeClearApp().run_test() as pilot: + tree = pilot.app.query_one(TestTree) + assert len(tree.root.children) > 1 + pilot.app.query_one(TestTree).clear("Jiangyin") + assert len(tree.root.children) == 0 + assert str(tree.root.label) == "Jiangyin" + assert isinstance(tree.root.data, VerseStar)