Merge pull request #6121 from TomJGooding/build-update-supported-python-versions

build!: update supported Python versions
This commit is contained in:
Will McGugan
2025-10-11 12:16:14 +01:00
committed by GitHub
29 changed files with 1796 additions and 1395 deletions

View File

@@ -20,7 +20,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
defaults:
run:
shell: bash
@@ -35,18 +35,18 @@ jobs:
cache: "poetry"
- name: Install dependencies
run: poetry install --no-interaction --extras syntax
if: ${{ matrix.python-version != '3.8' && matrix.python-version != '3.9' }}
- name: Install dependencies for 3.8 and 3.9
if: ${{ matrix.python-version != '3.9' }}
- name: Install dependencies for 3.9
run: poetry install --no-interaction
if: ${{ matrix.python-version == '3.8' || matrix.python-version == '3.9' }}
if: ${{ matrix.python-version == '3.9' }}
- name: Test with pytest (Py310+ - with syntax highlighting)
run: |
poetry run pytest tests -v --cov=./src/textual --cov-report=xml:./coverage.xml --cov-report term-missing
if: ${{ matrix.python-version != '3.8' && matrix.python-version != '3.9' }}
if: ${{ matrix.python-version != '3.9' }}
- name: Test with pytest (Py39 - without syntax highlighting)
run: |
poetry run pytest tests -v --cov=./src/textual --cov-report=xml:./coverage.xml --cov-report term-missing -m 'not syntax'
if: ${{ matrix.python-version == '3.8' || matrix.python-version == '3.9' }}
if: ${{ matrix.python-version == '3.9' }}
- name: Upload snapshot report
if: always()
uses: actions/upload-artifact@v4

View File

@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
## [6.3.0] - 2025-10-11
### Added
@@ -15,6 +15,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixed highlight not auto-detecting lexer https://github.com/Textualize/textual/pull/6167
### Changed
- Dropped support for Python3.8 https://github.com/Textualize/textual/pull/6121/
- Added support for Python3.14 https://github.com/Textualize/textual/pull/6121/
## [6.2.1] - 2025-10-01
- Fix inability to copy text outside of an input/textarea when it was focused https://github.com/Textualize/textual/pull/6148
@@ -3144,6 +3149,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040
- New handler system for messages that doesn't require inheritance
- Improved traceback handling
[6.3.0]: https://github.com/Textualize/textual/compare/v6.2.1...v6.3.0
[6.2.1]: https://github.com/Textualize/textual/compare/v6.2.0...v6.2.1
[6.2.0]: https://github.com/Textualize/textual/compare/v6.1.0...v6.2.0
[6.1.0]: https://github.com/Textualize/textual/compare/v6.0.0...v6.1.0

View File

@@ -1,7 +1,7 @@
[![Discord](https://img.shields.io/discord/1026214085173461072)](https://discord.gg/Enf6Z3qhVr)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/textual/1.0.0)](https://pypi.org/project/textual/)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/textual)](https://pypi.org/project/textual/)
[![PyPI version](https://badge.fury.io/py/textual.svg?)](https://badge.fury.io/py/textual)
![OS support](https://img.shields.io/badge/OS-macOS%20Linux%20Windows-red)

View File

@@ -3,7 +3,7 @@ All you need to get started building Textual apps.
## Requirements
Textual requires Python 3.8 or later (if you have a choice, pick the most recent Python). Textual runs on Linux, macOS, Windows and probably any OS where Python also runs.
Textual requires Python 3.9 or later (if you have a choice, pick the most recent Python). Textual runs on Linux, macOS, Windows and probably any OS where Python also runs.
!!! info "Your platform"

2863
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
version = "6.2.1"
version = "6.3.0"
homepage = "https://github.com/Textualize/textual"
repository = "https://github.com/Textualize/textual"
documentation = "https://textual.textualize.io/"
@@ -17,12 +17,12 @@ classifiers = [
"Operating System :: Microsoft :: Windows :: Windows 11",
"Operating System :: MacOS",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Typing :: Typed",
]
include = [
@@ -41,12 +41,12 @@ include = [
"Bug Tracker" = "https://github.com/Textualize/textual/issues"
[tool.ruff]
target-version = "py38"
target-version = "py39"
[tool.poetry.dependencies]
python = "^3.8.1"
python = "^3.9"
markdown-it-py = { extras = ["plugins", "linkify"], version = ">=2.1.0" }
rich = ">=13.3.3"
rich = ">=14.2.0"
#rich = {path="../rich", develop=true}
typing-extensions = "^4.4.0"
platformdirs = ">=3.6.0,<5"

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

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)
@@ -143,7 +143,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)
@@ -166,7 +166,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

@@ -286,7 +286,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")