import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import ExperimentCalendar from '../src/components/ExperimentCalendar'; const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ useNavigate: () => mockNavigate, })); const EXP_ID = 'aaaaaaaa-0000-0000-0000-000000000001'; const TEMPLATE = [ { fieldId: '00000000-0000-0000-0000-000000000002', key: 'vitals', label: 'Vitals', active: true, builtin: true }, { fieldId: 'ww000000-0000-0000-0000-000000000099', key: 'weights', label: 'Weights', active: true, builtin: false }, { fieldId: 'zz000000-0000-0000-0000-000000000001', key: 'notes', label: 'Notes', active: false, builtin: true }, ]; function todayStr() { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; } function makeStatus(animalId, date, customFields = {}, vitals = null) { return { id: `s-${animalId}-${date}`, animal_id: animalId, date: `${date}T00:00:00.000Z`, experiment_description: null, vitals, treatment: null, notes: null, custom_fields: customFields, analysis_summary: null, }; } beforeEach(() => { jest.clearAllMocks(); }); // ── Rendering ────────────────────────────────────────────────────────────────── describe('ExperimentCalendar rendering', () => { it('renders the current month and year', () => { render(); const now = new Date(); const monthLabel = now.toLocaleString('en-US', { month: 'long', year: 'numeric' }); expect(screen.getByText(monthLabel)).toBeInTheDocument(); }); it('renders day-of-week headers', () => { render(); for (const d of ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']) { expect(screen.getByText(d)).toBeInTheDocument(); } }); it('renders the field selector with active template fields only', () => { render(); const options = screen.getAllByRole('option').map((o) => o.textContent); expect(options).toContain('Vitals'); expect(options).toContain('Weights'); expect(options).not.toContain('Notes'); // inactive }); }); // ── Default field selection ──────────────────────────────────────────────────── describe('default field selection', () => { it('defaults to the field whose label contains "weight"', () => { render(); expect(screen.getByTestId('field-select')).toHaveValue('ww000000-0000-0000-0000-000000000099'); }); it('defaults to first field if none contains "weight"', () => { const noWeightTemplate = [ { fieldId: 'ff000000-0000-0000-0000-000000000001', key: 'treatment', label: 'Treatment', active: true, builtin: true }, { fieldId: 'ff000000-0000-0000-0000-000000000002', key: 'vitals', label: 'Vitals', active: true, builtin: true }, ]; render(); expect(screen.getByTestId('field-select')).toHaveValue('treatment'); }); }); // ── Count badges (computed from allStatuses) ─────────────────────────────────── describe('count badges', () => { it('shows a count badge for a day where the selected field is non-empty', () => { const dateStr = todayStr(); const statuses = [ makeStatus('a1', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }), makeStatus('a2', dateStr, { 'ww000000-0000-0000-0000-000000000099': '310' }), ]; render(); expect(screen.getByTestId(`count-${dateStr}`)).toHaveTextContent('2'); }); it('does not show count badge on days with no data', () => { render(); expect(screen.queryByTestId(`count-${todayStr()}`)).not.toBeInTheDocument(); }); it('shows a count badge for builtin field (vitals)', () => { const dateStr = todayStr(); const statuses = [makeStatus('a1', dateStr, {}, 'HR 72')]; render(); fireEvent.change(screen.getByTestId('field-select'), { target: { value: 'vitals' } }); expect(screen.getByTestId(`count-${dateStr}`)).toHaveTextContent('1'); }); it('recomputes counts after field change', () => { const dateStr = todayStr(); const statuses = [makeStatus('a1', dateStr, {}, 'HR 72')]; render(); // Default is weights — no badge expect(screen.queryByTestId(`count-${dateStr}`)).not.toBeInTheDocument(); // Switch to vitals — badge appears fireEvent.change(screen.getByTestId('field-select'), { target: { value: 'vitals' } }); expect(screen.getByTestId(`count-${dateStr}`)).toHaveTextContent('1'); }); }); // ── Month navigation ────────────────────────────────────────────────────────── describe('month navigation', () => { it('moves to the previous month on ‹ click', () => { render(); const now = new Date(); const prev = new Date(now.getFullYear(), now.getMonth() - 1, 1); const prevLabel = prev.toLocaleString('en-US', { month: 'long', year: 'numeric' }); fireEvent.click(screen.getByLabelText('Previous month')); expect(screen.getByText(prevLabel)).toBeInTheDocument(); }); it('moves to the next month on › click', () => { render(); const now = new Date(); const next = new Date(now.getFullYear(), now.getMonth() + 1, 1); const nextLabel = next.toLocaleString('en-US', { month: 'long', year: 'numeric' }); fireEvent.click(screen.getByLabelText('Next month')); expect(screen.getByText(nextLabel)).toBeInTheDocument(); }); }); // ── Day click navigation ────────────────────────────────────────────────────── describe('day click navigation', () => { it('navigates to the day view with field param when a day with data is clicked', () => { const dateStr = todayStr(); const statuses = [ makeStatus('a1', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }), ]; render(); fireEvent.click(screen.getByTestId(`day-${dateStr}`)); expect(mockNavigate).toHaveBeenCalledWith( `/experiments/${EXP_ID}/day/${dateStr}?field=ww000000-0000-0000-0000-000000000099`, ); }); it('does not navigate when clicking a day without data (button is disabled)', () => { render(); const dayBtn = screen.getByTestId(`day-${todayStr()}`); expect(dayBtn).toBeDisabled(); fireEvent.click(dayBtn); expect(mockNavigate).not.toHaveBeenCalled(); }); it('includes the currently selected field in the navigation URL', () => { const dateStr = todayStr(); const statuses = [makeStatus('a1', dateStr, {}, 'HR 72')]; render(); // Switch to vitals field first fireEvent.change(screen.getByTestId('field-select'), { target: { value: 'vitals' } }); fireEvent.click(screen.getByTestId(`day-${dateStr}`)); expect(mockNavigate).toHaveBeenCalledWith( `/experiments/${EXP_ID}/day/${dateStr}?field=vitals`, ); }); it('navigates with no field param when selectedField is null (no active fields)', () => { const emptyTemplate = []; render(); // No days have data with empty template, so nothing to click — just verify no crash expect(screen.queryByTestId(`count-${todayStr()}`)).not.toBeInTheDocument(); }); });