test: make tests async to fix failures on Python 3.9

After upgrading `pytest-asyncio` to the latest version, lots of tests
started failing in CI only on Python 3.9:

`RuntimeError: There is no current event loop in thread 'MainThread'`

Apparently these tests may have only been passing previously due to
issues in earlier versions of `pytest-asyncio`. Changing these tests to
async seems to fix the failures on Python 3.9.

Related issue:
https://github.com/pytest-dev/pytest-asyncio/issues/1039
This commit is contained in:
TomJGooding
2025-09-17 13:38:05 +01:00
parent e15e05df5a
commit 7a9d32fdbf
22 changed files with 78 additions and 78 deletions

View File

@@ -7,7 +7,7 @@ from textual.layout import WidgetPlacement
from textual.widget import Widget
def test_arrange_empty():
async def test_arrange_empty():
container = Widget(id="container")
result = arrange(container, [], Size(80, 24), Size(80, 24))
@@ -15,7 +15,7 @@ def test_arrange_empty():
assert result.widgets == set()
def test_arrange_dock_top():
async def test_arrange_dock_top():
container = Widget(id="container")
container._parent = App()
child = Widget(id="child")
@@ -36,7 +36,7 @@ def test_arrange_dock_top():
assert result.widgets == {child, header}
def test_arrange_dock_left():
async def test_arrange_dock_left():
container = Widget(id="container")
container._parent = App()
child = Widget(id="child")
@@ -61,7 +61,7 @@ def test_arrange_dock_left():
assert result.widgets == {child, header}
def test_arrange_dock_right():
async def test_arrange_dock_right():
container = Widget(id="container")
container._parent = App()
child = Widget(id="child")
@@ -86,7 +86,7 @@ def test_arrange_dock_right():
assert result.widgets == {child, header}
def test_arrange_dock_bottom():
async def test_arrange_dock_bottom():
container = Widget(id="container")
container._parent = App()
child = Widget(id="child")
@@ -111,7 +111,7 @@ def test_arrange_dock_bottom():
assert result.widgets == {child, header}
def test_arrange_dock_badly():
async def test_arrange_dock_badly():
child = Widget(id="child")
child.styles.dock = "nowhere"
with pytest.raises(AssertionError):

View File

@@ -31,7 +31,7 @@ def test_border_render_row():
]
def test_border_title_single_line():
async def test_border_title_single_line():
"""The border_title gets set to a single line even when multiple lines are provided."""
widget = Widget()
@@ -59,7 +59,7 @@ def test_border_title_single_line():
assert widget.border_title == "[bold]Hello World[/bold]"
def test_border_subtitle_single_line():
async def test_border_subtitle_single_line():
"""The border_subtitle gets set to a single line even when multiple lines are provided."""
widget = Widget()

View File

@@ -7,7 +7,7 @@ from textual.geometry import Size, Spacing
from textual.widget import Widget
def test_content_box():
async def test_content_box():
one = Fraction(1)
class TestWidget(Widget):
@@ -44,7 +44,7 @@ def test_content_box():
assert box_model == BoxModel(Fraction(14), Fraction(12), Spacing(0, 0, 0, 0))
def test_width():
async def test_width():
"""Test width settings."""
one = Fraction(1)
@@ -93,7 +93,7 @@ def test_width():
assert box_model == BoxModel(Fraction(27), Fraction(16), Spacing(1, 2, 3, 4))
def test_height():
async def test_height():
"""Test height settings."""
one = Fraction(1)
@@ -144,7 +144,7 @@ def test_height():
assert box_model == BoxModel(Fraction(54), Fraction(8), Spacing(1, 2, 3, 4))
def test_max():
async def test_max():
"""Check that max_width and max_height are respected."""
one = Fraction(1)
@@ -167,7 +167,7 @@ def test_max():
assert box_model == BoxModel(Fraction(40), Fraction(30), Spacing(0, 0, 0, 0))
def test_min():
async def test_min():
"""Check that min_width and min_height are respected."""
one = Fraction(1)

View File

