Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a clear method that removes all children. #1893

Merged
merged 4 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/1893.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Widgets now have a `.clear()` method to remove all child widgets.
16 changes: 15 additions & 1 deletion core/src/toga/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,22 +154,36 @@ def remove(self, *children):
Any nominated child widget that is not a child of this widget will
not have any change in parentage.
Refreshes the widget after removal if any children were removed.
Raises ``ValueError`` if this widget cannot have children.
:param children: The child nodes to remove.
"""
removed = False

for child in children:
if child.parent is self:
removed = True
super().remove(child)

child.app = None
child.window = None

self._impl.remove_child(child._impl)

if self.window:
if self.window and removed:
self.window.content.refresh()

def clear(self):
"""Remove all child widgets of this node.
Refreshes the widget after removal if any children were removed.
Raises ``ValueError`` if this widget cannot have children.
"""
self.remove(*self.children)

@property
def app(self):
"""The App to which this widget belongs.
Expand Down
86 changes: 86 additions & 0 deletions core/tests/widgets/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,92 @@ def test_remove_multiple_children(widget):
window.content.refresh.assert_called_once_with()


def test_clear_all_children(widget):
"All children can be simultaneously removed from a widget"
# Add children to the widget
child1 = TestLeafWidget(id="child1_id")
child2 = TestLeafWidget(id="child2_id")
child3 = TestLeafWidget(id="child3_id")
widget.add(child1, child2, child3)

app = toga.App("Test", "com.example.test")
window = Mock()
widget.app = app
widget.window = window

assert widget.children == [child1, child2, child3]
for child in widget.children:
assert child.parent == widget
assert child.app == app
assert child.window == window

# Clear children
widget.clear()

# Parent doesn't know about the removed children, and vice versa
assert widget.children == []
assert child1.parent is None
assert child2.parent is None
assert child3.parent is None

# App and window have been reset on the removed widgets
assert child1.app is None
assert child1.window is None

assert child2.app is None
assert child2.window is None

assert child3.app is None
assert child3.window is None

# The impl's remove_child has been invoked thrice
assert_action_performed_with(widget, "remove child", child=child1._impl)
assert_action_performed_with(widget, "remove child", child=child2._impl)
assert_action_performed_with(widget, "remove child", child=child3._impl)

# The window layout has been refreshed once
window.content.refresh.assert_called_once_with()


def test_clear_no_children(widget):
"No changes are made (no-op) if widget has no children"
app = toga.App("Test", "com.example.test")
window = Mock()
widget.app = app
widget.window = window

assert widget.children == []

# Clear children
widget.clear()

# Parent doesn't have any children still
assert widget.children == []

# The window layout has not been refreshed
window.content.refresh.assert_not_called()


def test_clear_leaf_node():
"No changes are made to leaf node that cannot have children"
leaf = TestLeafWidget()
app = toga.App("Test", "com.example.test")
window = Mock()
leaf.app = app
leaf.window = window

assert leaf.children == []

# Clear children
leaf.clear()

# Parent doesn't have any children still
assert leaf.children == []

# The window layout has not been refreshed
window.content.refresh.assert_not_called()


def test_remove_from_non_parent(widget):
"Trying to remove a child from a widget other than it's parent is a no-op"
# Create a second parent widget, and add a child to it
Expand Down