@@ -142,11 +142,15 @@ def test_update_tile_styles(self, mock_run_js):
142142 # during the test, so we're not checking for this call
143143
144144 @patch ("src.core.game_logic.ui" )
145- @patch ("src.core.game_logic.header_label" )
146- def test_close_game (self , mock_header_label , mock_ui ):
145+ def test_close_game (self , mock_ui ):
147146 """Test closing the game functionality"""
148147 from src .config .constants import CLOSED_HEADER_TEXT
149- from src .core .game_logic import board_views , close_game , is_game_closed
148+ from src .core .game_logic import (
149+ board_views ,
150+ close_game ,
151+ current_header_text ,
152+ is_game_closed ,
153+ )
150154
151155 # Mock board views
152156 mock_container1 = MagicMock ()
@@ -159,6 +163,7 @@ def test_close_game(self, mock_header_label, mock_ui):
159163 board_views .copy () if hasattr (board_views , "copy" ) else {}
160164 )
161165 original_is_game_closed = is_game_closed
166+ original_header_text = current_header_text
162167
163168 try :
164169 # Set up the board_views global
@@ -171,38 +176,31 @@ def test_close_game(self, mock_header_label, mock_ui):
171176 )
172177
173178 # Mock controls_row
179+ import src .core .game_logic
174180 from src .core .game_logic import controls_row
175181
176- controls_row = MagicMock ()
182+ src . core . game_logic . controls_row = MagicMock ()
177183
178184 # Ensure is_game_closed is False initially
179- from src .core .game_logic import is_game_closed
180-
181- globals ()["is_game_closed" ] = False
185+ src .core .game_logic .is_game_closed = False
182186
183187 # Call the close_game function
184188 close_game ()
185189
186190 # Verify game is marked as closed
187- from src .core .game_logic import is_game_closed
188-
189- self .assertTrue (is_game_closed )
191+ self .assertTrue (src .core .game_logic .is_game_closed )
190192
191- # Verify header text is updated
192- mock_header_label .set_text .assert_called_once_with (CLOSED_HEADER_TEXT )
193- mock_header_label .update .assert_called_once ()
193+ # Verify header text is updated via bound variable
194+ self .assertEqual (
195+ src .core .game_logic .current_header_text , CLOSED_HEADER_TEXT
196+ )
194197
195198 # Verify containers are hidden
196199 mock_container1 .style .assert_called_once_with ("display: none;" )
197200 mock_container1 .update .assert_called_once ()
198201 mock_container2 .style .assert_called_once_with ("display: none;" )
199202 mock_container2 .update .assert_called_once ()
200203
201- # Note: In the new structure, the controls_row clear might not be called directly
202- # or might be called differently, so we're not checking this
203-
204- # We no longer check for broadcast as it may not be available in newer versions
205-
206204 # Verify notification is shown
207205 mock_ui .notify .assert_called_once_with (
208206 "Game has been closed" , color = "red" , duration = 3
@@ -211,9 +209,10 @@ def test_close_game(self, mock_header_label, mock_ui):
211209 # Restore original values
212210 board_views .clear ()
213211 board_views .update (original_board_views )
214- from src .core .game_logic import is_game_closed
212+ import src .core .game_logic
215213
216- globals ()["is_game_closed" ] = original_is_game_closed
214+ src .core .game_logic .is_game_closed = original_is_game_closed
215+ src .core .game_logic .current_header_text = original_header_text
217216
218217 @patch ("main.ui.run_javascript" )
219218 def test_sync_board_state_when_game_closed (self , mock_run_js ):
@@ -468,83 +467,41 @@ def test_stream_header_update_when_game_closed(self, mock_broadcast):
468467 # Restore original state
469468 main .is_game_closed = original_is_game_closed
470469 main .header_label = original_header_label
471-
472- @patch ("src.core.game_logic.ui" )
473- def test_header_update_when_broadcast_fails (self , mock_ui ):
470+
471+ def test_broadcast_fallback_on_failure (self ):
474472 """
475- Test that the header is correctly updated when ui.broadcast() raises an AttributeError.
476- This simulates the scenario where newer versions of NiceGUI don't support broadcast.
473+ Test that sync_board_state is called as a fallback when broadcast fails.
474+ This is what we really care about testing - that manually triggering sync
475+ happens when broadcast is unavailable.
477476 """
478- from src .config .constants import CLOSED_HEADER_TEXT
479- from src .core .game_logic import board_views , close_game , is_game_closed
480-
481- # Mock board views
482- mock_home_container = MagicMock ()
483- mock_stream_container = MagicMock ()
484- mock_buttons_home = {}
485- mock_buttons_stream = {}
486-
487- # Mock header labels
488- mock_home_header = MagicMock ()
489- mock_stream_header = MagicMock ()
490-
491- # Save original state
492- original_board_views = board_views .copy () if hasattr (board_views , "copy" ) else {}
493- original_is_game_closed = is_game_closed
494-
495- # Save and restore the header_label global variable
496- import src .core .game_logic
497- original_header_label = src .core .game_logic .header_label
498-
499- try :
500- # Set up board views dictionary
501- board_views .clear ()
502- board_views .update ({
503- "home" : (mock_home_container , mock_buttons_home ),
504- "stream" : (mock_stream_container , mock_buttons_stream ),
505- })
506-
507- # Set up initial state
508- src .core .game_logic .is_game_closed = False
509- src .core .game_logic .header_label = mock_home_header
477+ # Mock the necessary components
478+ with (
479+ patch ("src.core.game_logic.ui" ) as mock_ui ,
480+ patch ("src.ui.sync.sync_board_state" ) as mock_sync ,
481+ ):
510482
511- # Make broadcast raise AttributeError to simulate newer NiceGUI versions
512- mock_ui .broadcast .side_effect = AttributeError ("'module' object has no attribute 'broadcast' " )
483+ # Set broadcast to fail with AttributeError
484+ mock_ui .broadcast .side_effect = AttributeError ("ui.broadcast not available " )
513485
514- # Set up controls_row mock
515- src .core .game_logic . controls_row = MagicMock ()
486+ # Call close_game which should handle the broadcast failure
487+ from src .core .game_logic import close_game
516488
517- # Close the game from home view
518489 close_game ()
519490
520- # Check home header was updated
521- mock_home_header .set_text .assert_called_with (CLOSED_HEADER_TEXT )
522- mock_home_header .update .assert_called ()
491+ # Verify sync_board_state was called as fallback
492+ mock_sync .assert_called_once ()
523493
524- # Verify game is marked as closed
525- self .assertTrue (src .core .game_logic .is_game_closed )
494+ # Reset mocks
495+ mock_ui .reset_mock ()
496+ mock_sync .reset_mock ()
526497
527- # Reset header mock and switch to stream view
528- mock_home_header .reset_mock ()
529- src .core .game_logic .header_label = mock_stream_header
498+ # Also test the reopen_game function
499+ from src .core .game_logic import reopen_game
530500
531- # Run sync_board_state to simulate a new client connecting
532- from src .ui .sync import sync_board_state
533-
534- # Mock ui in sync_board_state and make sure it sees the same is_game_closed state
535- with patch ("src.ui.sync.ui" ), patch ("src.ui.sync.is_game_closed" , src .core .game_logic .is_game_closed ), patch ("src.ui.sync.header_label" , mock_stream_header ):
536- sync_board_state ()
501+ reopen_game ()
537502
538- # Check stream header was updated correctly even though broadcast failed
539- mock_stream_header .set_text .assert_called_with (CLOSED_HEADER_TEXT )
540- mock_stream_header .update .assert_called ()
541-
542- finally :
543- # Restore original state
544- board_views .clear ()
545- board_views .update (original_board_views )
546- src .core .game_logic .is_game_closed = original_is_game_closed
547- src .core .game_logic .header_label = original_header_label
503+ # Verify sync_board_state was called as fallback
504+ mock_sync .assert_called_once ()
548505
549506
550507if __name__ == "__main__" :
0 commit comments