mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Widget collapsible (#2989)
* Collapsible container widget. * Expose collapsible widget. * Add collapsible container example * Rename member variables as label and apply formatting * Apply hover effect * Apply formatting * Add collapsible construction example with children. * Wrap contents within Container and move _collapsed flag to Collapsible class from Summary for easier access. * Add collapsible example that is expanded by default. * Update collapsed property to be reactive * Add footer to collapse and expand all with bound keys. * Expose summary property of Collapsible * Assign ids of ollapsed, expanded label instead of classes * Add unit tests of Collapsible * Rename class Summary to Title * Rename variables of expanded/collapsed symbols and add it to arguments.. * Add documentation for Collapsible * Update symbol ids of Collapsible title * Update src/textual/widgets/_collapsible.py Correct import path Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * Sort module names in alphabetical order * Clarify that collapsible is non-focusable in documentation. * Add version hint * Fix documentation of Collapsible. * Add snapshot test for collapsible widget * Stop on click event from Collapsible. * Handle Title.Toggle event to prevent event in Contents from propagating to the children or parents Collapsible widgets. * Update Collapsible default css to have 1 fraction of width instead of 100% * Update Collapsible custom symbol snapshot * Add Collapsible custom symbol snapshot as an example * Update docs/widgets/collapsible.md Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * Update src/textual/widgets/_collapsible.py Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * Fix typo in Collapsible docs * Rework collapsible documentation. --------- Co-authored-by: Sunyoung Yoo <luysunyoung@aifactory.page> Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
This commit is contained in:
47
docs/examples/widgets/collapsible.py
Normal file
47
docs/examples/widgets/collapsible.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Collapsible, Footer, Label, Markdown
|
||||
|
||||
LETO = """
|
||||
# Duke Leto I Atreides
|
||||
|
||||
Head of House Atreides.
|
||||
"""
|
||||
|
||||
JESSICA = """
|
||||
# Lady Jessica
|
||||
|
||||
Bene Gesserit and concubine of Leto, and mother of Paul and Alia.
|
||||
"""
|
||||
|
||||
PAUL = """
|
||||
# Paul Atreides
|
||||
|
||||
Son of Leto and Jessica.
|
||||
"""
|
||||
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
"""An example of colllapsible container."""
|
||||
|
||||
BINDINGS = [
|
||||
("c", "collapse_or_expand(True)", "Collapse All"),
|
||||
("e", "collapse_or_expand(False)", "Expand All"),
|
||||
]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Compose app with collapsible containers."""
|
||||
yield Footer()
|
||||
with Collapsible(collapsed=False, title="Leto"):
|
||||
yield Label(LETO)
|
||||
yield Collapsible(Markdown(JESSICA), collapsed=False, title="Jessica")
|
||||
with Collapsible(collapsed=True, title="Paul"):
|
||||
yield Markdown(PAUL)
|
||||
|
||||
def action_collapse_or_expand(self, collapse: bool) -> None:
|
||||
for child in self.walk_children(Collapsible):
|
||||
child.collapsed = collapse
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = CollapsibleApp()
|
||||
app.run()
|
||||
25
docs/examples/widgets/collapsible_custom_symbol.py
Normal file
25
docs/examples/widgets/collapsible_custom_symbol.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Horizontal
|
||||
from textual.widgets import Collapsible, Label
|
||||
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
with Horizontal():
|
||||
with Collapsible(
|
||||
collapsed_symbol=">>>",
|
||||
expanded_symbol="v",
|
||||
):
|
||||
yield Label("Hello, world.")
|
||||
|
||||
with Collapsible(
|
||||
collapsed_symbol=">>>",
|
||||
expanded_symbol="v",
|
||||
collapsed=False,
|
||||
):
|
||||
yield Label("Hello, world.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = CollapsibleApp()
|
||||
app.run()
|
||||
14
docs/examples/widgets/collapsible_nested.py
Normal file
14
docs/examples/widgets/collapsible_nested.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Collapsible, Label
|
||||
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
with Collapsible(collapsed=False):
|
||||
with Collapsible():
|
||||
yield Label("Hello, world.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = CollapsibleApp()
|
||||
app.run()
|
||||
153
docs/widgets/collapsible.md
Normal file
153
docs/widgets/collapsible.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Collapsible
|
||||
|
||||
!!! tip "Added in version 0.36"
|
||||
|
||||
Widget that wraps its contents in a collapsible container.
|
||||
|
||||
- [ ] Focusable
|
||||
- [x] Container
|
||||
|
||||
|
||||
## Composing
|
||||
|
||||
There are two ways to wrap other widgets.
|
||||
You can pass them as positional arguments to the [Collapsible][textual.widgets.Collapsible] constructor:
|
||||
|
||||
```python
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Collapsible(Label("Hello, world."))
|
||||
```
|
||||
|
||||
Alternatively, you can compose other widgets under the context manager:
|
||||
|
||||
```python
|
||||
def compose(self) -> ComposeResult:
|
||||
with Collapsible():
|
||||
yield Label("Hello, world.")
|
||||
```
|
||||
|
||||
## Title
|
||||
|
||||
The default title "Toggle" of the `Collapsible` widget can be customized by specifying the parameter `title` of the constructor:
|
||||
|
||||
```python
|
||||
def compose(self) -> ComposeResult:
|
||||
with Collapsible(title="An interesting story."):
|
||||
yield Label("Interesting but verbose story.")
|
||||
```
|
||||
|
||||
## Initial State
|
||||
|
||||
The initial state of the `Collapsible` widget can be customized via the parameter `collapsed` of the constructor:
|
||||
|
||||
```python
|
||||
def compose(self) -> ComposeResult:
|
||||
with Collapsible(title="Contents 1", collapsed=False):
|
||||
yield Label("Hello, world.")
|
||||
|
||||
with Collapsible(title="Contents 2", collapsed=True): # Default.
|
||||
yield Label("Hello, world.")
|
||||
```
|
||||
|
||||
## Collapse/Expand Symbols
|
||||
|
||||
The symbols `►` and `▼` of the `Collapsible` widget can be customized by specifying the parameters `collapsed_symbol` and `expanded_symbol`, respectively, of the `Collapsible` constructor:
|
||||
|
||||
```python
|
||||
def compose(self) -> ComposeResult:
|
||||
with Collapsible(collapsed_symbol=">>>", expanded_symbol="v"):
|
||||
yield Label("Hello, world.")
|
||||
```
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="tests/snapshot_tests/snapshot_apps/collapsible_custom_symbol.py"}
|
||||
```
|
||||
|
||||
=== "collapsible_custom_symbol.py"
|
||||
|
||||
```python
|
||||
--8<-- "tests/snapshot_tests/snapshot_apps/collapsible_custom_symbol.py"
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic example
|
||||
|
||||
The following example contains three `Collapsible`s in different states.
|
||||
|
||||
=== "All expanded"
|
||||
|
||||
```{.textual path="docs/examples/widgets/collapsible.py press="e"}
|
||||
```
|
||||
|
||||
=== "All collapsed"
|
||||
|
||||
```{.textual path="docs/examples/widgets/collapsible.py press="c"}
|
||||
```
|
||||
|
||||
=== "Mixed"
|
||||
|
||||
```{.textual path="docs/examples/widgets/collapsible.py"}
|
||||
```
|
||||
|
||||
=== "collapsible.py"
|
||||
|
||||
```python
|
||||
--8<-- "docs/examples/widgets/collapsible.py"
|
||||
```
|
||||
|
||||
### Setting Initial State
|
||||
|
||||
The example below shows nested `Collapsible` widgets and how to set their initial state.
|
||||
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="tests/snapshot_tests/snapshot_apps/collapsible_nested.py"}
|
||||
```
|
||||
|
||||
=== "collapsible_nested.py"
|
||||
|
||||
```python hl_lines="7"
|
||||
--8<-- "tests/snapshot_tests/snapshot_apps/collapsible_nested.py"
|
||||
```
|
||||
|
||||
### Custom Symbols
|
||||
|
||||
The app below shows `Collapsible` widgets with custom expand/collapse symbols.
|
||||
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="tests/snapshot_tests/snapshot_apps/collapsible_custom_symbol.py"}
|
||||
```
|
||||
|
||||
=== "collapsible_custom_symbol.py"
|
||||
|
||||
```python
|
||||
--8<-- "tests/snapshot_tests/snapshot_apps/collapsible_custom_symbol.py"
|
||||
```
|
||||
|
||||
## Reactive attributes
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ----------- | ------ | ------- | -------------------------------------------------------------- |
|
||||
| `collapsed` | `bool` | `True` | Controls the collapsed/expanded state of the widget. |
|
||||
|
||||
## Messages
|
||||
|
||||
- [Collapsible.Title.Toggle][textual.widgets.Collapsible.Title.Toggle]
|
||||
|
||||
<!--
|
||||
## See also
|
||||
|
||||
TODO: Add Accordion widgets later
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
|
||||
::: textual.widgets.Collapsible
|
||||
options:
|
||||
heading_level: 2
|
||||
@@ -12,6 +12,7 @@ if typing.TYPE_CHECKING:
|
||||
from ..widget import Widget
|
||||
from ._button import Button
|
||||
from ._checkbox import Checkbox
|
||||
from ._collapsible import Collapsible
|
||||
from ._content_switcher import ContentSwitcher
|
||||
from ._data_table import DataTable
|
||||
from ._digits import Digits
|
||||
@@ -44,10 +45,10 @@ if typing.TYPE_CHECKING:
|
||||
from ._tree import Tree
|
||||
from ._welcome import Welcome
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Button",
|
||||
"Checkbox",
|
||||
"Collapsible",
|
||||
"ContentSwitcher",
|
||||
"DataTable",
|
||||
"Digits",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# This stub file must re-export every classes exposed in the __init__.py's `__all__` list:
|
||||
from ._button import Button as Button
|
||||
from ._checkbox import Checkbox as Checkbox
|
||||
from ._collapsible import Collapsible as Collapsible
|
||||
from ._content_switcher import ContentSwitcher as ContentSwitcher
|
||||
from ._data_table import DataTable as DataTable
|
||||
from ._digits import Digits as Digits
|
||||
|
||||
156
src/textual/widgets/_collapsible.py
Normal file
156
src/textual/widgets/_collapsible.py
Normal file
@@ -0,0 +1,156 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from textual.widget import Widget
|
||||
|
||||
from .. import events
|
||||
from ..app import ComposeResult
|
||||
from ..containers import Container, Horizontal
|
||||
from ..message import Message
|
||||
from ..reactive import reactive
|
||||
from ..widget import Widget
|
||||
from ..widgets import Label
|
||||
|
||||
__all__ = ["Collapsible"]
|
||||
|
||||
|
||||
class Collapsible(Widget):
|
||||
"""A collapsible container."""
|
||||
|
||||
collapsed = reactive(True)
|
||||
|
||||
DEFAULT_CSS = """
|
||||
Collapsible {
|
||||
width: 1fr;
|
||||
height: auto;
|
||||
}
|
||||
"""
|
||||
|
||||
class Title(Horizontal):
|
||||
DEFAULT_CSS = """
|
||||
Title {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
Title:hover {
|
||||
background: grey;
|
||||
}
|
||||
|
||||
Title .label {
|
||||
padding: 0 0 0 1;
|
||||
}
|
||||
|
||||
Title #collapsed-symbol {
|
||||
display:none;
|
||||
}
|
||||
|
||||
Title.-collapsed #expanded-symbol {
|
||||
display:none;
|
||||
}
|
||||
|
||||
Title.-collapsed #collapsed-symbol {
|
||||
display:block;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
label: str,
|
||||
collapsed_symbol: str,
|
||||
expanded_symbol: str,
|
||||
name: str | None = None,
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
disabled: bool = False,
|
||||
) -> None:
|
||||
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
|
||||
self.collapsed_symbol = collapsed_symbol
|
||||
self.expanded_symbol = expanded_symbol
|
||||
self.label = label
|
||||
|
||||
class Toggle(Message):
|
||||
"""Request toggle."""
|
||||
|
||||
async def _on_click(self, event: events.Click) -> None:
|
||||
"""Inform ancestor we want to toggle."""
|
||||
event.stop()
|
||||
self.post_message(self.Toggle())
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Compose right/down arrow and label."""
|
||||
yield Label(self.expanded_symbol, classes="label", id="expanded-symbol")
|
||||
yield Label(self.collapsed_symbol, classes="label", id="collapsed-symbol")
|
||||
yield Label(self.label, classes="label")
|
||||
|
||||
class Contents(Container):
|
||||
DEFAULT_CSS = """
|
||||
Contents {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 0 0 0 3;
|
||||
}
|
||||
|
||||
Contents.-collapsed {
|
||||
display: none;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*children: Widget,
|
||||
title: str = "Toggle",
|
||||
collapsed: bool = True,
|
||||
collapsed_symbol: str = "►",
|
||||
expanded_symbol: str = "▼",
|
||||
name: str | None = None,
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
disabled: bool = False,
|
||||
) -> None:
|
||||
"""Initialize a Collapsible widget.
|
||||
|
||||
Args:
|
||||
*children: Contents that will be collapsed/expanded.
|
||||
title: Title of the collapsed/expanded contents.
|
||||
collapsed: Default status of the contents.
|
||||
collapsed_symbol: Collapsed symbol before the title.
|
||||
expanded_symbol: Expanded symbol before the title.
|
||||
name: The name of the collapsible.
|
||||
id: The ID of the collapsible in the DOM.
|
||||
classes: The CSS classes of the collapsible.
|
||||
disabled: Whether the collapsible is disabled or not.
|
||||
"""
|
||||
self._title = self.Title(
|
||||
label=title,
|
||||
collapsed_symbol=collapsed_symbol,
|
||||
expanded_symbol=expanded_symbol,
|
||||
)
|
||||
self._contents_list: list[Widget] = list(children)
|
||||
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
|
||||
self.collapsed = collapsed
|
||||
|
||||
def _on_title_toggle(self, event: Title.Toggle) -> None:
|
||||
event.stop()
|
||||
self.collapsed = not self.collapsed
|
||||
|
||||
def watch_collapsed(self) -> None:
|
||||
for child in self._nodes:
|
||||
child.set_class(self.collapsed, "-collapsed")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield from (
|
||||
child.set_class(self.collapsed, "-collapsed")
|
||||
for child in (
|
||||
self._title,
|
||||
self.Contents(*self._contents_list),
|
||||
)
|
||||
)
|
||||
|
||||
def compose_add_child(self, widget: Widget) -> None:
|
||||
"""When using the context manager compose syntax, we want to attach nodes to the contents.
|
||||
|
||||
Args:
|
||||
widget: A Widget to add.
|
||||
"""
|
||||
self._contents_list.append(widget)
|
||||
File diff suppressed because one or more lines are too long
@@ -330,6 +330,26 @@ def test_sparkline_component_classes_colors(snap_compare):
|
||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "sparkline_colors.py")
|
||||
|
||||
|
||||
def test_collapsible_render(snap_compare):
|
||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "collapsible.py")
|
||||
|
||||
|
||||
def test_collapsible_collapsed(snap_compare):
|
||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "collapsible.py", press=["c"])
|
||||
|
||||
|
||||
def test_collapsible_expanded(snap_compare):
|
||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "collapsible.py", press=["e"])
|
||||
|
||||
|
||||
def test_collapsible_nested(snap_compare):
|
||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "collapsible_nested.py")
|
||||
|
||||
|
||||
def test_collapsible_custom_symbol(snap_compare):
|
||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "collapsible_custom_symbol.py")
|
||||
|
||||
|
||||
# --- CSS properties ---
|
||||
# We have a canonical example for each CSS property that is shown in their docs.
|
||||
# If any of these change, something has likely broken, so snapshot each of them.
|
||||
|
||||
163
tests/test_collapsible.py
Normal file
163
tests/test_collapsible.py
Normal file
@@ -0,0 +1,163 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Collapsible, Label
|
||||
|
||||
COLLAPSED_CLASS = "-collapsed"
|
||||
|
||||
|
||||
def get_title(collapsible: Collapsible) -> Collapsible.Title:
|
||||
return collapsible.get_child_by_type(Collapsible.Title)
|
||||
|
||||
|
||||
def get_contents(collapsible: Collapsible) -> Collapsible.Contents:
|
||||
return collapsible.get_child_by_type(Collapsible.Contents)
|
||||
|
||||
|
||||
async def test_collapsible():
|
||||
"""It should be possible to access title and collapsed."""
|
||||
collapsible = Collapsible(title="Pilot", collapsed=True)
|
||||
assert collapsible._title.label == "Pilot"
|
||||
assert collapsible.collapsed
|
||||
|
||||
|
||||
async def test_compose_default_collapsible():
|
||||
"""Test default settings of Collapsible with 1 widget in contents."""
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Collapsible(Label("Some Contents"))
|
||||
|
||||
async with CollapsibleApp().run_test() as pilot:
|
||||
collapsible = pilot.app.query_one(Collapsible)
|
||||
assert get_title(collapsible).label == "Toggle"
|
||||
assert get_title(collapsible).has_class(COLLAPSED_CLASS)
|
||||
assert len(get_contents(collapsible).children) == 1
|
||||
assert get_contents(collapsible).has_class(COLLAPSED_CLASS)
|
||||
|
||||
|
||||
async def test_compose_empty_collapsible():
|
||||
"""It should be possible to create an empty Collapsible."""
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Collapsible()
|
||||
|
||||
async with CollapsibleApp().run_test() as pilot:
|
||||
collapsible = pilot.app.query_one(Collapsible)
|
||||
assert len(get_contents(collapsible).children) == 0
|
||||
|
||||
|
||||
async def test_compose_nested_collapsible():
|
||||
"""Children Collapsibles are independent from parents Collapsibles."""
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
with Collapsible(Label("Outer"), id="outer", collapsed=False):
|
||||
yield Collapsible(Label("Inner"), id="inner", collapsed=False)
|
||||
|
||||
async with CollapsibleApp().run_test() as pilot:
|
||||
outer: Collapsible = pilot.app.get_child_by_id("outer")
|
||||
inner: Collapsible = get_contents(outer).get_child_by_id("inner")
|
||||
outer.collapsed = True
|
||||
assert not inner.collapsed
|
||||
|
||||
|
||||
async def test_compose_expanded_collapsible():
|
||||
"""It should be possible to create a Collapsible with expanded contents."""
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Collapsible(collapsed=False)
|
||||
|
||||
async with CollapsibleApp().run_test() as pilot:
|
||||
collapsible = pilot.app.query_one(Collapsible)
|
||||
assert not get_title(collapsible).has_class(COLLAPSED_CLASS)
|
||||
assert not get_contents(collapsible).has_class(COLLAPSED_CLASS)
|
||||
|
||||
|
||||
async def test_collapsible_collapsed_title_label():
|
||||
"""Collapsed title label should be displayed."""
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Collapsible(Label("Some Contents"), collapsed=True)
|
||||
|
||||
async with CollapsibleApp().run_test() as pilot:
|
||||
title = get_title(pilot.app.query_one(Collapsible))
|
||||
assert not title.get_child_by_id("expanded-symbol").display
|
||||
assert title.get_child_by_id("collapsed-symbol").display
|
||||
|
||||
|
||||
async def test_collapsible_expanded_title_label():
|
||||
"""Expanded title label should be displayed."""
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Collapsible(Label("Some Contents"), collapsed=False)
|
||||
|
||||
async with CollapsibleApp().run_test() as pilot:
|
||||
title = get_title(pilot.app.query_one(Collapsible))
|
||||
assert title.get_child_by_id("expanded-symbol").display
|
||||
assert not title.get_child_by_id("collapsed-symbol").display
|
||||
|
||||
|
||||
async def test_collapsible_collapsed_contents_display_false():
|
||||
"""Test default settings of Collapsible with 1 widget in contents."""
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Collapsible(Label("Some Contents"), collapsed=True)
|
||||
|
||||
async with CollapsibleApp().run_test() as pilot:
|
||||
collapsible = pilot.app.query_one(Collapsible)
|
||||
assert not get_contents(collapsible).display
|
||||
|
||||
|
||||
async def test_collapsible_expanded_contents_display_true():
|
||||
"""Test default settings of Collapsible with 1 widget in contents."""
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Collapsible(Label("Some Contents"), collapsed=False)
|
||||
|
||||
async with CollapsibleApp().run_test() as pilot:
|
||||
collapsible = pilot.app.query_one(Collapsible)
|
||||
assert get_contents(collapsible).display
|
||||
|
||||
|
||||
async def test_reactive_collapsed():
|
||||
"""Updating ``collapsed`` should change classes of children."""
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Collapsible(collapsed=False)
|
||||
|
||||
async with CollapsibleApp().run_test() as pilot:
|
||||
collapsible = pilot.app.query_one(Collapsible)
|
||||
assert not get_title(collapsible).has_class(COLLAPSED_CLASS)
|
||||
collapsible.collapsed = True
|
||||
assert get_contents(collapsible).has_class(COLLAPSED_CLASS)
|
||||
collapsible.collapsed = False
|
||||
assert not get_title(collapsible).has_class(COLLAPSED_CLASS)
|
||||
|
||||
|
||||
async def test_toggle_title():
|
||||
"""Clicking title should update ``collapsed``."""
|
||||
|
||||
class CollapsibleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Collapsible(collapsed=False)
|
||||
|
||||
async with CollapsibleApp().run_test() as pilot:
|
||||
collapsible = pilot.app.query_one(Collapsible)
|
||||
assert not collapsible.collapsed
|
||||
assert not get_title(collapsible).has_class(COLLAPSED_CLASS)
|
||||
|
||||
await pilot.click(Collapsible.Title)
|
||||
assert collapsible.collapsed
|
||||
assert get_contents(collapsible).has_class(COLLAPSED_CLASS)
|
||||
|
||||
await pilot.click(Collapsible.Title)
|
||||
assert not collapsible.collapsed
|
||||
assert not get_title(collapsible).has_class(COLLAPSED_CLASS)
|
||||
Reference in New Issue
Block a user