From 45ba7238f052aa2200b6157411c5ca5bea5f45de Mon Sep 17 00:00:00 2001 From: Jainam maru <157703342+Dev9269@users.noreply.github.com> Date: Thu, 6 Aug 2026 03:27:17 +0530 Subject: [PATCH 1/2] Fix SplitContainer.content setter to not orphan widgets on invalid assignment --- changes/4631.bugfix.md | 1 + core/src/toga/widgets/splitcontainer.py | 13 ++++++++----- core/tests/widgets/test_splitcontainer.py | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 changes/4631.bugfix.md diff --git a/changes/4631.bugfix.md b/changes/4631.bugfix.md new file mode 100644 index 0000000000..0910c73a40 --- /dev/null +++ b/changes/4631.bugfix.md @@ -0,0 +1 @@ +A failed `SplitContainer.content` assignment no longer orphans the currently-displayed widgets. diff --git a/core/src/toga/widgets/splitcontainer.py b/core/src/toga/widgets/splitcontainer.py index 069a9bdde8..54de69368b 100644 --- a/core/src/toga/widgets/splitcontainer.py +++ b/core/src/toga/widgets/splitcontainer.py @@ -97,11 +97,6 @@ def content(self) -> list[SplitContainerContentT]: @content.setter def content(self, content: Sequence[SplitContainerContentT]) -> None: - for old_content in self._content: - if old_content is not None: - old_content.app = None - old_content.window = None - try: if len(content) != 2: raise TypeError() @@ -131,6 +126,14 @@ def content(self, content: Sequence[SplitContainerContentT]) -> None: _content.append(widget) flex.append(flex_value) + # Only detach the old content once the new content is fully validated, + # so a failed assignment doesn't orphan the widgets currently shown. + for old_content in self._content: + if old_content is not None: + old_content.app = None + old_content.window = None + + for widget in _content: if widget: widget.app = self.app widget.window = self.window diff --git a/core/tests/widgets/test_splitcontainer.py b/core/tests/widgets/test_splitcontainer.py index 8bfadf5edb..c162b823ad 100644 --- a/core/tests/widgets/test_splitcontainer.py +++ b/core/tests/widgets/test_splitcontainer.py @@ -412,6 +412,24 @@ def test_set_content_invalid(splitcontainer, content, message): splitcontainer.content = content +def test_set_content_invalid_keeps_previous_content(content1, content2): + """A failed content assignment must not orphan the widgets currently shown + (regression: the old content was detached before validation).""" + app = toga.App("Test App", "org.beeware.toga.splitcontainer-test") + window = toga.Window() + splitcontainer = toga.SplitContainer(content=[content1, content2]) + window.content = splitcontainer + + assert content1.id in app.widgets + + with pytest.raises(ValueError): + splitcontainer.content = [content1] + + # The previous content is still attached and intact + assert content1.id in app.widgets + assert splitcontainer.content == [content1, content2] + + def test_direction(splitcontainer): """The direction of the splitcontainer can be changed.""" From c6eae9cbd4f47339f85455e8d23459894dcc5b6f Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 6 Aug 2026 09:19:33 +0800 Subject: [PATCH 2/2] Correct test comment. --- core/tests/widgets/test_splitcontainer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/tests/widgets/test_splitcontainer.py b/core/tests/widgets/test_splitcontainer.py index c162b823ad..c43010ab5a 100644 --- a/core/tests/widgets/test_splitcontainer.py +++ b/core/tests/widgets/test_splitcontainer.py @@ -413,8 +413,7 @@ def test_set_content_invalid(splitcontainer, content, message): def test_set_content_invalid_keeps_previous_content(content1, content2): - """A failed content assignment must not orphan the widgets currently shown - (regression: the old content was detached before validation).""" + """A failed content assignment must not orphan the widgets currently shown.""" app = toga.App("Test App", "org.beeware.toga.splitcontainer-test") window = toga.Window() splitcontainer = toga.SplitContainer(content=[content1, content2])