Skip to content

Commit 741e9ab

Browse files
committed
feat: add GUI components and utility functions for speed test application
1 parent 949e9d2 commit 741e9ab

File tree

6 files changed

+352
-12
lines changed

6 files changed

+352
-12
lines changed

main.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Entry point for macOS_application_speedtest application.
4+
"""
5+
import sys
6+
import os
7+
8+
# Add the project root directory to Python path
9+
current_dir = os.path.dirname(os.path.abspath(__file__))
10+
sys.path.insert(0, current_dir)
11+
12+
# Import the main function from the package
13+
from speedtest_app.alexs_speedtest import main
14+
15+
if __name__ == "__main__":
16+
main()

speedtest_app/alexs_speedtest.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from tkinter import messagebox
66
from tkinter import ttk
77
import speedtest as st
8-
import network_adapter_information
9-
from test_history import save_test_results, view_history, plot_history
8+
from speedtest_app import network_adapter_information
9+
from speedtest_app.test_history import save_test_results, view_history, plot_history
1010

1111

1212
# Настройка логирования
@@ -30,7 +30,6 @@ def setup_logging():
3030
# Инициализация логгера
3131
logger = setup_logging()
3232

33-
3433
# Get the history file path in the Documents folder
3534
def get_history_path():
3635
"""Returns the path to the history file in the Documents folder."""

speedtest_app/gui/__init__.py

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
"""
2+
GUI components for macOS_application_speedtest_for_python.
3+
"""
4+
import tkinter as tk
5+
from tkinter import ttk, messagebox, Menu
6+
import logging
7+
8+
logger = logging.getLogger("SpeedTest")
9+
10+
11+
class ResultsFrame(tk.Frame):
12+
"""Frame for displaying speed test results."""
13+
14+
def __init__(self, master, **kwargs):
15+
super().__init__(master, **kwargs)
16+
17+
# Create labels for results
18+
self.result_title = tk.Label(self, text="Test Results", font=("Helvetica", 14, "bold"))
19+
self.result_title.pack(pady=(10, 5))
20+
21+
# Download speed result
22+
self.download_frame = tk.Frame(self)
23+
self.download_frame.pack(fill="x", pady=2)
24+
25+
self.download_label = tk.Label(self.download_frame, text="Download Speed:", width=15,
26+
anchor="w")
27+
self.download_label.pack(side="left", padx=5)
28+
29+
self.download_value = tk.Label(self.download_frame, text="-- Mbps", width=15, anchor="e")
30+
self.download_value.pack(side="left", padx=5)
31+
32+
# Upload speed result
33+
self.upload_frame = tk.Frame(self)
34+
self.upload_frame.pack(fill="x", pady=2)
35+
36+
self.upload_label = tk.Label(self.upload_frame, text="Upload Speed:", width=15, anchor="w")
37+
self.upload_label.pack(side="left", padx=5)
38+
39+
self.upload_value = tk.Label(self.upload_frame, text="-- Mbps", width=15, anchor="e")
40+
self.upload_value.pack(side="left", padx=5)
41+
42+
# Ping result
43+
self.ping_frame = tk.Frame(self)
44+
self.ping_frame.pack(fill="x", pady=2)
45+
46+
self.ping_label = tk.Label(self.ping_frame, text="Ping:", width=15, anchor="w")
47+
self.ping_label.pack(side="left", padx=5)
48+
49+
self.ping_value = tk.Label(self.ping_frame, text="-- ms", width=15, anchor="e")
50+
self.ping_value.pack(side="left", padx=5)
51+
52+
# Timestamp
53+
self.timestamp_frame = tk.Frame(self)
54+
self.timestamp_frame.pack(fill="x", pady=2)
55+
56+
self.timestamp_label = tk.Label(self.timestamp_frame, text="Test Time:", width=15,
57+
anchor="w")
58+
self.timestamp_label.pack(side="left", padx=5)
59+
60+
self.timestamp_value = tk.Label(self.timestamp_frame, text="--", width=25, anchor="e")
61+
self.timestamp_value.pack(side="left", padx=5)
62+
63+
# Initially hide
64+
self.pack_forget()
65+
66+
def update_results(self, download, upload, ping, timestamp):
67+
"""Updates the result values."""
68+
self.download_value.config(text=f"{download} Mbps")
69+
self.upload_value.config(text=f"{upload} Mbps")
70+
self.ping_value.config(text=f"{ping} ms")
71+
self.timestamp_value.config(text=timestamp)
72+
self.pack(fill="both", expand=True, pady=10)
73+
74+
def clear(self):
75+
"""Clears the result values."""
76+
self.download_value.config(text="-- Mbps")
77+
self.upload_value.config(text="-- Mbps")
78+
self.ping_value.config(text="-- ms")
79+
self.timestamp_value.config(text="--")
80+
81+
82+
class SettingsWindow:
83+
"""Window for application settings."""
84+
85+
def __init__(self, parent, settings, save_callback):
86+
self.parent = parent
87+
self.settings = settings
88+
self.save_callback = save_callback
89+
90+
# Create a new window
91+
self.window = tk.Toplevel(parent)
92+
self.window.title("Settings")
93+
self.window.geometry("400x300")
94+
self.window.resizable(False, False)
95+
self.window.transient(parent)
96+
self.window.grab_set()
97+
98+
# Center on parent
99+
x = parent.winfo_x() + (parent.winfo_width() // 2) - (400 // 2)
100+
y = parent.winfo_y() + (parent.winfo_height() // 2) - (300 // 2)
101+
self.window.geometry(f"+{x}+{y}")
102+
103+
# Create a frame for settings
104+
self.frame = tk.Frame(self.window, padx=20, pady=20)
105+
self.frame.pack(fill="both", expand=True)
106+
107+
# Auto save results
108+
self.auto_save_var = tk.BooleanVar(value=settings.get("auto_save_results", True))
109+
self.auto_save_check = ttk.Checkbutton(
110+
self.frame,
111+
text="Automatically save test results",
112+
variable=self.auto_save_var
113+
)
114+
self.auto_save_check.pack(anchor="w", pady=5)
115+
116+
# Show network info
117+
self.show_network_var = tk.BooleanVar(value=settings.get("show_network_info", True))
118+
self.show_network_check = ttk.Checkbutton(
119+
self.frame,
120+
text="Show network adapter information",
121+
variable=self.show_network_var
122+
)
123+
self.show_network_check.pack(anchor="w", pady=5)
124+
125+
# Dark mode option
126+
self.dark_mode_var = tk.BooleanVar(value=settings.get("dark_mode", False))
127+
self.dark_mode_check = ttk.Checkbutton(
128+
self.frame,
129+
text="Dark mode (requires restart)",
130+
variable=self.dark_mode_var
131+
)
132+
self.dark_mode_check.pack(anchor="w", pady=5)
133+
134+
# Create buttons
135+
self.button_frame = tk.Frame(self.window)
136+
self.button_frame.pack(fill="x", padx=20, pady=20)
137+
138+
self.save_button = ttk.Button(
139+
self.button_frame,
140+
text="Save",
141+
command=self.save_settings
142+
)
143+
self.save_button.pack(side="right", padx=5)
144+
145+
self.cancel_button = ttk.Button(
146+
self.button_frame,
147+
text="Cancel",
148+
command=self.window.destroy
149+
)
150+
self.cancel_button.pack(side="right", padx=5)
151+
152+
def save_settings(self):
153+
"""Saves the settings and closes the window."""
154+
self.settings["auto_save_results"] = self.auto_save_var.get()
155+
self.settings["show_network_info"] = self.show_network_var.get()
156+
self.settings["dark_mode"] = self.dark_mode_var.get()
157+
158+
if self.save_callback:
159+
self.save_callback(self.settings)
160+
161+
self.window.destroy()
162+
163+
164+
def create_menu(root, settings_callback, about_callback):
165+
"""Creates the application menu bar."""
166+
menubar = Menu(root)
167+
168+
# File menu
169+
file_menu = Menu(menubar, tearoff=0)
170+
file_menu.add_command(label="Settings", command=settings_callback)
171+
file_menu.add_separator()
172+
file_menu.add_command(label="Exit", command=root.quit)
173+
menubar.add_cascade(label="File", menu=file_menu)
174+
175+
# Tools menu
176+
tools_menu = Menu(menubar, tearoff=0)
177+
tools_menu.add_command(label="Export All History", command=lambda: messagebox.showinfo(
178+
"Feature Coming Soon", "This feature will be available in the next update."
179+
))
180+
tools_menu.add_command(label="Clear History", command=lambda: messagebox.showinfo(
181+
"Feature Coming Soon", "This feature will be available in the next update."
182+
))
183+
menubar.add_cascade(label="Tools", menu=tools_menu)
184+
185+
# Help menu
186+
help_menu = Menu(menubar, tearoff=0)
187+
help_menu.add_command(label="View Help", command=lambda: messagebox.showinfo(
188+
"Help",
189+
"For help and documentation, please visit: https://github.com/AlexTkDev/"
190+
"macOS_application_speedtest_for_python"
191+
))
192+
help_menu.add_separator()
193+
help_menu.add_command(label="About", command=about_callback)
194+
menubar.add_cascade(label="Help", menu=help_menu)
195+
196+
root.config(menu=menubar)
197+
198+
return menubar
199+
200+
201+
def show_about_dialog(parent):
202+
"""Shows the About dialog."""
203+
from speedtest_app import __version__
204+
205+
about_window = tk.Toplevel(parent)
206+
about_window.title("About Internet Speed Test")
207+
about_window.geometry("300x200")
208+
about_window.resizable(False, False)
209+
about_window.transient(parent)
210+
about_window.grab_set()
211+
212+
# Center on parent
213+
x = parent.winfo_x() + (parent.winfo_width() // 2) - (300 // 2)
214+
y = parent.winfo_y() + (parent.winfo_height() // 2) - (200 // 2)
215+
about_window.geometry(f"+{x}+{y}")
216+
217+
# App name and version
218+
app_name = tk.Label(about_window, text="Internet Speed Test", font=("Helvetica", 16, "bold"))
219+
app_name.pack(pady=(20, 5))
220+
221+
version = tk.Label(about_window, text=f"Version {__version__}")
222+
version.pack()
223+
224+
# Separator
225+
separator = ttk.Separator(about_window, orient="horizontal")
226+
separator.pack(fill="x", padx=20, pady=10)
227+
228+
# Developer info
229+
dev_info = tk.Label(about_window, text="Developed by Aleksandr")
230+
dev_info.pack()
231+
232+
# Copyright info
233+
copyright_info = tk.Label(about_window, text="© 2024 Aleksandr. MIT License")
234+
copyright_info.pack()
235+
236+
# Close button
237+
close_button = ttk.Button(about_window, text="Close", command=about_window.destroy)
238+
close_button.pack(pady=20)

speedtest_app/tests/test_network_adapter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
class TestNetworkAdapter(unittest.TestCase):
88
"""Tests for the network_adapter_information module."""
99

10-
@patch('network_adapter_information.platform')
11-
@patch('network_adapter_information.psutil')
10+
@patch('speedtest_app.network_adapter_information.platform')
11+
@patch('speedtest_app.network_adapter_information.psutil')
1212
def test_get_network_info_success(self, mock_psutil, mock_platform):
1313
"""Test successful retrieval of network information."""
1414
# Setup mocks
@@ -25,12 +25,12 @@ def test_get_network_info_success(self, mock_psutil, mock_platform):
2525

2626
# Mock network interfaces
2727
mock_address_en0_ip = MagicMock(family=socket.AF_INET, address="192.168.1.10",
28-
netmask="255.255.255.0")
28+
netmask="255.255.255.0")
2929
mock_address_en0_mac = MagicMock(family=socket.AF_LINK, address="00:11:22:33:44:55")
3030
mock_address_en0_ipv6 = MagicMock(family=socket.AF_INET6, address="fe80::1")
3131

3232
mock_address_lo0_ip = MagicMock(family=socket.AF_INET, address="127.0.0.1",
33-
netmask="255.0.0.0")
33+
netmask="255.0.0.0")
3434
mock_address_lo0_mac = MagicMock(family=socket.AF_LINK, address="00:00:00:00:00:00")
3535

3636
mock_interfaces = {

speedtest_app/tests/test_test_history.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import tempfile
55
import shutil
66
from unittest.mock import patch, MagicMock
7-
import test_history
7+
from speedtest_app import test_history
88

99

1010
class TestHistory(unittest.TestCase):
@@ -22,7 +22,9 @@ def tearDown(self):
2222
def test_save_test_results_new_file(self):
2323
"""Test saving test results to a new file."""
2424
# Call function with test data
25-
test_history.save_test_results(100.5, 50.2, 20.1, self.history_path)
25+
test_history.save_test_results(
26+
100.5, 50.2, 20.1, self.history_path
27+
)
2628

2729
# Verify file was created and contains correct data
2830
self.assertTrue(os.path.exists(self.history_path))
@@ -48,7 +50,9 @@ def test_save_test_results_existing_file(self):
4850
json.dump(initial_data, f)
4951

5052
# Call function with new test data
51-
test_history.save_test_results(100.5, 50.2, 20.1, self.history_path)
53+
test_history.save_test_results(
54+
100.5, 50.2, 20.1, self.history_path
55+
)
5256

5357
# Verify file contains both entries
5458
with open(self.history_path, 'r', encoding='utf-8') as f:
@@ -65,7 +69,9 @@ def test_save_test_results_invalid_json(self):
6569
f.write("This is not valid JSON")
6670

6771
# Call function (should handle the error and create a new file)
68-
test_history.save_test_results(100.5, 50.2, 20.1, self.history_path)
72+
test_history.save_test_results(
73+
100.5, 50.2, 20.1, self.history_path
74+
)
6975

7076
# Verify file contains valid data now
7177
with open(self.history_path, 'r', encoding='utf-8') as f:
@@ -98,4 +104,4 @@ def test_plot_history_no_file(self, mock_messagebox):
98104

99105

100106
if __name__ == '__main__':
101-
unittest.main()
107+
unittest.main()

0 commit comments

Comments
 (0)