mirror of
https://github.com/rio-labs/rio.git
synced 2026-02-12 00:28:32 -06:00
reorganize layout tests
This commit is contained in:
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: "[BUG]: "
|
||||
description: Create a bug report to help us improve
|
||||
labels: [bug]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/feature-requests.yml
vendored
5
.github/ISSUE_TEMPLATE/feature-requests.yml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Feature Request
|
||||
description: Create a feature request to help us improve
|
||||
title: "[Feature Request]"
|
||||
labels: ["enhancement"]
|
||||
|
||||
body:
|
||||
@@ -25,10 +24,10 @@ body:
|
||||
id: suggested_solution
|
||||
attributes:
|
||||
label: Suggested Solution
|
||||
description: Describe the solution you'd like.
|
||||
description: If you have a specific solution in mind, describe it here.
|
||||
placeholder: "A clear and concise description of what you want to happen."
|
||||
validations:
|
||||
required: true
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
|
||||
@@ -107,6 +107,7 @@ dev-dependencies = [
|
||||
"hatch>=1.11.1",
|
||||
"pyfakefs>=5.7.3",
|
||||
"pytest-cov>=5.0",
|
||||
"asyncio-atexit>=1.0.1",
|
||||
]
|
||||
managed = true
|
||||
|
||||
|
||||
@@ -257,10 +257,32 @@ class ComponentPage:
|
||||
|
||||
for param_name, parameter in signature.parameters.items():
|
||||
# Is this a path parameter, a query parameter, or neither?
|
||||
type_info = introspection.typing.TypeInfo(
|
||||
parameter.annotation,
|
||||
forward_ref_context=parameter.forward_ref_context,
|
||||
)
|
||||
try:
|
||||
type_info = introspection.typing.TypeInfo(
|
||||
parameter.annotation,
|
||||
forward_ref_context=parameter.forward_ref_context,
|
||||
)
|
||||
except introspection.errors.CannotResolveForwardref:
|
||||
# If it seems likely that this was supposed to be a query
|
||||
# parameter, emit a warning. Otherwise, silently ignore it.
|
||||
if isinstance(parameter.annotation, t.ForwardRef):
|
||||
annotation_str = parameter.annotation.__forward_arg__
|
||||
else:
|
||||
annotation_str = str(parameter.annotation)
|
||||
|
||||
annotation_str = annotation_str.lower()
|
||||
|
||||
if "query" in annotation_str or "param" in annotation_str:
|
||||
warnings.warn(
|
||||
f"The type annotation of the {param_name!r} parameter"
|
||||
f" of the {self.build.__name__!r} `build` function"
|
||||
f" cannot be resolved. If this was intended to be a"
|
||||
f" query parameter, make sure the annotation is valid"
|
||||
f" at runtime."
|
||||
)
|
||||
|
||||
continue
|
||||
|
||||
if param_name not in self._url_pattern.path_parameter_names:
|
||||
if QUERY_PARAMETER not in type_info.annotations:
|
||||
continue
|
||||
|
||||
@@ -103,16 +103,8 @@ def _create_tests_for_docstring(
|
||||
def test_summary(self) -> None:
|
||||
assert docs.summary is not None, f"{docs.name} has no summary"
|
||||
|
||||
# Exceptions don't need details
|
||||
if not (
|
||||
isinstance(docs.object, type)
|
||||
and issubclass(docs.object, BaseException)
|
||||
):
|
||||
|
||||
def test_details(self) -> None:
|
||||
assert (
|
||||
docs.details is not None
|
||||
), f"{docs.name} has no details"
|
||||
def test_details(self) -> None:
|
||||
assert docs.details is not None, f"{docs.name} has no details"
|
||||
|
||||
@pytest.mark.parametrize("code", code_blocks, ids=code_block_ids)
|
||||
def test_code_block_is_formatted(self, code: str) -> None:
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
import asyncio
|
||||
import typing as t
|
||||
|
||||
import pytest
|
||||
|
||||
import rio.data_models
|
||||
import rio.debug.layouter
|
||||
import rio.testing
|
||||
from tests.utils.layouting import BrowserClient, cleanup, setup, verify_layout
|
||||
|
||||
# For debugging. Set this to a number > 0 if you want to look at the browser.
|
||||
#
|
||||
# Note: Chrome's console doesn't show `console.debug` messages per default. To
|
||||
# see them, click on "All levels" and check "Verbose".
|
||||
DEBUG_SHOW_BROWSER_DURATION = 0
|
||||
|
||||
if DEBUG_SHOW_BROWSER_DURATION:
|
||||
pytestmark = pytest.mark.async_timeout(DEBUG_SHOW_BROWSER_DURATION + 30)
|
||||
|
||||
import tests.utils.layouting
|
||||
|
||||
tests.utils.layouting.DEBUG_EXTRA_SLEEP_DURATION = (
|
||||
DEBUG_SHOW_BROWSER_DURATION
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
async def manage_server():
|
||||
await setup()
|
||||
yield
|
||||
await cleanup()
|
||||
|
||||
|
||||
async def test_dropdowns_work_in_dev_tools() -> None:
|
||||
# Dropdowns (and other popups) have often been broken in the dev tools, due
|
||||
# to z-index issues and other reasons. This test makes sure that they work.
|
||||
|
||||
async with BrowserClient(rio.Spacer, debug_mode=True) as client:
|
||||
# Click the 2nd entry in the sidebar, which is the "Icons" tab
|
||||
await client.execute_js(
|
||||
"document.querySelector('.rio-switcher-bar-option:nth-child(2) .rio-switcher-bar-icon').click()",
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# Click an arbitrary icon in the list
|
||||
await client.execute_js(
|
||||
"document.querySelector('.rio-switcher .rio-icon').click()"
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# Open the dropdown
|
||||
await client.execute_js(
|
||||
"document.querySelector('.rio-dropdown input').focus()"
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# Make sure the dropdown list is open and visible
|
||||
is_entirely_visible = await client.execute_js("""
|
||||
new Promise((resolve) => {
|
||||
let elem = document.querySelector(".rio-dropdown-options");
|
||||
|
||||
let observer = new IntersectionObserver((entries) => {
|
||||
resolve(entries[0].intersectionRatio === 1);
|
||||
});
|
||||
observer.observe(elem);
|
||||
});
|
||||
""")
|
||||
assert is_entirely_visible
|
||||
|
||||
|
||||
@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: t.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 | t.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(
|
||||
"scroll_x,scroll_y",
|
||||
[
|
||||
("never", "auto"),
|
||||
("auto", "never"),
|
||||
("auto", "auto"),
|
||||
],
|
||||
)
|
||||
async def test_scrolling(
|
||||
scroll_x: t.Literal["never", "always", "auto"],
|
||||
scroll_y: t.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,
|
||||
)
|
||||
)
|
||||
0
tests/test_layouting/__init__.py
Normal file
0
tests/test_layouting/__init__.py
Normal file
12
tests/test_layouting/conftest.py
Normal file
12
tests/test_layouting/conftest.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import pytest
|
||||
|
||||
from tests.utils.layouting import cleanup, setup
|
||||
|
||||
|
||||
# Putting this fixture here makes it run only once, even though we have a whole
|
||||
# bunch of submodules.
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
async def manage_server():
|
||||
await setup()
|
||||
yield
|
||||
await cleanup()
|
||||
41
tests/test_layouting/test_dev_tools.py
Normal file
41
tests/test_layouting/test_dev_tools.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import asyncio
|
||||
|
||||
import rio
|
||||
from tests.utils.layouting import BrowserClient
|
||||
|
||||
|
||||
async def test_dropdowns_work_in_dev_tools() -> None:
|
||||
# Dropdowns (and other popups) have often been broken in the dev tools, due
|
||||
# to z-index issues and other reasons. This test makes sure that they work.
|
||||
|
||||
async with BrowserClient(rio.Spacer, debug_mode=True) as client:
|
||||
# Click the 2nd entry in the sidebar, which is the "Icons" tab
|
||||
await client.execute_js(
|
||||
"document.querySelector('.rio-switcher-bar-option:nth-child(2) .rio-switcher-bar-icon').click()",
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# Click an arbitrary icon in the list
|
||||
await client.execute_js(
|
||||
"document.querySelector('.rio-switcher .rio-icon').click()"
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# Open the dropdown
|
||||
await client.execute_js(
|
||||
"document.querySelector('.rio-dropdown input').focus()"
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# Make sure the dropdown list is open and visible
|
||||
is_entirely_visible = await client.execute_js("""
|
||||
new Promise((resolve) => {
|
||||
let elem = document.querySelector(".rio-dropdown-options");
|
||||
|
||||
let observer = new IntersectionObserver((entries) => {
|
||||
resolve(entries[0].intersectionRatio === 1);
|
||||
});
|
||||
observer.observe(elem);
|
||||
});
|
||||
""")
|
||||
assert is_entirely_visible
|
||||
29
tests/test_layouting/test_flow_container.py
Normal file
29
tests/test_layouting/test_flow_container.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import pytest
|
||||
|
||||
import rio
|
||||
from tests.utils.layouting import verify_layout
|
||||
|
||||
|
||||
@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,
|
||||
)
|
||||
)
|
||||
131
tests/test_layouting/test_linear_containers.py
Normal file
131
tests/test_layouting/test_linear_containers.py
Normal file
@@ -0,0 +1,131 @@
|
||||
import typing as t
|
||||
|
||||
import pytest
|
||||
|
||||
import rio
|
||||
from tests.utils.layouting import verify_layout
|
||||
|
||||
|
||||
@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 | t.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,
|
||||
)
|
||||
)
|
||||
36
tests/test_layouting/test_popup.py
Normal file
36
tests/test_layouting/test_popup.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import asyncio
|
||||
|
||||
import rio
|
||||
from tests.utils.layouting import BrowserClient
|
||||
|
||||
|
||||
async def test_popup_moves_with_anchor():
|
||||
def build():
|
||||
return rio.Column(
|
||||
rio.Webview('<div id="expander"></div>'),
|
||||
rio.Popup(
|
||||
anchor=rio.Text("anchor", min_width=10, min_height=10),
|
||||
content=rio.Text("content"),
|
||||
position="center",
|
||||
is_open=True,
|
||||
),
|
||||
)
|
||||
|
||||
async def get_content_y_coordinate():
|
||||
return await client.execute_js("""
|
||||
document.querySelector('.rio-popup-content').getBoundingClientRect().top;
|
||||
""")
|
||||
|
||||
async with BrowserClient(build) as client:
|
||||
y1 = await get_content_y_coordinate()
|
||||
|
||||
# Move the anchor down by making the element above it taller
|
||||
await client.execute_js(
|
||||
"document.querySelector('#expander').style.height = '100px';"
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# Check if the popup moved down
|
||||
y2 = await get_content_y_coordinate()
|
||||
|
||||
assert y2 > y1
|
||||
31
tests/test_layouting/test_scroll_container.py
Normal file
31
tests/test_layouting/test_scroll_container.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import typing as t
|
||||
|
||||
import pytest
|
||||
|
||||
import rio
|
||||
from tests.utils.layouting import verify_layout
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scroll_x,scroll_y",
|
||||
[
|
||||
("never", "auto"),
|
||||
("auto", "never"),
|
||||
("auto", "auto"),
|
||||
],
|
||||
)
|
||||
async def test_scrolling(
|
||||
scroll_x: t.Literal["never", "always", "auto"],
|
||||
scroll_y: t.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,
|
||||
)
|
||||
)
|
||||
32
tests/test_layouting/test_stack.py
Normal file
32
tests/test_layouting/test_stack.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import rio
|
||||
from tests.utils.layouting import verify_layout
|
||||
|
||||
|
||||
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
|
||||
34
tests/test_layouting/test_text.py
Normal file
34
tests/test_layouting/test_text.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import pytest
|
||||
|
||||
import rio
|
||||
from tests.utils.layouting import verify_layout
|
||||
|
||||
|
||||
@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))
|
||||
|
||||
|
||||
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
|
||||
@@ -3,6 +3,7 @@ Matches URL patterns to URLs, verifying that they match what they should.
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -543,3 +544,35 @@ def test_layout_parameters_arent_url_parameters():
|
||||
)
|
||||
|
||||
assert kwargs == {"foo": 7}
|
||||
|
||||
|
||||
def test_annotations_dont_have_to_be_resolvable():
|
||||
# The type annotation is invalid, but it's quite clearly not supposed to be
|
||||
# a query parameter, so it should be silently ignored.
|
||||
def build(foo: "oops" = 0): # type: ignore
|
||||
return rio.Text(str(foo))
|
||||
|
||||
with warnings.catch_warnings(record=True) as warnings_list:
|
||||
rio.ComponentPage(
|
||||
name="Test Page",
|
||||
url_segment="foobar",
|
||||
build=build,
|
||||
)
|
||||
|
||||
assert not warnings_list
|
||||
|
||||
|
||||
def test_unresolvable_annotation_warning():
|
||||
# The type annotation is invalid, but it's likely meant to be a query
|
||||
# parameter, so rio should emit a warning.
|
||||
def build(foo: "QueryParameter[int]" = 0): # type: ignore
|
||||
return rio.Text(str(foo))
|
||||
|
||||
with warnings.catch_warnings(record=True) as warnings_list:
|
||||
rio.ComponentPage(
|
||||
name="Test Page",
|
||||
url_segment="foobar",
|
||||
build=build,
|
||||
)
|
||||
|
||||
assert len(warnings_list) == 1
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import typing as t
|
||||
|
||||
import asyncio_atexit
|
||||
import playwright.async_api
|
||||
import uvicorn
|
||||
|
||||
@@ -16,7 +17,11 @@ from rio.utils import choose_free_port
|
||||
__all__ = ["BrowserClient", "verify_layout", "setup", "cleanup"]
|
||||
|
||||
|
||||
DEBUG_EXTRA_SLEEP_DURATION = 0
|
||||
# For debugging. Set this to a number > 0 if you want to look at the browser.
|
||||
#
|
||||
# Note: Chrome's console doesn't show `console.debug` messages per default. To
|
||||
# see them, click on "All levels" and check "Verbose".
|
||||
DEBUG_SHOW_BROWSER_DURATION = 0
|
||||
|
||||
|
||||
server_manager: ServerManager | None = None
|
||||
@@ -86,7 +91,7 @@ class BrowserClient:
|
||||
|
||||
async def __aexit__(self, *args: t.Any) -> None:
|
||||
# Sleep to keep the browser open for debugging
|
||||
await asyncio.sleep(DEBUG_EXTRA_SLEEP_DURATION)
|
||||
await asyncio.sleep(DEBUG_SHOW_BROWSER_DURATION)
|
||||
|
||||
if self._page is not None:
|
||||
await self._page.close()
|
||||
@@ -175,6 +180,8 @@ class ServerManager:
|
||||
return self._browser
|
||||
|
||||
async def start(self) -> None:
|
||||
asyncio_atexit.register(self.stop)
|
||||
|
||||
await self._start_browser()
|
||||
await self._start_uvicorn_server()
|
||||
|
||||
@@ -239,7 +246,7 @@ class ServerManager:
|
||||
|
||||
try:
|
||||
browser = await playwright_obj.chromium.launch(
|
||||
headless=DEBUG_EXTRA_SLEEP_DURATION == 0
|
||||
headless=DEBUG_SHOW_BROWSER_DURATION == 0
|
||||
)
|
||||
except Exception:
|
||||
raise Exception(
|
||||
|
||||
Reference in New Issue
Block a user