mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'main' into directory-tree-work-in-worker
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
|
||||||
|
## Unrealeased
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- App `title` and `sub_title` attributes can be set to any type https://github.com/Textualize/textual/issues/2521
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `ZeroDivisionError` in `resolve_fraction_unit` https://github.com/Textualize/textual/issues/2502
|
||||||
|
- Fixed `TreeNode.expand` and `TreeNode.expand_all` not posting a `Tree.NodeExpanded` message https://github.com/Textualize/textual/issues/2535
|
||||||
|
- Fixed `TreeNode.collapse` and `TreeNode.collapse_all` not posting a `Tree.NodeCollapsed` message https://github.com/Textualize/textual/issues/2535
|
||||||
|
- Fixed `TreeNode.toggle` and `TreeNode.toggle_all` not posting a `Tree.NodeExpanded` or `Tree.NodeCollapsed` message https://github.com/Textualize/textual/issues/2535
|
||||||
|
|
||||||
## [0.24.1] - 2023-05-08
|
## [0.24.1] - 2023-05-08
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ def resolve_fraction_unit(
|
|||||||
resolved: list[Fraction | None] = [None] * len(resolve)
|
resolved: list[Fraction | None] = [None] * len(resolve)
|
||||||
remaining_fraction = Fraction(sum(scalar.value for scalar, _, _ in resolve))
|
remaining_fraction = Fraction(sum(scalar.value for scalar, _, _ in resolve))
|
||||||
|
|
||||||
while True:
|
while remaining_fraction > 0:
|
||||||
remaining_space_changed = False
|
remaining_space_changed = False
|
||||||
resolve_fraction = Fraction(remaining_space, remaining_fraction)
|
resolve_fraction = Fraction(remaining_space, remaining_fraction)
|
||||||
for index, (scalar, min_value, max_value) in enumerate(resolve):
|
for index, (scalar, min_value, max_value) in enumerate(resolve):
|
||||||
@@ -166,7 +166,7 @@ def resolve_fraction_unit(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
Fraction(remaining_space, remaining_fraction)
|
Fraction(remaining_space, remaining_fraction)
|
||||||
if remaining_space
|
if remaining_space > 0
|
||||||
else Fraction(1)
|
else Fraction(1)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -316,6 +316,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
the name of the app if it doesn't.
|
the name of the app if it doesn't.
|
||||||
|
|
||||||
Assign a new value to this attribute to change the title.
|
Assign a new value to this attribute to change the title.
|
||||||
|
The new value is always converted to string.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.sub_title = self.SUB_TITLE if self.SUB_TITLE is not None else ""
|
self.sub_title = self.SUB_TITLE if self.SUB_TITLE is not None else ""
|
||||||
@@ -328,6 +329,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
the file being worker on.
|
the file being worker on.
|
||||||
|
|
||||||
Assign a new value to this attribute to change the sub-title.
|
Assign a new value to this attribute to change the sub-title.
|
||||||
|
The new value is always converted to string.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._logger = Logger(self._log)
|
self._logger = Logger(self._log)
|
||||||
@@ -406,6 +408,14 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
self.set_class(self.dark, "-dark-mode")
|
self.set_class(self.dark, "-dark-mode")
|
||||||
self.set_class(not self.dark, "-light-mode")
|
self.set_class(not self.dark, "-light-mode")
|
||||||
|
|
||||||
|
def validate_title(self, title: Any) -> str:
|
||||||
|
"""Make sure the title is set to a string."""
|
||||||
|
return str(title)
|
||||||
|
|
||||||
|
def validate_sub_title(self, sub_title: Any) -> str:
|
||||||
|
"""Make sure the sub-title is set to a string."""
|
||||||
|
return str(sub_title)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def workers(self) -> WorkerManager:
|
def workers(self) -> WorkerManager:
|
||||||
"""The [worker](guide/workers/) manager.
|
"""The [worker](guide/workers/) manager.
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
"""
|
"""
|
||||||
self._expanded = True
|
self._expanded = True
|
||||||
self._updates += 1
|
self._updates += 1
|
||||||
|
self._tree.post_message(Tree.NodeExpanded(self._tree, self))
|
||||||
if expand_all:
|
if expand_all:
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
child._expand(expand_all)
|
child._expand(expand_all)
|
||||||
@@ -239,6 +240,7 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
"""
|
"""
|
||||||
self._expanded = False
|
self._expanded = False
|
||||||
self._updates += 1
|
self._updates += 1
|
||||||
|
self._tree.post_message(Tree.NodeCollapsed(self._tree, self))
|
||||||
if collapse_all:
|
if collapse_all:
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
child._collapse(collapse_all)
|
child._collapse(collapse_all)
|
||||||
@@ -1157,10 +1159,8 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
return
|
return
|
||||||
if node.is_expanded:
|
if node.is_expanded:
|
||||||
node.collapse()
|
node.collapse()
|
||||||
self.post_message(self.NodeCollapsed(self, node))
|
|
||||||
else:
|
else:
|
||||||
node.expand()
|
node.expand()
|
||||||
self.post_message(self.NodeExpanded(self, node))
|
|
||||||
|
|
||||||
async def _on_click(self, event: events.Click) -> None:
|
async def _on_click(self, event: events.Click) -> None:
|
||||||
meta = event.style.meta
|
meta = event.style.meta
|
||||||
|
|||||||
@@ -36,3 +36,33 @@ async def test_hover_update_styles():
|
|||||||
# We've hovered, so ensure the pseudoclass is present and background changed
|
# We've hovered, so ensure the pseudoclass is present and background changed
|
||||||
assert button.pseudo_classes == {"enabled", "hover"}
|
assert button.pseudo_classes == {"enabled", "hover"}
|
||||||
assert button.styles.background != initial_background
|
assert button.styles.background != initial_background
|
||||||
|
|
||||||
|
|
||||||
|
def test_setting_title():
|
||||||
|
app = MyApp()
|
||||||
|
app.title = None
|
||||||
|
assert app.title == "None"
|
||||||
|
|
||||||
|
app.title = ""
|
||||||
|
assert app.title == ""
|
||||||
|
|
||||||
|
app.title = 0.125
|
||||||
|
assert app.title == "0.125"
|
||||||
|
|
||||||
|
app.title = [True, False, 2]
|
||||||
|
assert app.title == "[True, False, 2]"
|
||||||
|
|
||||||
|
|
||||||
|
def test_setting_sub_title():
|
||||||
|
app = MyApp()
|
||||||
|
app.sub_title = None
|
||||||
|
assert app.sub_title == "None"
|
||||||
|
|
||||||
|
app.sub_title = ""
|
||||||
|
assert app.sub_title == ""
|
||||||
|
|
||||||
|
app.sub_title = 0.125
|
||||||
|
assert app.sub_title == "0.125"
|
||||||
|
|
||||||
|
app.sub_title = [True, False, 2]
|
||||||
|
assert app.sub_title == "[True, False, 2]"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -122,3 +123,22 @@ def test_resolve_fraction_unit():
|
|||||||
Fraction(32),
|
Fraction(32),
|
||||||
resolve_dimension="width",
|
resolve_dimension="width",
|
||||||
) == Fraction(2)
|
) == Fraction(2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_issue_2502():
|
||||||
|
"""Test https://github.com/Textualize/textual/issues/2502"""
|
||||||
|
|
||||||
|
widget = Widget()
|
||||||
|
widget.styles.width = "1fr"
|
||||||
|
widget.styles.min_width = 11
|
||||||
|
|
||||||
|
assert isinstance(
|
||||||
|
resolve_fraction_unit(
|
||||||
|
(widget.styles,),
|
||||||
|
Size(80, 24),
|
||||||
|
Size(80, 24),
|
||||||
|
Fraction(10),
|
||||||
|
resolve_dimension="width",
|
||||||
|
),
|
||||||
|
Fraction,
|
||||||
|
)
|
||||||
|
|||||||
@@ -78,6 +78,20 @@ async def test_tree_node_expanded_message() -> None:
|
|||||||
assert pilot.app.messages == [("NodeExpanded", "test-tree")]
|
assert pilot.app.messages == [("NodeExpanded", "test-tree")]
|
||||||
|
|
||||||
|
|
||||||
|
async def tree_node_expanded_by_code_message() -> None:
|
||||||
|
"""Expanding a node via the API should result in an expanded message being posted."""
|
||||||
|
async with TreeApp().run_test() as pilot:
|
||||||
|
pilot.app.query_one(Tree).root.children[0].expand()
|
||||||
|
assert pilot.app.messages == [("NodeExpanded", "test-tree")]
|
||||||
|
|
||||||
|
|
||||||
|
async def tree_node_all_expanded_by_code_message() -> None:
|
||||||
|
"""Expanding all nodes via the API should result in expanded messages being posted."""
|
||||||
|
async with TreeApp().run_test() as pilot:
|
||||||
|
pilot.app.query_one(Tree).root.children[0].expand_all()
|
||||||
|
assert pilot.app.messages == [("NodeExpanded", "test-tree")]
|
||||||
|
|
||||||
|
|
||||||
async def test_tree_node_collapsed_message() -> None:
|
async def test_tree_node_collapsed_message() -> None:
|
||||||
"""Collapsing a node should result in a collapsed message being emitted."""
|
"""Collapsing a node should result in a collapsed message being emitted."""
|
||||||
async with TreeApp().run_test() as pilot:
|
async with TreeApp().run_test() as pilot:
|
||||||
@@ -89,6 +103,46 @@ async def test_tree_node_collapsed_message() -> None:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def tree_node_collapsed_by_code_message() -> None:
|
||||||
|
"""Collapsing a node via the API should result in a collapsed message being posted."""
|
||||||
|
async with TreeApp().run_test() as pilot:
|
||||||
|
pilot.app.query_one(Tree).root.children[0].expand().collapse()
|
||||||
|
assert pilot.app.messages == [
|
||||||
|
("NodeExpanded", "test-tree"),
|
||||||
|
("NodeCollapsed", "test-tree"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def tree_node_all_collapsed_by_code_message() -> None:
|
||||||
|
"""Collapsing all nodes via the API should result in collapsed messages being posted."""
|
||||||
|
async with TreeApp().run_test() as pilot:
|
||||||
|
pilot.app.query_one(Tree).root.children[0].expand_all().collapse_all()
|
||||||
|
assert pilot.app.messages == [
|
||||||
|
("NodeExpanded", "test-tree"),
|
||||||
|
("NodeCollapsed", "test-tree"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def tree_node_toggled_by_code_message() -> None:
|
||||||
|
"""Toggling a node twice via the API should result in expanded and collapsed messages."""
|
||||||
|
async with TreeApp().run_test() as pilot:
|
||||||
|
pilot.app.query_one(Tree).root.children[0].toggle().toggle()
|
||||||
|
assert pilot.app.messages == [
|
||||||
|
("NodeExpanded", "test-tree"),
|
||||||
|
("NodeCollapsed", "test-tree"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def tree_node_all_toggled_by_code_message() -> None:
|
||||||
|
"""Toggling all nodes twice via the API should result in expanded and collapsed messages."""
|
||||||
|
async with TreeApp().run_test() as pilot:
|
||||||
|
pilot.app.query_one(Tree).root.children[0].toggle_all().toggle_all()
|
||||||
|
assert pilot.app.messages == [
|
||||||
|
("NodeExpanded", "test-tree"),
|
||||||
|
("NodeCollapsed", "test-tree"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def test_tree_node_highlighted_message() -> None:
|
async def test_tree_node_highlighted_message() -> None:
|
||||||
"""Highlighting a node should result in a highlighted message being emitted."""
|
"""Highlighting a node should result in a highlighted message being emitted."""
|
||||||
async with TreeApp().run_test() as pilot:
|
async with TreeApp().run_test() as pilot:
|
||||||
|
|||||||
Reference in New Issue
Block a user