@@ -278,7 +278,7 @@ def test_first_line():
assert first_line.spans == [Span(0, 3, "red")]
def test_split_and_tabs():
async def test_split_and_tabs():
spans = [
Span(0, 49, style="$text"),
]

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from textual.app import App
def test_textual_env_var(monkeypatch):
async def test_textual_env_var(monkeypatch):
monkeypatch.setenv("TEXTUAL", "")
app = App()
assert app.features == set()

View File

@@ -2,7 +2,7 @@ from textual.app import App, ComposeResult
from textual.widgets import Log
def test_process_line():
async def test_process_line():
log = Log()
assert log._process_line("foo") == "foo"
assert log._process_line("foo\t") == "foo "

View File

@@ -9,14 +9,14 @@ def test_empty_list():
assert len(NodeList()) == 0
def test_add_one():
async def test_add_one():
"""Does adding a node to the node list report as having one item?"""
nodes = NodeList()
nodes._append(Widget())
assert len(nodes) == 1
def test_length_hint():
async def test_length_hint():
"""Check length hint dunder method."""
nodes = NodeList()
assert nodes.__length_hint__() == 0
@@ -26,7 +26,7 @@ def test_length_hint():
assert nodes.__length_hint__() == 3
def test_repeat_add_one():
async def test_repeat_add_one():
"""Does adding the same item to the node list ignore the additional adds?"""
nodes = NodeList()
widget = Widget()
@@ -35,7 +35,7 @@ def test_repeat_add_one():
assert len(nodes) == 1
def test_insert():
async def test_insert():
nodes = NodeList()
widget1 = Widget()
widget2 = Widget()
@@ -46,7 +46,7 @@ def test_insert():
assert list(nodes) == [widget1, widget2, widget3]
def test_truthy():
async def test_truthy():
"""Does a node list act as a truthy object?"""
nodes = NodeList()
assert not bool(nodes)
@@ -54,7 +54,7 @@ def test_truthy():
assert bool(nodes)
def test_contains():
async def test_contains():
"""Can we check if a widget is (not) within the list?"""
widget = Widget()
nodes = NodeList()
@@ -64,7 +64,7 @@ def test_contains():
assert Widget() not in nodes
def test_index():
async def test_index():
"""Can we get the index of a widget in the list?"""
widget = Widget()
nodes = NodeList()
@@ -74,7 +74,7 @@ def test_index():
assert nodes.index(widget) == 0
def test_remove():
async def test_remove():
"""Can we remove a widget we've added?"""
widget = Widget()
nodes = NodeList()
@@ -84,7 +84,7 @@ def test_remove():
assert widget not in nodes
def test_clear():
async def test_clear():
"""Can we clear the list?"""
nodes = NodeList()
assert len(nodes) == 0
@@ -100,7 +100,7 @@ def test_clear():
assert widget not in nodes
def test_listy():
async def test_listy():
nodes = NodeList()
widget1 = Widget()
widget2 = Widget()

View File

@@ -4,12 +4,12 @@ from textual.widgets import Placeholder
from textual.widgets._placeholder import InvalidPlaceholderVariant
def test_invalid_placeholder_variant():
async def test_invalid_placeholder_variant():
with pytest.raises(InvalidPlaceholderVariant):
Placeholder(variant="this is clearly not a valid variant!")
def test_invalid_reactive_variant_change():
async def test_invalid_reactive_variant_change():
p = Placeholder()
with pytest.raises(InvalidPlaceholderVariant):
p.variant = "this is clearly not a valid variant!"

View File

