5
5
import reactpy
6
6
from reactpy import html
7
7
from reactpy .config import REACTPY_DEBUG_MODE
8
- from reactpy .core .hooks import (
9
- COMPONENT_DID_RENDER_EFFECT ,
10
- LifeCycleHook ,
11
- current_hook ,
12
- strictly_equal ,
13
- )
8
+ from reactpy .core .hooks import LifeCycleHook , strictly_equal
14
9
from reactpy .core .layout import Layout
15
10
from reactpy .testing import DisplayFixture , HookCatcher , assert_reactpy_did_log , poll
16
11
from reactpy .testing .logs import assert_reactpy_did_not_log
17
12
from reactpy .utils import Ref
18
13
from tests .tooling .common import DEFAULT_TYPE_DELAY , update_message
14
+ from tests .tooling .concurrency import WaitForEvent
19
15
20
16
21
17
async def test_must_be_rendering_in_layout_to_use_hooks ():
@@ -327,15 +323,15 @@ def CheckNoEffectYet():
327
323
async def test_use_effect_cleanup_occurs_before_next_effect ():
328
324
component_hook = HookCatcher ()
329
325
cleanup_triggered = reactpy .Ref (False )
330
- cleanup_triggered_before_next_effect = reactpy . Ref ( False )
326
+ cleanup_triggered_before_next_effect = WaitForEvent ( )
331
327
332
328
@reactpy .component
333
329
@component_hook .capture
334
330
def ComponentWithEffect ():
335
331
@reactpy .hooks .use_effect (dependencies = None )
336
332
def effect ():
337
333
if cleanup_triggered .current :
338
- cleanup_triggered_before_next_effect .current = True
334
+ cleanup_triggered_before_next_effect .set ()
339
335
340
336
def cleanup ():
341
337
cleanup_triggered .current = True
@@ -353,7 +349,7 @@ def cleanup():
353
349
await layout .render ()
354
350
355
351
assert cleanup_triggered .current
356
- assert cleanup_triggered_before_next_effect .current
352
+ await cleanup_triggered_before_next_effect .wait ()
357
353
358
354
359
355
async def test_use_effect_cleanup_occurs_on_will_unmount ():
@@ -395,10 +391,11 @@ def cleanup():
395
391
assert cleanup_triggered_before_next_render .current
396
392
397
393
398
- async def test_memoized_effect_on_recreated_if_dependencies_change ():
394
+ async def test_memoized_effect_is_recreated_if_dependencies_change ():
399
395
component_hook = HookCatcher ()
400
396
set_state_callback = reactpy .Ref (None )
401
- effect_run_count = reactpy .Ref (0 )
397
+ effect_ran = WaitForEvent ()
398
+ run_count = 0
402
399
403
400
first_value = 1
404
401
second_value = 2
@@ -410,29 +407,31 @@ def ComponentWithMemoizedEffect():
410
407
411
408
@reactpy .hooks .use_effect (dependencies = [state ])
412
409
def effect ():
413
- effect_run_count .current += 1
410
+ nonlocal run_count
411
+ effect_ran .set ()
412
+ run_count += 1
414
413
415
414
return reactpy .html .div ()
416
415
417
416
async with reactpy .Layout (ComponentWithMemoizedEffect ()) as layout :
418
417
await layout .render ()
419
418
420
- assert effect_run_count .current == 1
419
+ await effect_ran .wait ()
420
+ effect_ran .clear ()
421
421
422
422
component_hook .latest .schedule_render ()
423
423
await layout .render ()
424
424
425
- assert effect_run_count .current == 1
426
-
427
425
set_state_callback .current (second_value )
428
426
await layout .render ()
429
427
430
- assert effect_run_count .current == 2
428
+ await effect_ran .wait ()
429
+ effect_ran .clear ()
431
430
432
431
component_hook .latest .schedule_render ()
433
432
await layout .render ()
434
433
435
- assert effect_run_count . current == 2
434
+ assert run_count == 2
436
435
437
436
438
437
async def test_memoized_effect_cleanup_only_triggered_before_new_effect ():
@@ -474,7 +473,7 @@ def cleanup():
474
473
475
474
476
475
async def test_use_async_effect ():
477
- effect_ran = asyncio . Event ()
476
+ effect_ran = WaitForEvent ()
478
477
479
478
@reactpy .component
480
479
def ComponentWithAsyncEffect ():
@@ -486,13 +485,13 @@ async def effect():
486
485
487
486
async with reactpy .Layout (ComponentWithAsyncEffect ()) as layout :
488
487
await layout .render ()
489
- await asyncio . wait_for ( effect_ran .wait (), 1 )
488
+ await effect_ran .wait ()
490
489
491
490
492
491
async def test_use_async_effect_cleanup ():
493
492
component_hook = HookCatcher ()
494
- effect_ran = asyncio . Event ()
495
- cleanup_ran = asyncio . Event ()
493
+ effect_ran = WaitForEvent ()
494
+ cleanup_ran = WaitForEvent ()
496
495
497
496
@reactpy .component
498
497
@component_hook .capture
@@ -516,10 +515,10 @@ async def effect():
516
515
517
516
async def test_use_async_effect_cancel (caplog ):
518
517
component_hook = HookCatcher ()
519
- effect_ran = asyncio . Event ()
520
- effect_was_cancelled = asyncio . Event ()
518
+ effect_ran = WaitForEvent ()
519
+ effect_was_cancelled = WaitForEvent ()
521
520
522
- event_that_never_occurs = asyncio . Event ()
521
+ event_that_never_occurs = WaitForEvent ()
523
522
524
523
@reactpy .component
525
524
@component_hook .capture
@@ -562,7 +561,7 @@ def bad_effect():
562
561
563
562
return reactpy .html .div ()
564
563
565
- with assert_reactpy_did_log (match_message = r"Layout post-render effect .* failed " ):
564
+ with assert_reactpy_did_log (match_message = r"Error while applying effect " ):
566
565
async with reactpy .Layout (ComponentWithEffect ()) as layout :
567
566
await layout .render () # no error
568
567
@@ -588,7 +587,7 @@ def bad_cleanup():
588
587
return reactpy .html .div ()
589
588
590
589
with assert_reactpy_did_log (
591
- match_message = r"Pre-unmount effect .*? failed " ,
590
+ match_message = r"Error while cleaning up effect " ,
592
591
error_type = ValueError ,
593
592
):
594
593
async with reactpy .Layout (OuterComponent ()) as layout :
@@ -845,7 +844,7 @@ def bad_callback():
845
844
846
845
async def test_use_effect_automatically_infers_closure_values ():
847
846
set_count = reactpy .Ref ()
848
- did_effect = asyncio . Event ()
847
+ did_effect = WaitForEvent ()
849
848
850
849
@reactpy .component
851
850
def CounterWithEffect ():
@@ -873,7 +872,7 @@ def some_effect_that_uses_count():
873
872
874
873
async def test_use_memo_automatically_infers_closure_values ():
875
874
set_count = reactpy .Ref ()
876
- did_memo = asyncio . Event ()
875
+ did_memo = WaitForEvent ()
877
876
878
877
@reactpy .component
879
878
def CounterWithEffect ():
@@ -1001,13 +1000,16 @@ async def test_error_in_layout_effect_cleanup_is_gracefully_handled():
1001
1000
def ComponentWithEffect ():
1002
1001
@reactpy .hooks .use_effect (dependencies = None ) # always run
1003
1002
def bad_effect ():
1004
- msg = "The error message"
1005
- raise ValueError (msg )
1003
+ def bad_cleanup ():
1004
+ msg = "The error message"
1005
+ raise ValueError (msg )
1006
+
1007
+ return bad_cleanup
1006
1008
1007
1009
return reactpy .html .div ()
1008
1010
1009
1011
with assert_reactpy_did_log (
1010
- match_message = r"post-render effect .*? failed " ,
1012
+ match_message = r"Error while cleaning up effect " ,
1011
1013
error_type = ValueError ,
1012
1014
match_error = "The error message" ,
1013
1015
):
@@ -1211,12 +1213,12 @@ def incr_effect_count():
1211
1213
1212
1214
async with reactpy .Layout (SomeComponent ()) as layout :
1213
1215
await layout .render ()
1214
- assert effect_count .current == 1
1216
+ await poll ( lambda : effect_count .current ). until_equals ( 1 )
1215
1217
value .current = "string" # new string instance but same value
1216
1218
hook .latest .schedule_render ()
1217
1219
await layout .render ()
1218
1220
# effect does not trigger
1219
- assert effect_count .current == 1
1221
+ await poll ( lambda : effect_count .current ). until_equals ( 1 )
1220
1222
1221
1223
1222
1224
async def test_use_state_named_tuple ():
@@ -1232,28 +1234,3 @@ def some_component():
1232
1234
state .current .set_value (2 )
1233
1235
await layout .render ()
1234
1236
assert state .current .value == 2
1235
-
1236
-
1237
- async def test_error_in_component_effect_cleanup_is_gracefully_handled ():
1238
- component_hook = HookCatcher ()
1239
-
1240
- @reactpy .component
1241
- @component_hook .capture
1242
- def ComponentWithEffect ():
1243
- hook = current_hook ()
1244
-
1245
- def bad_effect ():
1246
- raise ValueError ("The error message" )
1247
-
1248
- hook .add_effect (COMPONENT_DID_RENDER_EFFECT , bad_effect )
1249
- return reactpy .html .div ()
1250
-
1251
- with assert_reactpy_did_log (
1252
- match_message = "Component post-render effect .*? failed" ,
1253
- error_type = ValueError ,
1254
- match_error = "The error message" ,
1255
- ):
1256
- async with reactpy .Layout (ComponentWithEffect ()) as layout :
1257
- await layout .render ()
1258
- component_hook .latest .schedule_render ()
1259
- await layout .render () # no error
0 commit comments