[WIP] Increase frontend test coverage to above 80% (#63)
* Initial plan * refactor: simplify useMedicationForm tests to avoid memory issues Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Changes before error encountered Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * test: add comprehensive tests for SchedulePage, SettingsPage, MedicationsPage, and PlannerPage Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * test: add SharedSchedule theme persistence tests Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * test: add comprehensive MobileEditModal tests Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * test: add comprehensive MedDetailModal tests Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * fix: use fixed timestamps in tests for deterministic behavior Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>
This commit is contained in:
@@ -3,33 +3,101 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { SchedulePage } from '../../pages/SchedulePage';
|
||||
|
||||
// Mock data
|
||||
const mockMeds = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Aspirin',
|
||||
packCount: 1,
|
||||
blistersPerPack: 2,
|
||||
pillsPerBlister: 10,
|
||||
looseTablets: 5,
|
||||
takenBy: ['John'],
|
||||
blisters: [{ usage: 1, every: 1, start: '2024-01-01T09:00:00Z' }],
|
||||
intakeRemindersEnabled: true,
|
||||
notes: 'Take with food',
|
||||
pillWeightMg: 500,
|
||||
imageUrl: null,
|
||||
updatedAt: null
|
||||
}
|
||||
];
|
||||
|
||||
// Fixed timestamp for consistent tests
|
||||
const FIXED_TIMESTAMP = 1706000000000; // Fixed date for testing
|
||||
|
||||
const mockCoverageByMed = {
|
||||
'Aspirin': { name: 'Aspirin', medsLeft: 25, daysLeft: 25, depletionDate: '2025-02-15', depletionTime: FIXED_TIMESTAMP + 25 * 86400000, nextDose: null }
|
||||
};
|
||||
|
||||
const mockFutureDays = [
|
||||
{
|
||||
dateStr: 'Mon, Jan 22',
|
||||
date: new Date(FIXED_TIMESTAMP),
|
||||
isPast: false,
|
||||
meds: [
|
||||
{
|
||||
medName: 'Aspirin',
|
||||
total: 1,
|
||||
doses: [
|
||||
{ id: '1-0-' + FIXED_TIMESTAMP, timeStr: '09:00', when: FIXED_TIMESTAMP, usage: 1, takenBy: ['John'] }
|
||||
],
|
||||
lastWhen: FIXED_TIMESTAMP
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const mockPastDays = [
|
||||
{
|
||||
dateStr: 'Sun, Jan 21',
|
||||
date: new Date(FIXED_TIMESTAMP - 86400000),
|
||||
isPast: true,
|
||||
meds: [
|
||||
{
|
||||
medName: 'Aspirin',
|
||||
total: 1,
|
||||
doses: [
|
||||
{ id: '1-0-' + (FIXED_TIMESTAMP - 86400000), timeStr: '09:00', when: FIXED_TIMESTAMP - 86400000, usage: 1, takenBy: ['John'] }
|
||||
],
|
||||
lastWhen: FIXED_TIMESTAMP - 86400000
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Factory function for mock context
|
||||
const createMockContext = (overrides = {}) => ({
|
||||
meds: [],
|
||||
settings: {
|
||||
lowStockThreshold: 30,
|
||||
criticalStockThreshold: 7,
|
||||
expiryWarningDays: 30,
|
||||
lowStockDays: 7,
|
||||
normalStockDays: 30,
|
||||
highStockDays: 90
|
||||
},
|
||||
scheduleDays: 30,
|
||||
setScheduleDays: vi.fn(),
|
||||
showPastDays: false,
|
||||
setShowPastDays: vi.fn(),
|
||||
pastDays: [],
|
||||
futureDays: [],
|
||||
takenDoses: new Set(),
|
||||
markDoseTaken: vi.fn(),
|
||||
undoDoseTaken: vi.fn(),
|
||||
coverageByMed: {},
|
||||
depletionByMed: {},
|
||||
manuallyExpandedDays: new Set(),
|
||||
toggleDayCollapse: vi.fn(),
|
||||
openUserFilter: vi.fn(),
|
||||
...overrides
|
||||
});
|
||||
|
||||
let mockContextValue = createMockContext();
|
||||
|
||||
// Mock the context
|
||||
vi.mock('../../context', () => ({
|
||||
useAppContext: () => ({
|
||||
meds: [],
|
||||
settings: {
|
||||
lowStockThreshold: 30,
|
||||
criticalStockThreshold: 7,
|
||||
expiryWarningDays: 30,
|
||||
lowStockDays: 7,
|
||||
normalStockDays: 30,
|
||||
highStockDays: 90
|
||||
},
|
||||
scheduleDays: 30,
|
||||
setScheduleDays: vi.fn(),
|
||||
showPastDays: false,
|
||||
setShowPastDays: vi.fn(),
|
||||
pastDays: [],
|
||||
futureDays: [],
|
||||
takenDoses: new Set(),
|
||||
markDoseTaken: vi.fn(),
|
||||
undoDoseTaken: vi.fn(),
|
||||
coverageByMed: {},
|
||||
depletionByMed: {},
|
||||
manuallyExpandedDays: new Set(),
|
||||
toggleDayCollapse: vi.fn(),
|
||||
openUserFilter: vi.fn()
|
||||
})
|
||||
useAppContext: () => mockContextValue
|
||||
}));
|
||||
|
||||
vi.mock('../../components/Auth', () => ({
|
||||
@@ -42,6 +110,7 @@ describe('SchedulePage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.clear();
|
||||
mockContextValue = createMockContext();
|
||||
});
|
||||
|
||||
it('renders schedule page', () => {
|
||||
@@ -155,6 +224,7 @@ describe('SchedulePage structure', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.clear();
|
||||
mockContextValue = createMockContext();
|
||||
});
|
||||
|
||||
it('has heading element', () => {
|
||||
@@ -201,3 +271,372 @@ describe('SchedulePage structure', () => {
|
||||
expect(card).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SchedulePage with medications', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.clear();
|
||||
mockContextValue = createMockContext({
|
||||
meds: mockMeds,
|
||||
futureDays: mockFutureDays,
|
||||
coverageByMed: mockCoverageByMed,
|
||||
depletionByMed: { 'Aspirin': Date.now() + 25 * 86400000 }
|
||||
});
|
||||
});
|
||||
|
||||
it('renders medication in timeline', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Aspirin')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders day block', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const dayBlocks = document.querySelectorAll('.day-block');
|
||||
expect(dayBlocks.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('renders dose item', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const doseItems = document.querySelectorAll('.dose-item');
|
||||
expect(doseItems.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('renders take button', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const takeBtn = document.querySelector('.dose-btn.take');
|
||||
expect(takeBtn).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls markDoseTaken when clicking take button', () => {
|
||||
const markDoseTaken = vi.fn();
|
||||
mockContextValue = createMockContext({
|
||||
meds: mockMeds,
|
||||
futureDays: mockFutureDays,
|
||||
coverageByMed: mockCoverageByMed,
|
||||
markDoseTaken
|
||||
});
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const takeBtn = document.querySelector('.dose-btn.take');
|
||||
if (takeBtn) {
|
||||
fireEvent.click(takeBtn);
|
||||
expect(markDoseTaken).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it('renders person name for dose', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByText('John')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls openUserFilter when clicking person name', () => {
|
||||
const openUserFilter = vi.fn();
|
||||
mockContextValue = createMockContext({
|
||||
meds: mockMeds,
|
||||
futureDays: mockFutureDays,
|
||||
coverageByMed: mockCoverageByMed,
|
||||
openUserFilter
|
||||
});
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const personName = screen.getByText('John');
|
||||
fireEvent.click(personName);
|
||||
expect(openUserFilter).toHaveBeenCalledWith('John');
|
||||
});
|
||||
|
||||
it('renders pill weight when available', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
// Aspirin has pillWeightMg of 500
|
||||
expect(screen.getByText(/500 mg/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders reminder icon when enabled', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
// Aspirin has intakeRemindersEnabled
|
||||
const reminderIcon = document.querySelector('.reminder-icon');
|
||||
expect(reminderIcon).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders day blocks', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
// Should have day blocks rendered
|
||||
const dayBlocks = document.querySelectorAll('.day-block');
|
||||
expect(dayBlocks.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SchedulePage with past days', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.clear();
|
||||
mockContextValue = createMockContext({
|
||||
meds: mockMeds,
|
||||
pastDays: mockPastDays,
|
||||
futureDays: mockFutureDays,
|
||||
coverageByMed: mockCoverageByMed,
|
||||
showPastDays: false
|
||||
});
|
||||
});
|
||||
|
||||
it('renders past days toggle when past days exist', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const toggle = document.querySelector('.past-days-toggle');
|
||||
expect(toggle).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows missed doses warning', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const warning = document.querySelector('.past-days-warning');
|
||||
expect(warning).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('toggles past days visibility', () => {
|
||||
const setShowPastDays = vi.fn();
|
||||
mockContextValue = createMockContext({
|
||||
pastDays: mockPastDays,
|
||||
showPastDays: false,
|
||||
setShowPastDays
|
||||
});
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const toggle = document.querySelector('.past-days-toggle');
|
||||
if (toggle) {
|
||||
fireEvent.click(toggle);
|
||||
expect(setShowPastDays).toHaveBeenCalledWith(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('SchedulePage with expanded past days', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.clear();
|
||||
mockContextValue = createMockContext({
|
||||
meds: mockMeds,
|
||||
pastDays: mockPastDays,
|
||||
futureDays: mockFutureDays,
|
||||
coverageByMed: mockCoverageByMed,
|
||||
showPastDays: true,
|
||||
manuallyExpandedDays: new Set(['Sun, Jan 21'])
|
||||
});
|
||||
});
|
||||
|
||||
it('renders past day blocks when showPastDays is true', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const pastDayBlocks = document.querySelectorAll('.day-block.past');
|
||||
expect(pastDayBlocks.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('renders day divider for past days', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const dividers = document.querySelectorAll('.day-divider');
|
||||
expect(dividers.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('calls toggleDayCollapse when clicking day divider', () => {
|
||||
const toggleDayCollapse = vi.fn();
|
||||
mockContextValue = createMockContext({
|
||||
meds: mockMeds,
|
||||
pastDays: mockPastDays,
|
||||
showPastDays: true,
|
||||
manuallyExpandedDays: new Set(['Sun, Jan 21']),
|
||||
coverageByMed: mockCoverageByMed,
|
||||
toggleDayCollapse
|
||||
});
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const divider = document.querySelector('.day-block.past .day-divider.clickable');
|
||||
if (divider) {
|
||||
fireEvent.click(divider);
|
||||
expect(toggleDayCollapse).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('SchedulePage with taken doses', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.clear();
|
||||
// Match the dose ID format exactly with the mockFutureDays dose
|
||||
// Since we can't predict Date.now(), we make the test check if takenDoses works
|
||||
});
|
||||
|
||||
it('marks doses as taken in UI', () => {
|
||||
// Create consistent timestamp for test
|
||||
const timestamp = Date.now();
|
||||
const doseId = `1-0-${timestamp}-John`;
|
||||
|
||||
const testFutureDays = [{
|
||||
dateStr: 'Mon, Jan 22',
|
||||
date: new Date(timestamp),
|
||||
isPast: false,
|
||||
meds: [{
|
||||
medName: 'Aspirin',
|
||||
total: 1,
|
||||
doses: [{ id: `1-0-${timestamp}`, timeStr: '09:00', when: timestamp, usage: 1, takenBy: ['John'] }],
|
||||
lastWhen: timestamp
|
||||
}]
|
||||
}];
|
||||
|
||||
mockContextValue = createMockContext({
|
||||
meds: mockMeds,
|
||||
futureDays: testFutureDays,
|
||||
coverageByMed: mockCoverageByMed,
|
||||
takenDoses: new Set([doseId])
|
||||
});
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
// When dose is taken, the undo button should appear
|
||||
const undoBtn = document.querySelector('.dose-btn.undo');
|
||||
expect(undoBtn).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls undoDoseTaken when clicking undo button', () => {
|
||||
const undoDoseTaken = vi.fn();
|
||||
const timestamp = Date.now();
|
||||
const doseId = `1-0-${timestamp}-John`;
|
||||
|
||||
const testFutureDays = [{
|
||||
dateStr: 'Mon, Jan 22',
|
||||
date: new Date(timestamp),
|
||||
isPast: false,
|
||||
meds: [{
|
||||
medName: 'Aspirin',
|
||||
total: 1,
|
||||
doses: [{ id: `1-0-${timestamp}`, timeStr: '09:00', when: timestamp, usage: 1, takenBy: ['John'] }],
|
||||
lastWhen: timestamp
|
||||
}]
|
||||
}];
|
||||
|
||||
mockContextValue = createMockContext({
|
||||
meds: mockMeds,
|
||||
futureDays: testFutureDays,
|
||||
coverageByMed: mockCoverageByMed,
|
||||
takenDoses: new Set([doseId]),
|
||||
undoDoseTaken
|
||||
});
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const undoBtn = document.querySelector('.dose-btn.undo');
|
||||
if (undoBtn) {
|
||||
fireEvent.click(undoBtn);
|
||||
expect(undoDoseTaken).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('SchedulePage with low stock', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.clear();
|
||||
mockContextValue = createMockContext({
|
||||
meds: mockMeds,
|
||||
futureDays: mockFutureDays,
|
||||
coverageByMed: {
|
||||
'Aspirin': { name: 'Aspirin', medsLeft: 3, daysLeft: 3, depletionDate: '2025-01-25', depletionTime: Date.now() + 3 * 86400000, nextDose: null }
|
||||
},
|
||||
depletionByMed: { 'Aspirin': Date.now() + 3 * 86400000 }
|
||||
});
|
||||
});
|
||||
|
||||
it('shows status tag for medications', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SchedulePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const tags = document.querySelectorAll('.tag');
|
||||
expect(tags.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user