@@ -11,7 +11,7 @@ from textual.widget import Widget
from textual.widgets import ProgressBar
def test_initial_status():
async def test_initial_status():
pb = ProgressBar()
assert pb.total is None
assert pb.progress == 0
@@ -23,7 +23,7 @@ def test_initial_status():
assert pb.percentage == 0
def test_advance():
async def test_advance():
pb = ProgressBar(total=100)
pb.advance(10)
@@ -39,7 +39,7 @@ def test_advance():
assert pb.percentage == approx(0.520625)
def test_advance_backwards():
async def test_advance_backwards():
pb = ProgressBar(total=100)
pb.progress = 50
@@ -48,7 +48,7 @@ def test_advance_backwards():
assert pb.progress == 40
def test_progress_overflow():
async def test_progress_overflow():
pb = ProgressBar(total=100)
pb.advance(999_999)
@@ -58,19 +58,19 @@ def test_progress_overflow():
assert pb.percentage == 1
def test_progress_underflow():
async def test_progress_underflow():
pb = ProgressBar(total=100)
pb.advance(-999_999)
assert pb.percentage == 0
def test_non_negative_total():
async def test_non_negative_total():
pb = ProgressBar(total=-100)
assert pb.total == 0
def test_update_total():
async def test_update_total():
pb = ProgressBar()
pb.update(total=100)
@@ -86,7 +86,7 @@ def test_update_total():
assert pb.total == 100
def test_update_progress():
async def test_update_progress():
pb = ProgressBar(total=100)
pb.update(progress=10)
@@ -99,7 +99,7 @@ def test_update_progress():
assert pb.progress == 40
def test_update_advance():
async def test_update_advance():
pb = ProgressBar(total=100)
pb.update(advance=10)
@@ -112,7 +112,7 @@ def test_update_advance():
assert pb.progress == 30
def test_update():
async def test_update():
pb = ProgressBar()
pb.update(total=100, progress=30, advance=20)
@@ -120,7 +120,7 @@ def test_update():
assert pb.progress == 50
def test_go_back_to_indeterminate():
async def test_go_back_to_indeterminate():
pb = ProgressBar()
pb.total = 100

View File

@@ -30,7 +30,7 @@ async def test_query_errors():
app.query_one("1")
def test_query():
async def test_query():
class View(Widget):
pass
@@ -159,7 +159,7 @@ def test_query():
_ = app.query(".float").last(View)
def test_query_classes():
async def test_query_classes():
class App(Widget):
pass
@@ -225,7 +225,7 @@ def test_query_classes():
assert len(app.query(".test")) == 0
def test_invalid_query():
async def test_invalid_query():
class App(Widget):
pass

View File

@@ -61,7 +61,7 @@ def test_resolve(scalars, total, gutter, result):
)
def test_resolve_fraction_unit():
async def test_resolve_fraction_unit():
"""Test resolving fraction units in combination with minimum widths."""
widget1 = Widget()
widget2 = Widget()
@@ -124,7 +124,7 @@ def test_resolve_fraction_unit():
) == Fraction(2)
def test_resolve_fraction_unit_stress_test():
async def test_resolve_fraction_unit_stress_test():
"""Check for zero division errors."""
# https://github.com/Textualize/textual/issues/2673
widget = Widget()
@@ -149,7 +149,7 @@ def test_resolve_fraction_unit_stress_test():
assert resolved_unit <= remaining_space
def test_resolve_issue_2502():
async def test_resolve_issue_2502():
"""Test https://github.com/Textualize/textual/issues/2502"""
widget = Widget()

View File

@@ -4,23 +4,23 @@ from textual.widgets import Rule
from textual.widgets.rule import InvalidLineStyle, InvalidRuleOrientation
def test_invalid_rule_orientation():
async def test_invalid_rule_orientation():
with pytest.raises(InvalidRuleOrientation):
Rule(orientation="invalid orientation!")
def test_invalid_rule_line_style():
async def test_invalid_rule_line_style():
with pytest.raises(InvalidLineStyle):
Rule(line_style="invalid line style!")
def test_invalid_reactive_rule_orientation_change():
async def test_invalid_reactive_rule_orientation_change():
rule = Rule()
with pytest.raises(InvalidRuleOrientation):
rule.orientation = "invalid orientation!"
def test_invalid_reactive_rule_line_style_change():
async def test_invalid_reactive_rule_line_style_change():
rule = Rule()
with pytest.raises(InvalidLineStyle):
rule.line_style = "invalid line style!"

View File

@@ -58,7 +58,7 @@ async def test_signal():
assert called == 3
def test_signal_errors():
async def test_signal_errors():
"""Check exceptions raised by Signal class."""
app = App()
test_signal = Signal(app, "test")

View File

@@ -2,7 +2,7 @@ from textual.content import Content
from textual.widgets import Static
def test_content_property():
async def test_content_property():
static = Static()
assert static.content == ""
static.content = "Foo"

