mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Add ContentSwitcher (#1983)
* Add the basic ContentSwitcher widget * Docstring tidy * Add a visible_content property to the ContentSwitcher * Clarify that children of ContentSwitcher with no IDs get ignored * Simplify setting the display value * Add the start of an example ContentSwitcher for the docs * Tweak the example layout to better fit in small spaces * Add the content switcher to the API docs * Add a guide entry for the ContentSwitcher This one is a wee bit more involved than most other widget entries in the guide in that it doesn't obviously do anything itself, but needs developer-input to make it do something useful. As such the outline here isn't as clean as it could be, but I think it conveys everything necessary without getting too complicated. * Add the reactive attribute table to the ContentSwitcher guide * Update the README * Add a refresh after everything has been flipped in the switcher As noted in the code, this should not be necessary and I don't believe it has anything to do with this code. I would suspect some lower-level issue with flipping between different widgets within a container. I need to find a way to make an isolated reproduction that isn't about this particular widget. Meanwhile though this works with the refresh(). * Swap current from var to reactive This solves the explicit refresh issue, but only because the refresh is implied due to the use of a reactive over a var. As such this sort of addresses #1979 by ignoring the issue rather than diving into it. I still suspect that I shouldn't need to do this, and that perhaps there's a refresh issue when you flip display. So I'll keep #1979 kicking around and at some point see if I can recreate in isolation. * Add unit tests for the content switcher * Add snapshot tests for the ContentSwitcher * Clarify that an exception can be thrown on a bad ID * Try and help other Pythons * Add a pause at the end of the second switcher snapshot test I'm getting a lot of fails in CI; none of them are actual problems. Hopefully this will cure it. * Paaaaaaaaause More of a test than anything else really. My particular snapshot test is failing but kinda randomly in each environment each time -- sometimes Windows, sometimes GNU/Linux, different Python versions. So... yeah, let's try this and see if it makes it through; otherwise I may need to rethink this. * New pause So it turns out that _ doesn't do anything any more; and instead there's a "wait:<n>" syntax! So let's give that a try. * Learning my alphabet... * Fix a typo in the docs. Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * Add missing full stop. Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * Add a missing word Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * Try a longer wait on the switcher I'm starting to suspect that this doesn't come down to a timing issue; especially given that the snapshot report seems to be showing some oddity in the length of the vertical scrollbar. But... I want to be as sure as possible so let's double the length of the wait. Bit a bit of me is starting to wonder if I've somehow managed to create the perfect storm for scrollbars and you don't always get the same result every time. Seems unlikely, but if it's not timing it's that or lots of cosmic rays. * Test a longer pause on the content switcher test The idea here being that it takes 200ms for the button to pop again. * Refresh the snapshots This time. THIS TIME! * Experiment: is the issue the same name for two tests? * Experiment: drop the different source files, try terminal size Having got over the issue of the button not ending up in the same state, we're stuck with the scrollbar having different sizes. Having tried other options let's go with tweaking the terminal size. * Do a little less work when changing current Rather than set everything invisible then the new one visible, every time current is changed, instead just make sure everything is invisible up front and then just swap the affected children each time. This does mean that if someone messes with the children under the hood they may see oddness happening, but less work while being less defensive seems fair here. --------- Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
This commit is contained in:
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Breaking change: Added `toggle_button` attribute to RadioButton and Checkbox events, replaces `input` https://github.com/Textualize/textual/pull/1940
|
- Breaking change: Added `toggle_button` attribute to RadioButton and Checkbox events, replaces `input` https://github.com/Textualize/textual/pull/1940
|
||||||
- A percentage alpha can now be applied to a border https://github.com/Textualize/textual/issues/1863
|
- A percentage alpha can now be applied to a border https://github.com/Textualize/textual/issues/1863
|
||||||
- Added `Color.multiply_alpha`.
|
- Added `Color.multiply_alpha`.
|
||||||
|
- Added `ContentSwitcher` https://github.com/Textualize/textual/issues/1945
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
1
docs/api/content_switcher.md
Normal file
1
docs/api/content_switcher.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
::: textual.widgets.ContentSwitcher
|
||||||
27
docs/examples/widgets/content_switcher.css
Normal file
27
docs/examples/widgets/content_switcher.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Screen {
|
||||||
|
align: center middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#buttons {
|
||||||
|
margin-top: 1;
|
||||||
|
height: 3;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentSwitcher {
|
||||||
|
background: $panel;
|
||||||
|
border: round $primary;
|
||||||
|
width: 90%;
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataTable {
|
||||||
|
background: $panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkdownH2 {
|
||||||
|
background: $primary;
|
||||||
|
color: yellow;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
64
docs/examples/widgets/content_switcher.py
Normal file
64
docs/examples/widgets/content_switcher.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.containers import Horizontal
|
||||||
|
from textual.widgets import Button, ContentSwitcher, DataTable, Markdown
|
||||||
|
|
||||||
|
MARKDOWN_EXAMPLE = """# Three Flavours Cornetto
|
||||||
|
|
||||||
|
The Three Flavours Cornetto trilogy is an anthology series of British
|
||||||
|
comedic genre films directed by Edgar Wright.
|
||||||
|
|
||||||
|
## Shaun of the Dead
|
||||||
|
|
||||||
|
| Flavour | UK Release Date | Director |
|
||||||
|
| -- | -- | -- |
|
||||||
|
| Strawberry | 2004-04-09 | Edgar Wright |
|
||||||
|
|
||||||
|
## Hot Fuzz
|
||||||
|
|
||||||
|
| Flavour | UK Release Date | Director |
|
||||||
|
| -- | -- | -- |
|
||||||
|
| Classico | 2007-02-17 | Edgar Wright |
|
||||||
|
|
||||||
|
## The World's End
|
||||||
|
|
||||||
|
| Flavour | UK Release Date | Director |
|
||||||
|
| -- | -- | -- |
|
||||||
|
| Mint | 2013-07-19 | Edgar Wright |
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ContentSwitcherApp(App[None]):
|
||||||
|
CSS_PATH = "content_switcher.css"
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with Horizontal(id="buttons"): # (1)!
|
||||||
|
yield Button("DataTable", id="data-table") # (2)!
|
||||||
|
yield Button("Markdown", id="markdown") # (3)!
|
||||||
|
|
||||||
|
with ContentSwitcher(initial="data-table"): # (4)!
|
||||||
|
yield DataTable(id="data-table")
|
||||||
|
yield Markdown(MARKDOWN_EXAMPLE, id="markdown")
|
||||||
|
|
||||||
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
|
self.query_one(ContentSwitcher).current = event.button.id # (5)!
|
||||||
|
|
||||||
|
def on_mount(self) -> None:
|
||||||
|
table = self.query_one(DataTable)
|
||||||
|
table.add_columns("Book", "Year")
|
||||||
|
table.add_rows(
|
||||||
|
[
|
||||||
|
(title.ljust(35), year)
|
||||||
|
for title, year in (
|
||||||
|
("Dune", 1965),
|
||||||
|
("Dune Messiah", 1969),
|
||||||
|
("Children of Dune", 1976),
|
||||||
|
("God Emperor of Dune", 1981),
|
||||||
|
("Heretics of Dune", 1984),
|
||||||
|
("Chapterhouse: Dune", 1985),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ContentSwitcherApp().run()
|
||||||
54
docs/widgets/content_switcher.md
Normal file
54
docs/widgets/content_switcher.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# ContentSwitcher
|
||||||
|
|
||||||
|
A widget for containing and switching display between multiple child
|
||||||
|
widgets.
|
||||||
|
|
||||||
|
- [ ] Focusable
|
||||||
|
- [X] Container
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
The example below uses a `ContentSwitcher` in combination with two `Button`s
|
||||||
|
to create a simple tabbed view. Note how each `Button` has an ID set, and
|
||||||
|
how each child of the `ContentSwitcher` has a corresponding ID; then a
|
||||||
|
`Button.Clicked` handler is used to set `ContentSwitcher.current` to switch
|
||||||
|
between the different views.
|
||||||
|
|
||||||
|
=== "Output"
|
||||||
|
|
||||||
|
```{.textual path="docs/examples/widgets/content_switcher.py"}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "content_switcher.py"
|
||||||
|
|
||||||
|
~~~python
|
||||||
|
--8<-- "docs/examples/widgets/content_switcher.py"
|
||||||
|
~~~
|
||||||
|
|
||||||
|
1. A `Horizontal` to hold the buttons, each with a unique ID.
|
||||||
|
2. This button will select the `DataTable` in the `ContentSwitcher`.
|
||||||
|
3. This button will select the `Markdown` in the `ContentSwitcher`.
|
||||||
|
4. Note that the intial visible content is set by its ID, see below.
|
||||||
|
5. When a button is pressed, its ID is used to switch to a different widget in the `ContentSwitcher`. Remember that IDs are unique within parent, so the buttons and the widgets in the `ContentSwitcher` can share IDs.
|
||||||
|
|
||||||
|
=== "content_switcher.css"
|
||||||
|
|
||||||
|
~~~sass
|
||||||
|
--8<-- "docs/examples/widgets/content_switcher.css"
|
||||||
|
~~~
|
||||||
|
|
||||||
|
When the user presses the "Markdown" button the view is switched:
|
||||||
|
|
||||||
|
```{.textual path="docs/examples/widgets/content_switcher.py" lines="40" press="tab,tab,enter"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reactive Attributes
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|-----------|-----------------|---------|----------------------------------------------------------------------|
|
||||||
|
| `current` | `str` \| `None` | `None` | The ID of the currently-visible child. `None` means nothing is visible. |
|
||||||
|
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
* [ContentSwitcher][textual.widgets.ContentSwitcher] code reference
|
||||||
@@ -122,6 +122,7 @@ nav:
|
|||||||
- Widgets:
|
- Widgets:
|
||||||
- "widgets/button.md"
|
- "widgets/button.md"
|
||||||
- "widgets/checkbox.md"
|
- "widgets/checkbox.md"
|
||||||
|
- "widgets/content_switcher.md"
|
||||||
- "widgets/data_table.md"
|
- "widgets/data_table.md"
|
||||||
- "widgets/directory_tree.md"
|
- "widgets/directory_tree.md"
|
||||||
- "widgets/footer.md"
|
- "widgets/footer.md"
|
||||||
@@ -148,6 +149,7 @@ nav:
|
|||||||
- "api/checkbox.md"
|
- "api/checkbox.md"
|
||||||
- "api/color.md"
|
- "api/color.md"
|
||||||
- "api/containers.md"
|
- "api/containers.md"
|
||||||
|
- "api/content_switcher.md"
|
||||||
- "api/coordinate.md"
|
- "api/coordinate.md"
|
||||||
- "api/data_table.md"
|
- "api/data_table.md"
|
||||||
- "api/directory_tree.md"
|
- "api/directory_tree.md"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ if typing.TYPE_CHECKING:
|
|||||||
from ..widget import Widget
|
from ..widget import Widget
|
||||||
from ._button import Button
|
from ._button import Button
|
||||||
from ._checkbox import Checkbox
|
from ._checkbox import Checkbox
|
||||||
|
from ._content_switcher import ContentSwitcher
|
||||||
from ._data_table import DataTable
|
from ._data_table import DataTable
|
||||||
from ._directory_tree import DirectoryTree
|
from ._directory_tree import DirectoryTree
|
||||||
from ._footer import Footer
|
from ._footer import Footer
|
||||||
@@ -35,6 +36,7 @@ if typing.TYPE_CHECKING:
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"Button",
|
"Button",
|
||||||
"Checkbox",
|
"Checkbox",
|
||||||
|
"ContentSwitcher",
|
||||||
"DataTable",
|
"DataTable",
|
||||||
"DirectoryTree",
|
"DirectoryTree",
|
||||||
"Footer",
|
"Footer",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# This stub file must re-export every classes exposed in the __init__.py's `__all__` list:
|
# This stub file must re-export every classes exposed in the __init__.py's `__all__` list:
|
||||||
from ._button import Button as Button
|
from ._button import Button as Button
|
||||||
from ._checkbox import Checkbox as Checkbox
|
from ._checkbox import Checkbox as Checkbox
|
||||||
|
from ._content_switcher import ContentSwitcher as ContentSwitcher
|
||||||
from ._data_table import DataTable as DataTable
|
from ._data_table import DataTable as DataTable
|
||||||
from ._directory_tree import DirectoryTree as DirectoryTree
|
from ._directory_tree import DirectoryTree as DirectoryTree
|
||||||
from ._footer import Footer as Footer
|
from ._footer import Footer as Footer
|
||||||
|
|||||||
90
src/textual/widgets/_content_switcher.py
Normal file
90
src/textual/widgets/_content_switcher.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
"""Provides a widget for switching between the display of its immediate children."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ..containers import Container
|
||||||
|
from ..reactive import reactive
|
||||||
|
from ..widget import Widget
|
||||||
|
|
||||||
|
|
||||||
|
class ContentSwitcher(Container):
|
||||||
|
"""A widget for switching between different children.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
All child widgets that are to be switched between need a unique ID.
|
||||||
|
Children that have no ID will be hidden and ignored.
|
||||||
|
"""
|
||||||
|
|
||||||
|
current: reactive[str | None] = reactive[Optional[str]](None)
|
||||||
|
"""The ID of the currently-displayed widget.
|
||||||
|
|
||||||
|
If set to `None` then no widget is visible.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
If set to an unknown ID, this will result in
|
||||||
|
[NoMatches][textual.css.query.NoMatches] being raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*children: Widget,
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
disabled: bool = False,
|
||||||
|
initial: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Initialise the content switching widget.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: The widgets to switch between.
|
||||||
|
name: The name of the content switcher.
|
||||||
|
id: The ID of the content switcher in the DOM.
|
||||||
|
classes: The CSS classes of the content switcher.
|
||||||
|
disabled: Whether the content switcher is disabled or not.
|
||||||
|
initial: The ID of the initial widget to show.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
If `initial` is not supplied no children will be shown to start
|
||||||
|
with.
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
*children,
|
||||||
|
name=name,
|
||||||
|
id=id,
|
||||||
|
classes=classes,
|
||||||
|
disabled=disabled,
|
||||||
|
)
|
||||||
|
self._initial = initial
|
||||||
|
|
||||||
|
def on_mount(self) -> None:
|
||||||
|
"""Perform the initial setup of the widget once the DOM is ready."""
|
||||||
|
# On startup, ensure everything is hidden.
|
||||||
|
with self.app.batch_update():
|
||||||
|
for child in self.children:
|
||||||
|
child.display = False
|
||||||
|
# Then set the initial display.
|
||||||
|
self.current = self._initial
|
||||||
|
|
||||||
|
@property
|
||||||
|
def visible_content(self) -> Widget | None:
|
||||||
|
"""A reference to the currently-visible widget.
|
||||||
|
|
||||||
|
`None` if nothing is visible.
|
||||||
|
"""
|
||||||
|
return self.get_child_by_id(self.current) if self.current is not None else None
|
||||||
|
|
||||||
|
def watch_current(self, old: str | None, new: str | None) -> None:
|
||||||
|
"""React to the current visible child choice being changed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
old: The old widget ID (or `None` if there was no widget).
|
||||||
|
new: The new widget ID (or `None` if nothing should be shown).
|
||||||
|
"""
|
||||||
|
with self.app.batch_update():
|
||||||
|
if old is not None:
|
||||||
|
self.get_child_by_id(old).display = False
|
||||||
|
if new is not None:
|
||||||
|
self.get_child_by_id(new).display = True
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
|
|||||||
|
from os import terminal_size
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -164,6 +165,16 @@ def test_radio_set_example(snap_compare):
|
|||||||
assert snap_compare(WIDGET_EXAMPLES_DIR / "radio_set.py")
|
assert snap_compare(WIDGET_EXAMPLES_DIR / "radio_set.py")
|
||||||
|
|
||||||
|
|
||||||
|
def test_content_switcher_example_initial(snap_compare):
|
||||||
|
assert snap_compare(WIDGET_EXAMPLES_DIR / "content_switcher.py")
|
||||||
|
|
||||||
|
|
||||||
|
def test_content_switcher_example_switch(snap_compare):
|
||||||
|
assert snap_compare(WIDGET_EXAMPLES_DIR / "content_switcher.py", press=[
|
||||||
|
"tab", "tab", "enter", "wait:500"
|
||||||
|
], terminal_size=(50, 50))
|
||||||
|
|
||||||
|
|
||||||
# --- CSS properties ---
|
# --- CSS properties ---
|
||||||
# We have a canonical example for each CSS property that is shown in their docs.
|
# 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.
|
# If any of these change, something has likely broken, so snapshot each of them.
|
||||||
|
|||||||
95
tests/test_content_switcher.py
Normal file
95
tests/test_content_switcher.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.css.query import NoMatches
|
||||||
|
from textual.widget import Widget
|
||||||
|
from textual.widgets import ContentSwitcher
|
||||||
|
|
||||||
|
|
||||||
|
class SwitcherApp(App[None]):
|
||||||
|
def __init__(self, initial: str | None = None) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self._initial = initial
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with ContentSwitcher(initial=self._initial):
|
||||||
|
for n in range(5):
|
||||||
|
yield Widget(id=f"w{n}")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_initial_display() -> None:
|
||||||
|
"""Test starting a content switcher with nothing shown."""
|
||||||
|
async with SwitcherApp().run_test() as pilot:
|
||||||
|
assert pilot.app.query_one(ContentSwitcher).current is None
|
||||||
|
assert all(
|
||||||
|
not child.display for child in pilot.app.query_one(ContentSwitcher).children
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_initial_display() -> None:
|
||||||
|
"""Test starting a content switcher with a widget initially shown."""
|
||||||
|
async with SwitcherApp("w3").run_test() as pilot:
|
||||||
|
assert pilot.app.query_one(ContentSwitcher).current == "w3"
|
||||||
|
for child in pilot.app.query_one(ContentSwitcher).children:
|
||||||
|
assert child.display is (child.id == "w3")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_initial_display_then_set() -> None:
|
||||||
|
"""Test starting a content switcher with nothing shown then setting the display."""
|
||||||
|
async with SwitcherApp().run_test() as pilot:
|
||||||
|
assert pilot.app.query_one(ContentSwitcher).current is None
|
||||||
|
assert all(
|
||||||
|
not child.display for child in pilot.app.query_one(ContentSwitcher).children
|
||||||
|
)
|
||||||
|
pilot.app.query_one(ContentSwitcher).current = "w3"
|
||||||
|
assert pilot.app.query_one(ContentSwitcher).current == "w3"
|
||||||
|
for child in pilot.app.query_one(ContentSwitcher).children:
|
||||||
|
assert child.display is (child.id == "w3")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_initial_display_then_change() -> None:
|
||||||
|
"""Test starting a content switcher with a widget initially shown then changing it."""
|
||||||
|
async with SwitcherApp("w3").run_test() as pilot:
|
||||||
|
assert pilot.app.query_one(ContentSwitcher).current == "w3"
|
||||||
|
for child in pilot.app.query_one(ContentSwitcher).children:
|
||||||
|
assert child.display is (child.id == "w3")
|
||||||
|
pilot.app.query_one(ContentSwitcher).current = "w2"
|
||||||
|
assert pilot.app.query_one(ContentSwitcher).current == "w2"
|
||||||
|
for child in pilot.app.query_one(ContentSwitcher).children:
|
||||||
|
assert child.display is (child.id == "w2")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_initial_display_then_hide() -> None:
|
||||||
|
"""Test starting a content switcher with a widget initially shown then hide all."""
|
||||||
|
async with SwitcherApp("w3").run_test() as pilot:
|
||||||
|
assert pilot.app.query_one(ContentSwitcher).current == "w3"
|
||||||
|
for child in pilot.app.query_one(ContentSwitcher).children:
|
||||||
|
assert child.display is (child.id == "w3")
|
||||||
|
pilot.app.query_one(ContentSwitcher).current = None
|
||||||
|
assert pilot.app.query_one(ContentSwitcher).current is None
|
||||||
|
assert all(
|
||||||
|
not child.display for child in pilot.app.query_one(ContentSwitcher).children
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(
|
||||||
|
reason="The expected exception doesn't appear to make it to pytest -- perhaps related to https://github.com/Textualize/textual/issues/1972"
|
||||||
|
)
|
||||||
|
async def test_initial_display_unknown_id() -> None:
|
||||||
|
"""Test setting an initial display to an unknown widget ID."""
|
||||||
|
with pytest.raises(NoMatches):
|
||||||
|
async with SwitcherApp("does-not-exist").run_test():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_current_to_unknown_id() -> None:
|
||||||
|
"""Test attempting to switch to an unknown widget ID."""
|
||||||
|
async with SwitcherApp().run_test() as pilot:
|
||||||
|
assert pilot.app.query_one(ContentSwitcher).current is None
|
||||||
|
assert all(
|
||||||
|
not child.display for child in pilot.app.query_one(ContentSwitcher).children
|
||||||
|
)
|
||||||
|
with pytest.raises(NoMatches):
|
||||||
|
pilot.app.query_one(ContentSwitcher).current = "does-not-exist"
|
||||||
Reference in New Issue
Block a user