mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
tests for geometry
This commit is contained in:
@@ -485,7 +485,7 @@ class Spacing(NamedTuple):
|
|||||||
left: int = 0
|
left: int = 0
|
||||||
|
|
||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
return self == (0, 0, 0, 0)
|
return self != (0, 0, 0, 0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self) -> int:
|
def width(self) -> int:
|
||||||
@@ -507,16 +507,6 @@ class Spacing(NamedTuple):
|
|||||||
"""Bottom right space."""
|
"""Bottom right space."""
|
||||||
return (self.right, self.bottom)
|
return (self.right, self.bottom)
|
||||||
|
|
||||||
@property
|
|
||||||
def packed(self) -> str:
|
|
||||||
top, right, bottom, left = self
|
|
||||||
if top == right == bottom == left:
|
|
||||||
return f"{top}"
|
|
||||||
if (top, right) == (bottom, left):
|
|
||||||
return f"{top}, {right}"
|
|
||||||
else:
|
|
||||||
return f"{top}, {right}, {bottom}, {left}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def css(self) -> str:
|
def css(self) -> str:
|
||||||
top, right, bottom, left = self
|
top, right, bottom, left = self
|
||||||
@@ -532,16 +522,17 @@ class Spacing(NamedTuple):
|
|||||||
"""Unpack padding specified in CSS style."""
|
"""Unpack padding specified in CSS style."""
|
||||||
if isinstance(pad, int):
|
if isinstance(pad, int):
|
||||||
return cls(pad, pad, pad, pad)
|
return cls(pad, pad, pad, pad)
|
||||||
if len(pad) == 1:
|
pad_len = len(pad)
|
||||||
|
if pad_len == 1:
|
||||||
_pad = pad[0]
|
_pad = pad[0]
|
||||||
return cls(_pad, _pad, _pad, _pad)
|
return cls(_pad, _pad, _pad, _pad)
|
||||||
if len(pad) == 2:
|
if pad_len == 2:
|
||||||
pad_top, pad_right = cast(Tuple[int, int], pad)
|
pad_top, pad_right = cast(Tuple[int, int], pad)
|
||||||
return cls(pad_top, pad_right, pad_top, pad_right)
|
return cls(pad_top, pad_right, pad_top, pad_right)
|
||||||
if len(pad) == 4:
|
if pad_len == 4:
|
||||||
top, right, bottom, left = cast(Tuple[int, int, int, int], pad)
|
top, right, bottom, left = cast(Tuple[int, int, int, int], pad)
|
||||||
return cls(top, right, bottom, left)
|
return cls(top, right, bottom, left)
|
||||||
raise ValueError(f"1, 2 or 4 integers required for spacing; {len(pad)} given")
|
raise ValueError(f"1, 2 or 4 integers required for spacing; {pad_len} given")
|
||||||
|
|
||||||
def __add__(self, other: object) -> Spacing:
|
def __add__(self, other: object) -> Spacing:
|
||||||
if isinstance(other, tuple):
|
if isinstance(other, tuple):
|
||||||
|
|||||||
@@ -27,12 +27,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class NoWidget(Exception):
|
class NoWidget(Exception):
|
||||||
pass
|
"""Raised when there is no widget at the requested coordinate."""
|
||||||
|
|
||||||
|
|
||||||
class OrderedRegion(NamedTuple):
|
|
||||||
region: Region
|
|
||||||
order: tuple[int, int]
|
|
||||||
|
|
||||||
|
|
||||||
class ReflowResult(NamedTuple):
|
class ReflowResult(NamedTuple):
|
||||||
@@ -44,9 +39,10 @@ class ReflowResult(NamedTuple):
|
|||||||
|
|
||||||
|
|
||||||
class WidgetPlacement(NamedTuple):
|
class WidgetPlacement(NamedTuple):
|
||||||
|
"""The position, size, and relative order of a widget within its parent."""
|
||||||
|
|
||||||
region: Region
|
region: Region
|
||||||
widget: Widget | None = None
|
widget: Widget | None = None # A widget of None means empty space
|
||||||
order: int = 0
|
order: int = 0
|
||||||
|
|
||||||
def apply_margin(self) -> "WidgetPlacement":
|
def apply_margin(self) -> "WidgetPlacement":
|
||||||
@@ -61,7 +57,7 @@ class WidgetPlacement(NamedTuple):
|
|||||||
region, widget, order = self
|
region, widget, order = self
|
||||||
if widget is not None:
|
if widget is not None:
|
||||||
styles = widget.styles
|
styles = widget.styles
|
||||||
if any(styles.margin):
|
if styles.margin:
|
||||||
return WidgetPlacement(
|
return WidgetPlacement(
|
||||||
region=region.shrink(styles.margin),
|
region=region.shrink(styles.margin),
|
||||||
widget=widget,
|
widget=widget,
|
||||||
@@ -72,6 +68,8 @@ class WidgetPlacement(NamedTuple):
|
|||||||
|
|
||||||
@rich.repr.auto
|
@rich.repr.auto
|
||||||
class LayoutUpdate:
|
class LayoutUpdate:
|
||||||
|
"""A renderable containing the result of a render for a given region."""
|
||||||
|
|
||||||
def __init__(self, lines: Lines, region: Region) -> None:
|
def __init__(self, lines: Lines, region: Region) -> None:
|
||||||
self.lines = lines
|
self.lines = lines
|
||||||
self.region = region
|
self.region = region
|
||||||
@@ -79,12 +77,12 @@ class LayoutUpdate:
|
|||||||
def __rich_console__(
|
def __rich_console__(
|
||||||
self, console: Console, options: ConsoleOptions
|
self, console: Console, options: ConsoleOptions
|
||||||
) -> RenderResult:
|
) -> RenderResult:
|
||||||
yield Control.home().segment
|
yield Control.home()
|
||||||
x = self.region.x
|
x = self.region.x
|
||||||
new_line = Segment.line()
|
new_line = Segment.line()
|
||||||
move_to = Control.move_to
|
move_to = Control.move_to
|
||||||
for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)):
|
for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)):
|
||||||
yield move_to(x, y).segment
|
yield move_to(x, y)
|
||||||
yield from line
|
yield from line
|
||||||
if not last:
|
if not last:
|
||||||
yield new_line
|
yield new_line
|
||||||
@@ -187,7 +185,8 @@ class Layout(ABC):
|
|||||||
view.mount(*widgets)
|
view.mount(*widgets)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def map(self) -> LayoutMap | None:
|
def map(self) -> LayoutMap:
|
||||||
|
assert self._layout_map is not None
|
||||||
return self._layout_map
|
return self._layout_map
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[tuple[Widget, Region, Region]]:
|
def __iter__(self) -> Iterator[tuple[Widget, Region, Region]]:
|
||||||
|
|||||||
@@ -99,44 +99,6 @@ class Widget(DOMNode):
|
|||||||
yield "hover"
|
yield "hover"
|
||||||
# TODO: focus
|
# TODO: focus
|
||||||
|
|
||||||
def get_child_by_id(self, id: str) -> Widget:
|
|
||||||
"""Get a child with a given id.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id (str): A Widget id.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.MissingWidget: If the widget was not found.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Widget: A child widget.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for widget in self.children:
|
|
||||||
if widget.id == id:
|
|
||||||
return cast(Widget, widget)
|
|
||||||
raise errors.MissingWidget(f"Widget with id=={id!r} was not found in {self}")
|
|
||||||
|
|
||||||
def get_child_by_name(self, name: str) -> Widget:
|
|
||||||
"""Get a child widget with a given name.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name (str): A name. Defaults to None.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.MissingWidget: If no Widget is found.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Widget: A Widget with the given name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for widget in self.children:
|
|
||||||
if widget.name == name:
|
|
||||||
return cast(Widget, widget)
|
|
||||||
raise errors.MissingWidget(
|
|
||||||
f"Widget with name=={name!r} was not found in {self}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def watch(self, attribute_name, callback: Callable[[Any], Awaitable[None]]) -> None:
|
def watch(self, attribute_name, callback: Callable[[Any], Awaitable[None]]) -> None:
|
||||||
watch(self, attribute_name, callback)
|
watch(self, attribute_name, callback)
|
||||||
|
|
||||||
|
|||||||
@@ -63,31 +63,52 @@ def test_clamp():
|
|||||||
assert clamp(5, 10, 0) == 5
|
assert clamp(5, 10, 0) == 5
|
||||||
|
|
||||||
|
|
||||||
def test_point_is_origin():
|
def test_offset_bool():
|
||||||
|
assert Offset(1, 0)
|
||||||
|
assert Offset(0, 1)
|
||||||
|
assert Offset(0, -1)
|
||||||
|
assert not Offset(0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_offset_is_origin():
|
||||||
assert Offset(0, 0).is_origin
|
assert Offset(0, 0).is_origin
|
||||||
assert not Offset(1, 0).is_origin
|
assert not Offset(1, 0).is_origin
|
||||||
|
|
||||||
|
|
||||||
def test_point_add():
|
def test_offset_add():
|
||||||
assert Offset(1, 1) + Offset(2, 2) == Offset(3, 3)
|
assert Offset(1, 1) + Offset(2, 2) == Offset(3, 3)
|
||||||
assert Offset(1, 2) + Offset(3, 4) == Offset(4, 6)
|
assert Offset(1, 2) + Offset(3, 4) == Offset(4, 6)
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
Offset(1, 1) + "foo"
|
Offset(1, 1) + "foo"
|
||||||
|
|
||||||
|
|
||||||
def test_point_sub():
|
def test_offset_sub():
|
||||||
assert Offset(1, 1) - Offset(2, 2) == Offset(-1, -1)
|
assert Offset(1, 1) - Offset(2, 2) == Offset(-1, -1)
|
||||||
assert Offset(3, 4) - Offset(2, 1) == Offset(1, 3)
|
assert Offset(3, 4) - Offset(2, 1) == Offset(1, 3)
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
Offset(1, 1) - "foo"
|
Offset(1, 1) - "foo"
|
||||||
|
|
||||||
|
|
||||||
def test_point_blend():
|
def test_offset_mul():
|
||||||
|
assert Offset(2, 1) * 2 == Offset(4, 2)
|
||||||
|
assert Offset(2, 1) * -2 == Offset(-4, -2)
|
||||||
|
assert Offset(2, 1) * 0 == Offset(0, 0)
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
Offset(10, 20) * "foo"
|
||||||
|
|
||||||
|
|
||||||
|
def test_offset_blend():
|
||||||
assert Offset(1, 2).blend(Offset(3, 4), 0) == Offset(1, 2)
|
assert Offset(1, 2).blend(Offset(3, 4), 0) == Offset(1, 2)
|
||||||
assert Offset(1, 2).blend(Offset(3, 4), 1) == Offset(3, 4)
|
assert Offset(1, 2).blend(Offset(3, 4), 1) == Offset(3, 4)
|
||||||
assert Offset(1, 2).blend(Offset(3, 4), 0.5) == Offset(2, 3)
|
assert Offset(1, 2).blend(Offset(3, 4), 0.5) == Offset(2, 3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_offset_get_distance_to():
|
||||||
|
assert Offset(20, 30).get_distance_to(Offset(20, 30)) == 0
|
||||||
|
assert Offset(0, 0).get_distance_to(Offset(1, 0)) == 1.0
|
||||||
|
assert Offset(2, 1).get_distance_to(Offset(5, 5)) == 5.0
|
||||||
|
|
||||||
|
|
||||||
def test_region_null():
|
def test_region_null():
|
||||||
assert Region() == Region(0, 0, 0, 0)
|
assert Region() == Region(0, 0, 0, 0)
|
||||||
assert not Region()
|
assert not Region()
|
||||||
@@ -196,10 +217,14 @@ def test_region_union():
|
|||||||
|
|
||||||
def test_size_add():
|
def test_size_add():
|
||||||
assert Size(5, 10) + Size(2, 3) == Size(7, 13)
|
assert Size(5, 10) + Size(2, 3) == Size(7, 13)
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
Size(1, 2) + "foo"
|
||||||
|
|
||||||
|
|
||||||
def test_size_sub():
|
def test_size_sub():
|
||||||
assert Size(5, 10) - Size(2, 3) == Size(3, 7)
|
assert Size(5, 10) - Size(2, 3) == Size(3, 7)
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
Size(1, 2) - "foo"
|
||||||
|
|
||||||
|
|
||||||
def test_region_x_extents():
|
def test_region_x_extents():
|
||||||
@@ -224,3 +249,61 @@ def test_region_x_range():
|
|||||||
|
|
||||||
def test_region_y_range():
|
def test_region_y_range():
|
||||||
assert Region(5, 10, 20, 30).y_range == range(10, 40)
|
assert Region(5, 10, 20, 30).y_range == range(10, 40)
|
||||||
|
|
||||||
|
|
||||||
|
def test_region_expand():
|
||||||
|
assert Region(50, 10, 10, 5).expand((2, 3)) == Region(48, 7, 14, 11)
|
||||||
|
|
||||||
|
|
||||||
|
def test_spacing_bool():
|
||||||
|
assert Spacing(1, 0, 0, 0)
|
||||||
|
assert Spacing(0, 1, 0, 0)
|
||||||
|
assert Spacing(0, 1, 0, 0)
|
||||||
|
assert Spacing(0, 0, 1, 0)
|
||||||
|
assert Spacing(0, 0, 0, 1)
|
||||||
|
assert not Spacing(0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_spacing_width():
|
||||||
|
assert Spacing(2, 3, 4, 5).width == 8
|
||||||
|
|
||||||
|
|
||||||
|
def test_spacing_height():
|
||||||
|
assert Spacing(2, 3, 4, 5).height == 6
|
||||||
|
|
||||||
|
|
||||||
|
def test_spacing_top_left():
|
||||||
|
assert Spacing(2, 3, 4, 5).top_left == (5, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_spacing_bottom_right():
|
||||||
|
assert Spacing(2, 3, 4, 5).bottom_right == (3, 4)
|
||||||
|
|
||||||
|
|
||||||
|
def test_spacing_css():
|
||||||
|
assert Spacing(1, 1, 1, 1).css == "1"
|
||||||
|
assert Spacing(1, 2, 1, 2).css == "1 2"
|
||||||
|
assert Spacing(1, 2, 3, 4).css == "1 2 3 4"
|
||||||
|
|
||||||
|
|
||||||
|
def test_spacing_unpack():
|
||||||
|
assert Spacing.unpack(1) == Spacing(1, 1, 1, 1)
|
||||||
|
assert Spacing.unpack((1,)) == Spacing(1, 1, 1, 1)
|
||||||
|
assert Spacing.unpack((1, 2)) == Spacing(1, 2, 1, 2)
|
||||||
|
assert Spacing.unpack((1, 2, 3, 4)) == Spacing(1, 2, 3, 4)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
assert Spacing.unpack(()) == Spacing(1, 2, 1, 2)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
assert Spacing.unpack((1, 2, 3)) == Spacing(1, 2, 1, 2)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
assert Spacing.unpack((1, 2, 3, 4, 5)) == Spacing(1, 2, 1, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_spacing_add():
|
||||||
|
assert Spacing(1, 2, 3, 4) + Spacing(5, 6, 7, 8) == Spacing(6, 8, 10, 12)
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
Spacing(1, 2, 3, 4) + "foo"
|
||||||
|
|||||||
Reference in New Issue
Block a user