View File

@@ -3,7 +3,7 @@ from rich.text import Text
from textual.widgets import RichLog
def test_make_renderable_expand_tabs():
async def test_make_renderable_expand_tabs():
# Regression test for https://github.com/Textualize/textual/issues/3007
text_log = RichLog()
renderable = text_log._make_renderable("\tfoo")

View File

@@ -63,7 +63,7 @@ async def test_widget_construct():
["visible", True, "visible"],
],
)
def test_widget_set_visible_true(set_val, get_val, style_str):
async def test_widget_set_visible_true(set_val, get_val, style_str):
widget = Widget()
widget.visible = set_val
@@ -71,7 +71,7 @@ def test_widget_set_visible_true(set_val, get_val, style_str):
assert widget.styles.visibility == style_str
def test_widget_set_visible_invalid_string():
async def test_widget_set_visible_invalid_string():
widget = Widget()
with pytest.raises(StyleValueError):
@@ -80,7 +80,7 @@ def test_widget_set_visible_invalid_string():
assert widget.visible
def test_widget_content_width():
async def test_widget_content_width():
class TextWidget(Widget):
def __init__(self, text: str, id: str) -> None:
self.text = text
@@ -238,19 +238,19 @@ async def test_widget_mount_ids_must_be_unique_mounting_multiple_calls(hierarchy
parent.mount(widget2)
def test_get_pseudo_class_state():
async def test_get_pseudo_class_state():
widget = Widget()
pseudo_classes = widget.get_pseudo_class_state()
assert pseudo_classes == PseudoClasses(enabled=True, focus=False, hover=False)
def test_get_pseudo_class_state_disabled():
async def test_get_pseudo_class_state_disabled():
widget = Widget(disabled=True)
pseudo_classes = widget.get_pseudo_class_state()
assert pseudo_classes == PseudoClasses(enabled=False, focus=False, hover=False)
def test_get_pseudo_class_state_parent_disabled():
async def test_get_pseudo_class_state_parent_disabled():
child = Widget()
_parent = Widget(disabled=True)
child._attach(_parent)
@@ -258,14 +258,14 @@ def test_get_pseudo_class_state_parent_disabled():
assert pseudo_classes == PseudoClasses(enabled=False, focus=False, hover=False)
def test_get_pseudo_class_state_hover():
async def test_get_pseudo_class_state_hover():
widget = Widget()
widget.mouse_hover = True
pseudo_classes = widget.get_pseudo_class_state()
assert pseudo_classes == PseudoClasses(enabled=True, focus=False, hover=True)
def test_get_pseudo_class_state_focus():
async def test_get_pseudo_class_state_focus():
widget = Widget()
widget.has_focus = True
pseudo_classes = widget.get_pseudo_class_state()
@@ -314,7 +314,7 @@ async def test_remove_unmounted():
assert mounted
def test_render_str() -> None:
async def test_render_str() -> None:
widget = Label()
assert widget.render_str("foo") == Content("foo")
assert widget.render_str("[b]foo") == Content.from_markup("[b]foo")
@@ -371,11 +371,11 @@ def test_children_must_be_widgets():
Widget(1, 2, 3)
def test_orphan_widget_has_no_siblings():
async def test_orphan_widget_has_no_siblings():
assert Widget().siblings == []
def test__allow_scroll_default():
async def test__allow_scroll_default():
assert not Widget()._allow_scroll
@@ -407,7 +407,7 @@ async def test_offset_getter_setter():
assert label.offset == Offset(7, 3)
def test_get_set_tooltip():
async def test_get_set_tooltip():
widget = Widget()
assert widget.tooltip is None
widget.tooltip = "This is a tooltip."

View File

@@ -11,7 +11,7 @@ class Body(Widget):
pass
def test_find_dom_spot():
async def test_find_dom_spot():
# Build up a "fake" DOM for an application.
screen = Widget(name="Screen")
header = Widget(name="Header", id="header")

View File

@@ -6,7 +6,7 @@ from textual.widgets import Tree
from textual.widgets.tree import NodeID, UnknownNodeID
def test_get_tree_node_by_id() -> None:
async def test_get_tree_node_by_id() -> None:
"""It should be possible to get a TreeNode by its ID."""
tree = Tree[None]("Anakin")
child = tree.root.add("Leia")

View File

@@ -4,13 +4,13 @@ from textual.widgets import Tree
from textual.widgets.tree import AddNodeError
def test_tree_node_add_before_and_after_raises_exception():
async def test_tree_node_add_before_and_after_raises_exception():
tree = Tree[None]("root")
with pytest.raises(AddNodeError):
tree.root.add("error", before=99, after=0)
def test_tree_node_add_before_or_after_with_invalid_type_raises_exception():
async def test_tree_node_add_before_or_after_with_invalid_type_raises_exception():
tree = Tree[None]("root")
tree.root.add("node")
with pytest.raises(TypeError):
@@ -19,7 +19,7 @@ def test_tree_node_add_before_or_after_with_invalid_type_raises_exception():
tree.root.add("after node", after="node")
def test_tree_node_add_before_index():
async def test_tree_node_add_before_index():
tree = Tree[None]("root")
tree.root.add("node")
tree.root.add("before node", before=0)
@@ -38,7 +38,7 @@ def test_tree_node_add_before_index():
assert str(tree.root.children[6].label) == "last"
def test_tree_node_add_after_index():
async def test_tree_node_add_after_index():
tree = Tree[None]("root")
tree.root.add("node")
tree.root.add("after node", after=0)
@@ -57,7 +57,7 @@ def test_tree_node_add_after_index():
assert str(tree.root.children[6].label) == "last"
def test_tree_node_add_relative_to_unknown_node_raises_exception():
async def test_tree_node_add_relative_to_unknown_node_raises_exception():
tree = Tree[None]("root")
removed_node = tree.root.add("removed node")
removed_node.remove()
@@ -67,7 +67,7 @@ def test_tree_node_add_relative_to_unknown_node_raises_exception():
tree.root.add("node", after=removed_node)
def test_tree_node_add_before_node():
async def test_tree_node_add_before_node():
tree = Tree[None]("root")
node = tree.root.add("node")
before_node = tree.root.add("before node", before=node)
@@ -86,7 +86,7 @@ def test_tree_node_add_before_node():
assert str(tree.root.children[6].label) == "last"
def test_tree_node_add_after_node():
async def test_tree_node_add_after_node():
tree = Tree[None]("root")
node = tree.root.add("node")
after_node = tree.root.add("after node", after=node)
@@ -105,7 +105,7 @@ def test_tree_node_add_after_node():
assert str(tree.root.children[6].label) == "last"
def test_tree_node_add_leaf_before_or_after():
async def test_tree_node_add_leaf_before_or_after():
tree = Tree[None]("root")
leaf = tree.root.add_leaf("leaf")
tree.root.add_leaf("before leaf", before=leaf)

View File

@@ -9,7 +9,7 @@ def label_of(node: TreeNode[None]):
return str(node.label)
def test_tree_node_children() -> None:
async def test_tree_node_children() -> None:
"""A node's children property should act like an immutable list."""
CHILDREN = 23
tree = Tree[None]("Root")

View File

@@ -4,7 +4,7 @@ from textual.widgets import Tree
from textual.widgets.tree import TreeNode
def test_tree_node_label() -> None:
async def test_tree_node_label() -> None:
"""It should be possible to modify a TreeNode's label."""
node = TreeNode(Tree[None]("Xenomorph Lifecycle"), None, 0, "Facehugger")
assert node.label == Text("Facehugger")
@@ -12,7 +12,7 @@ def test_tree_node_label() -> None:
assert node.label == Text("Chestbuster")
def test_tree_node_label_via_tree() -> None:
async def test_tree_node_label_via_tree() -> None:
"""It should be possible to modify a TreeNode's label when created via a Tree."""
tree = Tree[None]("Xenomorph Lifecycle")
node = tree.root.add("Facehugger")

View File

@@ -1,7 +1,7 @@
from textual.widgets import Tree
def test_tree_node_parent() -> None:
async def test_tree_node_parent() -> None:
"""It should be possible to access a TreeNode's parent."""
tree = Tree[None]("Anakin")
child = tree.root.add("Leia")