Skip to content

Commit d98e2d8

Browse files
committed
FEATURE: improve test_frontend_tkinter_base.py
1 parent ca1cfa4 commit d98e2d8

File tree

1 file changed

+154
-10
lines changed

1 file changed

+154
-10
lines changed

tests/test_frontend_tkinter_base.py

Lines changed: 154 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
ProgressWindow,
2323
RichText,
2424
ScrollFrame,
25+
UsagePopupWindow,
2526
get_widget_font_family_and_size,
2627
show_error_message,
2728
show_no_connection_error,
@@ -57,6 +58,18 @@ def test_show_error_message(self, _mock_style, mock_tk, mock_showerror) -> None:
5758
# Assert that the Tkinter Tk instance's destroy method was called
5859
mock_tk.return_value.destroy.assert_called_once()
5960

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+
6073

6174
class TestShowTooltip(unittest.TestCase):
6275
"""Test cases for the show_tooltip function."""
@@ -111,6 +124,27 @@ def test_show_tooltip(self, mock_label, mock_toplevel) -> None:
111124
# Assert that the Tkinter Toplevel instance's withdraw method was called
112125
mock_toplevel.return_value.withdraw.assert_called()
113126

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+
114148

115149
class TestShowNoParamFilesError(unittest.TestCase):
116150
"""Test cases for the show_no_param_files_error function."""
@@ -203,6 +237,13 @@ def test_no_selection(self) -> None:
203237
self.combobox.set_entries_tupple(["ten", "eleven"], "")
204238
mock_logging_warning.assert_called_once()
205239

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+
206247

207248
class TestScrollFrame(unittest.TestCase):
208249
"""Test cases for the ScrollFrame class."""
@@ -250,6 +291,28 @@ def test_on_leave(self) -> None:
250291
self.scroll_frame.on_leave(None)
251292
mock_unbind_all.assert_called()
252293

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+
253316

254317
class TestProgressWindow(unittest.TestCase):
255318
"""Test cases for the ProgressWindow class."""
@@ -286,6 +349,12 @@ def test_destroy(self) -> None:
286349
# Check if the progress window has been destroyed
287350
assert not self.progress_window.progress_window.winfo_exists()
288351

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+
289358

290359
class TestRichText(unittest.TestCase):
291360
"""Test cases for the RichText class."""
@@ -323,6 +392,15 @@ def test_insert_text(self) -> None:
323392
assert self.rich_text.get("3.0", "3.end") == "Italic Text"
324393
assert self.rich_text.get("4.0", "4.end") == "Heading Text"
325394

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+
326404

327405
class TestGetWidgetFontFamilyAndSize(unittest.TestCase):
328406
"""Test cases for the get_widget_font_family_and_size function."""
@@ -380,16 +458,82 @@ def test_theme_and_style(self) -> None:
380458
assert style.theme_use() == "alt"
381459
assert style.lookup("Bold.TLabel", "font") == "TkDefaultFont 10 bold"
382460

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
393537

394538

395539
if __name__ == "__main__":

0 commit comments

Comments
 (0)