- Implemented MedicineManagementWindow for adding, editing, and removing medicines. - Created MedicineManager to handle medicine configurations, including loading and saving to JSON. - Updated UIManager to dynamically generate medicine-related UI components based on the MedicineManager. - Enhanced test suite with mock objects for MedicineManager to ensure proper functionality in DataManager tests. - Added validation for medicine input fields in the UI. - Introduced default medicine configurations for initial setup.
789 lines
31 KiB
Python
789 lines
31 KiB
Python
"""
|
|
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
|
|
|
|
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_enhanced_legend_functionality(self, parent_frame):
|
|
"""Test that the enhanced legend displays correctly with medicine data."""
|
|
df_with_doses = pd.DataFrame({
|
|
'date': ['2024-01-01', '2024-01-02'],
|
|
'depression': [3, 2],
|
|
'anxiety': [2, 3],
|
|
'sleep': [4, 3],
|
|
'appetite': [3, 4],
|
|
'bupropion': [1, 1],
|
|
'bupropion_doses': ['2024-01-01 08:00:00:150mg', '2024-01-02 08:00:00:200mg'],
|
|
'hydroxyzine': [0, 0],
|
|
'hydroxyzine_doses': ['', ''],
|
|
'gabapentin': [0, 0],
|
|
'gabapentin_doses': ['', ''],
|
|
'propranolol': [1, 1],
|
|
'propranolol_doses': ['2024-01-01 12:00:00:10mg', '2024-01-02 12:00:00:15mg'],
|
|
'quetiapine': [0, 0],
|
|
'quetiapine_doses': ['', ''],
|
|
})
|
|
|
|
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
|
mock_fig = Mock()
|
|
mock_ax = Mock()
|
|
mock_ax.get_legend_handles_labels.return_value = ([], [])
|
|
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)
|
|
|
|
# Enable some medicine toggles
|
|
gm.toggle_vars["bupropion"].set(True)
|
|
gm.toggle_vars["propranolol"].set(True)
|
|
gm.toggle_vars["hydroxyzine"].set(True) # No dose data
|
|
|
|
gm.update_graph(df_with_doses)
|
|
|
|
# Verify that legend is called with enhanced parameters
|
|
mock_ax.legend.assert_called()
|
|
legend_call = mock_ax.legend.call_args
|
|
|
|
# Check that enhanced legend parameters are used
|
|
assert 'ncol' in legend_call.kwargs
|
|
assert legend_call.kwargs['ncol'] == 2
|
|
assert 'fontsize' in legend_call.kwargs
|
|
assert legend_call.kwargs['fontsize'] == 'small'
|
|
assert 'frameon' in legend_call.kwargs
|
|
assert legend_call.kwargs['frameon'] is True
|
|
|
|
def test_legend_with_medicines_without_data(self, parent_frame):
|
|
"""Test that medicines without dose data are properly tracked in legend."""
|
|
df_with_partial_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': [''], # No dose data
|
|
'gabapentin': [0],
|
|
'gabapentin_doses': [''], # No dose data
|
|
'propranolol': [0],
|
|
'propranolol_doses': [''],
|
|
'quetiapine': [0],
|
|
'quetiapine_doses': [''],
|
|
})
|
|
|
|
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
|
mock_fig = Mock()
|
|
mock_ax = Mock()
|
|
|
|
# Mock the legend handles and labels
|
|
original_handles = [Mock()]
|
|
original_labels = ['Bupropion (avg: 150.0mg)']
|
|
mock_ax.get_legend_handles_labels.return_value = (original_handles, original_labels)
|
|
|
|
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)
|
|
|
|
# Enable medicines with and without data
|
|
gm.toggle_vars["bupropion"].set(True) # Has data
|
|
gm.toggle_vars["hydroxyzine"].set(True) # No data
|
|
gm.toggle_vars["gabapentin"].set(True) # No data
|
|
|
|
gm.update_graph(df_with_partial_doses)
|
|
|
|
# Verify legend was called
|
|
mock_ax.legend.assert_called()
|
|
|
|
# Check that the legend call includes additional handles/labels
|
|
legend_call = mock_ax.legend.call_args
|
|
handles, labels = legend_call.args[:2]
|
|
|
|
# Should have more labels than just the original ones
|
|
assert len(labels) > len(original_labels)
|
|
|
|
def test_average_dose_calculation_in_legend(self, parent_frame):
|
|
"""Test that average doses are correctly calculated and displayed in legend."""
|
|
df_with_varying_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, 1],
|
|
'bupropion_doses': ['2024-01-01 08:00:00:100mg',
|
|
'2024-01-02 08:00:00:200mg',
|
|
'2024-01-03 08:00:00:150mg'], # Average should be 150mg
|
|
'propranolol': [1, 1, 0],
|
|
'propranolol_doses': ['2024-01-01 12:00:00:10mg',
|
|
'2024-01-02 12:00:00:20mg',
|
|
''], # Average should be 15mg
|
|
'hydroxyzine': [0, 0, 0],
|
|
'hydroxyzine_doses': ['', '', ''],
|
|
'gabapentin': [0, 0, 0],
|
|
'gabapentin_doses': ['', '', ''],
|
|
'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)
|
|
|
|
# Test the average calculation directly
|
|
bup_avg = gm._calculate_daily_dose('2024-01-01 08:00:00:100mg')
|
|
assert bup_avg == 100.0
|
|
|
|
prop_avg = gm._calculate_daily_dose('2024-01-01 12:00:00:10mg')
|
|
assert prop_avg == 10.0
|
|
|
|
# Test with full data
|
|
gm.toggle_vars["bupropion"].set(True)
|
|
gm.toggle_vars["propranolol"].set(True)
|
|
gm.update_graph(df_with_varying_doses)
|
|
|
|
# Verify that bars were plotted (indicating dose data was processed)
|
|
mock_ax.bar.assert_called()
|
|
|
|
def test_legend_positioning_and_styling(self, parent_frame):
|
|
"""Test that legend positioning and styling parameters are correctly applied."""
|
|
df_simple = 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': [0],
|
|
'propranolol_doses': [''],
|
|
'quetiapine': [0],
|
|
'quetiapine_doses': [''],
|
|
})
|
|
|
|
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
|
mock_fig = Mock()
|
|
mock_ax = Mock()
|
|
mock_ax.get_legend_handles_labels.return_value = ([Mock()], ['Test Label'])
|
|
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_simple)
|
|
|
|
# Verify legend styling parameters
|
|
mock_ax.legend.assert_called()
|
|
legend_call = mock_ax.legend.call_args
|
|
|
|
expected_params = {
|
|
'loc': 'upper left',
|
|
'bbox_to_anchor': (0, 1),
|
|
'ncol': 2,
|
|
'fontsize': 'small',
|
|
'frameon': True,
|
|
'fancybox': True,
|
|
'shadow': True,
|
|
'framealpha': 0.9
|
|
}
|
|
|
|
for param, expected_value in expected_params.items():
|
|
assert param in legend_call.kwargs
|
|
assert legend_call.kwargs[param] == expected_value
|
|
|
|
def test_medicine_tracking_lists(self, parent_frame):
|
|
"""Test that medicines are correctly categorized into with_data and without_data lists."""
|
|
df_mixed_data = pd.DataFrame({
|
|
'date': ['2024-01-01', '2024-01-02'],
|
|
'depression': [3, 2],
|
|
'anxiety': [2, 3],
|
|
'sleep': [4, 3],
|
|
'appetite': [3, 4],
|
|
# Medicines with data
|
|
'bupropion': [1, 1],
|
|
'bupropion_doses': ['2024-01-01 08:00:00:150mg', '2024-01-02 08:00:00:200mg'],
|
|
'propranolol': [1, 1],
|
|
'propranolol_doses': ['2024-01-01 12:00:00:10mg', '2024-01-02 12:00:00:15mg'],
|
|
# Medicines without data (but toggled on)
|
|
'hydroxyzine': [0, 0],
|
|
'hydroxyzine_doses': ['', ''],
|
|
'gabapentin': [0, 0],
|
|
'gabapentin_doses': ['', ''],
|
|
'quetiapine': [0, 0],
|
|
'quetiapine_doses': ['', ''],
|
|
})
|
|
|
|
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
|
mock_fig = Mock()
|
|
mock_ax = Mock()
|
|
mock_ax.get_legend_handles_labels.return_value = ([], [])
|
|
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)
|
|
|
|
# Enable all medicines
|
|
gm.toggle_vars["bupropion"].set(True) # Has data
|
|
gm.toggle_vars["propranolol"].set(True) # Has data
|
|
gm.toggle_vars["hydroxyzine"].set(True) # No data
|
|
gm.toggle_vars["gabapentin"].set(True) # No data
|
|
gm.toggle_vars["quetiapine"].set(False) # Disabled
|
|
|
|
gm.update_graph(df_mixed_data)
|
|
|
|
# Verify that the method was called and plotting occurred
|
|
mock_ax.bar.assert_called() # Should be called for medicines with data
|
|
mock_ax.legend.assert_called() # Legend should be created
|
|
|
|
def test_legend_dummy_handle_creation(self, parent_frame):
|
|
"""Test that dummy handles are created for medicines without data."""
|
|
df_no_dose_data = pd.DataFrame({
|
|
'date': ['2024-01-01'],
|
|
'depression': [3],
|
|
'anxiety': [2],
|
|
'sleep': [4],
|
|
'appetite': [3],
|
|
'bupropion': [0],
|
|
'bupropion_doses': [''],
|
|
'hydroxyzine': [0],
|
|
'hydroxyzine_doses': [''],
|
|
'gabapentin': [0],
|
|
'gabapentin_doses': [''],
|
|
'propranolol': [0],
|
|
'propranolol_doses': [''],
|
|
'quetiapine': [0],
|
|
'quetiapine_doses': [''],
|
|
})
|
|
|
|
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
|
mock_fig = Mock()
|
|
mock_ax = Mock()
|
|
mock_ax.get_legend_handles_labels.return_value = ([Mock()], ['Depression'])
|
|
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
|
|
|
|
# Mock Rectangle import for dummy handle creation
|
|
with patch('matplotlib.patches.Rectangle') as mock_rectangle:
|
|
mock_dummy_handle = Mock()
|
|
mock_rectangle.return_value = mock_dummy_handle
|
|
|
|
gm = GraphManager(parent_frame)
|
|
|
|
# Enable some medicines without data
|
|
gm.toggle_vars["hydroxyzine"].set(True)
|
|
gm.toggle_vars["gabapentin"].set(True)
|
|
|
|
gm.update_graph(df_no_dose_data)
|
|
|
|
# If there are medicines without data, Rectangle should be called
|
|
# to create dummy handles
|
|
if gm.toggle_vars["hydroxyzine"].get() or gm.toggle_vars["gabapentin"].get():
|
|
mock_rectangle.assert_called()
|
|
|
|
def test_empty_dataframe_legend_handling(self, parent_frame):
|
|
"""Test that legend is handled correctly with empty DataFrame."""
|
|
empty_df = pd.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') as mock_canvas_class:
|
|
mock_canvas = Mock()
|
|
mock_canvas_class.return_value = mock_canvas
|
|
|
|
gm = GraphManager(parent_frame)
|
|
gm.update_graph(empty_df)
|
|
|
|
# With empty data, legend should not be called
|
|
mock_ax.legend.assert_not_called()
|
|
mock_ax.clear.assert_called()
|
|
mock_canvas.draw.assert_called()
|
|
|
|
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
|