mirror of
https://github.com/rio-labs/rio.git
synced 2026-04-25 21:58:32 -05:00
added more layout tests
This commit is contained in:
+37
-3
@@ -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
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user