11import sys
2+ from types import SimpleNamespace
23from unittest .mock import MagicMock , patch
34
45import pandas as pd
56import pytest
67
78from mols2grid import MolGrid
9+ from mols2grid .select import register
810from mols2grid .utils import is_running_within_marimo
911from mols2grid .widget import MolGridWidget
1012
1113
1214@pytest .fixture
13- def mock_marimo_module ():
14- with patch .dict (sys .modules , {"marimo" : MagicMock ()}):
15- yield
15+ def mock_marimo_module (monkeypatch : pytest .MonkeyPatch ):
16+ monkeypatch .setattr (sys , "modules" , {** sys .modules , "marimo" : MagicMock ()})
1617
1718
1819@pytest .mark .usefixtures ("mock_marimo_module" )
@@ -21,11 +22,9 @@ def test_is_running_within_marimo_true():
2122
2223
2324def test_is_running_within_marimo_false ():
24- # Ensure marimo is not in sys.modules for this test
25- with patch .dict (sys .modules ):
26- if "marimo" in sys .modules :
27- del sys .modules ["marimo" ]
28- assert is_running_within_marimo () is False
25+ if "marimo" in sys .modules :
26+ assert isinstance (sys .modules ["marimo" ], MagicMock )
27+ assert is_running_within_marimo () is False
2928
3029
3130@pytest .fixture
@@ -52,13 +51,6 @@ def test_display_in_marimo(grid_fixture):
5251 assert result == mock_anywidget .return_value
5352
5453
55- @pytest .mark .usefixtures ("mock_marimo_module" )
56- def test_get_marimo_selection_before_rendering_raises (grid_fixture ):
57- _ , mg = grid_fixture
58- with pytest .raises (RuntimeError , match = "run the `display` method first" ):
59- mg .get_marimo_selection ()
60-
61-
6254@pytest .mark .usefixtures ("mock_marimo_module" )
6355def test_get_selection_state_inside_marimo (grid_fixture ):
6456 _ , mg = grid_fixture
@@ -71,57 +63,53 @@ def test_get_selection_state_inside_marimo(grid_fixture):
7163 "marimo.state" , return_value = (mock_get_state , mock_set_state )
7264 ) as mock_state :
7365 # Call get_marimo_selection
74- state_getter = mg . get_marimo_selection ()
66+ state_getter , _ = register . link_marimo_state ()
7567
7668 # Check if marimo.state was called with empty list
77- mock_state .assert_called_once_with ([])
78-
79- # Check if _marimo_hooked is set
80- assert getattr (mg .widget , "_marimo_hooked" , False ) is True
69+ mock_state .assert_called_once_with ({})
8170
8271 # Verify return value
8372 assert state_getter == mock_get_state
8473
8574
86- def test_get_selection_state_outside_marimo (grid_fixture ):
87- _ , mg = grid_fixture
88-
75+ def test_get_selection_state_outside_marimo ():
8976 # Ensure marimo is not in sys.modules
9077 with patch .dict (sys .modules ):
9178 if "marimo" in sys .modules :
9279 del sys .modules ["marimo" ]
9380
9481 with pytest .raises (RuntimeError , match = "only available in a marimo notebook" ):
95- mg . get_marimo_selection ()
82+ register . link_marimo_state ()
9683
9784
9885@pytest .mark .usefixtures ("mock_marimo_module" )
99- def test_selection_state_update_logic (grid_fixture ):
86+ def test_selection_state_update_logic (grid_fixture , monkeypatch : pytest . MonkeyPatch ):
10087 _ , mg = grid_fixture
101- mg .render ()
102-
10388 mock_set_state = MagicMock ()
10489 with (
10590 patch ("marimo.state" , return_value = (MagicMock (), mock_set_state )),
106- patch .object (mg . widget , "observe" ) as mock_observe ,
91+ patch .object (MolGridWidget , "observe" ) as mock_observe ,
10792 ):
10893 # Inspect the observe call to capture the callback
109- mg .get_marimo_selection ()
94+ register .link_marimo_state ()
95+ mock_callback = MagicMock (wraps = register .CALLBACKS [- 1 ])
96+ monkeypatch .setattr (register , "CALLBACKS" , [mock_callback ])
11097
98+ mg .render ()
11199 # Verify observe was called
112100 mock_observe .assert_called ()
113- args , _ = mock_observe .call_args
114- callback = args [0 ]
115101
116102 # Simulate event with valid selection
117- # The widget returns a string representation of a dict
118- new_selection = {1 : "C" , 2 : "CC" }
119- event = {"new" : str (new_selection )}
120-
121- callback (event )
122- mock_set_state .assert_called_with ([1 , 2 ])
123-
124- # Test invalid input (should pass silently)
125- mock_set_state .reset_mock ()
126- callback ({"new" : "invalid json" })
127- mock_set_state .assert_not_called ()
103+ event_values = {1 : "C" , 2 : "CC" }
104+ event = SimpleNamespace (new = str (event_values ))
105+ register .selection_updated ("default" , event )
106+
107+ # check callback was called with expected selection
108+ mock_callback .assert_called_with ("default" , event_values )
109+
110+ # check inner lambda works as expected: given a current state with
111+ # mol 42 selected, and new state where only 1 and 2 are selected,
112+ # 42 should disappear and only 1, 2 remain
113+ lambda_setter = mock_set_state .call_args [0 ][0 ]
114+ result = lambda_setter ({"default" : [42 ]})
115+ assert result == {"default" : [1 , 2 ]}
0 commit comments