mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'main' into review-styles-reference
This commit is contained in:
53
tests/test_concurrency.py
Normal file
53
tests/test_concurrency.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import pytest
|
||||
|
||||
from threading import Thread
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import TextLog
|
||||
|
||||
|
||||
def test_call_from_thread_app_not_running():
|
||||
app = App()
|
||||
|
||||
# Should fail if app is not running
|
||||
with pytest.raises(RuntimeError):
|
||||
app.call_from_thread(print)
|
||||
|
||||
|
||||
def test_call_from_thread():
|
||||
"""Test the call_from_thread method."""
|
||||
|
||||
class BackgroundThread(Thread):
|
||||
"""A background thread which will modify app in some way."""
|
||||
|
||||
def __init__(self, app: App) -> None:
|
||||
self.app = app
|
||||
super().__init__()
|
||||
|
||||
def run(self) -> None:
|
||||
def write_stuff(text: str) -> None:
|
||||
"""Write stuff to a widget."""
|
||||
self.app.query_one(TextLog).write(text)
|
||||
|
||||
self.app.call_from_thread(write_stuff, "Hello")
|
||||
# Exit the app with a code we can assert
|
||||
self.app.call_from_thread(self.app.exit, 123)
|
||||
|
||||
class ThreadTestApp(App):
|
||||
"""Trivial app with a single widget."""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield TextLog()
|
||||
|
||||
def on_ready(self) -> None:
|
||||
"""Launch a thread which will modify the app."""
|
||||
try:
|
||||
self.call_from_thread(print)
|
||||
except RuntimeError as error:
|
||||
# Calling this from the same thread as the app is an error
|
||||
self._runtime_error = error
|
||||
BackgroundThread(self).start()
|
||||
|
||||
app = ThreadTestApp()
|
||||
result = app.run(headless=True, size=(80, 24))
|
||||
assert isinstance(app._runtime_error, RuntimeError)
|
||||
assert result == 123
|
||||
@@ -24,7 +24,7 @@ def test_non_empty_immutable_sequence() -> None:
|
||||
|
||||
def test_no_assign_to_immutable_sequence() -> None:
|
||||
"""It should not be possible to assign into an immutable sequence."""
|
||||
tester = wrap([1,2,3,4,5])
|
||||
tester = wrap([1, 2, 3, 4, 5])
|
||||
with pytest.raises(TypeError):
|
||||
tester[0] = 23
|
||||
with pytest.raises(TypeError):
|
||||
@@ -33,7 +33,7 @@ def test_no_assign_to_immutable_sequence() -> None:
|
||||
|
||||
def test_no_del_from_iummutable_sequence() -> None:
|
||||
"""It should not be possible delete an item from an immutable sequence."""
|
||||
tester = wrap([1,2,3,4,5])
|
||||
tester = wrap([1, 2, 3, 4, 5])
|
||||
with pytest.raises(TypeError):
|
||||
del tester[0]
|
||||
|
||||
@@ -46,23 +46,23 @@ def test_get_item_from_immutable_sequence() -> None:
|
||||
|
||||
def test_get_slice_from_immutable_sequence() -> None:
|
||||
"""It should be possible to get a slice from an immutable sequence."""
|
||||
assert list(wrap(range(10))[0:2]) == [0,1]
|
||||
assert list(wrap(range(10))[0:-1]) == [0,1,2,3,4,5,6,7,8]
|
||||
assert list(wrap(range(10))[0:2]) == [0, 1]
|
||||
assert list(wrap(range(10))[0:-1]) == [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
||||
|
||||
|
||||
def test_immutable_sequence_contains() -> None:
|
||||
"""It should be possible to see if an immutable sequence contains a value."""
|
||||
tester = wrap([1,2,3,4,5])
|
||||
tester = wrap([1, 2, 3, 4, 5])
|
||||
assert 1 in tester
|
||||
assert 11 not in tester
|
||||
|
||||
|
||||
def test_immutable_sequence_index() -> None:
|
||||
tester = wrap([1,2,3,4,5])
|
||||
tester = wrap([1, 2, 3, 4, 5])
|
||||
assert tester.index(1) == 0
|
||||
with pytest.raises(ValueError):
|
||||
_ = tester.index(11)
|
||||
|
||||
|
||||
def test_reverse_immutable_sequence() -> None:
|
||||
assert list(reversed(wrap([1,2]))) == [2,1]
|
||||
assert list(reversed(wrap([1, 2]))) == [2, 1]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import pytest
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from textual._segment_tools import NoCellPositionForIndex
|
||||
from textual.strip import Strip
|
||||
from textual._filter import Monochrome
|
||||
|
||||
@@ -62,9 +64,7 @@ def test_eq():
|
||||
|
||||
|
||||
def test_adjust_cell_length():
|
||||
|
||||
for repeat in range(3):
|
||||
|
||||
assert Strip([]).adjust_cell_length(3) == Strip([Segment(" ")])
|
||||
assert Strip([Segment("f")]).adjust_cell_length(3) == Strip(
|
||||
[Segment("f"), Segment(" ")]
|
||||
@@ -119,9 +119,7 @@ def test_style_links():
|
||||
|
||||
|
||||
def test_crop():
|
||||
|
||||
for repeat in range(3):
|
||||
|
||||
assert Strip([Segment("foo")]).crop(0, 3) == Strip([Segment("foo")])
|
||||
assert Strip([Segment("foo")]).crop(0, 2) == Strip([Segment("fo")])
|
||||
assert Strip([Segment("foo")]).crop(0, 1) == Strip([Segment("f")])
|
||||
@@ -136,10 +134,42 @@ def test_crop():
|
||||
|
||||
|
||||
def test_divide():
|
||||
|
||||
for repeat in range(3):
|
||||
|
||||
assert Strip([Segment("foo")]).divide([1, 2]) == [
|
||||
Strip([Segment("f")]),
|
||||
Strip([Segment("o")]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,cell_position",
|
||||
[
|
||||
(0, 0),
|
||||
(1, 1),
|
||||
(2, 2),
|
||||
(3, 3),
|
||||
(4, 4),
|
||||
(5, 6),
|
||||
(6, 8),
|
||||
(7, 10),
|
||||
(8, 11),
|
||||
(9, 12),
|
||||
(10, 13),
|
||||
(11, 14),
|
||||
],
|
||||
)
|
||||
def test_index_to_cell_position(index, cell_position):
|
||||
strip = Strip([Segment("ab"), Segment("cd日本語ef"), Segment("gh")])
|
||||
assert cell_position == strip.index_to_cell_position(index)
|
||||
|
||||
|
||||
def test_index_cell_position_no_segments():
|
||||
strip = Strip([])
|
||||
with pytest.raises(NoCellPositionForIndex):
|
||||
strip.index_to_cell_position(2)
|
||||
|
||||
|
||||
def test_index_cell_position_index_too_large():
|
||||
strip = Strip([Segment("abcdef"), Segment("ghi")])
|
||||
with pytest.raises(NoCellPositionForIndex):
|
||||
strip.index_to_cell_position(100)
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import pytest
|
||||
from textual.widgets import Tree, TreeNode
|
||||
|
||||
def label_of(node: TreeNode[None]):
|
||||
"""Get the label of a node.
|
||||
|
||||
TODO: This is just a helper function to reduce the number of type
|
||||
errors, which can and will be remove once this code is merged with a
|
||||
version of main that also has the TreeNode.label PR merged.
|
||||
"""
|
||||
return str(node._label)
|
||||
def label_of(node: TreeNode[None]):
|
||||
"""Get the label of a node as a string"""
|
||||
return str(node.label)
|
||||
|
||||
|
||||
def test_tree_node_children() -> None:
|
||||
"""A node's children property should act like an immutable list."""
|
||||
CHILDREN=23
|
||||
CHILDREN = 23
|
||||
tree = Tree[None]("Root")
|
||||
for child in range(CHILDREN):
|
||||
tree.root.add(str(child))
|
||||
assert len(tree.root.children)==CHILDREN
|
||||
assert len(tree.root.children) == CHILDREN
|
||||
for child in range(CHILDREN):
|
||||
assert label_of(tree.root.children[child]) == str(child)
|
||||
assert label_of(tree.root.children[0]) == "0"
|
||||
assert label_of(tree.root.children[-1]) == str(CHILDREN-1)
|
||||
assert [label_of(node) for node in tree.root.children] == [str(n) for n in range(CHILDREN)]
|
||||
assert [label_of(node) for node in tree.root.children[:2]] == [str(n) for n in range(2)]
|
||||
assert label_of(tree.root.children[-1]) == str(CHILDREN - 1)
|
||||
assert [label_of(node) for node in tree.root.children] == [
|
||||
str(n) for n in range(CHILDREN)
|
||||
]
|
||||
assert [label_of(node) for node in tree.root.children[:2]] == [
|
||||
str(n) for n in range(2)
|
||||
]
|
||||
with pytest.raises(TypeError):
|
||||
tree.root.children[0] = tree.root.children[1]
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from textual.widgets import Tree, TreeNode
|
||||
from rich.text import Text
|
||||
|
||||
|
||||
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")
|
||||
@@ -8,6 +9,7 @@ def test_tree_node_label() -> None:
|
||||
node.label = "Chestbuster"
|
||||
assert node.label == Text("Chestbuster")
|
||||
|
||||
|
||||
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")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from textual.widgets import TreeNode, Tree
|
||||
|
||||
|
||||
def test_tree_node_parent() -> None:
|
||||
"""It should be possible to access a TreeNode's parent."""
|
||||
tree = Tree[None]("Anakin")
|
||||
|
||||
Reference in New Issue
Block a user