1010SPDX-License-Identifier: GPL-3.0-or-later
1111"""
1212
13+ import tkinter as tk
1314from unittest .mock import Mock , patch
1415
1516import pytest
@@ -907,15 +908,15 @@ def mock_successful_download(callback) -> ParDict:
907908 for name in expected_defaults :
908909 assert name in result
909910
910- def test_user_receives_none_when_no_parameters_downloaded (
911+ def test_user_receives_empty_parameter_defaults_when_no_parameters_downloaded (
911912 self , mock_tkinter_context , configured_flight_controller : Mock
912913 ) -> None :
913914 """
914- User receives None when no parameters have been downloaded.
915+ User receives empty parameter defaults when no parameters have been downloaded.
915916
916917 GIVEN: A window that has not completed parameter download
917918 WHEN: The user requests parameter default values
918- THEN: The method should return None
919+ THEN: An empty but valid parameter dictionary is returned
919920 AND: No errors should occur
920921 """
921922 # Given
@@ -936,61 +937,6 @@ def test_user_receives_none_when_no_parameters_downloaded(
936937 assert isinstance (result , ParDict )
937938 assert len (result ) == 0
938939
939- def test_user_sees_accurate_progress_tracking_during_parameter_download (
940- self , mock_tkinter_context , configured_flight_controller : Mock
941- ) -> None :
942- """
943- User sees accurate progress tracking during parameter download.
944-
945- GIVEN: A flight controller with a known number of parameters
946- WHEN: The user starts parameter download
947- THEN: Progress should start at 0%
948- AND: Progress should increment accurately with each parameter
949- AND: Progress should reach 100% upon completion
950- """
951- # Given
952- stack , patches = mock_tkinter_context ()
953- total_parameters = 10
954- progress_history = []
955-
956- def mock_tracked_download (callback ) -> ParDict :
957- for current in range (1 , total_parameters + 1 ):
958- if callback :
959- callback (current , total_parameters )
960- progress_history .append ((current , total_parameters ))
961- # Return a ParDict with test parameters
962- result = ParDict ()
963- for i in range (total_parameters ):
964- result [f"PARAM_{ i } " ] = Par (float (i ), f"Test param { i } " )
965- return result
966-
967- with stack :
968- for patch_obj in patches :
969- stack .enter_context (patch_obj )
970-
971- window = FlightControllerInfoWindow .__new__ (FlightControllerInfoWindow )
972- window .presenter = FlightControllerInfoPresenter (configured_flight_controller )
973- window .presenter .download_parameters = Mock (side_effect = mock_tracked_download )
974- window .root = Mock ()
975- window .progress_bar = Mock ()
976- window .progress_label = Mock ()
977- window .progress_frame = Mock ()
978-
979- # Mock the update_progress_bar method to track calls
980- progress_calls = []
981- window .update_progress_bar = Mock (side_effect = lambda c , m : progress_calls .append ((c , m )))
982-
983- # When
984- window ._download_flight_controller_parameters ()
985-
986- # Then - The download method was called
987- window .presenter .download_parameters .assert_called_once ()
988-
989- # And progress tracking would be accurate if callback was used
990- # (This tests the structure is in place for progress reporting)
991- assert hasattr (window , "update_progress_bar" )
992- assert callable (window .update_progress_bar )
993-
994940
995941class TestFlightControllerErrorHandling :
996942 """Test error handling scenarios for flight controller operations in BDD style."""
@@ -1075,3 +1021,205 @@ def test_user_sees_informative_display_when_flight_controller_info_unavailable(
10751021
10761022 # And no information rows are created for empty info
10771023 assert mock_entry .call_count == 0
1024+
1025+
1026+ class TestFlightControllerInfoWindowInitialization : # pylint: disable=too-few-public-methods
1027+ """Test window initialization and setup behavior in BDD style."""
1028+
1029+ def test_user_experiences_proper_window_initialization_and_setup (
1030+ self , mock_tkinter_context , configured_flight_controller : Mock
1031+ ) -> None :
1032+ """
1033+ Test that users experience proper window initialization and setup.
1034+
1035+ GIVEN: A flight controller is available and UI environment is ready
1036+ WHEN: A user creates a FlightControllerInfoWindow
1037+ THEN: The window is properly initialized with correct title, geometry, and components
1038+ AND: Flight controller information is logged
1039+ AND: Parameter download is scheduled
1040+ """
1041+ # Given
1042+ stack , patches = mock_tkinter_context ()
1043+
1044+ with stack :
1045+ # Start all patches for UI isolation
1046+ for patch_obj in patches :
1047+ stack .enter_context (patch_obj )
1048+
1049+ mock_root = Mock ()
1050+ mock_mainloop = Mock ()
1051+
1052+ with (
1053+ patch ("tkinter.ttk.Frame" ),
1054+ patch ("tkinter.ttk.Progressbar" ),
1055+ patch ("tkinter.ttk.Label" ),
1056+ patch .object (FlightControllerInfoWindow , "_create_info_display" ) as mock_create_display ,
1057+ patch .object (FlightControllerInfoWindow , "_download_flight_controller_parameters" ) as mock_download ,
1058+ patch ("tkinter.Tk" , return_value = mock_root ) as mock_tk ,
1059+ patch .object (mock_root , "mainloop" , mock_mainloop ),
1060+ patch .object (mock_root , "after" ) as mock_after ,
1061+ ):
1062+ # When
1063+ window = FlightControllerInfoWindow (configured_flight_controller )
1064+
1065+ # Then - Window is properly initialized
1066+ mock_tk .assert_called_once ()
1067+ assert window .root == mock_root
1068+ assert isinstance (window .presenter , FlightControllerInfoPresenter )
1069+ assert window .presenter .flight_controller == configured_flight_controller
1070+
1071+ # And window properties are set correctly
1072+ mock_root .title .assert_called_once ()
1073+ mock_root .geometry .assert_called_once_with ("500x420" )
1074+
1075+ # And UI components are created
1076+ mock_create_display .assert_called_once ()
1077+
1078+ # And flight controller info is logged
1079+ configured_flight_controller .info .log_flight_controller_info .assert_called_once ()
1080+
1081+ # And parameter download is scheduled
1082+ mock_after .assert_called_once_with (50 , mock_download )
1083+
1084+ # And mainloop is called to start the UI
1085+ mock_mainloop .assert_called_once ()
1086+
1087+
1088+ class TestFlightControllerInfoProgressBarErrorHandling :
1089+ """Test progress bar error handling in BDD style."""
1090+
1091+ def test_user_sees_graceful_handling_when_progress_bar_widgets_are_destroyed (
1092+ self , mock_tkinter_context , configured_flight_controller : Mock
1093+ ) -> None :
1094+ """
1095+ Test that users see graceful handling when progress bar widgets are destroyed.
1096+
1097+ GIVEN: A flight controller info window with progress bar widgets that get destroyed
1098+ WHEN: The system attempts to update progress
1099+ THEN: The update is safely skipped without errors
1100+ """
1101+ # Given
1102+ stack , patches = mock_tkinter_context ()
1103+
1104+ with stack :
1105+ # Start all patches for UI isolation
1106+ for patch_obj in patches :
1107+ stack .enter_context (patch_obj )
1108+
1109+ with (
1110+ patch ("tkinter.ttk.Frame" ),
1111+ patch ("tkinter.ttk.Progressbar" ),
1112+ patch ("tkinter.ttk.Label" ),
1113+ patch .object (FlightControllerInfoWindow , "_create_info_display" ),
1114+ patch .object (FlightControllerInfoWindow , "_download_flight_controller_parameters" ),
1115+ patch ("tkinter.Tk.mainloop" ),
1116+ ):
1117+ window = FlightControllerInfoWindow .__new__ (FlightControllerInfoWindow )
1118+ window .presenter = FlightControllerInfoPresenter (configured_flight_controller )
1119+
1120+ # Simulate progress bar widget being destroyed
1121+ window .progress_bar = None
1122+
1123+ # When
1124+ window .update_progress_bar (5 , 10 )
1125+
1126+ # Then - No errors occur, method returns early safely
1127+
1128+ def test_user_sees_graceful_handling_when_window_lift_fails_due_to_tcl_error (
1129+ self , mock_tkinter_context , configured_flight_controller : Mock
1130+ ) -> None :
1131+ """
1132+ Test that users see graceful handling when window lift fails due to TclError.
1133+
1134+ GIVEN: A flight controller info window where tkinter operations may fail
1135+ WHEN: The system attempts to update progress but window lift fails
1136+ THEN: The error is logged and progress update continues safely
1137+ """
1138+ # Given
1139+ stack , patches = mock_tkinter_context ()
1140+
1141+ with stack :
1142+ # Start all patches for UI isolation
1143+ for patch_obj in patches :
1144+ stack .enter_context (patch_obj )
1145+
1146+ with (
1147+ patch ("tkinter.ttk.Frame" ),
1148+ patch ("tkinter.ttk.Progressbar" ),
1149+ patch ("tkinter.ttk.Label" ),
1150+ patch .object (FlightControllerInfoWindow , "_create_info_display" ),
1151+ patch .object (FlightControllerInfoWindow , "_download_flight_controller_parameters" ),
1152+ patch ("tkinter.Tk.mainloop" ),
1153+ ):
1154+ window = FlightControllerInfoWindow .__new__ (FlightControllerInfoWindow )
1155+ window .presenter = FlightControllerInfoPresenter (configured_flight_controller )
1156+
1157+ # Set up mock widgets
1158+ mock_progress_bar = Mock ()
1159+ mock_progress_bar .__setitem__ = Mock ()
1160+ mock_progress_label = Mock ()
1161+ mock_root = Mock ()
1162+ mock_root .lift .side_effect = tk .TclError ("window not found" )
1163+
1164+ window .progress_bar = mock_progress_bar
1165+ window .progress_label = mock_progress_label
1166+ window .root = mock_root
1167+ window .progress_frame = Mock ()
1168+
1169+ # When
1170+ with patch ("logging.error" ) as mock_log_error :
1171+ window .update_progress_bar (5 , 10 )
1172+
1173+ # Then - Error is logged
1174+ mock_log_error .assert_called_once ()
1175+ assert "Lifting window:" in mock_log_error .call_args [0 ][0 ]
1176+
1177+ # And progress update does NOT continue due to the return statement in the except block
1178+ mock_progress_bar .__setitem__ .assert_not_called ()
1179+
1180+ def test_user_sees_progress_bar_hidden_when_download_completes (
1181+ self , mock_tkinter_context , configured_flight_controller : Mock
1182+ ) -> None :
1183+ """
1184+ Test that users see progress bar hidden when download completes.
1185+
1186+ GIVEN: A flight controller info window with active progress tracking
1187+ WHEN: The parameter download completes (current == max)
1188+ THEN: The progress bar is automatically hidden
1189+ """
1190+ # Given
1191+ stack , patches = mock_tkinter_context ()
1192+
1193+ with stack :
1194+ # Start all patches for UI isolation
1195+ for patch_obj in patches :
1196+ stack .enter_context (patch_obj )
1197+
1198+ with (
1199+ patch ("tkinter.ttk.Frame" ),
1200+ patch ("tkinter.ttk.Progressbar" ),
1201+ patch ("tkinter.ttk.Label" ),
1202+ patch .object (FlightControllerInfoWindow , "_create_info_display" ),
1203+ patch .object (FlightControllerInfoWindow , "_download_flight_controller_parameters" ),
1204+ patch ("tkinter.Tk.mainloop" ),
1205+ ):
1206+ window = FlightControllerInfoWindow .__new__ (FlightControllerInfoWindow )
1207+ window .presenter = FlightControllerInfoPresenter (configured_flight_controller )
1208+
1209+ # Set up mock widgets
1210+ mock_progress_bar = Mock ()
1211+ mock_progress_bar .__setitem__ = Mock ()
1212+ mock_progress_label = Mock ()
1213+ mock_progress_frame = Mock ()
1214+ mock_root = Mock ()
1215+
1216+ window .progress_bar = mock_progress_bar
1217+ window .progress_label = mock_progress_label
1218+ window .progress_frame = mock_progress_frame
1219+ window .root = mock_root
1220+
1221+ # When - Download completes
1222+ window .update_progress_bar (10 , 10 )
1223+
1224+ # Then - Progress bar is hidden
1225+ mock_progress_frame .pack_forget .assert_called_once ()
0 commit comments