From 6e7efc4f404d3d6afac56779b239660aeb56a857 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 8 Oct 2026 09:10:36 -0400 Subject: [PATCH] Publish the supplied Collapsible title without a default overwrite --- src/textual/widgets/_collapsible.py | 9 +++++--- tests/test_collapsible.py | 36 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/textual/widgets/_collapsible.py b/src/textual/widgets/_collapsible.py index 409627811c..cf18c9bde6 100644 --- a/src/textual/widgets/_collapsible.py +++ b/src/textual/widgets/_collapsible.py @@ -104,7 +104,10 @@ class Collapsible(Widget): ALLOW_MAXIMIZE = True collapsed = reactive(True, init=False) - title = reactive("Toggle") + # The constructor publishes the supplied title through the normal watcher + # path. The default is not a separate title publication; Content's text-only + # equality must also not suppress changes to spans or action metadata. + title: reactive[ContentText] = reactive("Toggle", init=False, always_update=True) DEFAULT_CSS = """ Collapsible { @@ -177,7 +180,7 @@ class Contents(Container): def __init__( self, *children: Widget, - title: str = "Toggle", + title: ContentText = "Toggle", collapsed: bool = True, collapsed_symbol: str = "▶", expanded_symbol: str = "▼", @@ -249,5 +252,5 @@ def compose_add_child(self, widget: Widget) -> None: """ self._contents_list.append(widget) - def _watch_title(self, title: str) -> None: + def _watch_title(self, title: ContentText) -> None: self._title.label = title diff --git a/tests/test_collapsible.py b/tests/test_collapsible.py index 05247bc376..cb006c1c85 100644 --- a/tests/test_collapsible.py +++ b/tests/test_collapsible.py @@ -271,3 +271,39 @@ def compose(self) -> ComposeResult: assert get_title(collapsible).label == "Old title" collapsible.title = "New title" assert get_title(collapsible).label == "New title" + + +async def test_parent_title_publishes_supplied_value_and_styled_changes(): + class WatchedCollapsible(Collapsible): + def __init__(self, **kwargs): + self.publications = [] + super().__init__(Label("arbitrary child"), **kwargs) + + def watch_title(self, old, new): + self.publications.append((old, new)) + + original = Content.from_markup("[red]supplied[/red]") + changed = Content.from_markup("[blue]supplied[/blue]") + collapsible = WatchedCollapsible(title=original, collapsed=False) + default = WatchedCollapsible() + assert collapsible.publications == [("Toggle", original)] + assert default.publications == [("Toggle", "Toggle")] + assert collapsible._title.label.is_same(original) + app = App() + async with app.run_test() as pilot: + await app.mount(collapsible, default) + await pilot.pause() + child = get_contents(collapsible).query_one(Label) + revision = collapsible._title._layout_updates + collapsible.title = changed + assert collapsible._title.label.is_same(changed) + assert collapsible._title.content.is_same( + Content.assemble("▼", " ", changed) + ) + assert collapsible._title._layout_updates == revision + assert collapsible.publications[-1] == (original, changed) + collapsible.collapsed = True + await pilot.pause() + assert not get_contents(collapsible).display + assert child.is_mounted + assert collapsible._title.content.plain == "▶ supplied"