Feat/frontend tests (#62)

* test(frontend): add vitest test infrastructure

- Add vitest, testing-library, jsdom dependencies
- Configure vitest with jsdom environment
- Add test setup with mocks for fetch, localStorage, matchMedia
- Set 75% coverage threshold

* Add frontend tests (#61)

* Initial plan

* Add frontend tests - utilities, hooks, and components (21% coverage)

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Add more component tests (Auth, AboutModal, ExportModal) and useRefill hook tests - 30% coverage

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Add useMedicationForm utility function tests - 30% coverage

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Add AppHeader tests and more schedule.ts tests - 32% coverage

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Changes before error encountered

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Fix page tests and add more tests - 326 tests passing, 34% coverage

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Add tests for ProfileModal, UserFilterModal, MedDetailModal - 361 tests, 36% coverage

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Add SharedSchedule tests - 366 tests, 39% coverage

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Expand page tests - 383 tests, 39% coverage

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Add MobileEditModal tests - 409 tests, 40% coverage

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Expand Dashboard and Schedule page tests - 427 tests, 40% coverage

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* Fix code review issues - remove invalid remindEnabled property

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>

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>
This commit is contained in:
Daniel Volz
2026-01-22 10:25:11 +01:00
committed by GitHub
parent 8718311876
commit fd055a3a2a
36 changed files with 7602 additions and 3 deletions
@@ -0,0 +1,211 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { MedDetailModal } from '../../components/MedDetailModal';
import type { Medication, Coverage, StockThresholds, RefillEntry } from '../../types';
const defaultSettings: StockThresholds = {
lowStockDays: 7,
normalStockDays: 30,
highStockDays: 90
};
const mockMedication: Medication = {
id: 1,
name: 'Test Med',
genericName: 'Generic Name',
packCount: 1,
blistersPerPack: 1,
pillsPerBlister: 30,
looseTablets: 0,
takenBy: ['John'],
blisters: [{ usage: 1, every: 1, start: '2024-01-01T09:00:00' }],
updatedAt: null,
expiryDate: '2025-12-31',
notes: 'Test notes'
};
const mockCoverage: Coverage = {
name: 'Test Med',
medsLeft: 25,
daysLeft: 25,
depletionDate: '2024-04-01',
depletionTime: Date.now() + 25 * 86400000,
nextDose: null
};
const defaultProps = {
selectedMed: mockMedication,
coverage: { all: [mockCoverage] },
settings: defaultSettings,
showImageLightbox: false,
showRefillModal: false,
showEditStockModal: false,
onClose: vi.fn(),
onOpenImageLightbox: vi.fn(),
onCloseImageLightbox: vi.fn(),
onOpenRefillModal: vi.fn(),
onCloseRefillModal: vi.fn(),
onOpenEditStockModal: vi.fn(),
onCloseEditStockModal: vi.fn(),
refillPacks: 0,
onRefillPacksChange: vi.fn(),
refillLoose: 0,
onRefillLooseChange: vi.fn(),
refillSaving: false,
refillHistory: [] as RefillEntry[],
refillHistoryExpanded: false,
onRefillHistoryExpandedChange: vi.fn(),
onSubmitRefill: vi.fn(),
editStockFullBlisters: 0,
onEditStockFullBlistersChange: vi.fn(),
editStockPartialBlisterPills: 0,
onEditStockPartialBlisterPillsChange: vi.fn(),
editStockSaving: false,
onSubmitStockCorrection: vi.fn()
};
describe('MedDetailModal', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renders nothing when selectedMed is null', () => {
render(<MedDetailModal {...defaultProps} selectedMed={null} />);
expect(screen.queryByText('Test Med')).not.toBeInTheDocument();
});
it('renders modal when medication is selected', () => {
render(<MedDetailModal {...defaultProps} />);
expect(screen.getByText('Test Med')).toBeInTheDocument();
});
it('displays medication name', () => {
render(<MedDetailModal {...defaultProps} />);
expect(screen.getByText('Test Med')).toBeInTheDocument();
});
it('displays generic name', () => {
render(<MedDetailModal {...defaultProps} />);
expect(screen.getByText('Generic Name')).toBeInTheDocument();
});
it('renders close button', () => {
render(<MedDetailModal {...defaultProps} />);
const closeBtn = screen.getByText('×');
expect(closeBtn).toBeInTheDocument();
});
it('calls onClose when close button clicked', () => {
const onClose = vi.fn();
render(<MedDetailModal {...defaultProps} onClose={onClose} />);
const closeBtn = screen.getByText('×');
fireEvent.click(closeBtn);
expect(onClose).toHaveBeenCalledTimes(1);
});
it('calls onClose when overlay clicked', () => {
const onClose = vi.fn();
render(<MedDetailModal {...defaultProps} onClose={onClose} />);
const overlay = document.querySelector('.modal-overlay');
if (overlay) {
fireEvent.click(overlay);
}
expect(onClose).toHaveBeenCalledTimes(1);
});
it('does not call onClose when modal content clicked', () => {
const onClose = vi.fn();
render(<MedDetailModal {...defaultProps} onClose={onClose} />);
const content = document.querySelector('.modal-content');
if (content) {
fireEvent.click(content);
}
expect(onClose).not.toHaveBeenCalled();
});
it('displays notes when available', () => {
render(<MedDetailModal {...defaultProps} />);
expect(screen.getByText('Test notes')).toBeInTheDocument();
});
it('displays schedule information', () => {
render(<MedDetailModal {...defaultProps} />);
// Should have schedule section
const scheduleSection = document.querySelector('.med-detail-schedules');
expect(scheduleSection).toBeInTheDocument();
});
it('renders med detail header', () => {
render(<MedDetailModal {...defaultProps} />);
const header = document.querySelector('.med-detail-header');
expect(header).toBeInTheDocument();
});
it('renders med detail body', () => {
render(<MedDetailModal {...defaultProps} />);
const body = document.querySelector('.med-detail-body');
expect(body).toBeInTheDocument();
});
});
describe('MedDetailModal without coverage', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('works without coverage data', () => {
render(<MedDetailModal {...defaultProps} coverage={{ all: [] }} />);
// Should still render the medication name
expect(screen.getByText('Test Med')).toBeInTheDocument();
});
});
describe('MedDetailModal without optional fields', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('works without generic name', () => {
const med = { ...mockMedication, genericName: null };
render(<MedDetailModal {...defaultProps} selectedMed={med} />);
expect(screen.getByText('Test Med')).toBeInTheDocument();
});
it('works without notes', () => {
const med = { ...mockMedication, notes: null };
render(<MedDetailModal {...defaultProps} selectedMed={med} />);
expect(screen.getByText('Test Med')).toBeInTheDocument();
});
it('works without takenBy', () => {
const med = { ...mockMedication, takenBy: [] };
render(<MedDetailModal {...defaultProps} selectedMed={med} />);
expect(screen.getByText('Test Med')).toBeInTheDocument();
});
it('works without expiryDate', () => {
const med = { ...mockMedication, expiryDate: null };
render(<MedDetailModal {...defaultProps} selectedMed={med} />);
expect(screen.getByText('Test Med')).toBeInTheDocument();
});
});