""" Tests for the main application and MedTrackerApp class. """ import os import pytest import tkinter as tk from unittest.mock import Mock, patch import pandas as pd import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) from src.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_application_icon was called on UI manager app.ui_manager.setup_application_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_application_icon was called with fallback path app.ui_manager.setup_application_icon.assert_called_with(img_path="./chart-671.png") def test_add_new_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, 'refresh_data_display') as mock_load: app.add_new_entry() mock_info.assert_called_once() mock_clear.assert_called_once() mock_load.assert_called_once() def test_add_new_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_new_entry() mock_error.assert_called_once_with( "Error", "Please enter a date.", parent=app.root ) def test_add_new_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_new_entry() mock_error.assert_called_once() assert "already exists" in mock_error.call_args[0][1] def test_handle_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.handle_double_click(mock_event) mock_create_edit.assert_called_once() def test_handle_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.handle_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, 'refresh_data_display') 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, 'refresh_data_display') 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_refresh_data_display(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.refresh_data_display() # 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_refresh_data_display_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.refresh_data_display() # Graph should still be updated even with empty data app.graph_manager.update_graph.assert_called_with(empty_df) def test_handle_window_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.handle_window_closing() mock_confirm.assert_called_once() app.graph_manager.close.assert_called_once() def test_handle_window_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.handle_window_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