mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
583 lines
16 KiB
Python
583 lines
16 KiB
Python
from typing import Literal
|
|
|
|
import pytest
|
|
|
|
from textual.geometry import Offset, Region, Size, Spacing, clamp
|
|
|
|
|
|
def test_dimensions_region():
|
|
assert Size(30, 40).region == Region(0, 0, 30, 40)
|
|
|
|
|
|
def test_dimensions_contains():
|
|
assert Size(10, 10).contains(5, 5)
|
|
assert Size(10, 10).contains(9, 9)
|
|
assert Size(10, 10).contains(0, 0)
|
|
assert not Size(10, 10).contains(10, 9)
|
|
assert not Size(10, 10).contains(9, 10)
|
|
assert not Size(10, 10).contains(-1, 0)
|
|
assert not Size(10, 10).contains(0, -1)
|
|
|
|
|
|
def test_dimensions_contains_point():
|
|
assert Size(10, 10).contains_point(Offset(5, 5))
|
|
assert Size(10, 10).contains_point(Offset(9, 9))
|
|
assert Size(10, 10).contains_point(Offset(0, 0))
|
|
assert not Size(10, 10).contains_point(Offset(10, 9))
|
|
assert not Size(10, 10).contains_point(Offset(9, 10))
|
|
assert not Size(10, 10).contains_point(Offset(-1, 0))
|
|
assert not Size(10, 10).contains_point(Offset(0, -1))
|
|
|
|
|
|
def test_dimensions_contains_special():
|
|
with pytest.raises(TypeError):
|
|
(1, 2, 3) in Size(10, 10)
|
|
|
|
assert (5, 5) in Size(10, 10)
|
|
assert (9, 9) in Size(10, 10)
|
|
assert (0, 0) in Size(10, 10)
|
|
assert (10, 9) not in Size(10, 10)
|
|
assert (9, 10) not in Size(10, 10)
|
|
assert (-1, 0) not in Size(10, 10)
|
|
assert (0, -1) not in Size(10, 10)
|
|
|
|
|
|
def test_dimensions_bool():
|
|
assert Size(1, 1)
|
|
assert Size(3, 4)
|
|
assert not Size(0, 1)
|
|
assert not Size(1, 0)
|
|
|
|
|
|
def test_dimensions_area():
|
|
assert Size(0, 0).area == 0
|
|
assert Size(1, 0).area == 0
|
|
assert Size(1, 1).area == 1
|
|
assert Size(4, 5).area == 20
|
|
|
|
|
|
def test_clamp():
|
|
assert clamp(5, 0, 10) == 5
|
|
assert clamp(-1, 0, 10) == 0
|
|
assert clamp(11, 0, 10) == 10
|
|
assert clamp(0, 0, 10) == 0
|
|
assert clamp(10, 0, 10) == 10
|
|
assert clamp(5, 10, 0) == 5
|
|
|
|
# range in reverse order
|
|
assert clamp(5, 10, 0) == 5
|
|
assert clamp(-1, 10, 0) == 0
|
|
assert clamp(11, 10, 0) == 10
|
|
assert clamp(0, 10, 0) == 0
|
|
assert clamp(10, 10, 0) == 10
|
|
assert clamp(5, 0, 10) == 5
|
|
|
|
|
|
def test_offset_bool():
|
|
assert Offset(1, 0)
|
|
assert Offset(0, 1)
|
|
assert Offset(0, -1)
|
|
assert not Offset(0, 0)
|
|
|
|
|
|
def test_offset_transpose():
|
|
assert Offset(1, 2).transpose == (2, 1)
|
|
assert Offset(5, 10).transpose == (10, 5)
|
|
|
|
|
|
def test_offset_is_origin():
|
|
assert Offset(0, 0).is_origin
|
|
assert not Offset(1, 0).is_origin
|
|
|
|
|
|
def test_clamped():
|
|
assert Offset(-10, 0).clamped == Offset(0, 0)
|
|
assert Offset(-10, -5).clamped == Offset(0, 0)
|
|
assert Offset(5, -5).clamped == Offset(5, 0)
|
|
assert Offset(5, 10).clamped == Offset(5, 10)
|
|
|
|
|
|
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_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_offset_neg():
|
|
assert Offset(0, 0) == Offset(0, 0)
|
|
assert -Offset(2, -3) == Offset(-2, 3)
|
|
|
|
|
|
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()
|
|
|
|
|
|
def test_region_from_union():
|
|
with pytest.raises(ValueError):
|
|
Region.from_union([])
|
|
regions = [
|
|
Region(10, 20, 30, 40),
|
|
Region(15, 25, 5, 5),
|
|
Region(30, 25, 20, 10),
|
|
]
|
|
assert Region.from_union(regions) == Region(10, 20, 40, 40)
|
|
|
|
|
|
def test_region_from_offset():
|
|
assert Region.from_offset(Offset(3, 4), (5, 6)) == Region(3, 4, 5, 6)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"window,region,scroll",
|
|
[
|
|
(Region(0, 0, 200, 100), Region(0, 0, 200, 100), Offset(0, 0)),
|
|
(Region(0, 0, 200, 100), Region(0, -100, 10, 10), Offset(0, -100)),
|
|
(Region(10, 15, 20, 10), Region(0, 0, 50, 50), Offset(-10, -15)),
|
|
],
|
|
)
|
|
def test_get_scroll_to_visible(window, region, scroll):
|
|
assert Region.get_scroll_to_visible(window, region) == scroll
|
|
assert region.overlaps(window + scroll)
|
|
|
|
|
|
def test_region_area():
|
|
assert Region(3, 4, 0, 0).area == 0
|
|
assert Region(3, 4, 5, 6).area == 30
|
|
|
|
|
|
def test_region_size():
|
|
assert isinstance(Region(3, 4, 5, 6).size, Size)
|
|
assert Region(3, 4, 5, 6).size == Size(5, 6)
|
|
|
|
|
|
def test_region_origin():
|
|
assert Region(1, 2, 3, 4).offset == Offset(1, 2)
|
|
|
|
|
|
def test_region_bottom_left():
|
|
assert Region(1, 2, 3, 4).bottom_left == Offset(1, 6)
|
|
|
|
|
|
def test_region_top_right():
|
|
assert Region(1, 2, 3, 4).top_right == Offset(4, 2)
|
|
|
|
|
|
def test_region_bottom_right():
|
|
assert Region(1, 2, 3, 4).bottom_right == Offset(4, 6)
|
|
assert Region(1, 2, 3, 4).bottom_right_inclusive == Offset(3, 5)
|
|
|
|
|
|
def test_region_add():
|
|
assert Region(1, 2, 3, 4) + (10, 20) == Region(11, 22, 3, 4)
|
|
with pytest.raises(TypeError):
|
|
Region(1, 2, 3, 4) + "foo"
|
|
|
|
|
|
def test_region_sub():
|
|
assert Region(11, 22, 3, 4) - (10, 20) == Region(1, 2, 3, 4)
|
|
with pytest.raises(TypeError):
|
|
Region(1, 2, 3, 4) - "foo"
|
|
|
|
|
|
def test_region_at_offset():
|
|
assert Region(10, 10, 30, 40).at_offset((0, 0)) == Region(0, 0, 30, 40)
|
|
assert Region(10, 10, 30, 40).at_offset((-15, 30)) == Region(-15, 30, 30, 40)
|
|
|
|
|
|
def test_crop_size():
|
|
assert Region(10, 20, 100, 200).crop_size((50, 40)) == Region(10, 20, 50, 40)
|
|
assert Region(10, 20, 100, 200).crop_size((500, 40)) == Region(10, 20, 100, 40)
|
|
|
|
|
|
def test_region_overlaps():
|
|
assert Region(10, 10, 30, 20).overlaps(Region(0, 0, 20, 20))
|
|
assert not Region(10, 10, 5, 5).overlaps(Region(15, 15, 20, 20))
|
|
|
|
assert not Region(10, 10, 5, 5).overlaps(Region(0, 0, 50, 10))
|
|
assert Region(10, 10, 5, 5).overlaps(Region(0, 0, 50, 11))
|
|
assert not Region(10, 10, 5, 5).overlaps(Region(0, 15, 50, 10))
|
|
assert Region(10, 10, 5, 5).overlaps(Region(0, 14, 50, 10))
|
|
|
|
|
|
def test_region_contains():
|
|
assert Region(10, 10, 20, 30).contains(10, 10)
|
|
assert Region(10, 10, 20, 30).contains(29, 39)
|
|
assert not Region(10, 10, 20, 30).contains(30, 40)
|
|
|
|
|
|
def test_region_contains_point():
|
|
assert Region(10, 10, 20, 30).contains_point((10, 10))
|
|
assert Region(10, 10, 20, 30).contains_point((29, 39))
|
|
assert not Region(10, 10, 20, 30).contains_point((30, 40))
|
|
with pytest.raises(TypeError):
|
|
Region(10, 10, 20, 30).contains_point((1, 2, 3))
|
|
|
|
|
|
def test_region_contains_region():
|
|
assert Region(10, 10, 20, 30).contains_region(Region(10, 10, 5, 5))
|
|
assert not Region(10, 10, 20, 30).contains_region(Region(10, 9, 5, 5))
|
|
assert not Region(10, 10, 20, 30).contains_region(Region(9, 10, 5, 5))
|
|
assert Region(10, 10, 20, 30).contains_region(Region(10, 10, 20, 30))
|
|
assert not Region(10, 10, 20, 30).contains_region(Region(10, 10, 21, 30))
|
|
assert not Region(10, 10, 20, 30).contains_region(Region(10, 10, 20, 31))
|
|
|
|
|
|
def test_region_translate():
|
|
assert Region(1, 2, 3, 4).translate((10, 20)) == Region(11, 22, 3, 4)
|
|
assert Region(1, 2, 3, 4).translate((0, 20)) == Region(1, 22, 3, 4)
|
|
|
|
|
|
def test_region_contains_special():
|
|
assert (10, 10) in Region(10, 10, 20, 30)
|
|
assert (9, 10) not in Region(10, 10, 20, 30)
|
|
assert Region(10, 10, 5, 5) in Region(10, 10, 20, 30)
|
|
assert Region(5, 5, 5, 5) not in Region(10, 10, 20, 30)
|
|
assert "foo" not in Region(0, 0, 10, 10)
|
|
|
|
|
|
def test_clip():
|
|
assert Region(10, 10, 20, 30).clip(20, 25) == Region(10, 10, 10, 15)
|
|
|
|
|
|
def test_region_shrink():
|
|
margin = Spacing(top=1, right=2, bottom=3, left=4)
|
|
region = Region(x=10, y=10, width=50, height=50)
|
|
assert region.shrink(margin) == Region(x=14, y=11, width=44, height=46)
|
|
|
|
|
|
def test_region_grow():
|
|
margin = Spacing(top=1, right=2, bottom=3, left=4)
|
|
region = Region(x=10, y=10, width=50, height=50)
|
|
assert region.grow(margin) == Region(x=6, y=9, width=56, height=54)
|
|
|
|
|
|
def test_region_intersection():
|
|
assert Region(0, 0, 100, 50).intersection(Region(10, 10, 10, 10)) == Region(
|
|
10, 10, 10, 10
|
|
)
|
|
assert Region(10, 10, 30, 20).intersection(Region(20, 15, 60, 40)) == Region(
|
|
20, 15, 20, 15
|
|
)
|
|
|
|
assert not Region(10, 10, 20, 30).intersection(Region(50, 50, 100, 200))
|
|
|
|
|
|
def test_region_union():
|
|
assert Region(5, 5, 10, 10).union(Region(20, 30, 10, 5)) == Region(5, 5, 25, 30)
|
|
|
|
|
|
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_size_line_range():
|
|
assert Size(20, 0).line_range == range(0)
|
|
assert Size(0, 20).line_range == range(20)
|
|
|
|
|
|
def test_region_x_extents():
|
|
assert Region(5, 10, 20, 30).column_span == (5, 25)
|
|
|
|
|
|
def test_region_y_extents():
|
|
assert Region(5, 10, 20, 30).line_span == (10, 40)
|
|
|
|
|
|
def test_region_x_max():
|
|
assert Region(5, 10, 20, 30).right == 25
|
|
|
|
|
|
def test_region_y_max():
|
|
assert Region(5, 10, 20, 30).bottom == 40
|
|
|
|
|
|
def test_region_columns_range():
|
|
assert Region(5, 10, 20, 30).column_range == range(5, 25)
|
|
|
|
|
|
def test_region_lines_range():
|
|
assert Region(5, 10, 20, 30).line_range == range(10, 40)
|
|
|
|
|
|
def test_region_reset_offset():
|
|
assert Region(5, 10, 20, 30).reset_offset == Region(0, 0, 20, 30)
|
|
|
|
|
|
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_totals():
|
|
assert Spacing(2, 3, 4, 5).totals == (8, 6)
|
|
|
|
|
|
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"
|
|
|
|
|
|
def test_spacing_sub():
|
|
assert Spacing(1, 2, 3, 4) - Spacing(5, 6, 7, 8) == Spacing(-4, -4, -4, -4)
|
|
|
|
with pytest.raises(TypeError):
|
|
Spacing(1, 2, 3, 4) - "foo"
|
|
|
|
|
|
def test_spacing_convenience_constructors():
|
|
assert Spacing.vertical(2) == Spacing(2, 0, 2, 0)
|
|
assert Spacing.horizontal(2) == Spacing(0, 2, 0, 2)
|
|
assert Spacing.all(2) == Spacing(2, 2, 2, 2)
|
|
|
|
|
|
def test_split():
|
|
assert Region(10, 5, 22, 15).split(10, 5) == (
|
|
Region(10, 5, 10, 5),
|
|
Region(20, 5, 12, 5),
|
|
Region(10, 10, 10, 10),
|
|
Region(20, 10, 12, 10),
|
|
)
|
|
|
|
|
|
def test_split_negative():
|
|
assert Region(10, 5, 22, 15).split(-1, -1) == (
|
|
Region(10, 5, 21, 14),
|
|
Region(31, 5, 1, 14),
|
|
Region(10, 19, 21, 1),
|
|
Region(31, 19, 1, 1),
|
|
)
|
|
|
|
|
|
def test_split_vertical():
|
|
assert Region(10, 5, 22, 15).split_vertical(10) == (
|
|
Region(10, 5, 10, 15),
|
|
Region(20, 5, 12, 15),
|
|
)
|
|
|
|
|
|
def test_split_vertical_negative():
|
|
assert Region(10, 5, 22, 15).split_vertical(-1) == (
|
|
Region(10, 5, 21, 15),
|
|
Region(31, 5, 1, 15),
|
|
)
|
|
|
|
|
|
def test_split_horizontal():
|
|
assert Region(10, 5, 22, 15).split_horizontal(5) == (
|
|
Region(10, 5, 22, 5),
|
|
Region(10, 10, 22, 10),
|
|
)
|
|
|
|
|
|
def test_split_horizontal_negative():
|
|
assert Region(10, 5, 22, 15).split_horizontal(-1) == (
|
|
Region(10, 5, 22, 14),
|
|
Region(10, 19, 22, 1),
|
|
)
|
|
|
|
|
|
def test_translate_inside():
|
|
# Needs to be moved up
|
|
assert Region(10, 20, 10, 20).translate_inside(Region(0, 0, 30, 25)) == Region(
|
|
10, 5, 10, 20
|
|
)
|
|
|
|
# Already inside
|
|
assert Region(10, 10, 20, 5).translate_inside(Region(0, 0, 100, 100)) == Region(
|
|
10, 10, 20, 5
|
|
)
|
|
|
|
|
|
def test_inflect():
|
|
assert Region(0, 0, 1, 1).inflect() == Region(1, 1, 1, 1)
|
|
assert Region(0, 0, 1, 1).inflect(margin=Spacing.unpack(1)) == Region(2, 2, 1, 1)
|
|
|
|
# Default inflect positive
|
|
assert Region(10, 10, 30, 20).inflect(margin=Spacing(2, 2, 2, 2)) == Region(
|
|
42, 32, 30, 20
|
|
)
|
|
|
|
# Inflect y axis negative
|
|
assert Region(10, 10, 30, 20).inflect(
|
|
y_axis=-1, margin=Spacing(2, 2, 2, 2)
|
|
) == Region(42, -12, 30, 20)
|
|
|
|
# Inflect y axis negative
|
|
assert Region(10, 10, 30, 20).inflect(
|
|
x_axis=-1, margin=Spacing(2, 2, 2, 2)
|
|
) == Region(-22, 32, 30, 20)
|
|
|
|
|
|
def test_size_with_height():
|
|
"""Test Size.with_height"""
|
|
assert Size(1, 2).with_height(10) == Size(1, 10)
|
|
|
|
|
|
def test_size_with_width():
|
|
"""Test Size.with_width"""
|
|
assert Size(1, 2).with_width(10) == Size(10, 2)
|
|
|
|
|
|
def test_offset_clamp():
|
|
assert Offset(1, 2).clamp(3, 3) == Offset(1, 2)
|
|
assert Offset(3, 2).clamp(3, 3) == Offset(2, 2)
|
|
assert Offset(-3, 2).clamp(3, 3) == Offset(0, 2)
|
|
assert Offset(5, 4).clamp(3, 3) == Offset(2, 2)
|
|
|
|
|
|
def test_size_clamp_offset():
|
|
assert Size(3, 3).clamp_offset(Offset(1, 2)) == Offset(1, 2)
|
|
assert Size(3, 3).clamp_offset(Offset(3, 2)) == Offset(2, 2)
|
|
assert Size(3, 3).clamp_offset(Offset(-3, 2)) == Offset(0, 2)
|
|
assert Size(3, 3).clamp_offset(Offset(5, 4)) == Offset(2, 2)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("region1", "region2", "expected"),
|
|
[
|
|
(Region(0, 0, 100, 80), Region(0, 0, 100, 80), Spacing(0, 0, 0, 0)),
|
|
(Region(0, 0, 100, 80), Region(10, 10, 10, 10), Spacing(10, 80, 60, 10)),
|
|
],
|
|
)
|
|
def test_get_spacing_between(region1: Region, region2: Region, expected: Spacing):
|
|
spacing = region1.get_spacing_between(region2)
|
|
assert spacing == expected
|
|
assert region1.shrink(spacing) == region2
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"constrain_x,constrain_y,margin,region,container,expected",
|
|
[
|
|
# A null-op
|
|
(
|
|
"none",
|
|
"none",
|
|
Spacing.unpack(0),
|
|
Region(0, 0, 10, 10),
|
|
Region(0, 0, 100, 100),
|
|
Region(0, 0, 10, 10),
|
|
),
|
|
# Negative offset gets moved to 0, 0 + margin
|
|
(
|
|
"inside",
|
|
"inside",
|
|
Spacing.unpack(1),
|
|
Region(-5, -5, 10, 10),
|
|
Region(0, 0, 100, 100),
|
|
Region(1, 1, 10, 10),
|
|
),
|
|
# Overlapping region gets moved in, with offset
|
|
(
|
|
"inside",
|
|
"inside",
|
|
Spacing.unpack(1),
|
|
Region(95, 95, 10, 10),
|
|
Region(0, 0, 100, 100),
|
|
Region(89, 89, 10, 10),
|
|
),
|
|
# X coordinate moved inside, region reflected around it's Y axis
|
|
(
|
|
"inside",
|
|
"inflect",
|
|
Spacing.unpack(1),
|
|
Region(-5, -5, 10, 10),
|
|
Region(0, 0, 100, 100),
|
|
Region(1, 6, 10, 10),
|
|
),
|
|
],
|
|
)
|
|
def test_constrain(
|
|
constrain_x: Literal["none", "inside", "inflect"],
|
|
constrain_y: Literal["none", "inside", "inflect"],
|
|
margin: Spacing,
|
|
region: Region,
|
|
container: Region,
|
|
expected: Region,
|
|
) -> None:
|
|
assert region.constrain(constrain_x, constrain_y, margin, container) == expected
|