Skip to content

Commit 62c1f5a

Browse files
committed
fix(component editor): fix regression tests
1 parent f23dc8b commit 62c1f5a

File tree

2 files changed

+179
-156
lines changed

2 files changed

+179
-156
lines changed

tests/test_frontend_tkinter_component_editor.py

Lines changed: 0 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -532,87 +532,6 @@ def test_validate_entry_limits_ui_voltage_path(self, editor_with_mocked_root: Co
532532
editor_with_mocked_root.data_model.validate_entry_limits.assert_called_once_with("4.2", path)
533533
assert result is True
534534

535-
def test_validate_data_all_valid(self, editor_with_mocked_root: ComponentEditorWindow) -> None:
536-
"""Test validating all data when everything is valid."""
537-
# Set up mock entry widgets with proper 3-level paths
538-
mock_entry1 = MagicMock()
539-
mock_entry1.get.return_value = "1000"
540-
mock_entry2 = MagicMock()
541-
mock_entry2.get.return_value = "PWM"
542-
543-
editor_with_mocked_root.entry_widgets = {
544-
("Motor", "Specifications", "KV"): mock_entry1,
545-
("RC Receiver", "FC Connection", "Protocol"): mock_entry2,
546-
("invalid", "path"): MagicMock(), # Should be ignored (wrong length)
547-
}
548-
549-
editor_with_mocked_root.data_model.validate_all_data = MagicMock(return_value=(True, []))
550-
551-
result = editor_with_mocked_root.validate_data_and_highlight_errors_in_red()
552-
553-
expected_entry_values = {
554-
("Motor", "Specifications", "KV"): "1000",
555-
("RC Receiver", "FC Connection", "Protocol"): "PWM",
556-
}
557-
editor_with_mocked_root.data_model.validate_all_data.assert_called_once_with(expected_entry_values)
558-
assert result == ""
559-
560-
def test_validate_data_with_errors(self, editor_with_mocked_root: ComponentEditorWindow) -> None:
561-
"""Test validating all data with validation errors."""
562-
# Set up mock entry widgets with proper 3-level paths
563-
mock_combobox = MagicMock(spec=ttk.Combobox)
564-
mock_combobox.get.return_value = "INVALID"
565-
mock_entry = MagicMock()
566-
mock_entry.get.return_value = "99999"
567-
568-
editor_with_mocked_root.entry_widgets = {
569-
("RC Receiver", "FC Connection", "Protocol"): mock_combobox,
570-
("Motor", "Specifications", "KV"): mock_entry,
571-
}
572-
573-
# Mock data model validation
574-
editor_with_mocked_root.data_model.validate_all_data = MagicMock(
575-
return_value=(False, ["Error 1", "Error 2", "Error 3", "Error 4"])
576-
)
577-
editor_with_mocked_root.data_model.get_combobox_values_for_path = MagicMock(return_value=("PWM", "SBUS"))
578-
editor_with_mocked_root.data_model.validate_entry_limits = MagicMock(return_value=("Value too high", None))
579-
580-
with patch("ardupilot_methodic_configurator.frontend_tkinter_component_editor_base.show_error_message") as mock_error:
581-
result = editor_with_mocked_root.validate_data_and_highlight_errors_in_red()
582-
583-
# Should configure widgets with invalid styles
584-
mock_combobox.configure.assert_called_once_with(style="comb_input_invalid.TCombobox")
585-
mock_entry.configure.assert_called_once_with(style="entry_input_invalid.TEntry")
586-
587-
# Should show error message with first 3 errors
588-
mock_error.assert_called_once()
589-
error_call_args = mock_error.call_args[0]
590-
assert "Error 1" in error_call_args[1]
591-
assert "Error 2" in error_call_args[1]
592-
assert "Error 3" in error_call_args[1]
593-
assert "1 more errors" in error_call_args[1]
594-
595-
assert result != ""
596-
597-
def test_validate_data_combobox_valid_after_correction(self, editor_with_mocked_root: ComponentEditorWindow) -> None:
598-
"""Test validating combobox that becomes valid after data model validation."""
599-
# Set up mock combobox with value that becomes valid
600-
mock_combobox = MagicMock(spec=ttk.Combobox)
601-
mock_combobox.get.return_value = "PWM"
602-
603-
editor_with_mocked_root.entry_widgets = {
604-
("RC Receiver", "FC Connection", "Protocol"): mock_combobox,
605-
}
606-
607-
editor_with_mocked_root.data_model.validate_all_data = MagicMock(return_value=(False, ["Some error"]))
608-
editor_with_mocked_root.data_model.get_combobox_values_for_path = MagicMock(return_value=("PWM", "SBUS"))
609-
610-
with patch("ardupilot_methodic_configurator.frontend_tkinter_component_editor_base.show_error_message"):
611-
editor_with_mocked_root.validate_data_and_highlight_errors_in_red()
612-
613-
# Should configure combobox as valid since value is in allowed values
614-
mock_combobox.configure.assert_called_once_with(style="comb_input_valid.TCombobox")
615-
616535

617536
class TestArgumentParser(SharedTestArgumentParser):
618537
"""Test cases for the argument_parser function."""

tests/test_frontend_tkinter_component_editor_base.py

Lines changed: 179 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import tkinter as tk
1414
from argparse import ArgumentParser
15+
from tkinter import ttk
1516
from typing import Union, get_args, get_origin
1617
from unittest.mock import MagicMock, patch
1718

@@ -275,69 +276,202 @@ def test_user_can_configure_different_log_levels_for_debugging(self) -> None:
275276
class TestDataValidationWorkflows:
276277
"""Test user workflows for data validation."""
277278

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:
281280
"""
282-
User receives confirmation that their component data is valid.
281+
User receives no error messages when all component data is valid.
283282
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
287286
"""
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+
}
289298

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, []))
293301

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()
297304

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:
299312
"""
300-
User receives clear feedback when component data is invalid or incomplete.
313+
User sees visual feedback when entry fields contain invalid values.
301314
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
305318
"""
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
310322

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)
314331
)
315332

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 != ""
319341

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:
321345
"""
322-
User can work with edge cases like valid structure but missing components.
346+
User sees visual feedback when combobox selections are invalid.
323347
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
327351
"""
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"
332355

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+
}
337359

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 == ""
341475

342476

343477
class TestComponentDataManagementWorkflows:
@@ -1506,36 +1640,6 @@ def test_user_does_not_see_template_controls_in_simple_mode(
15061640
editor_for_template_tests.template_manager.add_template_controls.assert_not_called()
15071641

15081642

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-
15391643
class TestUsageInstructionsWorkflows:
15401644
"""Test user workflows for usage instructions display."""
15411645

0 commit comments

Comments
 (0)