mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
@@ -14,5 +14,3 @@ class GridApp(App):
|
|||||||
|
|
||||||
|
|
||||||
app = GridApp(css_path="grid.css")
|
app = GridApp(css_path="grid.css")
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run()
|
|
||||||
|
|||||||
@@ -13,6 +13,3 @@ class LinksApp(App):
|
|||||||
|
|
||||||
|
|
||||||
app = LinksApp(css_path="links.css")
|
app = LinksApp(css_path="links.css")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run()
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ class CheckboxApp(App):
|
|||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Static("[b]Example checkboxes\n", classes="label")
|
yield Static("[b]Example checkboxes\n", classes="label")
|
||||||
yield Horizontal(
|
yield Horizontal(
|
||||||
Static("off: ", classes="label"), Checkbox(), classes="container"
|
Static("off: ", classes="label"),
|
||||||
|
Checkbox(animate=False),
|
||||||
|
classes="container",
|
||||||
)
|
)
|
||||||
yield Horizontal(
|
yield Horizontal(
|
||||||
Static("on: ", classes="label"),
|
Static("on: ", classes="label"),
|
||||||
|
|||||||
@@ -4,7 +4,16 @@ from textual.widgets import Footer
|
|||||||
|
|
||||||
|
|
||||||
class FooterApp(App):
|
class FooterApp(App):
|
||||||
BINDINGS = [Binding(key="q", action="quit", description="Quit the app")]
|
BINDINGS = [
|
||||||
|
Binding(key="q", action="quit", description="Quit the app"),
|
||||||
|
Binding(
|
||||||
|
key="question_mark",
|
||||||
|
action="help",
|
||||||
|
description="Show help screen",
|
||||||
|
key_display="?",
|
||||||
|
),
|
||||||
|
Binding(key="j", action="down", description="Scroll down", show=False),
|
||||||
|
]
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ The example below populates a table with CSV data.
|
|||||||
|
|
||||||
=== "Output"
|
=== "Output"
|
||||||
|
|
||||||
```{.textual path="docs/examples/widgets/table.py"}
|
```{.textual path="docs/examples/widgets/data_table.py"}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "table.py"
|
=== "data_table.py"
|
||||||
|
|
||||||
```python
|
```python
|
||||||
--8<-- "docs/examples/widgets/table.py"
|
--8<-- "docs/examples/widgets/data_table.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -605,7 +605,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
assert press
|
assert press
|
||||||
driver = app._driver
|
driver = app._driver
|
||||||
assert driver is not None
|
assert driver is not None
|
||||||
await asyncio.sleep(0.01)
|
await asyncio.sleep(0.02)
|
||||||
for key in press:
|
for key in press:
|
||||||
if key == "_":
|
if key == "_":
|
||||||
print("(pause 50ms)")
|
print("(pause 50ms)")
|
||||||
@@ -632,7 +632,13 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
print(f"press {key!r} (char={char!r})")
|
print(f"press {key!r} (char={char!r})")
|
||||||
key_event = events.Key(self, key, char)
|
key_event = events.Key(self, key, char)
|
||||||
driver.send_event(key_event)
|
driver.send_event(key_event)
|
||||||
await asyncio.sleep(0.01)
|
# TODO: A bit of a fudge - extra sleep after tabbing to help guard against race
|
||||||
|
# condition between widget-level key handling and app/screen level handling.
|
||||||
|
# More information here: https://github.com/Textualize/textual/issues/1009
|
||||||
|
# This conditional sleep can be removed after that issue is closed.
|
||||||
|
if key == "tab":
|
||||||
|
await asyncio.sleep(0.05)
|
||||||
|
await asyncio.sleep(0.02)
|
||||||
|
|
||||||
await app._animator.wait_for_idle()
|
await app._animator.wait_for_idle()
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -59,6 +59,7 @@ def snap_compare(
|
|||||||
"""
|
"""
|
||||||
node = request.node
|
node = request.node
|
||||||
app = import_app(app_path)
|
app = import_app(app_path)
|
||||||
|
compare.app = app
|
||||||
actual_screenshot = take_svg_screenshot(
|
actual_screenshot = take_svg_screenshot(
|
||||||
app=app,
|
app=app,
|
||||||
press=press,
|
press=press,
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
from pathlib import Path, PurePosixPath
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from textual.app import App
|
||||||
|
from textual.widgets import Input, Button
|
||||||
|
|
||||||
|
|
||||||
|
# --- Layout related stuff ---
|
||||||
|
|
||||||
def test_grid_layout_basic(snap_compare):
|
def test_grid_layout_basic(snap_compare):
|
||||||
assert snap_compare("docs/examples/guide/layout/grid_layout1.py")
|
assert snap_compare("docs/examples/guide/layout/grid_layout1.py")
|
||||||
|
|
||||||
@@ -26,7 +36,73 @@ def test_dock_layout_sidebar(snap_compare):
|
|||||||
assert snap_compare("docs/examples/guide/layout/dock_layout2_sidebar.py")
|
assert snap_compare("docs/examples/guide/layout/dock_layout2_sidebar.py")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Widgets - rendering and basic interactions ---
|
||||||
|
# Each widget should have a canonical example that is display in the docs.
|
||||||
|
# When adding a new widget, ideally we should also create a snapshot test
|
||||||
|
# from these examples which test rendering and simple interactions with it.
|
||||||
|
|
||||||
def test_checkboxes(snap_compare):
|
def test_checkboxes(snap_compare):
|
||||||
"""Tests checkboxes but also acts a regression test for using
|
"""Tests checkboxes but also acts a regression test for using
|
||||||
width: auto in a Horizontal layout context."""
|
width: auto in a Horizontal layout context."""
|
||||||
assert snap_compare("docs/examples/widgets/checkbox.py")
|
press = [
|
||||||
|
"shift+tab",
|
||||||
|
"enter", # toggle off
|
||||||
|
"shift+tab",
|
||||||
|
"wait:20",
|
||||||
|
"enter", # toggle on
|
||||||
|
"wait:20",
|
||||||
|
]
|
||||||
|
assert snap_compare("docs/examples/widgets/checkbox.py", press=press)
|
||||||
|
|
||||||
|
|
||||||
|
def test_input_and_focus(snap_compare):
|
||||||
|
press = [
|
||||||
|
"tab",
|
||||||
|
*"Darren", # Focus first input, write "Darren"
|
||||||
|
"tab",
|
||||||
|
*"Burns", # Tab focus to second input, write "Burns"
|
||||||
|
]
|
||||||
|
assert snap_compare("docs/examples/widgets/input.py", press=press)
|
||||||
|
|
||||||
|
# Assert that the state of the Input is what we'd expect
|
||||||
|
app: App = snap_compare.app
|
||||||
|
input: Input = app.query_one(Input)
|
||||||
|
assert input.value == "Darren"
|
||||||
|
assert input.cursor_position == 6
|
||||||
|
assert input.view_position == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_buttons_render(snap_compare):
|
||||||
|
# Testing button rendering. We press tab to focus the first button too.
|
||||||
|
assert snap_compare("docs/examples/widgets/button.py", press=["tab"])
|
||||||
|
|
||||||
|
app = snap_compare.app
|
||||||
|
button: Button = app.query_one(Button)
|
||||||
|
assert app.focused is button
|
||||||
|
|
||||||
|
|
||||||
|
def test_datatable_render(snap_compare):
|
||||||
|
press = ["tab", "down", "down", "right", "up", "left"]
|
||||||
|
assert snap_compare("docs/examples/widgets/data_table.py", press=press)
|
||||||
|
|
||||||
|
|
||||||
|
def test_footer_render(snap_compare):
|
||||||
|
assert snap_compare("docs/examples/widgets/footer.py")
|
||||||
|
|
||||||
|
|
||||||
|
def test_header_render(snap_compare):
|
||||||
|
assert snap_compare("docs/examples/widgets/header.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.
|
||||||
|
|
||||||
|
PATHS = [
|
||||||
|
str(PurePosixPath(path)) for path in Path("docs/examples/styles").iterdir() if path.suffix == ".py"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("path", PATHS)
|
||||||
|
def test_css_property_snapshot(path, snap_compare):
|
||||||
|
assert snap_compare(path)
|
||||||
|
|||||||
Reference in New Issue
Block a user