diff --git a/ardupilot_methodic_configurator/configuration_steps_ArduCopter.json b/ardupilot_methodic_configurator/configuration_steps_ArduCopter.json index 757d03382..08c58d0e3 100644 --- a/ardupilot_methodic_configurator/configuration_steps_ArduCopter.json +++ b/ardupilot_methodic_configurator/configuration_steps_ArduCopter.json @@ -214,7 +214,7 @@ "wiki_text": "Follow the blog instructions and use Mission Planner instead of this tool to configure the mandatory hardware parameters.", "wiki_url": "", "external_tool_text": "Mission Planner", - "external_tool_url": "https://github.com/ArduPilot/MethodicConfigurator/blob/latest/TUNING_GUIDE_ArduCopter.md#212-configure-mandatory-hardware-parameters-17", + "external_tool_url": "https://ardupilot.org/copter/docs/configuring-hardware.html", "mandatory_text": "100% mandatory (0% optional)", "auto_changed_by": "Mission Planner. If you have not done this step in Mission Planner yet, close this application and use Mission Planner", "old_filenames": ["11_mp_setup_mandatory_hardware.param"] diff --git a/ardupilot_methodic_configurator/frontend_tkinter_parameter_editor.py b/ardupilot_methodic_configurator/frontend_tkinter_parameter_editor.py index 0e3d853c1..51839f68b 100755 --- a/ardupilot_methodic_configurator/frontend_tkinter_parameter_editor.py +++ b/ardupilot_methodic_configurator/frontend_tkinter_parameter_editor.py @@ -541,6 +541,18 @@ def __should_copy_fc_values_to_file(self, selected_file: str) -> None: # pylint message_label = tk.Label(dialog, text=msg, justify=tk.LEFT, padx=20, pady=10) message_label.pack(padx=10, pady=10) + # Clickable link to tuning guide + safe_font_config = get_safe_font_config() + link_label = tk.Label( + dialog, + text=_("Click here to open the Tuning Guide relevant Section"), + fg="blue", + cursor="hand2", + font=(str(safe_font_config["family"]), int(safe_font_config["size"]), "underline"), + ) + link_label.pack(pady=(0, 10)) + link_label.bind("", lambda _e: self.configuration_manager.open_documentation_in_browser(selected_file)) + # Result variable result: list[Optional[Literal[True, False]]] = [None] diff --git a/tests/test_frontend_tkinter_parameter_editor.py b/tests/test_frontend_tkinter_parameter_editor.py index 2fb48f49b..9705108d4 100755 --- a/tests/test_frontend_tkinter_parameter_editor.py +++ b/tests/test_frontend_tkinter_parameter_editor.py @@ -11,7 +11,7 @@ """ from typing import Any -from unittest.mock import MagicMock, patch +from unittest.mock import ANY, MagicMock, patch import pytest @@ -245,7 +245,7 @@ def side_effect(*args, **kwargs) -> None: # noqa: ARG001 # pylint: disable=unus @patch("tkinter.Label") @patch("tkinter.Frame") @patch("tkinter.Button") - def test_dialog_creation( + def test_dialog_creation( # pylint: disable=too-many-locals self, mock_button: MagicMock, mock_frame: MagicMock, @@ -281,5 +281,42 @@ def fake_wait_window(*args: Any, **kwargs: Any) -> None: # noqa: ANN401 # pylin mock_toplevel.assert_called_once() # Check for label, buttons, and frame creation - mock_label.assert_called_once() + assert mock_label.call_count == 2 # message label and link label + assert mock_button.call_count == 3 # Close, Yes, No buttons mock_frame.assert_called_once() + + # Verify message label creation + message_label_call = mock_label.call_args_list[0] + assert message_label_call[1]["text"].startswith("This configuration step requires external changes by: External Tool") + assert message_label_call[1]["justify"] == "left" + assert message_label_call[1]["padx"] == 20 + assert message_label_call[1]["pady"] == 10 + + # Verify link label creation + link_label_call = mock_label.call_args_list[1] + assert link_label_call[1]["text"] == "Click here to open the Tuning Guide relevant Section" + assert link_label_call[1]["fg"] == "blue" + assert link_label_call[1]["cursor"] == "hand2" + # Font should be a tuple with underline + font_arg = link_label_call[1]["font"] + assert isinstance(font_arg, tuple) + assert len(font_arg) == 3 + assert font_arg[2] == "underline" + + # Verify link label is packed and bound correctly + link_label_mock = mock_label.return_value + # Both labels call pack, so check that the link label's pack call was made + assert link_label_mock.pack.call_count == 2 + link_label_mock.pack.assert_any_call(pady=(0, 10)) # link label pack call + link_label_mock.pack.assert_any_call(padx=10, pady=10) # message label pack call + link_label_mock.bind.assert_called_once_with("", ANY) # lambda function, so use ANY + + # Verify buttons are created with correct text + button_calls = mock_button.call_args_list + assert button_calls[0][1]["text"] == "Close" + assert button_calls[1][1]["text"] == "Yes" + assert button_calls[2][1]["text"] == "No" + + # Verify button frame is packed correctly + frame_mock = mock_frame.return_value + frame_mock.pack.assert_called_once_with(pady=10)