tests for geometry

This commit is contained in:
Will McGugan
2022-02-23 10:56:06 +00:00
parent cc945b2ca7
commit 3ee5eb8a2b
4 changed files with 103 additions and 68 deletions

View File

@@ -485,7 +485,7 @@ class Spacing(NamedTuple):
left: int = 0
def __bool__(self) -> bool:
return self == (0, 0, 0, 0)
return self != (0, 0, 0, 0)
@property
def width(self) -> int:
@@ -507,16 +507,6 @@ class Spacing(NamedTuple):
"""Bottom right space."""
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
def css(self) -> str:
top, right, bottom, left = self
@@ -532,16 +522,17 @@ class Spacing(NamedTuple):
"""Unpack padding specified in CSS style."""
if isinstance(pad, int):
return cls(pad, pad, pad, pad)
if len(pad) == 1:
pad_len = len(pad)
if pad_len == 1:
_pad = pad[0]
return cls(_pad, _pad, _pad, _pad)
if len(pad) == 2:
if pad_len == 2:
pad_top, pad_right = cast(Tuple[int, int], pad)
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)
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:
if isinstance(other, tuple):

View File

@@ -27,12 +27,7 @@ if TYPE_CHECKING:
class NoWidget(Exception):
pass
class OrderedRegion(NamedTuple):
region: Region
order: tuple[int, int]
"""Raised when there is no widget at the requested coordinate."""
class ReflowResult(NamedTuple):
@@ -44,9 +39,10 @@ class ReflowResult(NamedTuple):
class WidgetPlacement(NamedTuple):
"""The position, size, and relative order of a widget within its parent."""
region: Region
widget: Widget | None = None
widget: Widget | None = None # A widget of None means empty space
order: int = 0
def apply_margin(self) -> "WidgetPlacement":
@@ -61,7 +57,7 @@ class WidgetPlacement(NamedTuple):
region, widget, order = self
if widget is not None:
styles = widget.styles
if any(styles.margin):
if styles.margin:
return WidgetPlacement(
region=region.shrink(styles.margin),
widget=widget,
@@ -72,6 +68,8 @@ class WidgetPlacement(NamedTuple):
@rich.repr.auto
class LayoutUpdate:
"""A renderable containing the result of a render for a given region."""
def __init__(self, lines: Lines, region: Region) -> None:
self.lines = lines
self.region = region
@@ -79,12 +77,12 @@ class LayoutUpdate:
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
yield Control.home().segment
yield Control.home()
x = self.region.x
new_line = Segment.line()
move_to = Control.move_to
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
if not last:
yield new_line
@@ -187,7 +185,8 @@ class Layout(ABC):
view.mount(*widgets)
@property
def map(self) -> LayoutMap | None:
def map(self) -> LayoutMap:
assert self._layout_map is not None
return self._layout_map
def __iter__(self) -> Iterator[tuple[Widget, Region, Region]]:

View File

@@ -99,44 +99,6 @@ class Widget(DOMNode):
yield "hover"
# 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:
watch(self, attribute_name, callback)

View File

@@ -63,31 +63,52 @@ def test_clamp():
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 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, 2) + Offset(3, 4) == Offset(4, 6)
with pytest.raises(TypeError):
Offset(1, 1) + "foo"
def test_point_sub():
def test_offset_sub():
assert Offset(1, 1) - Offset(2, 2) == Offset(-1, -1)
assert Offset(3, 4) - Offset(2, 1) == Offset(1, 3)
with pytest.raises(TypeError):
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), 1) == Offset(3, 4)
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():
assert Region() == Region(0, 0, 0, 0)
assert not Region()
@@ -196,10 +217,14 @@ def test_region_union():
def test_size_add():
assert Size(5, 10) + Size(2, 3) == Size(7, 13)
with pytest.raises(TypeError):
Size(1, 2) + "foo"
def test_size_sub():
assert Size(5, 10) - Size(2, 3) == Size(3, 7)
with pytest.raises(TypeError):
Size(1, 2) - "foo"
def test_region_x_extents():
@@ -224,3 +249,61 @@ def test_region_x_range():
def test_region_y_range():
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"