|
12 | 12 |
|
13 | 13 | import tkinter as tk
|
14 | 14 | from argparse import ArgumentParser
|
| 15 | +from tkinter import ttk |
15 | 16 | from typing import Union, get_args, get_origin
|
16 | 17 | from unittest.mock import MagicMock, patch
|
17 | 18 |
|
@@ -275,69 +276,202 @@ def test_user_can_configure_different_log_levels_for_debugging(self) -> None:
|
275 | 276 | class TestDataValidationWorkflows:
|
276 | 277 | """Test user workflows for data validation."""
|
277 | 278 |
|
278 |
| - def test_user_sees_valid_data_when_components_are_properly_configured( |
279 |
| - self, configured_editor: ComponentEditorWindowBase |
280 |
| - ) -> None: |
| 279 | + def test_user_sees_no_errors_when_all_data_is_valid(self, editor_with_mocked_root: ComponentEditorWindowBase) -> None: |
281 | 280 | """
|
282 |
| - User receives confirmation that their component data is valid. |
| 281 | + User receives no error messages when all component data is valid. |
283 | 282 |
|
284 |
| - GIVEN: A user has properly configured vehicle components |
285 |
| - WHEN: The system validates the component data |
286 |
| - THEN: The data should be recognized as valid and complete |
| 283 | + GIVEN: A user has filled in all component fields with valid data |
| 284 | + WHEN: The system validates all entered data |
| 285 | + THEN: No error messages should be displayed and validation should pass |
287 | 286 | """
|
288 |
| - # Arrange: Editor is configured with valid data via fixture |
| 287 | + # Arrange: Set up valid entry widgets with proper data |
| 288 | + mock_entry = MagicMock(spec=ttk.Entry) |
| 289 | + mock_entry.get.return_value = "1000" |
| 290 | + |
| 291 | + mock_combobox = MagicMock(spec=ttk.Combobox) |
| 292 | + mock_combobox.get.return_value = "PWM" |
| 293 | + |
| 294 | + editor_with_mocked_root.entry_widgets = { |
| 295 | + ("Motor", "Specifications", "KV"): mock_entry, |
| 296 | + ("RC Receiver", "FC Connection", "Protocol"): mock_combobox, |
| 297 | + } |
289 | 298 |
|
290 |
| - # Act: Check data validation (simulated through data model state) |
291 |
| - is_valid = configured_editor.data_model.is_valid_component_data.return_value |
292 |
| - has_components = configured_editor.data_model.has_components.return_value |
| 299 | + # Mock data model to return valid validation |
| 300 | + editor_with_mocked_root.data_model.validate_all_data = MagicMock(return_value=(True, [])) |
293 | 301 |
|
294 |
| - # Assert: Data should be valid and components should exist |
295 |
| - assert is_valid is True |
296 |
| - assert has_components is True |
| 302 | + # Act: User triggers validation |
| 303 | + result = editor_with_mocked_root.validate_data_and_highlight_errors_in_red() |
297 | 304 |
|
298 |
| - def test_user_receives_appropriate_feedback_for_invalid_data(self, mock_filesystem: MagicMock) -> None: |
| 305 | + # Assert: No errors should be returned |
| 306 | + assert result == "" |
| 307 | + editor_with_mocked_root.data_model.validate_all_data.assert_called_once() |
| 308 | + |
| 309 | + def test_user_sees_error_highlighting_for_invalid_entry_values( |
| 310 | + self, editor_with_mocked_root: ComponentEditorWindowBase |
| 311 | + ) -> None: |
299 | 312 | """
|
300 |
| - User receives clear feedback when component data is invalid or incomplete. |
| 313 | + User sees visual feedback when entry fields contain invalid values. |
301 | 314 |
|
302 |
| - GIVEN: A user has incomplete or invalid component configuration |
303 |
| - WHEN: The system validates the component data |
304 |
| - THEN: The validation should properly identify the issues |
| 315 | + GIVEN: A user has entered invalid data in text entry fields |
| 316 | + WHEN: The system validates the data |
| 317 | + THEN: Invalid entries should be highlighted in red and error messages displayed |
305 | 318 | """
|
306 |
| - # Arrange: Create a data model that reports invalid data |
307 |
| - invalid_data_model = MagicMock(spec=ComponentDataModel) |
308 |
| - invalid_data_model.is_valid_component_data.return_value = False |
309 |
| - invalid_data_model.has_components.return_value = False |
| 319 | + # Arrange: Set up invalid entry data |
| 320 | + mock_invalid_entry = MagicMock(spec=ttk.Entry) |
| 321 | + mock_invalid_entry.get.return_value = "99999" # Invalid high value |
310 | 322 |
|
311 |
| - # Act: Create editor with invalid data |
312 |
| - ComponentEditorWindowBase.create_for_testing( |
313 |
| - version="1.0.0", local_filesystem=mock_filesystem, data_model=invalid_data_model |
| 323 | + editor_with_mocked_root.entry_widgets = { |
| 324 | + ("Motor", "Specifications", "KV"): mock_invalid_entry, |
| 325 | + } |
| 326 | + |
| 327 | + # Mock validation to return errors |
| 328 | + editor_with_mocked_root.data_model.validate_all_data = MagicMock(return_value=(False, ["KV value too high"])) |
| 329 | + editor_with_mocked_root.data_model.validate_entry_limits = MagicMock( |
| 330 | + return_value=("Value exceeds maximum limit", None) |
314 | 331 | )
|
315 | 332 |
|
316 |
| - # Assert: System should recognize invalid data |
317 |
| - assert not invalid_data_model.is_valid_component_data.return_value |
318 |
| - assert not invalid_data_model.has_components.return_value |
| 333 | + with patch("ardupilot_methodic_configurator.frontend_tkinter_component_editor_base.show_error_message") as mock_error: |
| 334 | + # Act: User triggers validation |
| 335 | + result = editor_with_mocked_root.validate_data_and_highlight_errors_in_red() |
| 336 | + |
| 337 | + # Assert: Entry should be styled as invalid and error shown |
| 338 | + mock_invalid_entry.configure.assert_called_once_with(style="entry_input_invalid.TEntry") |
| 339 | + mock_error.assert_called_once() |
| 340 | + assert result != "" |
319 | 341 |
|
320 |
| - def test_user_can_handle_edge_cases_in_data_validation(self, mock_filesystem: MagicMock) -> None: |
| 342 | + def test_user_sees_error_highlighting_for_invalid_combobox_selections( |
| 343 | + self, editor_with_mocked_root: ComponentEditorWindowBase |
| 344 | + ) -> None: |
321 | 345 | """
|
322 |
| - User can work with edge cases like valid structure but missing components. |
| 346 | + User sees visual feedback when combobox selections are invalid. |
323 | 347 |
|
324 |
| - GIVEN: A user has a valid data structure but no component data |
325 |
| - WHEN: The system validates the configuration |
326 |
| - THEN: The validation should handle this edge case appropriately |
| 348 | + GIVEN: A user has selected invalid options in combobox fields |
| 349 | + WHEN: The system validates the selections |
| 350 | + THEN: Invalid comboboxes should be highlighted in red |
327 | 351 | """
|
328 |
| - # Arrange: Create data model with valid structure but no components |
329 |
| - edge_case_data_model = MagicMock(spec=ComponentDataModel) |
330 |
| - edge_case_data_model.is_valid_component_data.return_value = True |
331 |
| - edge_case_data_model.has_components.return_value = False |
| 352 | + # Arrange: Set up invalid combobox selection |
| 353 | + mock_invalid_combobox = MagicMock(spec=ttk.Combobox) |
| 354 | + mock_invalid_combobox.get.return_value = "INVALID_PROTOCOL" |
332 | 355 |
|
333 |
| - # Act: Create editor with edge case data |
334 |
| - ComponentEditorWindowBase.create_for_testing( |
335 |
| - version="1.0.0", local_filesystem=mock_filesystem, data_model=edge_case_data_model |
336 |
| - ) |
| 356 | + editor_with_mocked_root.entry_widgets = { |
| 357 | + ("RC Receiver", "FC Connection", "Protocol"): mock_invalid_combobox, |
| 358 | + } |
337 | 359 |
|
338 |
| - # Assert: System should handle edge case appropriately |
339 |
| - assert edge_case_data_model.is_valid_component_data.return_value is True |
340 |
| - assert edge_case_data_model.has_components.return_value is False |
| 360 | + # Mock validation to return errors |
| 361 | + editor_with_mocked_root.data_model.validate_all_data = MagicMock(return_value=(False, ["Invalid protocol selected"])) |
| 362 | + editor_with_mocked_root.data_model.get_combobox_values_for_path = MagicMock(return_value=("PWM", "SBUS", "PPM")) |
| 363 | + |
| 364 | + with patch("ardupilot_methodic_configurator.frontend_tkinter_component_editor_base.show_error_message"): |
| 365 | + # Act: User triggers validation |
| 366 | + result = editor_with_mocked_root.validate_data_and_highlight_errors_in_red() |
| 367 | + |
| 368 | + # Assert: Combobox should be styled as invalid |
| 369 | + mock_invalid_combobox.configure.assert_called_once_with(style="comb_input_invalid.TCombobox") |
| 370 | + assert result != "" |
| 371 | + |
| 372 | + def test_user_sees_valid_styling_for_corrected_combobox_values( |
| 373 | + self, editor_with_mocked_root: ComponentEditorWindowBase |
| 374 | + ) -> None: |
| 375 | + """ |
| 376 | + User sees positive visual feedback when combobox values become valid. |
| 377 | +
|
| 378 | + GIVEN: A user has corrected a combobox selection to a valid value |
| 379 | + WHEN: The system validates the corrected data |
| 380 | + THEN: The combobox should be highlighted as valid |
| 381 | + """ |
| 382 | + # Arrange: Set up valid combobox selection |
| 383 | + mock_valid_combobox = MagicMock(spec=ttk.Combobox) |
| 384 | + mock_valid_combobox.get.return_value = "PWM" |
| 385 | + |
| 386 | + editor_with_mocked_root.entry_widgets = { |
| 387 | + ("RC Receiver", "FC Connection", "Protocol"): mock_valid_combobox, |
| 388 | + } |
| 389 | + |
| 390 | + # Mock validation - overall fails but this combobox is valid |
| 391 | + editor_with_mocked_root.data_model.validate_all_data = MagicMock(return_value=(False, ["Other validation error"])) |
| 392 | + editor_with_mocked_root.data_model.get_combobox_values_for_path = MagicMock(return_value=("PWM", "SBUS", "PPM")) |
| 393 | + |
| 394 | + with patch("ardupilot_methodic_configurator.frontend_tkinter_component_editor_base.show_error_message"): |
| 395 | + # Act: User triggers validation |
| 396 | + editor_with_mocked_root.validate_data_and_highlight_errors_in_red() |
| 397 | + |
| 398 | + # Assert: Combobox should be styled as valid |
| 399 | + mock_valid_combobox.configure.assert_called_once_with(style="comb_input_valid.TCombobox") |
| 400 | + |
| 401 | + def test_user_sees_limited_error_messages_when_many_errors_exist( |
| 402 | + self, editor_with_mocked_root: ComponentEditorWindowBase |
| 403 | + ) -> None: |
| 404 | + """ |
| 405 | + User sees a manageable number of error messages when many validation errors exist. |
| 406 | +
|
| 407 | + GIVEN: A user has multiple validation errors across many fields |
| 408 | + WHEN: The system validates all data |
| 409 | + THEN: Only the first 3 errors should be shown with a count of remaining errors |
| 410 | + """ |
| 411 | + # Arrange: Set up entry that will trigger validation |
| 412 | + mock_entry = MagicMock(spec=ttk.Entry) |
| 413 | + mock_entry.get.return_value = "invalid" |
| 414 | + |
| 415 | + editor_with_mocked_root.entry_widgets = { |
| 416 | + ("Motor", "Specifications", "KV"): mock_entry, |
| 417 | + } |
| 418 | + |
| 419 | + # Mock validation to return many errors |
| 420 | + many_errors = ["Error 1", "Error 2", "Error 3", "Error 4", "Error 5"] |
| 421 | + editor_with_mocked_root.data_model.validate_all_data = MagicMock(return_value=(False, many_errors)) |
| 422 | + editor_with_mocked_root.data_model.validate_entry_limits = MagicMock(return_value=("Invalid value", None)) |
| 423 | + |
| 424 | + with patch("ardupilot_methodic_configurator.frontend_tkinter_component_editor_base.show_error_message") as mock_error: |
| 425 | + # Act: User triggers validation |
| 426 | + result = editor_with_mocked_root.validate_data_and_highlight_errors_in_red() |
| 427 | + |
| 428 | + # Assert: Should show first 3 errors + count of remaining |
| 429 | + mock_error.assert_called_once() |
| 430 | + error_message = mock_error.call_args[0][1] |
| 431 | + assert "Error 1" in error_message |
| 432 | + assert "Error 2" in error_message |
| 433 | + assert "Error 3" in error_message |
| 434 | + assert "2 more errors" in error_message |
| 435 | + assert result != "" |
| 436 | + |
| 437 | + def test_user_validation_only_processes_entry_and_combobox_widgets( |
| 438 | + self, editor_with_mocked_root: ComponentEditorWindowBase |
| 439 | + ) -> None: |
| 440 | + """ |
| 441 | + User data validation only processes actual input widgets, ignoring other UI elements. |
| 442 | +
|
| 443 | + GIVEN: A user interface contains various widget types including input fields |
| 444 | + WHEN: The system validates user input data |
| 445 | + THEN: Only Entry and Combobox widgets should be included in validation |
| 446 | + """ |
| 447 | + # Arrange: Set up mixed widget types |
| 448 | + mock_entry = MagicMock(spec=ttk.Entry) |
| 449 | + mock_entry.get.return_value = "1000" |
| 450 | + |
| 451 | + mock_combobox = MagicMock(spec=ttk.Combobox) |
| 452 | + mock_combobox.get.return_value = "PWM" |
| 453 | + |
| 454 | + mock_label = MagicMock() # Non-input widget |
| 455 | + |
| 456 | + editor_with_mocked_root.entry_widgets = { |
| 457 | + ("Motor", "Specifications", "KV"): mock_entry, |
| 458 | + ("RC Receiver", "FC Connection", "Protocol"): mock_combobox, |
| 459 | + ("Some", "Label", "Widget"): mock_label, # Should be ignored |
| 460 | + } |
| 461 | + |
| 462 | + editor_with_mocked_root.data_model.validate_all_data = MagicMock(return_value=(True, [])) |
| 463 | + |
| 464 | + # Act: User triggers validation |
| 465 | + result = editor_with_mocked_root.validate_data_and_highlight_errors_in_red() |
| 466 | + |
| 467 | + # Assert: Only Entry and Combobox values should be validated |
| 468 | + expected_values = { |
| 469 | + ("Motor", "Specifications", "KV"): "1000", |
| 470 | + ("RC Receiver", "FC Connection", "Protocol"): "PWM", |
| 471 | + # Label widget should NOT be included |
| 472 | + } |
| 473 | + editor_with_mocked_root.data_model.validate_all_data.assert_called_once_with(expected_values) |
| 474 | + assert result == "" |
341 | 475 |
|
342 | 476 |
|
343 | 477 | class TestComponentDataManagementWorkflows:
|
@@ -1506,36 +1640,6 @@ def test_user_does_not_see_template_controls_in_simple_mode(
|
1506 | 1640 | editor_for_template_tests.template_manager.add_template_controls.assert_not_called()
|
1507 | 1641 |
|
1508 | 1642 |
|
1509 |
| -class TestWidgetValidationWorkflows: |
1510 |
| - """Test user workflows for widget validation and error handling.""" |
1511 |
| - |
1512 |
| - @pytest.fixture |
1513 |
| - def editor_for_widget_validation_tests(self, mock_filesystem: MagicMock) -> ComponentEditorWindowBase: |
1514 |
| - """Fixture providing an editor configured for widget validation testing.""" |
1515 |
| - editor = ComponentEditorWindowBase.create_for_testing(version="1.0.0", local_filesystem=mock_filesystem) |
1516 |
| - |
1517 |
| - # Override the abstract validate_data_and_highlight_errors_in_red method for testing |
1518 |
| - editor.validate_data_and_highlight_errors_in_red = MagicMock(return_value="Test validation error") |
1519 |
| - |
1520 |
| - return editor |
1521 |
| - |
1522 |
| - def test_user_can_trigger_validation_through_public_interface( |
1523 |
| - self, editor_for_widget_validation_tests: ComponentEditorWindowBase |
1524 |
| - ) -> None: |
1525 |
| - """ |
1526 |
| - User can trigger validation through the public interface. |
1527 |
| -
|
1528 |
| - GIVEN: A user has configured components and wants to validate |
1529 |
| - WHEN: They trigger validation through the interface |
1530 |
| - THEN: The validation process should execute and return results |
1531 |
| - """ |
1532 |
| - # Act: Trigger validation |
1533 |
| - result = editor_for_widget_validation_tests.validate_data_and_highlight_errors_in_red() |
1534 |
| - |
1535 |
| - # Assert: Validation should return expected result |
1536 |
| - assert result == "Test validation error" |
1537 |
| - |
1538 |
| - |
1539 | 1643 | class TestUsageInstructionsWorkflows:
|
1540 | 1644 | """Test user workflows for usage instructions display."""
|
1541 | 1645 |
|
|
0 commit comments