mirror of
https://github.com/rio-labs/rio.git
synced 2025-12-29 17:29:53 -06:00
fix allocation of HighLevelRootComponent
This commit is contained in:
@@ -466,7 +466,7 @@ export async function processMessageReturnResponse(
|
||||
break;
|
||||
|
||||
case "getUnittestClientLayoutInfo":
|
||||
response = getUnittestClientLayoutInfo();
|
||||
response = await getUnittestClientLayoutInfo();
|
||||
break;
|
||||
|
||||
case "removeDialog":
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getComponentLayout } from "./utils";
|
||||
import { getComponentLayout, sleep } from "./utils";
|
||||
import { pixelsPerRem } from "./app";
|
||||
import {
|
||||
componentsById,
|
||||
@@ -289,7 +289,14 @@ function dumpComponentRecursively(
|
||||
}
|
||||
}
|
||||
|
||||
export function getUnittestClientLayoutInfo(): UnittestClientLayoutInfo {
|
||||
export async function getUnittestClientLayoutInfo(): Promise<UnittestClientLayoutInfo> {
|
||||
// This function is only used by rio's unit tests. We know it runs after the
|
||||
// first `updateComponentStates`, so we don't need to worry about that. But,
|
||||
// since layouting includes quite a few `requestAnimationFrame`s and
|
||||
// `resizeObserver`s, some layouts may still be in flux. We'll wait a little
|
||||
// while before we fetch the layouts.
|
||||
await sleep(0.1);
|
||||
|
||||
// Prepare the result
|
||||
const result = {} as UnittestClientLayoutInfo;
|
||||
|
||||
|
||||
@@ -454,11 +454,15 @@ export function getComponentLayout(component: ComponentBase): ComponentLayout {
|
||||
result.topInViewportInner = innerRect.top / pixelsPerRem;
|
||||
|
||||
// Allocated size
|
||||
result.allocatedOuterWidth = outerRect.width / pixelsPerRem;
|
||||
result.allocatedOuterHeight = outerRect.height / pixelsPerRem;
|
||||
result.allocatedOuterWidth =
|
||||
getAllocatedWidthInPx(outerElement) / pixelsPerRem;
|
||||
result.allocatedOuterHeight =
|
||||
getAllocatedHeightInPx(outerElement) / pixelsPerRem;
|
||||
|
||||
result.allocatedInnerWidth = innerRect.width / pixelsPerRem;
|
||||
result.allocatedInnerHeight = innerRect.height / pixelsPerRem;
|
||||
result.allocatedInnerWidth =
|
||||
getAllocatedWidthInPx(innerElement) / pixelsPerRem;
|
||||
result.allocatedInnerHeight =
|
||||
getAllocatedHeightInPx(innerElement) / pixelsPerRem;
|
||||
|
||||
let naturalSizeInPixels = getNaturalSizeInPixels(innerElement);
|
||||
result.naturalWidth = naturalSizeInPixels[0] / pixelsPerRem;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import json
|
||||
import sys
|
||||
import typing as t
|
||||
@@ -44,23 +45,25 @@ def specialized(func: t.Callable[P, R]) -> t.Callable[P, R]:
|
||||
"""
|
||||
|
||||
def result(self, component, *args, **kwargs) -> t.Any:
|
||||
# Special case: A lot of containers behave in the same way - they pass
|
||||
# on all space. Avoid having to implement them all separately.
|
||||
if type(component) in FULL_SIZE_SINGLE_CONTAINERS or not isinstance(
|
||||
component,
|
||||
rio.components.fundamental_component.FundamentalComponent,
|
||||
):
|
||||
function_name = f"{func.__name__}_SingleContainer"
|
||||
else:
|
||||
function_name = f"{func.__name__}_{type(component).__name__}"
|
||||
|
||||
# Delegate to a specialized method if it exists
|
||||
# First, check if a specialized method for this component exists
|
||||
try:
|
||||
method = getattr(self, function_name)
|
||||
method = getattr(
|
||||
self,
|
||||
f"{func.__name__}_{type(component).__name__}",
|
||||
)
|
||||
except AttributeError:
|
||||
return func(self, component, *args, **kwargs) # type: ignore
|
||||
else:
|
||||
return method(component, *args, **kwargs)
|
||||
# Special case: A lot of containers behave in the same way - they pass
|
||||
# on all space. Avoid having to implement them all separately.
|
||||
if type(component) in FULL_SIZE_SINGLE_CONTAINERS or not isinstance(
|
||||
component,
|
||||
rio.components.fundamental_component.FundamentalComponent,
|
||||
):
|
||||
method = getattr(self, f"{func.__name__}_SingleContainer")
|
||||
else:
|
||||
# If all else fails, use the default implementation
|
||||
method = functools.partial(func, self)
|
||||
|
||||
return method(component, *args, **kwargs) # type: ignore (wtf?)
|
||||
|
||||
return result # type: ignore
|
||||
|
||||
@@ -234,7 +237,8 @@ class Layouter:
|
||||
# before its children.
|
||||
_ordered_components: list[rio.Component]
|
||||
|
||||
# Construction of this class is asynchronous. Make sure nobody does anything silly.
|
||||
# Construction of this class is asynchronous. Make sure nobody does anything
|
||||
# silly.
|
||||
def __init__(self) -> None:
|
||||
raise TypeError(
|
||||
"Creating this class is asynchronous. Use the `create` method instead."
|
||||
@@ -393,14 +397,6 @@ class Layouter:
|
||||
)
|
||||
|
||||
# 2. Update allocated width
|
||||
root_layout = self._layouts_should[
|
||||
self.session._high_level_root_component._id
|
||||
]
|
||||
root_layout.left_in_viewport_outer = 0
|
||||
root_layout.allocated_outer_width = max(
|
||||
self.window_width, root_layout.requested_outer_width
|
||||
)
|
||||
|
||||
for component in ordered_components:
|
||||
layout = self._layouts_should[component._id]
|
||||
|
||||
@@ -441,11 +437,6 @@ class Layouter:
|
||||
)
|
||||
|
||||
# 4. Update allocated height
|
||||
root_layout.top_in_viewport_outer = 0
|
||||
root_layout.allocated_outer_height = max(
|
||||
self.window_height, root_layout.requested_outer_height
|
||||
)
|
||||
|
||||
for component in ordered_components:
|
||||
layout = self._layouts_should[component._id]
|
||||
|
||||
@@ -559,6 +550,27 @@ class Layouter:
|
||||
child_layout_is.allocated_outer_width
|
||||
)
|
||||
|
||||
def _update_allocated_width_HighLevelRootComponent(
|
||||
self,
|
||||
component: rio.components.root_components.HighLevelRootComponent,
|
||||
) -> None:
|
||||
# Since the HighLevelRootComponent doesn't have a parent, it has to set
|
||||
# its own allocation
|
||||
layout_should = self._layouts_should[component._id]
|
||||
layout_is = self._layouts_are[component._id]
|
||||
|
||||
# Because scrolling differs between debug mode and release mode (user
|
||||
# content scrolls vs browser scrolls), we'll just copy the values from
|
||||
# the client.
|
||||
layout_should.left_in_viewport_outer = layout_is.left_in_viewport_outer
|
||||
layout_should.left_in_viewport_inner = layout_is.left_in_viewport_inner
|
||||
|
||||
layout_should.allocated_outer_width = layout_is.allocated_outer_width
|
||||
layout_should.allocated_inner_width = layout_is.allocated_inner_width
|
||||
|
||||
# Then behave like a regular SingleContainer
|
||||
self._update_allocated_width_SingleContainer(component)
|
||||
|
||||
def _update_allocated_width_Row(
|
||||
self,
|
||||
component: rio.Row,
|
||||
@@ -721,6 +733,27 @@ class Layouter:
|
||||
child_layout_is.top_in_viewport_outer
|
||||
)
|
||||
|
||||
def _update_allocated_height_HighLevelRootComponent(
|
||||
self,
|
||||
component: rio.components.root_components.HighLevelRootComponent,
|
||||
) -> None:
|
||||
# Since the HighLevelRootComponent doesn't have a parent, it has to set
|
||||
# its own allocation
|
||||
layout_should = self._layouts_should[component._id]
|
||||
layout_is = self._layouts_are[component._id]
|
||||
|
||||
# Because scrolling differs between debug mode and release mode (user
|
||||
# content scrolls vs browser scrolls), we'll just copy the values from
|
||||
# the client.
|
||||
layout_should.top_in_viewport_outer = layout_is.top_in_viewport_outer
|
||||
layout_should.top_in_viewport_inner = layout_is.top_in_viewport_inner
|
||||
|
||||
layout_should.allocated_outer_height = layout_is.allocated_outer_height
|
||||
layout_should.allocated_inner_height = layout_is.allocated_inner_height
|
||||
|
||||
# Then behave like a regular SingleContainer
|
||||
self._update_allocated_height_SingleContainer(component)
|
||||
|
||||
def _update_allocated_height_Row(
|
||||
self,
|
||||
component: rio.Row,
|
||||
|
||||
@@ -3539,6 +3539,8 @@ a.remove();
|
||||
hl_layout.aux = {}
|
||||
result.component_layouts[hl_root_component._id] = hl_layout
|
||||
|
||||
ll_layout.parent_id = hl_root_component._id
|
||||
|
||||
# Done
|
||||
return result
|
||||
|
||||
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
@@ -5,9 +5,9 @@ import typing as t
|
||||
|
||||
import imy.docstrings
|
||||
import pytest
|
||||
from utils.ruff import ruff_check, ruff_format # type: ignore
|
||||
|
||||
import rio.docs
|
||||
from tests.utils.ruff import ruff_check, ruff_format
|
||||
|
||||
|
||||
def parametrize_with_name(
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
import typing as t
|
||||
|
||||
import pytest
|
||||
from utils.layouting import cleanup, setup, verify_layout # type: ignore
|
||||
|
||||
import rio.data_models
|
||||
import rio.debug.layouter
|
||||
import rio.testing
|
||||
from tests.utils.layouting import cleanup, setup, verify_layout
|
||||
|
||||
# pytestmark = pytest.mark.async_timeout(30)
|
||||
# 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)
|
||||
|
||||
@@ -62,7 +62,7 @@ PAGES = [
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"relative_url_before_redirects,relative_url_after_redirects_should",
|
||||
"relative_url_before_redirects, relative_url_after_redirects_should",
|
||||
[
|
||||
# No redirects
|
||||
(
|
||||
@@ -158,9 +158,3 @@ def test_redirects(
|
||||
assert (
|
||||
absolute_url_after_redirects_is == absolute_url_after_redirects_should
|
||||
)
|
||||
|
||||
|
||||
test_redirects(
|
||||
"/guard-to-page-1",
|
||||
"/page-1",
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@ import asyncio
|
||||
import typing as t
|
||||
|
||||
import playwright.async_api
|
||||
import playwright.sync_api
|
||||
import uvicorn
|
||||
|
||||
import rio.app_server
|
||||
@@ -18,6 +17,9 @@ from rio.utils import choose_free_port
|
||||
__all__ = ["verify_layout", "setup", "cleanup"]
|
||||
|
||||
|
||||
DEBUG_EXTRA_SLEEP_DURATION = 0
|
||||
|
||||
|
||||
layouter_factory: LayouterFactory | None = None
|
||||
|
||||
|
||||
@@ -132,11 +134,14 @@ class LayouterFactory:
|
||||
self._app._build = build
|
||||
session, page = await self._create_session()
|
||||
|
||||
# FIXME: Give the client some time to process the layout
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
layouter = await Layouter.create(session)
|
||||
|
||||
# Sleep to keep the browser open for debugging. We do this *after*
|
||||
# obtaining the component layouts so that the
|
||||
# `getUnittestClientLayoutInfo` response can be seen in the browser
|
||||
# console.
|
||||
await asyncio.sleep(DEBUG_EXTRA_SLEEP_DURATION)
|
||||
|
||||
await page.close()
|
||||
await session._close(close_remote_session=False)
|
||||
|
||||
@@ -189,7 +194,9 @@ class LayouterFactory:
|
||||
playwright_obj = await self._playwright_context.__aenter__()
|
||||
|
||||
try:
|
||||
browser = await playwright_obj.chromium.launch()
|
||||
browser = await playwright_obj.chromium.launch(
|
||||
headless=DEBUG_EXTRA_SLEEP_DURATION == 0
|
||||
)
|
||||
except Exception:
|
||||
raise Exception(
|
||||
"Playwright cannot launch chromium. Please execute the"
|
||||
@@ -200,9 +207,11 @@ class LayouterFactory:
|
||||
|
||||
# With default settings, playwright gets detected as a crawler. So we
|
||||
# need to emulate a real device.
|
||||
self._browser = await browser.new_context(
|
||||
**playwright_obj.devices["Desktop Chrome"]
|
||||
)
|
||||
kwargs = dict(playwright_obj.devices["Desktop Chrome"])
|
||||
# The default window size is too large to fit on my screen, which sucks
|
||||
# when debugging. Make it smaller.
|
||||
kwargs["viewport"] = {"width": 800, "height": 600}
|
||||
self._browser = await browser.new_context(**kwargs)
|
||||
|
||||
async def _create_session(self) -> tuple[Session, t.Any]:
|
||||
assert (
|
||||
|
||||
Reference in New Issue
Block a user