|
22 | 22 | ProgressWindow,
|
23 | 23 | RichText,
|
24 | 24 | ScrollFrame,
|
| 25 | + UsagePopupWindow, |
25 | 26 | get_widget_font_family_and_size,
|
26 | 27 | show_error_message,
|
27 | 28 | show_no_connection_error,
|
@@ -57,6 +58,18 @@ def test_show_error_message(self, _mock_style, mock_tk, mock_showerror) -> None:
|
57 | 58 | # Assert that the Tkinter Tk instance's destroy method was called
|
58 | 59 | mock_tk.return_value.destroy.assert_called_once()
|
59 | 60 |
|
| 61 | + @patch("tkinter.messagebox.showerror") |
| 62 | + @patch("tkinter.Tk") |
| 63 | + @patch("tkinter.ttk.Style") |
| 64 | + def test_show_error_message_with_special_chars(self, mock_style, mock_tk, mock_showerror) -> None: |
| 65 | + """Test error message with special characters.""" |
| 66 | + mock_tk.return_value.withdraw.return_value = None |
| 67 | + mock_tk.return_value.destroy.return_value = None |
| 68 | + mock_style.return_value = MagicMock() |
| 69 | + |
| 70 | + show_error_message("Test & Title", "Test\nMessage with & special < chars >") |
| 71 | + mock_showerror.assert_called_once_with("Test & Title", "Test\nMessage with & special < chars >") |
| 72 | + |
60 | 73 |
|
61 | 74 | class TestShowTooltip(unittest.TestCase):
|
62 | 75 | """Test cases for the show_tooltip function."""
|
@@ -111,6 +124,27 @@ def test_show_tooltip(self, mock_label, mock_toplevel) -> None:
|
111 | 124 | # Assert that the Tkinter Toplevel instance's withdraw method was called
|
112 | 125 | mock_toplevel.return_value.withdraw.assert_called()
|
113 | 126 |
|
| 127 | + def test_tooltip_positioning(self) -> None: |
| 128 | + mock_widget = MagicMock() |
| 129 | + mock_widget.winfo_rootx.return_value = 100 |
| 130 | + mock_widget.winfo_rooty.return_value = 200 |
| 131 | + mock_widget.winfo_width.return_value = 50 |
| 132 | + mock_widget.winfo_height.return_value = 30 |
| 133 | + |
| 134 | + with patch("tkinter.Toplevel") as mock_toplevel: |
| 135 | + mock_toplevel_instance = MagicMock() |
| 136 | + mock_toplevel.return_value = mock_toplevel_instance |
| 137 | + show_tooltip(mock_widget, "Test Tooltip") |
| 138 | + |
| 139 | + # Trigger enter event |
| 140 | + enter_event = mock_widget.bind.call_args_list[0][0][1] |
| 141 | + enter_event(MagicMock()) |
| 142 | + |
| 143 | + # Check tooltip positioning |
| 144 | + expected_x = mock_widget.winfo_rootx() + min(mock_widget.winfo_width() // 2, 100) |
| 145 | + expected_y = mock_widget.winfo_rooty() + mock_widget.winfo_height() |
| 146 | + mock_toplevel_instance.geometry.assert_called_with(f"+{expected_x}+{expected_y}") |
| 147 | + |
114 | 148 |
|
115 | 149 | class TestShowNoParamFilesError(unittest.TestCase):
|
116 | 150 | """Test cases for the show_no_param_files_error function."""
|
@@ -203,6 +237,13 @@ def test_no_selection(self) -> None:
|
203 | 237 | self.combobox.set_entries_tupple(["ten", "eleven"], "")
|
204 | 238 | mock_logging_warning.assert_called_once()
|
205 | 239 |
|
| 240 | + def test_set_entries_with_spaces(self) -> None: |
| 241 | + """Test values with spaces.""" |
| 242 | + values = ["option one", "option two", "option three"] |
| 243 | + self.combobox.set_entries_tupple(values, "option two") |
| 244 | + assert self.combobox["values"] == tuple(values) |
| 245 | + assert self.combobox.get() == "option two" |
| 246 | + |
206 | 247 |
|
207 | 248 | class TestScrollFrame(unittest.TestCase):
|
208 | 249 | """Test cases for the ScrollFrame class."""
|
@@ -250,6 +291,28 @@ def test_on_leave(self) -> None:
|
250 | 291 | self.scroll_frame.on_leave(None)
|
251 | 292 | mock_unbind_all.assert_called()
|
252 | 293 |
|
| 294 | + def test_mouse_wheel_scroll_windows(self) -> None: |
| 295 | + """Test mouse wheel scrolling on Windows.""" |
| 296 | + with patch("platform.system", return_value="Windows"): |
| 297 | + event = MagicMock() |
| 298 | + event.delta = 120 |
| 299 | + with patch.object(self.scroll_frame.canvas, "yview_scroll") as mock_yview_scroll: |
| 300 | + self.scroll_frame.on_mouse_wheel(event) |
| 301 | + mock_yview_scroll.assert_called_with(-1, "units") |
| 302 | + |
| 303 | + def test_mouse_wheel_scroll_linux(self) -> None: |
| 304 | + """Test mouse wheel scrolling on Linux.""" |
| 305 | + with patch("platform.system", return_value="Linux"): |
| 306 | + event = MagicMock() |
| 307 | + event.num = 4 # Scroll up |
| 308 | + # Mock canvas methods needed for scroll test |
| 309 | + self.scroll_frame.canvas.bbox = MagicMock(return_value=(0, 0, 100, 1000)) |
| 310 | + self.scroll_frame.canvas.winfo_height = MagicMock(return_value=100) |
| 311 | + |
| 312 | + with patch.object(self.scroll_frame.canvas, "yview_scroll") as mock_yview_scroll: |
| 313 | + self.scroll_frame.on_mouse_wheel(event) |
| 314 | + mock_yview_scroll.assert_called_once_with(1, "units") # Linux scroll direction is inverted |
| 315 | + |
253 | 316 |
|
254 | 317 | class TestProgressWindow(unittest.TestCase):
|
255 | 318 | """Test cases for the ProgressWindow class."""
|
@@ -286,6 +349,12 @@ def test_destroy(self) -> None:
|
286 | 349 | # Check if the progress window has been destroyed
|
287 | 350 | assert not self.progress_window.progress_window.winfo_exists()
|
288 | 351 |
|
| 352 | + def test_update_progress_bar_exceeding_max(self) -> None: |
| 353 | + """Test updating progress bar with value exceeding maximum.""" |
| 354 | + self.progress_window.update_progress_bar(150, 100) |
| 355 | + assert self.progress_window.progress_bar["value"] == 150 |
| 356 | + assert self.progress_window.progress_bar["maximum"] == 100 |
| 357 | + |
289 | 358 |
|
290 | 359 | class TestRichText(unittest.TestCase):
|
291 | 360 | """Test cases for the RichText class."""
|
@@ -323,6 +392,15 @@ def test_insert_text(self) -> None:
|
323 | 392 | assert self.rich_text.get("3.0", "3.end") == "Italic Text"
|
324 | 393 | assert self.rich_text.get("4.0", "4.end") == "Heading Text"
|
325 | 394 |
|
| 395 | + def test_multiple_tags(self) -> None: |
| 396 | + """Test applying multiple tags to text.""" |
| 397 | + self.rich_text.insert("1.0", "Bold and Italic\n", ("bold", "italic")) |
| 398 | + self.rich_text.insert("2.0", "Bold and H1\n", ("bold", "h1")) |
| 399 | + assert "bold" in self.rich_text.tag_names("1.0") |
| 400 | + assert "italic" in self.rich_text.tag_names("1.0") |
| 401 | + assert "bold" in self.rich_text.tag_names("2.0") |
| 402 | + assert "h1" in self.rich_text.tag_names("2.0") |
| 403 | + |
326 | 404 |
|
327 | 405 | class TestGetWidgetFontFamilyAndSize(unittest.TestCase):
|
328 | 406 | """Test cases for the get_widget_font_family_and_size function."""
|
@@ -380,16 +458,82 @@ def test_theme_and_style(self) -> None:
|
380 | 458 | assert style.theme_use() == "alt"
|
381 | 459 | assert style.lookup("Bold.TLabel", "font") == "TkDefaultFont 10 bold"
|
382 | 460 |
|
383 |
| - def test_put_image_in_label(self) -> None: |
384 |
| - with patch("PIL.Image.open") as mock_open, patch("PIL.ImageTk.PhotoImage") as mock_photo: |
385 |
| - mock_image = MagicMock() |
386 |
| - mock_open.return_value = mock_image |
387 |
| - mock_image.size = (100, 100) |
388 |
| - return # FIXME the test does not pass yet pylint: disable=fixme |
389 |
| - label = BaseWindow.put_image_in_label(self.base_window.main_frame, "test_image.png", image_height=50) # pylint: disable=unreachable |
390 |
| - assert isinstance(label, ttk.Label) |
391 |
| - mock_open.assert_called_once_with("test_image.png") |
392 |
| - mock_photo.assert_called_once() |
| 461 | + @patch("PIL.Image.open") |
| 462 | + @patch("PIL.ImageTk.PhotoImage") |
| 463 | + @patch("tkinter.ttk.Label") |
| 464 | + def test_put_image_in_label(self, mock_label, mock_photo, mock_open) -> None: |
| 465 | + """Test creating a label with an image.""" |
| 466 | + # Set up image mock |
| 467 | + mock_image = MagicMock() |
| 468 | + mock_image.size = (100, 100) |
| 469 | + mock_image.resize = MagicMock(return_value=mock_image) |
| 470 | + mock_open.return_value = mock_image |
| 471 | + |
| 472 | + # Set up PhotoImage mock |
| 473 | + mock_photo_instance = MagicMock() |
| 474 | + mock_photo_instance._PhotoImage__photo = "photo1" # Required for Tkinter |
| 475 | + mock_photo.return_value = mock_photo_instance |
| 476 | + |
| 477 | + # Set up Label mock |
| 478 | + mock_label_instance = MagicMock() |
| 479 | + mock_label.return_value = mock_label_instance |
| 480 | + |
| 481 | + # Test the method |
| 482 | + label = BaseWindow.put_image_in_label(self.base_window.main_frame, "test_image.png", image_height=50) |
| 483 | + |
| 484 | + # Verify behavior |
| 485 | + mock_open.assert_called_once_with("test_image.png") |
| 486 | + mock_image.resize.assert_called_once_with((50, 50)) # Based on aspect ratio of 1:1 |
| 487 | + mock_photo.assert_called_once_with(mock_image) |
| 488 | + mock_label.assert_called_once() |
| 489 | + assert isinstance(label, MagicMock) |
| 490 | + |
| 491 | + def test_window_title(self) -> None: |
| 492 | + """Test setting window title.""" |
| 493 | + title = "Test Window" |
| 494 | + self.base_window.root.title(title) |
| 495 | + assert self.base_window.root.title() == title |
| 496 | + |
| 497 | + |
| 498 | +class TestUsagePopupWindow(unittest.TestCase): |
| 499 | + """Test cases for the UsagePopupWindow class.""" |
| 500 | + |
| 501 | + def setUp(self) -> None: |
| 502 | + self.root = tk.Tk() |
| 503 | + self.root.withdraw() |
| 504 | + |
| 505 | + def tearDown(self) -> None: |
| 506 | + self.root.destroy() |
| 507 | + |
| 508 | + @patch("ardupilot_methodic_configurator.frontend_tkinter_base.ProgramSettings.display_usage_popup") |
| 509 | + def test_should_display(self, mock_display_popup) -> None: |
| 510 | + """Test should_display method.""" |
| 511 | + mock_display_popup.return_value = True |
| 512 | + assert UsagePopupWindow.should_display("test_type") is True |
| 513 | + mock_display_popup.assert_called_once_with("test_type") |
| 514 | + |
| 515 | + @patch("tkinter.BooleanVar") |
| 516 | + @patch("ardupilot_methodic_configurator.frontend_tkinter_base.ProgramSettings.set_display_usage_popup") |
| 517 | + def test_display_popup(self, mock_set_display, mock_bool_var) -> None: |
| 518 | + """Test display method.""" |
| 519 | + mock_bool_var.return_value.get.return_value = True |
| 520 | + usage_window = BaseWindow(self.root) |
| 521 | + instructions = RichText(usage_window.main_frame) |
| 522 | + |
| 523 | + UsagePopupWindow.display( |
| 524 | + parent=self.root, |
| 525 | + usage_popup_window=usage_window, |
| 526 | + title="Test Usage", |
| 527 | + ptype="test_type", |
| 528 | + geometry="300x200", |
| 529 | + instructions_text=instructions, |
| 530 | + ) |
| 531 | + |
| 532 | + assert usage_window.root.title() == "Test Usage" |
| 533 | + assert usage_window.root.geometry().startswith("300x200") |
| 534 | + # Test button creation and checkbox state |
| 535 | + checkbuttons = [w for w in usage_window.main_frame.winfo_children() if isinstance(w, ttk.Checkbutton)] |
| 536 | + assert len(checkbuttons) == 1 |
393 | 537 |
|
394 | 538 |
|
395 | 539 | if __name__ == "__main__":
|
|
0 commit comments