11"""
2- tests/test_events .py
3- ====================
2+ tests/test_interactive/test_callbacks .py
3+ ========================================
44
55Tests for the unified object-level callback system.
66
7+ Covers:
78 * Event dataclass – event_type / source / data / attribute forwarding
89 * CallbackRegistry – connect / disconnect / fire (event_type dispatch only)
910 * Plot2D / Plot1D / PlotMesh / Plot3D – on_changed / on_release / on_click
10- * Widget-level – @wid.on_changed / @wid.on_release / @wid.on_click
1111 * Figure._on_event – JSON routing to widget + plot callbacks
1212 * Practical patterns
13- * Interactive FFT example – unit tests (pure Python, no browser)
13+
14+ Widget-level callback and event-dispatch integration tests live in
15+ ``test_widgets.py``.
1416"""
1517
1618from __future__ import annotations
@@ -457,65 +459,34 @@ def cb(event): fired.append(event)
457459
458460
459461# ─────────────────────────────────────────────────────────────────────────────
460- # 7. Widget-level callbacks (@wid.on_changed / on_release / on_click)
462+ # 7. Figure._on_event routing
461463# ─────────────────────────────────────────────────────────────────────────────
462464
463- class TestWidgetLevelCallbacks :
465+ class TestFigureEventRouting :
464466
465- def test_on_changed_fires_on_drag_frame (self ):
467+ def test_dispatch_reaches_plot_callbacks (self ):
466468 fig , ax = apl .subplots (1 , 1 )
467469 v = ax .imshow (np .zeros ((32 , 32 )))
468- wid = v .add_widget ("circle" )
469470 fired = []
470471
471- @wid . on_changed
472+ @v . on_release
472473 def cb (event ): fired .append (event )
473474
474- _simulate_js_event (fig , v , "on_changed" , widget_id = wid , cx = 10.0 , cy = 20.0 )
475+ _simulate_js_event (fig , v , "on_release" , cx = 10.0 , cy = 20.0 )
475476 assert len (fired ) == 1
476477 assert fired [0 ].cx == pytest .approx (10.0 )
477- assert fired [0 ].source is wid
478-
479- def test_on_release_fires_on_mouseup (self ):
480- fig , ax = apl .subplots (1 , 1 )
481- v = ax .imshow (np .zeros ((32 , 32 )))
482- wid = v .add_widget ("rectangle" )
483- fired = []
484-
485- @wid .on_release
486- def cb (event ): fired .append (event )
487-
488- _simulate_js_event (fig , v , "on_release" , widget_id = wid ,
489- x = 5.0 , y = 5.0 , w = 20.0 , h = 20.0 )
490- assert len (fired ) == 1
491- assert fired [0 ].event_type == "on_release"
492-
493- def test_on_click_fires_without_state_change (self ):
494- """on_click must fire even when no field values changed."""
495- fig , ax = apl .subplots (1 , 1 )
496- v = ax .imshow (np .zeros ((32 , 32 )))
497- wid = v .add_widget ("crosshair" , cx = 16.0 , cy = 16.0 )
498- fired = []
499-
500- @wid .on_click
501- def cb (event ): fired .append (event )
502478
503- _simulate_js_event (fig , v , "on_click" , widget_id = wid , cx = 16.0 , cy = 16.0 )
504- assert len (fired ) == 1
505-
506- def test_on_changed_not_fire_for_release (self ):
479+ def test_dispatch_with_widget_id_updates_widget (self ):
507480 fig , ax = apl .subplots (1 , 1 )
508481 v = ax .imshow (np .zeros ((32 , 32 )))
509- wid = v .add_widget ("circle" )
510- fired = []
511-
512- @wid .on_changed
513- def cb (event ): fired .append (event )
482+ wid = v .add_widget ("circle" , cx = 0.0 , cy = 0.0 )
514483
515- _simulate_js_event (fig , v , "on_release " , widget_id = wid , cx = 5.0 , cy = 5.0 )
516- assert fired == []
484+ _simulate_js_event (fig , v , "on_changed " , widget_id = wid , cx = 5.0 )
485+ assert wid . cx == pytest . approx ( 5.0 )
517486
518- def test_widget_and_plot_both_fire (self ):
487+ def test_widget_and_plot_callbacks_both_fire (self ):
488+ """A single JS event bearing a widget_id fires both the widget-level
489+ and the plot-level on_release callbacks, with the widget as source."""
519490 fig , ax = apl .subplots (1 , 1 )
520491 v = ax .imshow (np .zeros ((32 , 32 )))
521492 wid = v .add_widget ("circle" )
@@ -532,84 +503,6 @@ def pc(event): p_fired.append(event)
532503 assert w_fired [0 ].source is wid
533504 assert p_fired [0 ].source is wid
534505
535- def test_widget_state_updated_after_js_event (self ):
536- fig , ax = apl .subplots (1 , 1 )
537- v = ax .imshow (np .zeros ((32 , 32 )))
538- wid = v .add_widget ("rectangle" , x = 0.0 , y = 0.0 , w = 10.0 , h = 10.0 )
539-
540- _simulate_js_event (fig , v , "on_changed" , widget_id = wid ,
541- x = 50.0 , y = 60.0 , w = 20.0 , h = 20.0 )
542- assert wid .x == pytest .approx (50.0 )
543- assert wid .y == pytest .approx (60.0 )
544-
545- def test_no_echo_from_python_push (self ):
546- fig , ax = apl .subplots (1 , 1 )
547- v = ax .imshow (np .zeros ((32 , 32 )))
548- wid = v .add_widget ("circle" )
549- fired = []
550-
551- @wid .on_changed
552- def cb (event ): fired .append (event )
553-
554- fig ._on_event ({"new" : json .dumps ({
555- "source" : "python" , "panel_id" : v ._id ,
556- "widget_id" : wid ._id , "cx" : 99.0
557- })})
558- assert fired == []
559-
560- def test_1d_vline_widget_event (self ):
561- fig , ax = apl .subplots (1 , 1 )
562- v = ax .plot (np .zeros (64 ))
563- wid = v .add_vline_widget (x = 10.0 )
564- fired = []
565-
566- @wid .on_changed
567- def cb (event ): fired .append (event )
568-
569- _simulate_js_event (fig , v , "on_changed" , widget_id = wid , x = 30.0 )
570- assert len (fired ) == 1
571- assert fired [0 ].x == pytest .approx (30.0 )
572-
573- def test_1d_range_widget_event (self ):
574- fig , ax = apl .subplots (1 , 1 )
575- v = ax .plot (np .zeros (64 ))
576- wid = v .add_range_widget (x0 = 5.0 , x1 = 15.0 )
577- fired = []
578-
579- @wid .on_release
580- def cb (event ): fired .append (event )
581-
582- _simulate_js_event (fig , v , "on_release" , widget_id = wid , x0 = 8.0 , x1 = 20.0 )
583- assert len (fired ) == 1
584- assert fired [0 ].x0 == pytest .approx (8.0 )
585-
586-
587- # ─────────────────────────────────────────────────────────────────────────────
588- # 8. Figure._on_event routing
589- # ─────────────────────────────────────────────────────────────────────────────
590-
591- class TestFigureOnEvent :
592-
593- def test_dispatch_reaches_plot_callbacks (self ):
594- fig , ax = apl .subplots (1 , 1 )
595- v = ax .imshow (np .zeros ((32 , 32 )))
596- fired = []
597-
598- @v .on_release
599- def cb (event ): fired .append (event )
600-
601- _simulate_js_event (fig , v , "on_release" , cx = 10.0 , cy = 20.0 )
602- assert len (fired ) == 1
603- assert fired [0 ].cx == pytest .approx (10.0 )
604-
605- def test_dispatch_with_widget_id_updates_widget (self ):
606- fig , ax = apl .subplots (1 , 1 )
607- v = ax .imshow (np .zeros ((32 , 32 )))
608- wid = v .add_widget ("circle" , cx = 0.0 , cy = 0.0 )
609-
610- _simulate_js_event (fig , v , "on_changed" , widget_id = wid , cx = 5.0 )
611- assert wid .cx == pytest .approx (5.0 )
612-
613506 def test_dispatch_wrong_panel_id_ignored (self ):
614507 fig , ax = apl .subplots (1 , 1 )
615508 v = ax .imshow (np .zeros ((32 , 32 )))
@@ -690,7 +583,7 @@ def cb(event): fired.append(event)
690583
691584
692585# ─────────────────────────────────────────────────────────────────────────────
693- # 9 . Practical patterns
586+ # 8 . Practical patterns
694587# ─────────────────────────────────────────────────────────────────────────────
695588
696589class TestPracticalPatterns :
0 commit comments