mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'main' into add-options
This commit is contained in:
@@ -7,9 +7,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed crash when creating a `DirectoryTree` starting anywhere other than `.`
|
||||||
|
- Fixed line drawing in `Tree` when `Tree.show_root` is `True` https://github.com/Textualize/textual/issues/2397
|
||||||
|
- Fixed line drawing in `Tree` not marking branches as selected when first getting focus https://github.com/Textualize/textual/issues/2397
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- The DataTable cursor is now scrolled into view when the cursor coordinate is changed programmatically https://github.com/Textualize/textual/issues/2459
|
- The DataTable cursor is now scrolled into view when the cursor coordinate is changed programmatically https://github.com/Textualize/textual/issues/2459
|
||||||
|
- run_worker exclusive parameter is now `False` by default https://github.com/Textualize/textual/pull/2470
|
||||||
|
- Added `always_update` as an optional argument for `reactive.var`
|
||||||
|
|
||||||
## [0.23.0] - 2023-05-03
|
## [0.23.0] - 2023-05-03
|
||||||
|
|
||||||
|
|||||||
29
FAQ.md
29
FAQ.md
@@ -2,20 +2,22 @@
|
|||||||
<!-- Do not edit by hand! -->
|
<!-- Do not edit by hand! -->
|
||||||
|
|
||||||
# Frequently Asked Questions
|
# Frequently Asked Questions
|
||||||
- [Does Textual support images?](#does-textual-support-images)
|
- [Frequently Asked Questions](#frequently-asked-questions)
|
||||||
- [How can I fix ImportError cannot import name ComposeResult from textual.app ?](#how-can-i-fix-importerror-cannot-import-name-composeresult-from-textualapp-)
|
- [Does Textual support images?](#does-textual-support-images)
|
||||||
- [How can I select and copy text in a Textual app?](#how-can-i-select-and-copy-text-in-a-textual-app)
|
- [How can I fix ImportError cannot import name ComposeResult from textual.app ?](#how-can-i-fix-importerror-cannot-import-name-composeresult-from-textualapp-)
|
||||||
- [How can I set a translucent app background?](#how-can-i-set-a-translucent-app-background)
|
- [How can I select and copy text in a Textual app?](#how-can-i-select-and-copy-text-in-a-textual-app)
|
||||||
- [How do I center a widget in a screen?](#how-do-i-center-a-widget-in-a-screen)
|
- [How can I set a translucent app background?](#how-can-i-set-a-translucent-app-background)
|
||||||
- [How do I pass arguments to an app?](#how-do-i-pass-arguments-to-an-app)
|
- [How do I center a widget in a screen?](#how-do-i-center-a-widget-in-a-screen)
|
||||||
- [Why do some key combinations never make it to my app?](#why-do-some-key-combinations-never-make-it-to-my-app)
|
- [How do I pass arguments to an app?](#how-do-i-pass-arguments-to-an-app)
|
||||||
- [Why doesn't Textual look good on macOS?](#why-doesn't-textual-look-good-on-macos)
|
- [Why do some key combinations never make it to my app?](#why-do-some-key-combinations-never-make-it-to-my-app)
|
||||||
- [Why doesn't Textual support ANSI themes?](#why-doesn't-textual-support-ansi-themes)
|
- [Why doesn't Textual look good on macOS?](#why-doesnt-textual-look-good-on-macos)
|
||||||
|
- [Why doesn't Textual support ANSI themes?](#why-doesnt-textual-support-ansi-themes)
|
||||||
|
- [Why doesn't the `DataTable` scroll programmatically?](#why-doesnt-the-datatable-scroll-programmatically)
|
||||||
|
|
||||||
<a name="does-textual-support-images"></a>
|
<a name="does-textual-support-images"></a>
|
||||||
## Does Textual support images?
|
## Does Textual support images?
|
||||||
|
|
||||||
Textual doesn't have built in support for images yet, but it is on the [Roadmap](https://textual.textualize.io/roadmap/).
|
Textual doesn't have built-in support for images yet, but it is on the [Roadmap](https://textual.textualize.io/roadmap/).
|
||||||
|
|
||||||
See also the [rich-pixels](https://github.com/darrenburns/rich-pixels) project for a Rich renderable for images that works with Textual.
|
See also the [rich-pixels](https://github.com/darrenburns/rich-pixels) project for a Rich renderable for images that works with Textual.
|
||||||
|
|
||||||
@@ -231,6 +233,13 @@ Textual has a design system which guarantees apps will be readable on all platfo
|
|||||||
|
|
||||||
There is currently a light and dark version of the design system, but more are planned. It will also be possible for users to customize the source colors on a per-app or per-system basis. This means that in the future you will be able to modify the core colors to blend in with your chosen terminal theme.
|
There is currently a light and dark version of the design system, but more are planned. It will also be possible for users to customize the source colors on a per-app or per-system basis. This means that in the future you will be able to modify the core colors to blend in with your chosen terminal theme.
|
||||||
|
|
||||||
|
<a name="why-doesn't-the-`datatable`-scroll-programmatically"></a>
|
||||||
|
## Why doesn't the `DataTable` scroll programmatically?
|
||||||
|
|
||||||
|
If scrolling in your `DataTable` is _apparently_ broken, it may be because your `DataTable` is using the default value of `height: auto`.
|
||||||
|
This means that the table will be sized to fit its rows without scrolling, which may cause the *container* (typically the screen) to scroll.
|
||||||
|
If you would like the table itself to scroll, set the height to something other than `auto`, like `100%`.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
Generated by [FAQtory](https://github.com/willmcgugan/faqtory)
|
Generated by [FAQtory](https://github.com/willmcgugan/faqtory)
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ If you want to change the table based solely on coordinates, you can use the [co
|
|||||||
|
|
||||||
### Cursors
|
### Cursors
|
||||||
|
|
||||||
The coordinate of the cursor is exposed via the `cursor_coordinate` reactive attribute.
|
The coordinate of the cursor is exposed via the [`cursor_coordinate`][textual.widgets.DataTable.cursor_coordinate] reactive attribute.
|
||||||
Three types of cursors are supported: `cell`, `row`, and `column`.
|
Three types of cursors are supported: `cell`, `row`, and `column`.
|
||||||
Change the cursor type by assigning to the `cursor_type` reactive attribute.
|
Change the cursor type by assigning to the [`cursor_type`][textual.widgets.DataTable.cursor_type] reactive attribute.
|
||||||
|
|
||||||
=== "Column Cursor"
|
=== "Column Cursor"
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ Your questions should go in this directory.
|
|||||||
|
|
||||||
Question files should be named with the extension ".question.md".
|
Question files should be named with the extension ".question.md".
|
||||||
|
|
||||||
To build the faq, install [faqtory](https://github.com/willmcgugan/faqtory) if you haven't already:
|
To build the FAQ, install [faqtory](https://github.com/willmcgugan/faqtory) if you haven't already:
|
||||||
|
|
||||||
```
|
```
|
||||||
pip install faqtory
|
pip install faqtory
|
||||||
```
|
```
|
||||||
|
|
||||||
The run the following from the top of the repository:
|
Then run the following from the top of the repository:
|
||||||
|
|
||||||
```
|
```
|
||||||
faqtory build
|
faqtory build
|
||||||
|
|||||||
10
questions/datatable-doesnt-scroll.question.md
Normal file
10
questions/datatable-doesnt-scroll.question.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: "Why doesn't the `DataTable` scroll programmatically?"
|
||||||
|
alt_titles:
|
||||||
|
- "Scroll bindings from `DataTable` not working."
|
||||||
|
- "Datatable cursor goes off screen and doesn't scroll."
|
||||||
|
---
|
||||||
|
|
||||||
|
If scrolling in your `DataTable` is _apparently_ broken, it may be because your `DataTable` is using the default value of `height: auto`.
|
||||||
|
This means that the table will be sized to fit its rows without scrolling, which may cause the *container* (typically the screen) to scroll.
|
||||||
|
If you would like the table itself to scroll, set the height to something other than `auto`, like `100%`.
|
||||||
@@ -3,9 +3,8 @@ title: "Does Textual support images?"
|
|||||||
alt_titles:
|
alt_titles:
|
||||||
- "Can Textual display PNG / SVG files?"
|
- "Can Textual display PNG / SVG files?"
|
||||||
- "Render images"
|
- "Render images"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Textual doesn't have built in support for images yet, but it is on the [Roadmap](https://textual.textualize.io/roadmap/).
|
Textual doesn't have built-in support for images yet, but it is on the [Roadmap](https://textual.textualize.io/roadmap/).
|
||||||
|
|
||||||
See also the [rich-pixels](https://github.com/darrenburns/rich-pixels) project for a Rich renderable for images that works with Textual.
|
See also the [rich-pixels](https://github.com/darrenburns/rich-pixels) project for a Rich renderable for images that works with Textual.
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ class DOMNode(MessagePump):
|
|||||||
description: str = "",
|
description: str = "",
|
||||||
exit_on_error: bool = True,
|
exit_on_error: bool = True,
|
||||||
start: bool = True,
|
start: bool = True,
|
||||||
exclusive: bool = True,
|
exclusive: bool = False,
|
||||||
) -> Worker[ResultType]:
|
) -> Worker[ResultType]:
|
||||||
"""Run work in a worker.
|
"""Run work in a worker.
|
||||||
|
|
||||||
|
|||||||
@@ -76,11 +76,13 @@ class Reactive(Generic[ReactiveType]):
|
|||||||
def var(
|
def var(
|
||||||
cls,
|
cls,
|
||||||
default: ReactiveType | Callable[[], ReactiveType],
|
default: ReactiveType | Callable[[], ReactiveType],
|
||||||
|
always_update: bool = False,
|
||||||
) -> Reactive:
|
) -> Reactive:
|
||||||
"""A reactive variable that doesn't update or layout.
|
"""A reactive variable that doesn't update or layout.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
default: A default value or callable that returns a default.
|
default: A default value or callable that returns a default.
|
||||||
|
always_update: Call watchers even when the new value equals the old value.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A Reactive descriptor.
|
A Reactive descriptor.
|
||||||
@@ -326,18 +328,21 @@ class var(Reactive[ReactiveType]):
|
|||||||
Args:
|
Args:
|
||||||
default: A default value or callable that returns a default.
|
default: A default value or callable that returns a default.
|
||||||
init: Call watchers on initialize (post mount).
|
init: Call watchers on initialize (post mount).
|
||||||
|
always_update: Call watchers even when the new value equals the old value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
default: ReactiveType | Callable[[], ReactiveType],
|
default: ReactiveType | Callable[[], ReactiveType],
|
||||||
init: bool = True,
|
init: bool = True,
|
||||||
|
always_update: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
default,
|
default,
|
||||||
layout=False,
|
layout=False,
|
||||||
repaint=False,
|
repaint=False,
|
||||||
init=init,
|
init=init,
|
||||||
|
always_update=always_update,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ from ._context import NoActiveAppError
|
|||||||
from ._types import CallbackType, MessageTarget, WatchCallbackType
|
from ._types import CallbackType, MessageTarget, WatchCallbackType
|
||||||
from .actions import ActionParseResult
|
from .actions import ActionParseResult
|
||||||
from .css.styles import RenderStyles
|
from .css.styles import RenderStyles
|
||||||
|
from .widgets._data_table import CursorType
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"ActionParseResult",
|
"ActionParseResult",
|
||||||
"Animatable",
|
"Animatable",
|
||||||
"CallbackType",
|
"CallbackType",
|
||||||
|
"CursorType",
|
||||||
"EasingFunction",
|
"EasingFunction",
|
||||||
"MessageTarget",
|
"MessageTarget",
|
||||||
"NoActiveAppError",
|
"NoActiveAppError",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ CellCacheKey: TypeAlias = (
|
|||||||
LineCacheKey: TypeAlias = "tuple[int, int, int, int, Coordinate, Coordinate, Style, CursorType, bool, int, PseudoClasses]"
|
LineCacheKey: TypeAlias = "tuple[int, int, int, int, Coordinate, Coordinate, Style, CursorType, bool, int, PseudoClasses]"
|
||||||
RowCacheKey: TypeAlias = "tuple[RowKey, int, Style, Coordinate, Coordinate, CursorType, bool, bool, int, PseudoClasses]"
|
RowCacheKey: TypeAlias = "tuple[RowKey, int, Style, Coordinate, Coordinate, CursorType, bool, bool, int, PseudoClasses]"
|
||||||
CursorType = Literal["cell", "row", "column", "none"]
|
CursorType = Literal["cell", "row", "column", "none"]
|
||||||
|
"""The legal types of cursors for [`DataTable.cursor_type`][textual.widgets.DataTable.cursor_type]."""
|
||||||
CellType = TypeVar("CellType")
|
CellType = TypeVar("CellType")
|
||||||
|
|
||||||
CELL_X_PADDING = 2
|
CELL_X_PADDING = 2
|
||||||
@@ -304,7 +305,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
zebra_stripes = Reactive(False)
|
zebra_stripes = Reactive(False)
|
||||||
header_height = Reactive(1)
|
header_height = Reactive(1)
|
||||||
show_cursor = Reactive(True)
|
show_cursor = Reactive(True)
|
||||||
cursor_type = Reactive("cell")
|
cursor_type: Reactive[CursorType] = Reactive[CursorType]("cell")
|
||||||
|
"""The type of the cursor of the `DataTable`."""
|
||||||
|
|
||||||
cursor_coordinate: Reactive[Coordinate] = Reactive(
|
cursor_coordinate: Reactive[Coordinate] = Reactive(
|
||||||
Coordinate(0, 0), repaint=False, always_update=True
|
Coordinate(0, 0), repaint=False, always_update=True
|
||||||
@@ -312,6 +314,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
hover_coordinate: Reactive[Coordinate] = Reactive(
|
hover_coordinate: Reactive[Coordinate] = Reactive(
|
||||||
Coordinate(0, 0), repaint=False, always_update=True
|
Coordinate(0, 0), repaint=False, always_update=True
|
||||||
)
|
)
|
||||||
|
"""The coordinate of the `DataTable` that is being hovered."""
|
||||||
|
|
||||||
class CellHighlighted(Message, bubble=True):
|
class CellHighlighted(Message, bubble=True):
|
||||||
"""Posted when the cursor moves to highlight a new cell.
|
"""Posted when the cursor moves to highlight a new cell.
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ class DirectoryTree(Tree[DirEntry]):
|
|||||||
classes: A space-separated list of classes, or None for no classes.
|
classes: A space-separated list of classes, or None for no classes.
|
||||||
disabled: Whether the directory tree is disabled or not.
|
disabled: Whether the directory tree is disabled or not.
|
||||||
"""
|
"""
|
||||||
self.path = path
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
str(path),
|
str(path),
|
||||||
data=DirEntry(Path(path)),
|
data=DirEntry(Path(path)),
|
||||||
@@ -112,6 +111,7 @@ class DirectoryTree(Tree[DirEntry]):
|
|||||||
classes=classes,
|
classes=classes,
|
||||||
disabled=disabled,
|
disabled=disabled,
|
||||||
)
|
)
|
||||||
|
self.path = path
|
||||||
|
|
||||||
def reload(self) -> None:
|
def reload(self) -> None:
|
||||||
"""Reload the `DirectoryTree` contents."""
|
"""Reload the `DirectoryTree` contents."""
|
||||||
|
|||||||
@@ -429,7 +429,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
"""Show the root of the tree."""
|
"""Show the root of the tree."""
|
||||||
hover_line = var(-1)
|
hover_line = var(-1)
|
||||||
"""The line number under the mouse pointer, or -1 if not under the mouse pointer."""
|
"""The line number under the mouse pointer, or -1 if not under the mouse pointer."""
|
||||||
cursor_line = var(-1)
|
cursor_line = var(-1, always_update=True)
|
||||||
"""The line with the cursor, or -1 if no cursor."""
|
"""The line with the cursor, or -1 if no cursor."""
|
||||||
show_guides = reactive(True)
|
show_guides = reactive(True)
|
||||||
"""Enable display of tree guide lines."""
|
"""Enable display of tree guide lines."""
|
||||||
@@ -976,8 +976,8 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
"tree--guides-selected", partial=True
|
"tree--guides-selected", partial=True
|
||||||
)
|
)
|
||||||
|
|
||||||
hover = self.root._hover
|
hover = line.path[0]._hover
|
||||||
selected = self.root._selected and self.has_focus
|
selected = line.path[0]._selected and self.has_focus
|
||||||
|
|
||||||
def get_guides(style: Style) -> tuple[str, str, str, str]:
|
def get_guides(style: Style) -> tuple[str, str, str, str]:
|
||||||
"""Get the guide strings for a given style.
|
"""Get the guide strings for a given style.
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ class CheckboxApp(App[None]):
|
|||||||
yield Checkbox(value=True, id="cb3")
|
yield Checkbox(value=True, id="cb3")
|
||||||
|
|
||||||
def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
|
def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
|
||||||
self.events_received.append((event.checkbox.id, event.checkbox.value))
|
self.events_received.append(
|
||||||
|
(event.checkbox.id, event.checkbox.value, event.checkbox == event.control)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_checkbox_initial_state() -> None:
|
async def test_checkbox_initial_state() -> None:
|
||||||
@@ -43,7 +45,7 @@ async def test_checkbox_toggle() -> None:
|
|||||||
]
|
]
|
||||||
await pilot.pause()
|
await pilot.pause()
|
||||||
assert pilot.app.events_received == [
|
assert pilot.app.events_received == [
|
||||||
("cb1", True),
|
("cb1", True, True),
|
||||||
("cb2", True),
|
("cb2", True, True),
|
||||||
("cb3", False),
|
("cb3", False, True),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ class RadioButtonApp(App[None]):
|
|||||||
yield RadioButton(value=True, id="rb3")
|
yield RadioButton(value=True, id="rb3")
|
||||||
|
|
||||||
def on_radio_button_changed(self, event: RadioButton.Changed) -> None:
|
def on_radio_button_changed(self, event: RadioButton.Changed) -> None:
|
||||||
self.events_received.append((event.radio_button.id, event.radio_button.value))
|
self.events_received.append(
|
||||||
|
(
|
||||||
|
event.radio_button.id,
|
||||||
|
event.radio_button.value,
|
||||||
|
event.radio_button == event.control,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_radio_button_initial_state() -> None:
|
async def test_radio_button_initial_state() -> None:
|
||||||
@@ -51,7 +57,7 @@ async def test_radio_button_toggle() -> None:
|
|||||||
]
|
]
|
||||||
await pilot.pause()
|
await pilot.pause()
|
||||||
assert pilot.app.events_received == [
|
assert pilot.app.events_received == [
|
||||||
("rb1", True),
|
("rb1", True, True),
|
||||||
("rb2", True),
|
("rb2", True, True),
|
||||||
("rb3", False),
|
("rb3", False, True),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class RadioSetApp(App[None]):
|
|||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with RadioSet(id="from_buttons"):
|
with RadioSet(id="from_buttons"):
|
||||||
yield RadioButton()
|
yield RadioButton(id="clickme")
|
||||||
yield RadioButton()
|
yield RadioButton()
|
||||||
yield RadioButton(value=True)
|
yield RadioButton(value=True)
|
||||||
yield RadioSet("One", "True", "Three", id="from_strings")
|
yield RadioSet("One", "True", "Three", id="from_strings")
|
||||||
@@ -36,6 +36,14 @@ async def test_radio_sets_initial_state():
|
|||||||
assert pilot.app.events_received == []
|
assert pilot.app.events_received == []
|
||||||
|
|
||||||
|
|
||||||
|
async def test_click_sets_focus():
|
||||||
|
"""Clicking within a radio set should set focus."""
|
||||||
|
async with RadioSetApp().run_test() as pilot:
|
||||||
|
assert pilot.app.screen.focused is None
|
||||||
|
await pilot.click("#clickme")
|
||||||
|
assert pilot.app.screen.focused == pilot.app.query_one("#from_buttons")
|
||||||
|
|
||||||
|
|
||||||
async def test_radio_sets_toggle():
|
async def test_radio_sets_toggle():
|
||||||
"""Test the status of the radio sets after they've been toggled."""
|
"""Test the status of the radio sets after they've been toggled."""
|
||||||
async with RadioSetApp().run_test() as pilot:
|
async with RadioSetApp().run_test() as pilot:
|
||||||
@@ -52,17 +60,42 @@ async def test_radio_sets_toggle():
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_radioset_same_button_mash():
|
||||||
|
"""Mashing the same button should have no effect."""
|
||||||
|
async with RadioSetApp().run_test() as pilot:
|
||||||
|
assert pilot.app.query_one("#from_buttons", RadioSet).pressed_index == 2
|
||||||
|
pilot.app.query_one("#from_buttons", RadioSet)._nodes[2].toggle()
|
||||||
|
assert pilot.app.query_one("#from_buttons", RadioSet).pressed_index == 2
|
||||||
|
assert pilot.app.events_received == []
|
||||||
|
|
||||||
|
|
||||||
async def test_radioset_inner_navigation():
|
async def test_radioset_inner_navigation():
|
||||||
"""Using the cursor keys should navigate between buttons in a set."""
|
"""Using the cursor keys should navigate between buttons in a set."""
|
||||||
async with RadioSetApp().run_test() as pilot:
|
async with RadioSetApp().run_test() as pilot:
|
||||||
assert pilot.app.screen.focused is None
|
assert pilot.app.screen.focused is None
|
||||||
await pilot.press("tab")
|
await pilot.press("tab")
|
||||||
for key, landing in (("down", 1), ("up", 0), ("right", 1), ("left", 0)):
|
for key, landing in (
|
||||||
|
("down", 1),
|
||||||
|
("up", 0),
|
||||||
|
("right", 1),
|
||||||
|
("left", 0),
|
||||||
|
("up", 2),
|
||||||
|
("down", 0),
|
||||||
|
):
|
||||||
await pilot.press(key, "enter")
|
await pilot.press(key, "enter")
|
||||||
assert (
|
assert (
|
||||||
pilot.app.query_one("#from_buttons", RadioSet).pressed_button
|
pilot.app.query_one("#from_buttons", RadioSet).pressed_button
|
||||||
== pilot.app.query_one("#from_buttons").children[landing]
|
== pilot.app.query_one("#from_buttons").children[landing]
|
||||||
)
|
)
|
||||||
|
async with RadioSetApp().run_test() as pilot:
|
||||||
|
assert pilot.app.screen.focused is None
|
||||||
|
await pilot.press("tab")
|
||||||
|
assert pilot.app.screen.focused is pilot.app.screen.query_one("#from_buttons")
|
||||||
|
await pilot.press("tab")
|
||||||
|
assert pilot.app.screen.focused is pilot.app.screen.query_one("#from_strings")
|
||||||
|
assert pilot.app.query_one("#from_strings", RadioSet)._selected == 0
|
||||||
|
await pilot.press("down")
|
||||||
|
assert pilot.app.query_one("#from_strings", RadioSet)._selected == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_radioset_breakout_navigation():
|
async def test_radioset_breakout_navigation():
|
||||||
|
|||||||
Reference in New Issue
Block a user