""" Tests for the GraphManager class. """ import os import pytest import pandas as pd import tkinter as tk from tkinter import ttk from unittest.mock import Mock, patch, MagicMock import matplotlib.pyplot as plt import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) from src.graph_manager import GraphManager class TestGraphManager: """Test cases for the GraphManager class.""" @pytest.fixture def root_window(self): """Create a root window for testing.""" root = tk.Tk() yield root root.destroy() @pytest.fixture def parent_frame(self, root_window): """Create a parent frame for testing.""" frame = ttk.LabelFrame(root_window, text="Test Frame") frame.pack() return frame def test_init(self, parent_frame): """Test GraphManager initialization.""" gm = GraphManager(parent_frame) assert gm.parent_frame == parent_frame assert isinstance(gm.toggle_vars, dict) # Check symptom toggles assert "depression" in gm.toggle_vars assert "anxiety" in gm.toggle_vars assert "sleep" in gm.toggle_vars assert "appetite" in gm.toggle_vars # Check medicine toggles assert "bupropion" in gm.toggle_vars assert "hydroxyzine" in gm.toggle_vars assert "gabapentin" in gm.toggle_vars assert "propranolol" in gm.toggle_vars assert "quetiapine" in gm.toggle_vars # Check that symptom toggles are initially True for symptom in ["depression", "anxiety", "sleep", "appetite"]: assert gm.toggle_vars[symptom].get() is True # Check that some medicine toggles are True by default assert gm.toggle_vars["bupropion"].get() is True assert gm.toggle_vars["propranolol"].get() is True # Check that some medicine toggles are False by default assert gm.toggle_vars["hydroxyzine"].get() is False assert gm.toggle_vars["gabapentin"].get() is False assert gm.toggle_vars["quetiapine"].get() is False def test_toggle_controls_creation(self, parent_frame): """Test that toggle controls are created properly.""" gm = GraphManager(parent_frame) # Check that control frame exists assert hasattr(gm, 'control_frame') assert isinstance(gm.control_frame, ttk.Frame) # Check that all toggle variables exist expected_toggles = ["depression", "anxiety", "sleep", "appetite", "bupropion", "hydroxyzine", "gabapentin", "propranolol", "quetiapine"] for toggle in expected_toggles: assert toggle in gm.toggle_vars assert isinstance(gm.toggle_vars[toggle], tk.BooleanVar) def test_graph_frame_creation(self, parent_frame): """Test that graph frame is created properly.""" gm = GraphManager(parent_frame) assert hasattr(gm, 'graph_frame') assert isinstance(gm.graph_frame, ttk.Frame) @patch('matplotlib.pyplot.subplots') def test_matplotlib_initialization(self, mock_subplots, parent_frame): """Test matplotlib figure and canvas initialization.""" mock_fig = Mock() mock_ax = Mock() mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class: mock_canvas = Mock() mock_canvas_class.return_value = mock_canvas gm = GraphManager(parent_frame) assert gm.fig == mock_fig assert gm.ax == mock_ax assert gm.canvas == mock_canvas mock_canvas_class.assert_called_once_with(figure=mock_fig, master=gm.graph_frame) def test_update_graph_empty_dataframe(self, parent_frame): """Test updating graph with empty DataFrame.""" with patch('matplotlib.pyplot.subplots') as mock_subplots: mock_fig = Mock() mock_ax = Mock() mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg'): gm = GraphManager(parent_frame) # Test with empty DataFrame empty_df = pd.DataFrame() gm.update_graph(empty_df) # Verify ax.clear() was called mock_ax.clear.assert_called() def test_update_graph_with_data(self, parent_frame, sample_dataframe): """Test updating graph with valid data.""" with patch('matplotlib.pyplot.subplots') as mock_subplots: mock_fig = Mock() mock_ax = Mock() mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class: mock_canvas = Mock() mock_canvas_class.return_value = mock_canvas gm = GraphManager(parent_frame) gm.update_graph(sample_dataframe) # Verify methods were called mock_ax.clear.assert_called() mock_canvas.draw.assert_called() def test_toggle_functionality(self, parent_frame, sample_dataframe): """Test that toggle variables affect graph display.""" with patch('matplotlib.pyplot.subplots') as mock_subplots: mock_fig = Mock() mock_ax = Mock() mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class: mock_canvas = Mock() mock_canvas_class.return_value = mock_canvas gm = GraphManager(parent_frame) # Turn off depression toggle gm.toggle_vars["depression"].set(False) gm.update_graph(sample_dataframe) # The graph should still update (specific plotting logic would need more detailed testing) mock_ax.clear.assert_called() mock_canvas.draw.assert_called() def test_close_method(self, parent_frame): """Test the close method.""" with patch('matplotlib.pyplot.subplots') as mock_subplots: mock_fig = Mock() mock_ax = Mock() mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class: mock_canvas = Mock() mock_canvas_class.return_value = mock_canvas with patch('matplotlib.pyplot.close') as mock_plt_close: gm = GraphManager(parent_frame) gm.close() mock_plt_close.assert_called_once_with(mock_fig) def test_date_parsing_in_update_graph(self, parent_frame): """Test that date parsing works correctly in update_graph.""" # Create a DataFrame with date strings df_with_dates = pd.DataFrame({ 'date': ['2024-01-01', '2024-01-02', '2024-01-03'], 'depression': [3, 2, 4], 'anxiety': [2, 3, 1], 'sleep': [4, 3, 5], 'appetite': [3, 4, 2], 'bupropion': [1, 1, 0], 'hydroxyzine': [0, 1, 0], 'gabapentin': [2, 2, 1], 'propranolol': [1, 0, 1], 'note': ['Test note 1', 'Test note 2', ''] }) with patch('matplotlib.pyplot.subplots') as mock_subplots: mock_fig = Mock() mock_ax = Mock() mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class: mock_canvas = Mock() mock_canvas_class.return_value = mock_canvas with patch('pandas.to_datetime') as mock_to_datetime: gm = GraphManager(parent_frame) gm.update_graph(df_with_dates) # Verify pandas.to_datetime was called mock_to_datetime.assert_called() @patch('matplotlib.pyplot.subplots') def test_exception_handling_in_update_graph(self, mock_subplots, parent_frame, sample_dataframe): """Test exception handling in update_graph method.""" mock_fig = Mock() mock_ax = Mock() mock_ax.plot.side_effect = Exception("Plot error") mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class: mock_canvas = Mock() mock_canvas_class.return_value = mock_canvas gm = GraphManager(parent_frame) # This should not raise an exception, but handle it gracefully try: gm.update_graph(sample_dataframe) except Exception as e: pytest.fail(f"update_graph should handle exceptions gracefully, but raised: {e}") def test_grid_configuration(self, parent_frame): """Test that grid configuration is set up correctly.""" gm = GraphManager(parent_frame) # The parent frame should have grid configuration # Note: In a real test, you might need to check grid_info() or similar # This is a basic structure test assert hasattr(gm, 'parent_frame') assert hasattr(gm, 'control_frame') assert hasattr(gm, 'graph_frame') def test_canvas_widget_packing(self, parent_frame): """Test that canvas widget is properly packed.""" with patch('matplotlib.pyplot.subplots') as mock_subplots: mock_fig = Mock() mock_ax = Mock() mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class: mock_canvas = Mock() mock_canvas.get_tk_widget.return_value = Mock() mock_canvas_class.return_value = mock_canvas gm = GraphManager(parent_frame) # Verify get_tk_widget was called (for packing) mock_canvas.get_tk_widget.assert_called() def test_multiple_toggle_combinations(self, parent_frame, sample_dataframe): """Test various combinations of toggle states.""" with patch('matplotlib.pyplot.subplots') as mock_subplots: mock_fig = Mock() mock_ax = Mock() mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class: mock_canvas = Mock() mock_canvas_class.return_value = mock_canvas gm = GraphManager(parent_frame) # Test all toggles off for toggle in gm.toggle_vars.values(): toggle.set(False) gm.update_graph(sample_dataframe) # Test mixed toggles gm.toggle_vars["depression"].set(True) gm.toggle_vars["anxiety"].set(False) gm.update_graph(sample_dataframe) # Verify the graph was updated in each case assert mock_ax.clear.call_count >= 2 assert mock_canvas.draw.call_count >= 2 def test_calculate_daily_dose_empty_input(self, parent_frame): """Test dose calculation with empty/invalid input.""" gm = GraphManager(parent_frame) # Test empty string assert gm._calculate_daily_dose("") == 0.0 # Test NaN values assert gm._calculate_daily_dose("nan") == 0.0 assert gm._calculate_daily_dose("NaN") == 0.0 # Test None (will be converted to string) assert gm._calculate_daily_dose(None) == 0.0 def test_calculate_daily_dose_standard_format(self, parent_frame): """Test dose calculation with standard timestamp:dose format.""" gm = GraphManager(parent_frame) # Single dose dose_str = "2025-07-28 18:59:45:150mg" assert gm._calculate_daily_dose(dose_str) == 150.0 # Multiple doses dose_str = "2025-07-28 18:59:45:150mg|2025-07-28 19:34:19:75mg" assert gm._calculate_daily_dose(dose_str) == 225.0 # Doses without units dose_str = "2025-07-28 18:59:45:10|2025-07-28 19:34:19:5" assert gm._calculate_daily_dose(dose_str) == 15.0 def test_calculate_daily_dose_with_symbols(self, parent_frame): """Test dose calculation with bullet symbols.""" gm = GraphManager(parent_frame) # With bullet symbols dose_str = "• • • • 2025-07-30 07:50:00:300" assert gm._calculate_daily_dose(dose_str) == 300.0 # Multiple bullets dose_str = "• 2025-07-30 22:50:00:10|• 2025-07-30 23:50:00:5" assert gm._calculate_daily_dose(dose_str) == 15.0 def test_calculate_daily_dose_no_timestamp(self, parent_frame): """Test dose calculation without timestamp.""" gm = GraphManager(parent_frame) # Just dose value dose_str = "150mg" assert gm._calculate_daily_dose(dose_str) == 150.0 # Multiple values without timestamp dose_str = "100|50" assert gm._calculate_daily_dose(dose_str) == 150.0 def test_calculate_daily_dose_decimal_values(self, parent_frame): """Test dose calculation with decimal values.""" gm = GraphManager(parent_frame) # Decimal dose dose_str = "2025-07-28 18:59:45:12.5mg" assert gm._calculate_daily_dose(dose_str) == 12.5 # Multiple decimal doses dose_str = "2025-07-28 18:59:45:12.5mg|2025-07-28 19:34:19:7.5mg" assert gm._calculate_daily_dose(dose_str) == 20.0 def test_medicine_dose_plotting(self, parent_frame): """Test that medicine doses are plotted correctly.""" # Create a DataFrame with dose data df_with_doses = pd.DataFrame({ 'date': ['2024-01-01', '2024-01-02', '2024-01-03'], 'depression': [3, 2, 4], 'anxiety': [2, 3, 1], 'sleep': [4, 3, 5], 'appetite': [3, 4, 2], 'bupropion': [1, 1, 0], 'bupropion_doses': ['2024-01-01 08:00:00:150mg', '2024-01-02 08:00:00:300mg', ''], 'hydroxyzine': [0, 1, 0], 'hydroxyzine_doses': ['', '2024-01-02 20:00:00:25mg', ''], 'gabapentin': [0, 0, 0], 'gabapentin_doses': ['', '', ''], 'propranolol': [1, 0, 1], 'propranolol_doses': ['2024-01-01 12:00:00:10mg', '', '2024-01-03 12:00:00:20mg'], 'quetiapine': [0, 0, 0], 'quetiapine_doses': ['', '', ''], }) with patch('matplotlib.pyplot.subplots') as mock_subplots: mock_fig = Mock() mock_ax = Mock() mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class: mock_canvas = Mock() mock_canvas_class.return_value = mock_canvas gm = GraphManager(parent_frame) gm.update_graph(df_with_doses) # Verify that bar plots were called (for medicines with doses) mock_ax.bar.assert_called() # Verify canvas was redrawn mock_canvas.draw.assert_called() def test_medicine_toggle_functionality(self, parent_frame): """Test that medicine toggles affect dose display.""" df_with_doses = pd.DataFrame({ 'date': ['2024-01-01'], 'depression': [3], 'anxiety': [2], 'sleep': [4], 'appetite': [3], 'bupropion': [1], 'bupropion_doses': ['2024-01-01 08:00:00:150mg'], 'hydroxyzine': [0], 'hydroxyzine_doses': [''], 'gabapentin': [0], 'gabapentin_doses': [''], 'propranolol': [1], 'propranolol_doses': ['2024-01-01 12:00:00:10mg'], 'quetiapine': [0], 'quetiapine_doses': [''], }) with patch('matplotlib.pyplot.subplots') as mock_subplots: mock_fig = Mock() mock_ax = Mock() mock_subplots.return_value = (mock_fig, mock_ax) with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class: mock_canvas = Mock() mock_canvas_class.return_value = mock_canvas gm = GraphManager(parent_frame) # Turn off bupropion toggle gm.toggle_vars["bupropion"].set(False) gm.update_graph(df_with_doses) # Turn on hydroxyzine toggle (though it has no doses) gm.toggle_vars["hydroxyzine"].set(True) gm.update_graph(df_with_doses) # Verify the graph was updated assert mock_ax.clear.call_count >= 2 assert mock_canvas.draw.call_count >= 2 def test_dose_calculation_comprehensive(self, parent_frame, sample_dose_data): """Test dose calculation with comprehensive test cases.""" gm = GraphManager(parent_frame) # Test all sample dose data cases assert gm._calculate_daily_dose(sample_dose_data['standard_format']) == 225.0 assert gm._calculate_daily_dose(sample_dose_data['with_bullets']) == 300.0 assert gm._calculate_daily_dose(sample_dose_data['decimal_doses']) == 20.0 assert gm._calculate_daily_dose(sample_dose_data['no_timestamp']) == 150.0 assert gm._calculate_daily_dose(sample_dose_data['mixed_format']) == 85.0 assert gm._calculate_daily_dose(sample_dose_data['empty_string']) == 0.0 assert gm._calculate_daily_dose(sample_dose_data['nan_value']) == 0.0 assert gm._calculate_daily_dose(sample_dose_data['no_units']) == 15.0 def test_dose_calculation_edge_cases(self, parent_frame): """Test dose calculation with edge cases.""" gm = GraphManager(parent_frame) # Test with malformed data assert gm._calculate_daily_dose("malformed:data") == 0.0 assert gm._calculate_daily_dose("::::") == 0.0 assert gm._calculate_daily_dose("2025-07-28:") == 0.0 assert gm._calculate_daily_dose("2025-07-28::mg") == 0.0 # Test with partial data assert gm._calculate_daily_dose("2025-07-28 18:59:45:150") == 150.0 # no units assert gm._calculate_daily_dose("150mg") == 150.0 # no timestamp # Test with spaces and special characters assert gm._calculate_daily_dose(" 2025-07-28 18:59:45:150mg ") == 150.0 assert gm._calculate_daily_dose("••• 2025-07-28 18:59:45:150mg •••") == 150.0