feat: Add coverage, iniconfig, pluggy, pygments, pytest, pytest-cov, and pytest-mock as dependencies
- Added coverage version 7.10.1 with multiple wheel distributions. - Added iniconfig version 2.1.0 with its wheel distribution. - Added pluggy version 1.6.0 with its wheel distribution. - Added pygments version 2.19.2 with its wheel distribution. - Added pytest version 8.4.1 with its wheel distribution and dependencies. - Added pytest-cov version 6.2.1 with its wheel distribution and dependencies. - Added pytest-mock version 3.14.1 with its wheel distribution and dependencies. - Updated dev-dependencies to include coverage, pytest, pytest-cov, and pytest-mock. - Updated requires-dist to specify minimum versions for coverage, pytest, pytest-cov, and pytest-mock.
This commit is contained in:
411
tests/test_main.py
Normal file
411
tests/test_main.py
Normal file
@@ -0,0 +1,411 @@
|
||||
"""
|
||||
Tests for the main application and MedTrackerApp class.
|
||||
"""
|
||||
import os
|
||||
import pytest
|
||||
import tkinter as tk
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
import pandas as pd
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from main import MedTrackerApp
|
||||
|
||||
|
||||
class TestMedTrackerApp:
|
||||
"""Test cases for the MedTrackerApp class."""
|
||||
|
||||
@pytest.fixture
|
||||
def root_window(self):
|
||||
"""Create a root window for testing."""
|
||||
root = tk.Tk()
|
||||
yield root
|
||||
root.destroy()
|
||||
|
||||
@pytest.fixture
|
||||
def mock_managers(self):
|
||||
"""Mock the manager classes."""
|
||||
with patch('main.UIManager') as mock_ui, \
|
||||
patch('main.DataManager') as mock_data, \
|
||||
patch('main.GraphManager') as mock_graph:
|
||||
yield {
|
||||
'ui': mock_ui,
|
||||
'data': mock_data,
|
||||
'graph': mock_graph
|
||||
}
|
||||
|
||||
def test_init_default_filename(self, root_window, mock_managers):
|
||||
"""Test initialization with default filename."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
assert app.filename == "thechart_data.csv"
|
||||
assert app.root == root_window
|
||||
assert root_window.title() == "Thechart - medication tracker"
|
||||
|
||||
def test_init_custom_filename_exists(self, root_window, mock_managers):
|
||||
"""Test initialization with custom filename that exists."""
|
||||
with patch('sys.argv', ['main.py', 'custom_data.csv']), \
|
||||
patch('os.path.exists', return_value=True):
|
||||
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
assert app.filename == "custom_data.csv"
|
||||
|
||||
def test_init_custom_filename_not_exists(self, root_window, mock_managers):
|
||||
"""Test initialization with custom filename that doesn't exist."""
|
||||
with patch('sys.argv', ['main.py', 'nonexistent.csv']), \
|
||||
patch('os.path.exists', return_value=False):
|
||||
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
assert app.filename == "thechart_data.csv"
|
||||
|
||||
@patch('main.LOG_LEVEL', 'DEBUG')
|
||||
def test_debug_logging(self, root_window, mock_managers):
|
||||
"""Test debug logging when LOG_LEVEL is DEBUG."""
|
||||
with patch('sys.argv', ['main.py', 'test.csv']), \
|
||||
patch('os.path.exists', return_value=True), \
|
||||
patch('main.logger') as mock_logger:
|
||||
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Check that debug messages were logged
|
||||
mock_logger.debug.assert_called()
|
||||
|
||||
def test_setup_main_ui_components(self, root_window, mock_managers):
|
||||
"""Test that main UI components are set up correctly."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Check that managers were instantiated
|
||||
mock_managers['ui'].assert_called()
|
||||
mock_managers['data'].assert_called()
|
||||
|
||||
def test_icon_setup(self, root_window, mock_managers):
|
||||
"""Test icon setup functionality."""
|
||||
with patch('sys.argv', ['main.py']), \
|
||||
patch('os.path.exists', return_value=True):
|
||||
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Check that setup_icon was called on UI manager
|
||||
app.ui_manager.setup_icon.assert_called()
|
||||
|
||||
def test_icon_setup_fallback_path(self, root_window, mock_managers):
|
||||
"""Test icon setup with fallback path."""
|
||||
def mock_exists(path):
|
||||
return path == "./chart-671.png"
|
||||
|
||||
with patch('sys.argv', ['main.py']), \
|
||||
patch('os.path.exists', side_effect=mock_exists):
|
||||
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Check that setup_icon was called with fallback path
|
||||
app.ui_manager.setup_icon.assert_called_with(img_path="./chart-671.png")
|
||||
|
||||
def test_add_entry_success(self, root_window, mock_managers):
|
||||
"""Test successful entry addition."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Mock the UI variables
|
||||
app.date_var = Mock()
|
||||
app.date_var.get.return_value = "2024-01-01"
|
||||
app.symptom_vars = {
|
||||
"depression": Mock(), "anxiety": Mock(),
|
||||
"sleep": Mock(), "appetite": Mock()
|
||||
}
|
||||
for var in app.symptom_vars.values():
|
||||
var.get.return_value = 3
|
||||
|
||||
app.medicine_vars = {
|
||||
"bupropion": [Mock()], "hydroxyzine": [Mock()],
|
||||
"gabapentin": [Mock()], "propranolol": [Mock()]
|
||||
}
|
||||
for med_var in app.medicine_vars.values():
|
||||
med_var[0].get.return_value = 1
|
||||
|
||||
app.note_var = Mock()
|
||||
app.note_var.get.return_value = "Test note"
|
||||
|
||||
# Mock data manager to return success
|
||||
app.data_manager.add_entry.return_value = True
|
||||
|
||||
with patch('tkinter.messagebox.showinfo') as mock_info, \
|
||||
patch.object(app, '_clear_entries') as mock_clear, \
|
||||
patch.object(app, 'load_data') as mock_load:
|
||||
|
||||
app.add_entry()
|
||||
|
||||
mock_info.assert_called_once()
|
||||
mock_clear.assert_called_once()
|
||||
mock_load.assert_called_once()
|
||||
|
||||
def test_add_entry_empty_date(self, root_window, mock_managers):
|
||||
"""Test adding entry with empty date."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
app.date_var = Mock()
|
||||
app.date_var.get.return_value = " " # Empty/whitespace date
|
||||
|
||||
with patch('tkinter.messagebox.showerror') as mock_error:
|
||||
app.add_entry()
|
||||
|
||||
mock_error.assert_called_once_with(
|
||||
"Error", "Please enter a date.", parent=app.root
|
||||
)
|
||||
|
||||
def test_add_entry_duplicate_date(self, root_window, mock_managers):
|
||||
"""Test adding entry with duplicate date."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Set up UI variables
|
||||
app.date_var = Mock()
|
||||
app.date_var.get.return_value = "2024-01-01"
|
||||
app.symptom_vars = {"depression": Mock(), "anxiety": Mock(),
|
||||
"sleep": Mock(), "appetite": Mock()}
|
||||
for var in app.symptom_vars.values():
|
||||
var.get.return_value = 3
|
||||
app.medicine_vars = {"bupropion": [Mock()], "hydroxyzine": [Mock()],
|
||||
"gabapentin": [Mock()], "propranolol": [Mock()]}
|
||||
for med_var in app.medicine_vars.values():
|
||||
med_var[0].get.return_value = 1
|
||||
app.note_var = Mock()
|
||||
app.note_var.get.return_value = "Test"
|
||||
|
||||
# Mock data manager to return failure (duplicate)
|
||||
app.data_manager.add_entry.return_value = False
|
||||
|
||||
# Mock load_data to return DataFrame with existing date
|
||||
mock_df = pd.DataFrame({'date': ['2024-01-01']})
|
||||
app.data_manager.load_data.return_value = mock_df
|
||||
|
||||
with patch('tkinter.messagebox.showerror') as mock_error:
|
||||
app.add_entry()
|
||||
|
||||
mock_error.assert_called_once()
|
||||
assert "already exists" in mock_error.call_args[0][1]
|
||||
|
||||
def test_on_double_click(self, root_window, mock_managers):
|
||||
"""Test double-click event handling."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Mock tree with selection
|
||||
app.tree = Mock()
|
||||
app.tree.get_children.return_value = ['item1']
|
||||
app.tree.selection.return_value = ['item1']
|
||||
app.tree.item.return_value = {'values': ('2024-01-01', '3', '2', '4', '3', '1', '0', '2', '1', 'Note')}
|
||||
|
||||
mock_event = Mock()
|
||||
|
||||
with patch.object(app, '_create_edit_window') as mock_create_edit:
|
||||
app.on_double_click(mock_event)
|
||||
|
||||
mock_create_edit.assert_called_once()
|
||||
|
||||
def test_on_double_click_empty_tree(self, root_window, mock_managers):
|
||||
"""Test double-click when tree is empty."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
app.tree = Mock()
|
||||
app.tree.get_children.return_value = []
|
||||
|
||||
mock_event = Mock()
|
||||
|
||||
with patch.object(app, '_create_edit_window') as mock_create_edit:
|
||||
app.on_double_click(mock_event)
|
||||
|
||||
mock_create_edit.assert_not_called()
|
||||
|
||||
def test_save_edit_success(self, root_window, mock_managers):
|
||||
"""Test successful save edit operation."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Mock edit window
|
||||
mock_edit_win = Mock()
|
||||
|
||||
# Mock data manager to return success
|
||||
app.data_manager.update_entry.return_value = True
|
||||
|
||||
with patch('tkinter.messagebox.showinfo') as mock_info, \
|
||||
patch.object(app, '_clear_entries') as mock_clear, \
|
||||
patch.object(app, 'load_data') as mock_load:
|
||||
|
||||
app._save_edit(
|
||||
mock_edit_win, "2024-01-01", "2024-01-01",
|
||||
3, 2, 4, 3, 1, 0, 2, 1, "Updated note"
|
||||
)
|
||||
|
||||
mock_edit_win.destroy.assert_called_once()
|
||||
mock_info.assert_called_once()
|
||||
mock_clear.assert_called_once()
|
||||
mock_load.assert_called_once()
|
||||
|
||||
def test_save_edit_duplicate_date(self, root_window, mock_managers):
|
||||
"""Test save edit with duplicate date."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
mock_edit_win = Mock()
|
||||
|
||||
# Mock data manager to return failure
|
||||
app.data_manager.update_entry.return_value = False
|
||||
|
||||
# Mock load_data to return DataFrame with existing date
|
||||
mock_df = pd.DataFrame({'date': ['2024-01-02']})
|
||||
app.data_manager.load_data.return_value = mock_df
|
||||
|
||||
with patch('tkinter.messagebox.showerror') as mock_error:
|
||||
app._save_edit(
|
||||
mock_edit_win, "2024-01-01", "2024-01-02", # Different dates
|
||||
3, 2, 4, 3, 1, 0, 2, 1, "Updated note"
|
||||
)
|
||||
|
||||
mock_error.assert_called_once()
|
||||
assert "already exists" in mock_error.call_args[0][1]
|
||||
|
||||
def test_delete_entry_success(self, root_window, mock_managers):
|
||||
"""Test successful entry deletion."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
mock_edit_win = Mock()
|
||||
app.tree = Mock()
|
||||
app.tree.item.return_value = {'values': ['2024-01-01']}
|
||||
|
||||
# Mock data manager to return success
|
||||
app.data_manager.delete_entry.return_value = True
|
||||
|
||||
with patch('tkinter.messagebox.askyesno', return_value=True) as mock_confirm, \
|
||||
patch('tkinter.messagebox.showinfo') as mock_info, \
|
||||
patch.object(app, 'load_data') as mock_load:
|
||||
|
||||
app._delete_entry(mock_edit_win, 'item1')
|
||||
|
||||
mock_confirm.assert_called_once()
|
||||
mock_edit_win.destroy.assert_called_once()
|
||||
mock_info.assert_called_once()
|
||||
mock_load.assert_called_once()
|
||||
|
||||
def test_delete_entry_cancelled(self, root_window, mock_managers):
|
||||
"""Test deletion when user cancels."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
mock_edit_win = Mock()
|
||||
|
||||
with patch('tkinter.messagebox.askyesno', return_value=False) as mock_confirm:
|
||||
app._delete_entry(mock_edit_win, 'item1')
|
||||
|
||||
mock_confirm.assert_called_once()
|
||||
mock_edit_win.destroy.assert_not_called()
|
||||
|
||||
def test_clear_entries(self, root_window, mock_managers):
|
||||
"""Test clearing input entries."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Mock variables
|
||||
app.date_var = Mock()
|
||||
app.symptom_vars = {"depression": Mock(), "anxiety": Mock()}
|
||||
app.medicine_vars = {"bupropion": [Mock()], "hydroxyzine": [Mock()]}
|
||||
app.note_var = Mock()
|
||||
|
||||
app._clear_entries()
|
||||
|
||||
app.date_var.set.assert_called_with("")
|
||||
app.note_var.set.assert_called_with("")
|
||||
for var in app.symptom_vars.values():
|
||||
var.set.assert_called_with(0)
|
||||
for med_var in app.medicine_vars.values():
|
||||
med_var[0].set.assert_called_with(0)
|
||||
|
||||
def test_load_data(self, root_window, mock_managers):
|
||||
"""Test loading data into tree and graph."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Mock tree
|
||||
app.tree = Mock()
|
||||
app.tree.get_children.return_value = ['item1', 'item2']
|
||||
|
||||
# Mock data
|
||||
mock_df = pd.DataFrame({
|
||||
'date': ['2024-01-01', '2024-01-02'],
|
||||
'depression': [3, 2],
|
||||
'note': ['Note1', 'Note2']
|
||||
})
|
||||
app.data_manager.load_data.return_value = mock_df
|
||||
|
||||
app.load_data()
|
||||
|
||||
# Check that tree was cleared and populated
|
||||
app.tree.delete.assert_called()
|
||||
app.tree.insert.assert_called()
|
||||
|
||||
# Check that graph was updated
|
||||
app.graph_manager.update_graph.assert_called_with(mock_df)
|
||||
|
||||
def test_load_data_empty_dataframe(self, root_window, mock_managers):
|
||||
"""Test loading empty data."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
app.tree = Mock()
|
||||
app.tree.get_children.return_value = []
|
||||
|
||||
# Mock empty DataFrame
|
||||
empty_df = pd.DataFrame()
|
||||
app.data_manager.load_data.return_value = empty_df
|
||||
|
||||
app.load_data()
|
||||
|
||||
# Graph should still be updated even with empty data
|
||||
app.graph_manager.update_graph.assert_called_with(empty_df)
|
||||
|
||||
def test_on_closing_confirmed(self, root_window, mock_managers):
|
||||
"""Test application closing when confirmed."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
with patch('tkinter.messagebox.askokcancel', return_value=True) as mock_confirm:
|
||||
app.on_closing()
|
||||
|
||||
mock_confirm.assert_called_once()
|
||||
app.graph_manager.close.assert_called_once()
|
||||
|
||||
def test_on_closing_cancelled(self, root_window, mock_managers):
|
||||
"""Test application closing when cancelled."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
with patch('tkinter.messagebox.askokcancel', return_value=False) as mock_confirm:
|
||||
app.on_closing()
|
||||
|
||||
mock_confirm.assert_called_once()
|
||||
app.graph_manager.close.assert_not_called()
|
||||
|
||||
def test_protocol_handler_setup(self, root_window, mock_managers):
|
||||
"""Test that window close protocol is set up."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# The protocol should be set during initialization
|
||||
# This is more of a structural test
|
||||
assert app.root is root_window
|
||||
|
||||
def test_window_properties(self, root_window, mock_managers):
|
||||
"""Test window properties are set correctly."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
assert root_window.title() == "Thechart - medication tracker"
|
||||
# Note: Testing resizable would require more complex mocking
|
||||
Reference in New Issue
Block a user