1+ import asyncio
12import gc
23from weakref import finalize
34
45import pytest
56
67import idom
7- from idom .core .layout import LayoutUpdate
88
9- from tests .general_utils import assert_unordered_equal
10-
11- from .utils import HookCatcher
9+ from tests .general_utils import assert_unordered_equal , HookCatcher
1210
1311
1412def test_layout_repr ():
@@ -40,13 +38,7 @@ async def SimpleElement():
4038 path , changes = await layout .render ()
4139
4240 assert path == ""
43- assert_unordered_equal (
44- changes ,
45- [
46- {"op" : "add" , "path" : "/eventHandlers" , "value" : {}},
47- {"op" : "add" , "path" : "/tagName" , "value" : "div" },
48- ],
49- )
41+ assert changes == [{"op" : "add" , "path" : "/tagName" , "value" : "div" }]
5042
5143 set_state_hook .current ("table" )
5244 path , changes = await layout .render ()
@@ -77,16 +69,12 @@ async def Child():
7769 assert_unordered_equal (
7870 changes ,
7971 [
80- {"op" : "add" , "path" : "/tagName" , "value" : "div" },
8172 {
8273 "op" : "add" ,
8374 "path" : "/children" ,
84- "value" : [
85- "0" ,
86- {"children" : ["0" ], "eventHandlers" : {}, "tagName" : "div" },
87- ],
75+ "value" : ["0" , {"tagName" : "div" , "children" : ["0" ]}],
8876 },
89- {"op" : "add" , "path" : "/eventHandlers " , "value" : {} },
77+ {"op" : "add" , "path" : "/tagName " , "value" : "div" },
9078 ],
9179 )
9280
@@ -104,93 +92,67 @@ async def Child():
10492
10593
10694async def test_layout_render_error_has_partial_update ():
107- history = RenderHistory ()
108-
109- @history .track ("main" )
11095 @idom .element
11196 async def Main ():
11297 return idom .html .div ([OkChild (), BadChild (), OkChild ()])
11398
114- @history .track ("ok_child" )
11599 @idom .element
116100 async def OkChild ():
117101 return idom .html .div (["hello" ])
118102
119- @history .track ("bad_child" )
120103 @idom .element
121104 async def BadChild ():
122105 raise ValueError ("Something went wrong :(" )
123106
124107 async with idom .Layout (Main ()) as layout :
125-
126- src , new , old , errors = await layout .render ()
127-
128- assert src == history .main_1 .id
129-
130- assert new == {
131- history .main_1 .id : {
132- "tagName" : "div" ,
133- "children" : [
134- {"type" : "ref" , "data" : history .ok_child_1 .id },
135- {"type" : "ref" , "data" : history .bad_child_1 .id },
136- {"type" : "ref" , "data" : history .ok_child_2 .id },
137- ],
138- },
139- history .ok_child_1 .id : {
140- "tagName" : "div" ,
141- "children" : [{"type" : "str" , "data" : "hello" }],
142- },
143- history .bad_child_1 .id : {"tagName" : "div" },
144- history .ok_child_2 .id : {
145- "tagName" : "div" ,
146- "children" : [{"type" : "str" , "data" : "hello" }],
147- },
148- }
149-
150- assert old == []
151-
152- assert len (errors ) == 1
153- assert isinstance (errors [0 ], ValueError )
154- assert str (errors [0 ]) == "Something went wrong :("
108+ patch = await layout .render ()
109+ assert_unordered_equal (
110+ patch .changes ,
111+ [
112+ {
113+ "op" : "add" ,
114+ "path" : "/children" ,
115+ "value" : [
116+ {"tagName" : "div" , "children" : ["hello" ]},
117+ {"tagName" : "div" , "__error__" : "Something went wrong :(" },
118+ {"tagName" : "div" , "children" : ["hello" ]},
119+ ],
120+ },
121+ {"op" : "add" , "path" : "/tagName" , "value" : "div" },
122+ ],
123+ )
155124
156125
157126async def test_render_raw_vdom_dict_with_single_element_object_as_children ():
158- history = RenderHistory ()
159-
160- @history .track ("main" )
161127 @idom .element
162128 async def Main ():
163129 return {"tagName" : "div" , "children" : Child ()}
164130
165- @history .track ("child" )
166131 @idom .element
167132 async def Child ():
168133 return {"tagName" : "div" , "children" : {"tagName" : "h1" }}
169134
170135 async with idom .Layout (Main ()) as layout :
171- render = await layout .render ()
172-
173- assert render == LayoutUpdate (
174- src = history .main_1 .id ,
175- new = {
176- history .child_1 .id : {
177- "tagName" : "div" ,
178- "children" : [{"type" : "obj" , "data" : {"tagName" : "h1" }}],
179- },
180- history .main_1 .id : {
181- "tagName" : "div" ,
182- "children" : [{"type" : "ref" , "data" : history .child_1 .id }],
183- },
184- },
185- old = [],
186- errors = [],
187- )
136+ patch = await layout .render ()
137+ assert_unordered_equal (
138+ patch .changes ,
139+ [
140+ {
141+ "op" : "add" ,
142+ "path" : "/children" ,
143+ "value" : [{"tagName" : "div" , "children" : [{"tagName" : "h1" }]}],
144+ },
145+ {"op" : "add" , "path" : "/tagName" , "value" : "div" },
146+ ],
147+ )
188148
189149
190150async def test_elements_are_garbage_collected ():
191151 live_elements = set ()
152+ outer_element_hook = HookCatcher ()
192153
193154 @idom .element
155+ @outer_element_hook .capture
194156 async def Outer ():
195157 element = idom .hooks .current_hook ().element
196158 live_elements .add (element .id )
@@ -213,22 +175,54 @@ async def Inner():
213175
214176 async with idom .Layout (Outer ()) as layout :
215177 await layout .render ()
178+
216179 assert len (live_elements ) == 2
180+
217181 last_live_elements = live_elements .copy ()
218182 # The existing `Outer` element rerenders. A new `Inner` element is created and
219183 # the the old `Inner` element should be deleted. Thus there should be one
220184 # changed element in the set of `live_elements` the old `Inner` deleted and new
221185 # `Inner` added.
222- await layout . dispatch ( idom . core . layout . LayoutEvent ( "force-update" , []) )
186+ outer_element_hook . schedule_render ( )
223187 await layout .render ()
188+
224189 assert len (live_elements - last_live_elements ) == 1
225190
226191 # The layout still holds a reference to the root so that's
227192 # only deleted once we release a reference to it.
228193 del layout
194+ # the hook also contains a reference to the root element
195+ del outer_element_hook
196+
229197 gc .collect ()
230198 assert not live_elements
231199
232200
233- def test_double_updated_element_is_not_double_rendered ():
234- assert False
201+ async def test_double_updated_element_is_not_double_rendered ():
202+ hook = HookCatcher ()
203+ run_count = idom .Ref (0 )
204+
205+ @idom .element
206+ @hook .capture
207+ async def AnElement ():
208+ run_count .current += 1
209+ return idom .html .div ()
210+
211+ async with idom .Layout (AnElement ()) as layout :
212+ await layout .render ()
213+
214+ assert run_count .current == 1
215+
216+ hook .schedule_render ()
217+ hook .schedule_render ()
218+
219+ await layout .render ()
220+ try :
221+ asyncio .wait_for (
222+ [layout .render ()],
223+ timeout = 0.1 , # this should have been plenty of time
224+ )
225+ except asyncio .CancelledError :
226+ pass # the render should still be rendering since we only update once
227+
228+ assert run_count .current == 2
0 commit comments