@@ -468,6 +468,83 @@ def test_stream_header_update_when_game_closed(self, mock_broadcast):
468468 # Restore original state
469469 main .is_game_closed = original_is_game_closed
470470 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 ):
474+ """
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.
477+ """
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
510+
511+ # Make broadcast raise AttributeError to simulate newer NiceGUI versions
512+ mock_ui .broadcast .side_effect = AttributeError ("'module' object has no attribute 'broadcast'" )
513+
514+ # Set up controls_row mock
515+ src .core .game_logic .controls_row = MagicMock ()
516+
517+ # Close the game from home view
518+ close_game ()
519+
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 ()
523+
524+ # Verify game is marked as closed
525+ self .assertTrue (src .core .game_logic .is_game_closed )
526+
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
530+
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 ()
537+
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
471548
472549
473550if __name__ == "__main__" :
0 commit comments