Skip to content

Commit e805007

Browse files
committed
Hold a closed layout to what it no longer owes
The two guards which refuse to schedule on behalf of a layout that has begun closing, and the walk which stops when nothing remains above an element, had no test that reached them - and the project asks that every line be answered for. Each is now asked about directly: a closed layout is asked for an acknowledgement and a render and refuses both without touching what teardown released, and an element whose parent has gone is asked which component owns it and is told that none does.
1 parent 4719a46 commit e805007

1 file changed

Lines changed: 65 additions & 1 deletion

File tree

‎tests/test_core/test_layout.py‎

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from reactpy.core.component import component
2121
from reactpy.core.events import EventHandler
2222
from reactpy.core.hooks import use_async_effect, use_effect, use_state
23-
from reactpy.core.layout import Layout, _ThreadSafeQueue
23+
from reactpy.core.layout import Layout, _ModelState, _ThreadSafeQueue
2424
from reactpy.testing import (
2525
HookCatcher,
2626
StaticEventHandler,
@@ -1933,3 +1933,67 @@ def note_late_render(self, model_state):
19331933
"a render went ahead after the layout had closed, on a layout which has "
19341934
"already let go of what it renders with"
19351935
)
1936+
1937+
1938+
async def test_a_closed_layout_owes_neither_an_ack_delivery_nor_a_render():
1939+
"""Nothing is promised on behalf of a layout which has closed
1940+
1941+
An acknowledgement reaches the client inside a render of the element it
1942+
describes, and a render is a task belonging to the layout's loop. Once the
1943+
layout has closed there is no element left, no task to create, and no client
1944+
to receive any of it - only the memory of the tables which used to hold all
1945+
that, and reaching for those would raise where nothing is left to catch it.
1946+
"""
1947+
static = StaticEventHandler()
1948+
handler = EventHandler(lambda e: None, target=static.target)
1949+
1950+
@component
1951+
def Root():
1952+
return html.input({"value": "text", "onChange": handler})
1953+
1954+
layout = Layout(Root())
1955+
1956+
async with layout:
1957+
await layout.render()
1958+
1959+
layout._schedule_ack_delivery(static.target, 4)
1960+
layout._schedule_render_task("a-component-which-is-no-longer-here")
1961+
1962+
# Neither request was allowed to reach for what teardown has released: the
1963+
# acknowledgement tables are gone and no task was created to replace them
1964+
assert getattr(layout, "_pending_ack_by_target", None) is None
1965+
assert layout._render_tasks == set()
1966+
1967+
1968+
def test_an_element_with_nothing_above_it_prompts_no_render():
1969+
"""The component which would carry an acknowledgement is the one above
1970+
1971+
Walking up from an element finds the component nearest above it, because that
1972+
is the one whose re-render rewrites the element's model. An element with
1973+
nothing left above it has no component to prompt - its model will carry the
1974+
acknowledgement the moment it is rendered again, which is all anyone can ask.
1975+
"""
1976+
parent = _ModelState(
1977+
parent=None,
1978+
index=-1,
1979+
key=None,
1980+
model=Ref({"tagName": "", "children": []}),
1981+
patch_path="",
1982+
children_by_key={},
1983+
targets_by_event={},
1984+
)
1985+
element = _ModelState(
1986+
parent=parent,
1987+
index=0,
1988+
key=None,
1989+
model=Ref({"tagName": "input", "attributes": {"value": "text"}}),
1990+
patch_path="/children/0",
1991+
children_by_key={},
1992+
targets_by_event={"onChange": "/0:onChange"},
1993+
)
1994+
1995+
# Whatever was above the element is gone, so there is no component to prompt
1996+
del parent
1997+
gc.collect()
1998+
1999+
assert Layout._owner_component_id(element) is None

0 commit comments

Comments
 (0)