Testing guide (#3329)
* testing docs * words * words * testing doc * Apply suggestions from code review Co-authored-by: Gobion <1312216+brokenshield@users.noreply.github.com> --------- Co-authored-by: Gobion <1312216+brokenshield@users.noreply.github.com>
This commit is contained in:
parent
31eaf3ffb8
commit
b99da2d6b9
6 changed files with 469 additions and 217 deletions
42
docs/examples/guide/testing/rgb.py
Normal file
42
docs/examples/guide/testing/rgb.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from textual import on
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Horizontal
|
||||
from textual.widgets import Button, Footer
|
||||
|
||||
|
||||
class RGBApp(App):
|
||||
CSS = """
|
||||
Screen {
|
||||
align: center middle;
|
||||
}
|
||||
Horizontal {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
("r", "switch_color('red')", "Go Red"),
|
||||
("g", "switch_color('green')", "Go Green"),
|
||||
("b", "switch_color('blue')", "Go Blue"),
|
||||
]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Horizontal():
|
||||
yield Button("Red", id="red")
|
||||
yield Button("Green", id="green")
|
||||
yield Button("Blue", id="blue")
|
||||
yield Footer()
|
||||
|
||||
@on(Button.Pressed)
|
||||
def pressed_button(self, event: Button.Pressed) -> None:
|
||||
assert event.button.id is not None
|
||||
self.action_switch_color(event.button.id)
|
||||
|
||||
def action_switch_color(self, color: str) -> None:
|
||||
self.screen.styles.background = color
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = RGBApp()
|
||||
app.run()
|
||||
42
docs/examples/guide/testing/test_rgb.py
Normal file
42
docs/examples/guide/testing/test_rgb.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from rgb import RGBApp
|
||||
|
||||
from textual.color import Color
|
||||
|
||||
|
||||
async def test_keys(): # (1)!
|
||||
"""Test pressing keys has the desired result."""
|
||||
app = RGBApp()
|
||||
async with app.run_test() as pilot: # (2)!
|
||||
# Test pressing the R key
|
||||
await pilot.press("r") # (3)!
|
||||
assert app.screen.styles.background == Color.parse("red") # (4)!
|
||||
|
||||
# Test pressing the G key
|
||||
await pilot.press("g")
|
||||
assert app.screen.styles.background == Color.parse("green")
|
||||
|
||||
# Test pressing the B key
|
||||
await pilot.press("b")
|
||||
assert app.screen.styles.background == Color.parse("blue")
|
||||
|
||||
# Test pressing the X key
|
||||
await pilot.press("x")
|
||||
# No binding (so no change to the color)
|
||||
assert app.screen.styles.background == Color.parse("blue")
|
||||
|
||||
|
||||
async def test_buttons():
|
||||
"""Test pressing keys has the desired result."""
|
||||
app = RGBApp()
|
||||
async with app.run_test() as pilot:
|
||||
# Test clicking the "red" button
|
||||
await pilot.click("#red") # (5)!
|
||||
assert app.screen.styles.background == Color.parse("red")
|
||||
|
||||
# Test clicking the "green" button
|
||||
await pilot.click("#green")
|
||||
assert app.screen.styles.background == Color.parse("green")
|
||||
|
||||
# Test clicking the "blue" button
|
||||
await pilot.click("#blue")
|
||||
assert app.screen.styles.background == Color.parse("blue")
|
||||
168
docs/guide/testing.md
Normal file
168
docs/guide/testing.md
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# Testing
|
||||
|
||||
Code testing is an important part of software development.
|
||||
This chapter will cover how to write tests for your Textual apps.
|
||||
|
||||
## What is testing?
|
||||
|
||||
It is common to write tests alongside your app.
|
||||
A *test* is simply a function that confirms your app is working correctly.
|
||||
|
||||
!!! tip "Learn more about testing"
|
||||
|
||||
We recommend [Python Testing with pytest](https://pythontest.com/pytest-book/) for a comprehensive guide to writing tests.
|
||||
|
||||
## Do you need to write tests?
|
||||
|
||||
The short answer is "no", you don't *need* to write tests.
|
||||
|
||||
In practice however, it is almost always a good idea to write tests.
|
||||
Writing code that is completely bug free is virtually impossible, even for experienced developers.
|
||||
If you want to have confidence that your application will run as you intended it to, then you should write tests.
|
||||
Your test code will help you find bugs early, and alert you if you accidentally break something in the future.
|
||||
|
||||
## Testing frameworks for Textual
|
||||
|
||||
Textual doesn't require any particular test framework.
|
||||
You can use any test framework you are familiar with, but we will be using [pytest](https://docs.pytest.org/) in this chapter.
|
||||
|
||||
|
||||
## Testing apps
|
||||
|
||||
You can often test Textual code in the same way as any other app, and use similar techniques.
|
||||
But when testing user interface interactions, you may need to use Textual's dedicated test features.
|
||||
|
||||
Let's write a simple Textual app so we can demonstrate how to test it.
|
||||
The following app shows three buttons labelled "red", "green", and "blue".
|
||||
Clicking one of those buttons or pressing a corresponding ++r++, ++g++, and ++b++ key will change the background color.
|
||||
|
||||
=== "rgb.py"
|
||||
|
||||
```python
|
||||
--8<-- "docs/examples/guide/testing/rgb.py"
|
||||
```
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/guide/testing/rgb.py"}
|
||||
```
|
||||
|
||||
Although it is straightforward to test an app like this manually, it is not practical to click every button and hit every key in your app after changing a single line of code.
|
||||
Tests allow us to automate such testing so we can quickly simulate user interactions and check the result.
|
||||
|
||||
To test our simple app we will use the [`run_test()`][textual.app.App.run_test] method on the `App` class.
|
||||
This replaces the usual call to [`run()`][textual.app.App.run] and will run the app in *headless* mode, which prevents Textual from updating the terminal but otherwise behaves as normal.
|
||||
|
||||
The `run_test()` method is an *async context manager* which returns a [`Pilot`][textual.pilot.Pilot] object.
|
||||
You can use this object to interact with the app as if you were operating it with a keyboard and mouse.
|
||||
|
||||
Let's look at the tests for the example above:
|
||||
|
||||
```python title="test_rgb.py"
|
||||
--8<-- "docs/examples/guide/testing/test_rgb.py"
|
||||
```
|
||||
|
||||
1. The `run_test()` method requires that it run in a coroutine, so tests must use the `async` keyword.
|
||||
2. This runs the app and returns a Pilot instance we can use to interact with it.
|
||||
3. Simulates pressing the ++r++ key.
|
||||
4. This checks that pressing the ++r++ key has resulted in the background color changing.
|
||||
5. Simulates clicking on the widget with an `id` of `red` (the button labelled "Red").
|
||||
|
||||
There are two tests defined in `test_rgb.py`.
|
||||
The first to test keys and the second to test button clicks.
|
||||
Both tests first construct an instance of the app and then call `run_test()` to get a Pilot object.
|
||||
The `test_keys` function simulates key presses with [`Pilot.press`][textual.pilot.Pilot.press], and `test_buttons` simulates button clicks with [`Pilot.click`][textual.pilot.Pilot.click].
|
||||
|
||||
After simulating a user interaction, Textual tests will typically check the state has been updated with an `assert` statement.
|
||||
The `pytest` module will record any failures of these assert statements as a test fail.
|
||||
|
||||
If you run the tests with `pytest test_rgb.py` you should get 2 passes, which will confirm that the user will be able to click buttons or press the keys to change the background color.
|
||||
|
||||
If you later update this app, and accidentally break this functionality, one or more of your tests will fail.
|
||||
Knowing which test has failed will help you quickly track down where your code was broken.
|
||||
|
||||
## Simulating key presses
|
||||
|
||||
We've seen how the [`press`][textual.pilot.Pilot] method simulates keys.
|
||||
You can also supply multiple keys to simulate the user typing in to the app.
|
||||
Here's an example of simulating the user typing the word "hello".
|
||||
|
||||
```python
|
||||
await pilot.press("h", "e", "l", "l", "o")
|
||||
```
|
||||
|
||||
Each string creates a single keypress.
|
||||
You can also use the name for non-printable keys (such as "enter") and the "ctrl+" modifier.
|
||||
These are the same identifiers as used for key events, which you can experiment with by running `textual keys`.
|
||||
|
||||
## Simulating clicks
|
||||
|
||||
You can simulate mouse clicks in a similar way with [`Pilot.click`][textual.pilot.Pilot.click].
|
||||
If you supply a CSS selector Textual will simulate clicking on the matching widget.
|
||||
|
||||
!!! note
|
||||
|
||||
If there is another widget in front of the widget you want to click, you may end up clicking the topmost widget rather than the widget indicated in the selector.
|
||||
This is generally what you want, because a real user would experience the same thing.
|
||||
|
||||
### Clicking the screen
|
||||
|
||||
If you don't supply a CSS selector, then the click will be relative to the screen.
|
||||
For example, the following simulates a click at (0, 0):
|
||||
|
||||
```python
|
||||
await pilot.click()
|
||||
```
|
||||
|
||||
### Click offsets
|
||||
|
||||
If you supply an `offset` value, it will be added to the coordinates of the simulated click.
|
||||
For example the following line would simulate a click at the coordinates (10, 5).
|
||||
|
||||
|
||||
```python
|
||||
await pilot.click(offset=(10, 5))
|
||||
```
|
||||
|
||||
If you combine this with a selector, then the offset will be relative to the widget.
|
||||
Here's how you would click the line *above* a button.
|
||||
|
||||
```python
|
||||
await pilot.click(Button, offset(0, -1))
|
||||
```
|
||||
|
||||
### Modifier keys
|
||||
|
||||
You can simulate clicks in combination with modifier keys, by setting the `shift`, `meta`, or `control` parameters.
|
||||
Here's how you could simulate ctrl-clicking a widget with an id of "slider":
|
||||
|
||||
```python
|
||||
await pilot.click("#slider", control=True)
|
||||
```
|
||||
|
||||
## Changing the screen size
|
||||
|
||||
The default size of a simulated app is (80, 24).
|
||||
You may want to test what happens when the app has a different size.
|
||||
To do this, set the `size` parameter of [`run_test`][textual.app.App.run_test] to a different size.
|
||||
For example, here is how you would simulate a terminal resized to 100 columns and 50 lines:
|
||||
|
||||
```python
|
||||
async with app.run_test(size=(100, 50)) as pilot:
|
||||
...
|
||||
```
|
||||
|
||||
## Pausing the pilot
|
||||
|
||||
Some actions in a Textual app won't change the state immediately.
|
||||
For instance, messages may take a moment to bubble from the widget that sent them.
|
||||
If you were to post a message and immediately `assert` you may find that it fails because the message hasn't yet been processed.
|
||||
|
||||
You can generally solve this by calling [`pause()`][textual.pilot.Pilot.pause] which will wait for all pending messages to be processed.
|
||||
You can also supply a `delay` parameter, which will insert a delay prior to waiting for pending messages.
|
||||
|
||||
|
||||
## Textual's test
|
||||
|
||||
Textual itself has a large battery of tests.
|
||||
If you are interested in how we write tests, see the [tests/](https://github.com/Textualize/textual/tree/main/tests) directory in the Textual repository.
|
||||
423
mkdocs-nav.yml
423
mkdocs-nav.yml
|
|
@ -1,212 +1,213 @@
|
|||
nav:
|
||||
- Introduction:
|
||||
- "index.md"
|
||||
- "getting_started.md"
|
||||
- "help.md"
|
||||
- "tutorial.md"
|
||||
- Guide:
|
||||
- "guide/index.md"
|
||||
- "guide/devtools.md"
|
||||
- "guide/app.md"
|
||||
- "guide/styles.md"
|
||||
- "guide/CSS.md"
|
||||
- "guide/design.md"
|
||||
- "guide/queries.md"
|
||||
- "guide/layout.md"
|
||||
- "guide/events.md"
|
||||
- "guide/input.md"
|
||||
- "guide/actions.md"
|
||||
- "guide/reactivity.md"
|
||||
- "guide/widgets.md"
|
||||
- "guide/animation.md"
|
||||
- "guide/screens.md"
|
||||
- "guide/workers.md"
|
||||
- "guide/command_palette.md"
|
||||
- "widget_gallery.md"
|
||||
- Reference:
|
||||
- "reference/index.md"
|
||||
- CSS Types:
|
||||
- "css_types/index.md"
|
||||
- "css_types/border.md"
|
||||
- "css_types/color.md"
|
||||
- "css_types/horizontal.md"
|
||||
- "css_types/integer.md"
|
||||
- "css_types/name.md"
|
||||
- "css_types/number.md"
|
||||
- "css_types/overflow.md"
|
||||
- "css_types/percentage.md"
|
||||
- "css_types/scalar.md"
|
||||
- "css_types/text_align.md"
|
||||
- "css_types/text_style.md"
|
||||
- "css_types/vertical.md"
|
||||
- Events:
|
||||
- "events/index.md"
|
||||
- "events/blur.md"
|
||||
- "events/descendant_blur.md"
|
||||
- "events/descendant_focus.md"
|
||||
- "events/enter.md"
|
||||
- "events/focus.md"
|
||||
- "events/hide.md"
|
||||
- "events/key.md"
|
||||
- "events/leave.md"
|
||||
- "events/load.md"
|
||||
- "events/mount.md"
|
||||
- "events/mouse_capture.md"
|
||||
- "events/click.md"
|
||||
- "events/mouse_down.md"
|
||||
- "events/mouse_move.md"
|
||||
- "events/mouse_release.md"
|
||||
- "events/mouse_scroll_down.md"
|
||||
- "events/mouse_scroll_up.md"
|
||||
- "events/mouse_up.md"
|
||||
- "events/paste.md"
|
||||
- "events/resize.md"
|
||||
- "events/screen_resume.md"
|
||||
- "events/screen_suspend.md"
|
||||
- "events/show.md"
|
||||
- Styles:
|
||||
- "styles/align.md"
|
||||
- "styles/background.md"
|
||||
- "styles/border.md"
|
||||
- "styles/border_subtitle_align.md"
|
||||
- "styles/border_subtitle_background.md"
|
||||
- "styles/border_subtitle_color.md"
|
||||
- "styles/border_subtitle_style.md"
|
||||
- "styles/border_title_align.md"
|
||||
- "styles/border_title_background.md"
|
||||
- "styles/border_title_color.md"
|
||||
- "styles/border_title_style.md"
|
||||
- "styles/box_sizing.md"
|
||||
- "styles/color.md"
|
||||
- "styles/content_align.md"
|
||||
- "styles/display.md"
|
||||
- "styles/dock.md"
|
||||
- "styles/index.md"
|
||||
- Grid:
|
||||
- "styles/grid/index.md"
|
||||
- "styles/grid/column_span.md"
|
||||
- "styles/grid/grid_columns.md"
|
||||
- "styles/grid/grid_gutter.md"
|
||||
- "styles/grid/grid_rows.md"
|
||||
- "styles/grid/grid_size.md"
|
||||
- "styles/grid/row_span.md"
|
||||
- "styles/height.md"
|
||||
- "styles/layer.md"
|
||||
- "styles/layers.md"
|
||||
- "styles/layout.md"
|
||||
- Links:
|
||||
- "styles/links/index.md"
|
||||
- "styles/links/link_background.md"
|
||||
- "styles/links/link_color.md"
|
||||
- "styles/links/link_hover_background.md"
|
||||
- "styles/links/link_hover_color.md"
|
||||
- "styles/links/link_hover_style.md"
|
||||
- "styles/links/link_style.md"
|
||||
- "styles/margin.md"
|
||||
- "styles/max_height.md"
|
||||
- "styles/max_width.md"
|
||||
- "styles/min_height.md"
|
||||
- "styles/min_width.md"
|
||||
- "styles/offset.md"
|
||||
- "styles/opacity.md"
|
||||
- "styles/outline.md"
|
||||
- "styles/overflow.md"
|
||||
- "styles/padding.md"
|
||||
- Scrollbar colors:
|
||||
- "styles/scrollbar_colors/index.md"
|
||||
- "styles/scrollbar_colors/scrollbar_background.md"
|
||||
- "styles/scrollbar_colors/scrollbar_background_active.md"
|
||||
- "styles/scrollbar_colors/scrollbar_background_hover.md"
|
||||
- "styles/scrollbar_colors/scrollbar_color.md"
|
||||
- "styles/scrollbar_colors/scrollbar_color_active.md"
|
||||
- "styles/scrollbar_colors/scrollbar_color_hover.md"
|
||||
- "styles/scrollbar_colors/scrollbar_corner_color.md"
|
||||
- "styles/scrollbar_gutter.md"
|
||||
- "styles/scrollbar_size.md"
|
||||
- "styles/text_align.md"
|
||||
- "styles/text_opacity.md"
|
||||
- "styles/text_style.md"
|
||||
- "styles/tint.md"
|
||||
- "styles/visibility.md"
|
||||
- "styles/width.md"
|
||||
- Widgets:
|
||||
- "widgets/button.md"
|
||||
- "widgets/checkbox.md"
|
||||
- "widgets/collapsible.md"
|
||||
- "widgets/content_switcher.md"
|
||||
- "widgets/data_table.md"
|
||||
- "widgets/digits.md"
|
||||
- "widgets/directory_tree.md"
|
||||
- "widgets/footer.md"
|
||||
- "widgets/header.md"
|
||||
- "widgets/index.md"
|
||||
- "widgets/input.md"
|
||||
- "widgets/label.md"
|
||||
- "widgets/list_item.md"
|
||||
- "widgets/list_view.md"
|
||||
- "widgets/loading_indicator.md"
|
||||
- "widgets/log.md"
|
||||
- "widgets/markdown_viewer.md"
|
||||
- "widgets/markdown.md"
|
||||
- "widgets/option_list.md"
|
||||
- "widgets/placeholder.md"
|
||||
- "widgets/pretty.md"
|
||||
- "widgets/progress_bar.md"
|
||||
- "widgets/radiobutton.md"
|
||||
- "widgets/radioset.md"
|
||||
- "widgets/rich_log.md"
|
||||
- "widgets/rule.md"
|
||||
- "widgets/select.md"
|
||||
- "widgets/selection_list.md"
|
||||
- "widgets/sparkline.md"
|
||||
- "widgets/static.md"
|
||||
- "widgets/switch.md"
|
||||
- "widgets/tabbed_content.md"
|
||||
- "widgets/tabs.md"
|
||||
- "widgets/tree.md"
|
||||
- API:
|
||||
- "api/index.md"
|
||||
- "api/app.md"
|
||||
- "api/await_remove.md"
|
||||
- "api/binding.md"
|
||||
- "api/color.md"
|
||||
- "api/command.md"
|
||||
- "api/containers.md"
|
||||
- "api/coordinate.md"
|
||||
- "api/dom_node.md"
|
||||
- "api/events.md"
|
||||
- "api/errors.md"
|
||||
- "api/filter.md"
|
||||
- "api/fuzzy_matcher.md"
|
||||
- "api/geometry.md"
|
||||
- "api/logger.md"
|
||||
- "api/logging.md"
|
||||
- "api/map_geometry.md"
|
||||
- "api/message_pump.md"
|
||||
- "api/message.md"
|
||||
- "api/on.md"
|
||||
- "api/pilot.md"
|
||||
- "api/query.md"
|
||||
- "api/reactive.md"
|
||||
- "api/screen.md"
|
||||
- "api/scrollbar.md"
|
||||
- "api/scroll_view.md"
|
||||
- "api/strip.md"
|
||||
- "api/suggester.md"
|
||||
- "api/system_commands_source.md"
|
||||
- "api/timer.md"
|
||||
- "api/types.md"
|
||||
- "api/validation.md"
|
||||
- "api/walk.md"
|
||||
- "api/widget.md"
|
||||
- "api/work.md"
|
||||
- "api/worker.md"
|
||||
- "api/worker_manager.md"
|
||||
- "How To":
|
||||
- "how-to/index.md"
|
||||
- "how-to/center-things.md"
|
||||
- "how-to/design-a-layout.md"
|
||||
- "FAQ.md"
|
||||
- "roadmap.md"
|
||||
- "Blog":
|
||||
- blog/index.md
|
||||
- Introduction:
|
||||
- "index.md"
|
||||
- "getting_started.md"
|
||||
- "help.md"
|
||||
- "tutorial.md"
|
||||
- Guide:
|
||||
- "guide/index.md"
|
||||
- "guide/devtools.md"
|
||||
- "guide/app.md"
|
||||
- "guide/styles.md"
|
||||
- "guide/CSS.md"
|
||||
- "guide/design.md"
|
||||
- "guide/queries.md"
|
||||
- "guide/layout.md"
|
||||
- "guide/events.md"
|
||||
- "guide/input.md"
|
||||
- "guide/actions.md"
|
||||
- "guide/reactivity.md"
|
||||
- "guide/widgets.md"
|
||||
- "guide/animation.md"
|
||||
- "guide/screens.md"
|
||||
- "guide/workers.md"
|
||||
- "guide/command_palette.md"
|
||||
- "guide/testing.md"
|
||||
- "widget_gallery.md"
|
||||
- Reference:
|
||||
- "reference/index.md"
|
||||
- CSS Types:
|
||||
- "css_types/index.md"
|
||||
- "css_types/border.md"
|
||||
- "css_types/color.md"
|
||||
- "css_types/horizontal.md"
|
||||
- "css_types/integer.md"
|
||||
- "css_types/name.md"
|
||||
- "css_types/number.md"
|
||||
- "css_types/overflow.md"
|
||||
- "css_types/percentage.md"
|
||||
- "css_types/scalar.md"
|
||||
- "css_types/text_align.md"
|
||||
- "css_types/text_style.md"
|
||||
- "css_types/vertical.md"
|
||||
- Events:
|
||||
- "events/index.md"
|
||||
- "events/blur.md"
|
||||
- "events/descendant_blur.md"
|
||||
- "events/descendant_focus.md"
|
||||
- "events/enter.md"
|
||||
- "events/focus.md"
|
||||
- "events/hide.md"
|
||||
- "events/key.md"
|
||||
- "events/leave.md"
|
||||
- "events/load.md"
|
||||
- "events/mount.md"
|
||||
- "events/mouse_capture.md"
|
||||
- "events/click.md"
|
||||
- "events/mouse_down.md"
|
||||
- "events/mouse_move.md"
|
||||
- "events/mouse_release.md"
|
||||
- "events/mouse_scroll_down.md"
|
||||
- "events/mouse_scroll_up.md"
|
||||
- "events/mouse_up.md"
|
||||
- "events/paste.md"
|
||||
- "events/resize.md"
|
||||
- "events/screen_resume.md"
|
||||
- "events/screen_suspend.md"
|
||||
- "events/show.md"
|
||||
- Styles:
|
||||
- "styles/align.md"
|
||||
- "styles/background.md"
|
||||
- "styles/border.md"
|
||||
- "styles/border_subtitle_align.md"
|
||||
- "styles/border_subtitle_background.md"
|
||||
- "styles/border_subtitle_color.md"
|
||||
- "styles/border_subtitle_style.md"
|
||||
- "styles/border_title_align.md"
|
||||
- "styles/border_title_background.md"
|
||||
- "styles/border_title_color.md"
|
||||
- "styles/border_title_style.md"
|
||||
- "styles/box_sizing.md"
|
||||
- "styles/color.md"
|
||||
- "styles/content_align.md"
|
||||
- "styles/display.md"
|
||||
- "styles/dock.md"
|
||||
- "styles/index.md"
|
||||
- Grid:
|
||||
- "styles/grid/index.md"
|
||||
- "styles/grid/column_span.md"
|
||||
- "styles/grid/grid_columns.md"
|
||||
- "styles/grid/grid_gutter.md"
|
||||
- "styles/grid/grid_rows.md"
|
||||
- "styles/grid/grid_size.md"
|
||||
- "styles/grid/row_span.md"
|
||||
- "styles/height.md"
|
||||
- "styles/layer.md"
|
||||
- "styles/layers.md"
|
||||
- "styles/layout.md"
|
||||
- Links:
|
||||
- "styles/links/index.md"
|
||||
- "styles/links/link_background.md"
|
||||
- "styles/links/link_color.md"
|
||||
- "styles/links/link_hover_background.md"
|
||||
- "styles/links/link_hover_color.md"
|
||||
- "styles/links/link_hover_style.md"
|
||||
- "styles/links/link_style.md"
|
||||
- "styles/margin.md"
|
||||
- "styles/max_height.md"
|
||||
- "styles/max_width.md"
|
||||
- "styles/min_height.md"
|
||||
- "styles/min_width.md"
|
||||
- "styles/offset.md"
|
||||
- "styles/opacity.md"
|
||||
- "styles/outline.md"
|
||||
- "styles/overflow.md"
|
||||
- "styles/padding.md"
|
||||
- Scrollbar colors:
|
||||
- "styles/scrollbar_colors/index.md"
|
||||
- "styles/scrollbar_colors/scrollbar_background.md"
|
||||
- "styles/scrollbar_colors/scrollbar_background_active.md"
|
||||
- "styles/scrollbar_colors/scrollbar_background_hover.md"
|
||||
- "styles/scrollbar_colors/scrollbar_color.md"
|
||||
- "styles/scrollbar_colors/scrollbar_color_active.md"
|
||||
- "styles/scrollbar_colors/scrollbar_color_hover.md"
|
||||
- "styles/scrollbar_colors/scrollbar_corner_color.md"
|
||||
- "styles/scrollbar_gutter.md"
|
||||
- "styles/scrollbar_size.md"
|
||||
- "styles/text_align.md"
|
||||
- "styles/text_opacity.md"
|
||||
- "styles/text_style.md"
|
||||
- "styles/tint.md"
|
||||
- "styles/visibility.md"
|
||||
- "styles/width.md"
|
||||
- Widgets:
|
||||
- "widgets/button.md"
|
||||
- "widgets/checkbox.md"
|
||||
- "widgets/collapsible.md"
|
||||
- "widgets/content_switcher.md"
|
||||
- "widgets/data_table.md"
|
||||
- "widgets/digits.md"
|
||||
- "widgets/directory_tree.md"
|
||||
- "widgets/footer.md"
|
||||
- "widgets/header.md"
|
||||
- "widgets/index.md"
|
||||
- "widgets/input.md"
|
||||
- "widgets/label.md"
|
||||
- "widgets/list_item.md"
|
||||
- "widgets/list_view.md"
|
||||
- "widgets/loading_indicator.md"
|
||||
- "widgets/log.md"
|
||||
- "widgets/markdown_viewer.md"
|
||||
- "widgets/markdown.md"
|
||||
- "widgets/option_list.md"
|
||||
- "widgets/placeholder.md"
|
||||
- "widgets/pretty.md"
|
||||
- "widgets/progress_bar.md"
|
||||
- "widgets/radiobutton.md"
|
||||
- "widgets/radioset.md"
|
||||
- "widgets/rich_log.md"
|
||||
- "widgets/rule.md"
|
||||
- "widgets/select.md"
|
||||
- "widgets/selection_list.md"
|
||||
- "widgets/sparkline.md"
|
||||
- "widgets/static.md"
|
||||
- "widgets/switch.md"
|
||||
- "widgets/tabbed_content.md"
|
||||
- "widgets/tabs.md"
|
||||
- "widgets/tree.md"
|
||||
- API:
|
||||
- "api/index.md"
|
||||
- "api/app.md"
|
||||
- "api/await_remove.md"
|
||||
- "api/binding.md"
|
||||
- "api/color.md"
|
||||
- "api/command.md"
|
||||
- "api/containers.md"
|
||||
- "api/coordinate.md"
|
||||
- "api/dom_node.md"
|
||||
- "api/events.md"
|
||||
- "api/errors.md"
|
||||
- "api/filter.md"
|
||||
- "api/fuzzy_matcher.md"
|
||||
- "api/geometry.md"
|
||||
- "api/logger.md"
|
||||
- "api/logging.md"
|
||||
- "api/map_geometry.md"
|
||||
- "api/message_pump.md"
|
||||
- "api/message.md"
|
||||
- "api/on.md"
|
||||
- "api/pilot.md"
|
||||
- "api/query.md"
|
||||
- "api/reactive.md"
|
||||
- "api/screen.md"
|
||||
- "api/scrollbar.md"
|
||||
- "api/scroll_view.md"
|
||||
- "api/strip.md"
|
||||
- "api/suggester.md"
|
||||
- "api/system_commands_source.md"
|
||||
- "api/timer.md"
|
||||
- "api/types.md"
|
||||
- "api/validation.md"
|
||||
- "api/walk.md"
|
||||
- "api/widget.md"
|
||||
- "api/work.md"
|
||||
- "api/worker.md"
|
||||
- "api/worker_manager.md"
|
||||
- "How To":
|
||||
- "how-to/index.md"
|
||||
- "how-to/center-things.md"
|
||||
- "how-to/design-a-layout.md"
|
||||
- "FAQ.md"
|
||||
- "roadmap.md"
|
||||
- "Blog":
|
||||
- blog/index.md
|
||||
|
|
|
|||
|
|
@ -1196,9 +1196,9 @@ class App(Generic[ReturnType], DOMNode):
|
|||
notifications: bool = False,
|
||||
message_hook: Callable[[Message], None] | None = None,
|
||||
) -> AsyncGenerator[Pilot, None]:
|
||||
"""An asynchronous context manager for testing app.
|
||||
"""An asynchronous context manager for testing apps.
|
||||
|
||||
Use this to run your app in "headless" (no output) mode and driver the app via a [Pilot][textual.pilot.Pilot] object.
|
||||
Use this to run your app in "headless" mode (no output) and drive the app via a [Pilot][textual.pilot.Pilot] object.
|
||||
|
||||
Example:
|
||||
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@ import rich.repr
|
|||
from ._wait import wait_for_idle
|
||||
from .app import App, ReturnType
|
||||
from .events import Click, MouseDown, MouseMove, MouseUp
|
||||
from .geometry import Offset
|
||||
from .widget import Widget
|
||||
|
||||
|
||||
def _get_mouse_message_arguments(
|
||||
target: Widget,
|
||||
offset: Offset = Offset(),
|
||||
offset: tuple[int, int] = (0, 0),
|
||||
button: int = 0,
|
||||
shift: bool = False,
|
||||
meta: bool = False,
|
||||
|
|
@ -74,7 +73,7 @@ class Pilot(Generic[ReturnType]):
|
|||
async def click(
|
||||
self,
|
||||
selector: type[Widget] | str | None = None,
|
||||
offset: Offset = Offset(),
|
||||
offset: tuple[int, int] = (0, 0),
|
||||
shift: bool = False,
|
||||
meta: bool = False,
|
||||
control: bool = False,
|
||||
|
|
@ -112,7 +111,7 @@ class Pilot(Generic[ReturnType]):
|
|||
async def hover(
|
||||
self,
|
||||
selector: type[Widget] | str | None | None = None,
|
||||
offset: Offset = Offset(),
|
||||
offset: tuple[int, int] = (0, 0),
|
||||
) -> None:
|
||||
"""Simulate hovering with the mouse cursor.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue