added more layout tests

This commit is contained in:
Jakob Pinterits
2024-07-04 22:03:30 +02:00
parent 8b01f28c73
commit 1de4e679e2
2 changed files with 166 additions and 7 deletions
+37 -3
View File
@@ -229,6 +229,10 @@ class Layouter:
# given component, that component and all of its children are ignored.
_filter: Callable[[rio.Component], bool]
# All components in the session, ordered such that each parent appears
# before its children.
_ordered_components: list[rio.Component]
# Construction of this class is asynchronous. Make sure nobody does anything silly.
def __init__(self) -> None:
raise TypeError(
@@ -248,7 +252,7 @@ class Layouter:
# Get a sorted list of components. Each parent appears before its
# children.
ordered_components: list[rio.Component] = list(
self._ordered_components = list(
self._get_toposorted(self.session._root_component)
)
@@ -268,7 +272,7 @@ class Layouter:
# Make sure the received components match expectations
component_ids_client = set(self._layouts_are.keys())
component_ids_server = {c._id for c in ordered_components}
component_ids_server = {c._id for c in self._ordered_components}
missing_component_ids = component_ids_server - component_ids_client
assert not missing_component_ids, missing_component_ids
@@ -279,12 +283,42 @@ class Layouter:
# Compute server-side layouts
self._layouts_should = {}
self._compute_layouts_should(
ordered_components=ordered_components,
ordered_components=self._ordered_components,
)
# Done!
return self
def get_layout_by_key(
self,
key: str | int,
) -> UnittestComponentLayout:
"""
Returns the layout for the component with the given key.
This function is intended to be used for additional tests after one has
already checked that all attributes from layout-should and layout-are
match. Because of this, this function simply returns the "should"
variant of the layout, since it is bound to be identical to the other
one anyway.
However, the "should" variant has one advantage: Because it wasn't
calculated by a browser with real-world pixels, any contained values are
exact, meaning you can compare the values using a simple `==` rather
than a fuzzy comparison.
## Raises
`KeyError`: If there is no component with the given key.
"""
for component in self._ordered_components:
if component.key == key:
return self._layouts_should[component._id]
raise KeyError(f"There is no component with key `{key}`")
def _get_toposorted(
self,
root: rio.Component,
+129 -4
View File
@@ -1,16 +1,20 @@
import math
from collections.abc import Callable
from typing import * # type: ignore
import pytest
import rio.data_models
import rio.debug.layouter
import rio.testing
from tests.utils.headless_client import HeadlessClient
pytestmark = pytest.mark.async_timeout(20)
async def verify_layout(build: Callable[[], rio.Component]) -> None:
async def verify_layout(
build: Callable[[], rio.Component],
) -> rio.debug.layouter.Layouter:
"""
Rio contains two layout implementations: One on the client side, which
determines the real layout of components, and a second one on the server
@@ -46,6 +50,8 @@ async def verify_layout(build: Callable[[], rio.Component]) -> None:
+ "\n- ".join(differences)
)
return layouter
@pytest.mark.parametrize(
"text",
@@ -121,13 +127,132 @@ async def test_linear_container_with_extra_width_and_no_growers(
async def test_stack() -> None:
await verify_layout(
layouter = await verify_layout(
lambda: rio.Stack(
rio.Text("Small", width=10, height=20),
rio.Text("Large", width=30, height=40),
rio.Text("Small", key="small_text", width=10, height=20),
rio.Text("Large", key="large_text", width=30, 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 == 40
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,
),
width=parent_width,
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, 100, 50),
(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,
width=child_specified_width,
height=child_specified_height,
key="child",
),
aspect_ratio=child_aspect_ratio,
),
width=20,
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
)
async def test_scrolling_in_both_directions() -> None:
await verify_layout(