import math from typing import * # type: ignore import pytest import rio.data_models import rio.debug.layouter import rio.testing from tests.utils.layouting import cleanup, verify_layout # pytestmark = pytest.mark.async_timeout(30) @pytest.fixture(scope="module", autouse=True) async def cleanup_layouter(): yield await cleanup() @pytest.mark.parametrize( "text", [ "", "short-text", "-".join(["long-text"] * 100), ], ) async def test_single_component(text: str) -> None: """ Just one component - this should fill the whole screen. """ await verify_layout(lambda: rio.Text(text)) @pytest.mark.parametrize( "container_type", [rio.Row, rio.Column], ) async def test_linear_container_with_no_extra_width( container_type: Type, ) -> None: await verify_layout( lambda: container_type( rio.Text("hi", min_width=100), rio.Button("clicky", min_width=400), ) ) @pytest.mark.parametrize( "horizontal", [True, False], ) @pytest.mark.parametrize( "first_child_grows", [True, False], ) @pytest.mark.parametrize( "second_child_grows", [True, False], ) @pytest.mark.parametrize( "proportions", [ None, "homogeneous", [1, 2], [2, 1], ], ) async def test_linear_container_with_extra_width( horizontal: bool, first_child_grows: bool, second_child_grows: bool, proportions: None | Literal["homogeneous"] | List[int], ) -> None: """ A battery of scenarios to test the most common containers - Rows & Columns. """ if horizontal: container_type = rio.Row if first_child_grows: first_child_width = 0 first_child_grow_x = True else: first_child_width = 10 first_child_grow_x = False if second_child_grows: second_child_width = 0 second_child_grow_x = True else: second_child_width = 20 second_child_grow_x = False first_child_height = 0 second_child_height = 0 first_child_grow_y = False second_child_grow_y = False parent_width = 50 parent_height = 0 else: container_type = rio.Column if first_child_grows: first_child_height = 0 first_child_grow_y = True else: first_child_height = 10 first_child_grow_y = False if second_child_grows: second_child_height = 0 second_child_grow_y = True else: second_child_height = 20 second_child_grow_y = False first_child_width = 0 second_child_width = 0 first_child_grow_x = False second_child_grow_x = False parent_width = 0 parent_height = 50 await verify_layout( lambda: container_type( rio.Text( "short-text", min_width=first_child_width, min_height=first_child_height, grow_x=first_child_grow_x, grow_y=first_child_grow_y, ), rio.Text( "very-much-longer-text", min_width=second_child_width, min_height=second_child_height, grow_x=second_child_grow_x, grow_y=second_child_grow_y, ), # It would be nice to vary the spacing as well, but that would once # again double the number of tests this case already has. Simply # always specify a spacing, since that is the harder case anyway. spacing=2, proportions=proportions, min_width=parent_width, min_height=parent_height, align_x=0.5, align_y=0.5, ) ) async def test_stack() -> None: """ All children in stacks should be the same size. """ layouter = await verify_layout( lambda: rio.Stack( rio.Text("Small", key="small_text", min_width=10, min_height=20), rio.Text("Large", key="large_text", min_width=30, min_height=40), align_x=0, align_y=0, ) ) small_layout = layouter.get_layout_by_key("small_text") assert small_layout.left_in_viewport_inner == 0 assert small_layout.top_in_viewport_inner == 0 assert small_layout.allocated_inner_width == 30 assert small_layout.allocated_inner_height == 40 large_layout = layouter.get_layout_by_key("large_text") assert large_layout.left_in_viewport_inner == 0 assert large_layout.top_in_viewport_inner == 0 assert large_layout.allocated_inner_width == 30 assert large_layout.allocated_inner_height == 40 @pytest.mark.parametrize( "parent_width,parent_height", [ (10, 50), (50, 10), ], ) async def test_aspect_ratio_container_small_child( parent_width: float, parent_height: float, ) -> None: """ Create a rectangle with a fixed aspect ratio, and make sure it matches expectations. The child is smaller than the parent and will thus be pulled larger. """ parent_aspect_ratio = parent_width / parent_height child_aspect_ratio = 2 layout = await verify_layout( lambda: rio.Container( rio.AspectRatioContainer( rio.Rectangle( fill=rio.Color.RED, key="child", ), aspect_ratio=child_aspect_ratio, ), min_width=parent_width, min_height=parent_height, align_x=0, align_y=0, ) ) # How large should the child be? if parent_aspect_ratio > child_aspect_ratio: child_width_should = parent_height * child_aspect_ratio child_height_should = parent_height else: child_width_should = parent_width child_height_should = parent_width / child_aspect_ratio # Is it though? child_layout = layout.get_layout_by_key("child") assert math.isclose(child_layout.allocated_inner_width, child_width_should) assert math.isclose( child_layout.allocated_inner_height, child_height_should ) @pytest.mark.parametrize( "child_specified_width,child_specified_height,child_width_should,child_height_should", [ (10, 50, 20, 100), (50, 10, 50, 25), ], ) async def test_aspect_ratio_container_large_child( child_specified_width: float, child_specified_height: float, child_width_should: float, child_height_should: float, ) -> None: """ Create a rectangle with a fixed aspect ratio, and make sure it matches expectations. The child is larger than the parent and will thus push the parent larger. """ child_aspect_ratio = 2 layout = await verify_layout( lambda: rio.Container( rio.AspectRatioContainer( rio.Rectangle( fill=rio.Color.RED, min_width=child_specified_width, min_height=child_specified_height, key="child", ), aspect_ratio=child_aspect_ratio, ), min_width=20, min_height=20, align_x=0, align_y=0, ) ) # Is it though? child_layout = layout.get_layout_by_key("child") assert math.isclose(child_layout.allocated_inner_width, child_width_should) assert math.isclose( child_layout.allocated_inner_height, child_height_should ) @pytest.mark.parametrize( "scroll_x,scroll_y", [ ("never", "auto"), ("auto", "never"), ("auto", "auto"), ], ) async def test_scrolling( scroll_x: Literal["never", "always", "auto"], scroll_y: Literal["never", "always", "auto"], ) -> None: await verify_layout( lambda: rio.ScrollContainer( rio.Text("hi", min_width=30, min_height=30), scroll_x=scroll_x, scroll_y=scroll_y, min_width=20, min_height=20, align_x=0.5, align_y=0.5, ) ) async def test_ellipsized_text() -> None: layouter = await verify_layout( lambda: rio.Text( "My natural size should become 0", overflow="ellipsize", align_x=0, key="text", ) ) layout = layouter.get_layout_by_key("text") assert layout.natural_width == 0 @pytest.mark.parametrize( "justify", [ "left", "right", "center", "justified", "grow", ], ) async def test_flow_container_layout(justify: str) -> None: await verify_layout( lambda: rio.FlowContainer( rio.Text("foo", min_width=5), rio.Text("bar", min_width=10), rio.Text("qux", min_width=4), column_spacing=3, row_spacing=2, justify=justify, # type: ignore min_width=20, align_x=0, ) )