feat: replace calendar day modal with dedicated day-view page
Calendar day clicks now navigate to /experiments/:id/day/:date (with ?field= param). ExperimentDayView shows a sticky-column table of all subjects with data that day, inline edit via modal, and a session-metrics bar chart from analysis_summary. Subject name links to /daily-statuses/:id. Backend adds ?date= filtering to GET /experiments/:id/daily-statuses. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,36 +1,18 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import ExperimentCalendar from '../src/components/ExperimentCalendar';
|
||||
import { experimentsApi } from '../src/api/client';
|
||||
|
||||
jest.mock('../src/api/client', () => ({
|
||||
experimentsApi: {
|
||||
getCalendar: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('recharts', () => ({
|
||||
ResponsiveContainer: ({ children }) => <div>{children}</div>,
|
||||
BarChart: ({ children }) => <div data-testid="bar-chart">{children}</div>,
|
||||
Bar: () => null,
|
||||
XAxis: () => null,
|
||||
YAxis: () => null,
|
||||
CartesianGrid: () => null,
|
||||
Tooltip: () => null,
|
||||
Legend: () => null,
|
||||
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 },
|
||||
];
|
||||
|
||||
const ANIMALS = [
|
||||
{ id: 'a1000000-0000-0000-0000-000000000001', animal_name: 'Rat A', animal_id_string: 'R001' },
|
||||
{ id: 'a2000000-0000-0000-0000-000000000002', animal_name: 'Rat B', animal_id_string: 'R002' },
|
||||
{ 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() {
|
||||
@@ -38,7 +20,7 @@ function todayStr() {
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function makeStatus(animalId, date, customFields = {}, vitals = null, analysisSummary = null) {
|
||||
function makeStatus(animalId, date, customFields = {}, vitals = null) {
|
||||
return {
|
||||
id: `s-${animalId}-${date}`,
|
||||
animal_id: animalId,
|
||||
@@ -48,7 +30,7 @@ function makeStatus(animalId, date, customFields = {}, vitals = null, analysisSu
|
||||
treatment: null,
|
||||
notes: null,
|
||||
custom_fields: customFields,
|
||||
analysis_summary: analysisSummary,
|
||||
analysis_summary: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,33 +42,25 @@ beforeEach(() => {
|
||||
|
||||
describe('ExperimentCalendar rendering', () => {
|
||||
it('renders the current month and year', () => {
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
|
||||
);
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} />);
|
||||
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(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
|
||||
);
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} />);
|
||||
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(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
|
||||
);
|
||||
expect(screen.getByTestId('field-select')).toBeInTheDocument();
|
||||
const options = screen.getAllByRole('option');
|
||||
const labels = options.map((o) => o.textContent);
|
||||
expect(labels).toContain('Vitals');
|
||||
expect(labels).toContain('Weights');
|
||||
expect(labels).not.toContain('Notes'); // inactive
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} />);
|
||||
const options = screen.getAllByRole('option').map((o) => o.textContent);
|
||||
expect(options).toContain('Vitals');
|
||||
expect(options).toContain('Weights');
|
||||
expect(options).not.toContain('Notes'); // inactive
|
||||
});
|
||||
});
|
||||
|
||||
@@ -94,9 +68,7 @@ describe('ExperimentCalendar rendering', () => {
|
||||
|
||||
describe('default field selection', () => {
|
||||
it('defaults to the field whose label contains "weight"', () => {
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
|
||||
);
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} />);
|
||||
expect(screen.getByTestId('field-select')).toHaveValue('ww000000-0000-0000-0000-000000000099');
|
||||
});
|
||||
|
||||
@@ -105,9 +77,7 @@ describe('default field selection', () => {
|
||||
{ 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(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={noWeightTemplate} allStatuses={[]} animals={ANIMALS} />,
|
||||
);
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={noWeightTemplate} allStatuses={[]} />);
|
||||
expect(screen.getByTestId('field-select')).toHaveValue('treatment');
|
||||
});
|
||||
});
|
||||
@@ -118,30 +88,33 @@ describe('count badges', () => {
|
||||
it('shows a count badge for a day where the selected field is non-empty', () => {
|
||||
const dateStr = todayStr();
|
||||
const statuses = [
|
||||
makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }),
|
||||
makeStatus('a2000000-0000-0000-0000-000000000002', dateStr, { 'ww000000-0000-0000-0000-000000000099': '310' }),
|
||||
makeStatus('a1', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }),
|
||||
makeStatus('a2', dateStr, { 'ww000000-0000-0000-0000-000000000099': '310' }),
|
||||
];
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />,
|
||||
);
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} />);
|
||||
expect(screen.getByTestId(`count-${dateStr}`)).toHaveTextContent('2');
|
||||
});
|
||||
|
||||
it('does not show count badge on days with no data', () => {
|
||||
const dateStr = todayStr();
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
|
||||
);
|
||||
expect(screen.queryByTestId(`count-${dateStr}`)).not.toBeInTheDocument();
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} />);
|
||||
expect(screen.queryByTestId(`count-${todayStr()}`)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows a count badge for builtin field (vitals)', () => {
|
||||
const dateStr = todayStr();
|
||||
const statuses = [makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, {}, 'HR 72')];
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />,
|
||||
);
|
||||
// Switch to vitals field
|
||||
const statuses = [makeStatus('a1', dateStr, {}, 'HR 72')];
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} />);
|
||||
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(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} />);
|
||||
// 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');
|
||||
});
|
||||
@@ -151,9 +124,7 @@ describe('count badges', () => {
|
||||
|
||||
describe('month navigation', () => {
|
||||
it('moves to the previous month on ‹ click', () => {
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
|
||||
);
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} />);
|
||||
const now = new Date();
|
||||
const prev = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const prevLabel = prev.toLocaleString('en-US', { month: 'long', year: 'numeric' });
|
||||
@@ -162,172 +133,54 @@ describe('month navigation', () => {
|
||||
});
|
||||
|
||||
it('moves to the next month on › click', () => {
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
|
||||
);
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} />);
|
||||
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();
|
||||
});
|
||||
|
||||
it('recomputes counts after field change', () => {
|
||||
const dateStr = todayStr();
|
||||
// Only has vitals, not weights
|
||||
const statuses = [makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, {}, 'HR 72')];
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />,
|
||||
);
|
||||
// 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');
|
||||
});
|
||||
});
|
||||
|
||||
// ── Day click → modal ─────────────────────────────────────────────────────────
|
||||
// ── Day click navigation ──────────────────────────────────────────────────────
|
||||
|
||||
describe('day click modal', () => {
|
||||
it('opens a modal when a day with data is clicked', async () => {
|
||||
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('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }),
|
||||
makeStatus('a1', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }),
|
||||
];
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />,
|
||||
);
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} />);
|
||||
fireEvent.click(screen.getByTestId(`day-${dateStr}`));
|
||||
await waitFor(() => {
|
||||
const dialog = screen.getByRole('dialog');
|
||||
expect(within(dialog).getAllByText('Rat A').length).toBeGreaterThan(0);
|
||||
});
|
||||
expect(mockNavigate).toHaveBeenCalledWith(
|
||||
`/experiments/${EXP_ID}/day/${dateStr}?field=ww000000-0000-0000-0000-000000000099`,
|
||||
);
|
||||
});
|
||||
|
||||
it('shows subject value in read-only view after clicking day', async () => {
|
||||
const dateStr = todayStr();
|
||||
const statuses = [
|
||||
makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }),
|
||||
];
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />,
|
||||
);
|
||||
fireEvent.click(screen.getByTestId(`day-${dateStr}`));
|
||||
await waitFor(() => expect(screen.getByText('325')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('does not open a modal when clicking a day without data', () => {
|
||||
const dateStr = todayStr();
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
|
||||
);
|
||||
const dayBtn = screen.getByTestId(`day-${dateStr}`);
|
||||
it('does not navigate when clicking a day without data (button is disabled)', () => {
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} />);
|
||||
const dayBtn = screen.getByTestId(`day-${todayStr()}`);
|
||||
expect(dayBtn).toBeDisabled();
|
||||
fireEvent.click(dayBtn);
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
expect(mockNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows only animals with data — excludes subjects with no record that day', async () => {
|
||||
it('includes the currently selected field in the navigation URL', () => {
|
||||
const dateStr = todayStr();
|
||||
// Only Rat A has a record; Rat B does not
|
||||
const statuses = [
|
||||
makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '320' }),
|
||||
];
|
||||
render(
|
||||
<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />,
|
||||
const statuses = [makeStatus('a1', dateStr, {}, 'HR 72')];
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} />);
|
||||
// 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`,
|
||||
);
|
||||
fireEvent.click(screen.getByTestId(`day-${dateStr}`));
|
||||
await waitFor(() => {
|
||||
const dialog = screen.getByRole('dialog');
|
||||
expect(within(dialog).getAllByText('Rat A').length).toBeGreaterThan(0);
|
||||
expect(within(dialog).queryAllByText('Rat B')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ── Bar chart (inside day modal) ──────────────────────────────────────────────
|
||||
//
|
||||
// The bar chart reads from status.analysis_summary — a pre-computed field on
|
||||
// each DailyStatus record containing trial-level session metrics:
|
||||
// { total: number, counts: { Success: number, Failure: number }, success_rate: number }
|
||||
//
|
||||
// X-axis: animals that have a valid (non-empty) value for the chosen calendar
|
||||
// field on THAT specific day. Each day can show a different set of animals.
|
||||
// Bars: Total / Success / Failure from analysis_summary for that animal on that day.
|
||||
//
|
||||
// Chart is hidden when no animal on that day has analysis_summary.total > 0.
|
||||
// Do NOT compute success/failure from the field value itself — always use analysis_summary.
|
||||
|
||||
describe('bar chart inside day modal', () => {
|
||||
it('renders session metrics bar chart when at least one animal has analysis_summary', async () => {
|
||||
const dateStr = todayStr();
|
||||
const summary = { total: 18, counts: { Success: 5, Failure: 13 }, success_rate: 0.28 };
|
||||
const statuses = [
|
||||
makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }, null, summary),
|
||||
];
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />);
|
||||
fireEvent.click(screen.getByTestId(`day-${dateStr}`));
|
||||
await waitFor(() => {
|
||||
expect(within(screen.getByRole('dialog')).getByTestId('bar-chart')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('hides bar chart when no animal has analysis_summary (null or total=0)', async () => {
|
||||
const dateStr = todayStr();
|
||||
const statuses = [
|
||||
// Has field data (so day is clickable) but no analysis_summary
|
||||
makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }),
|
||||
];
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />);
|
||||
fireEvent.click(screen.getByTestId(`day-${dateStr}`));
|
||||
await waitFor(() => screen.getByRole('dialog'));
|
||||
expect(screen.queryByTestId('bar-chart')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hides bar chart when analysis_summary exists but total is 0', async () => {
|
||||
const dateStr = todayStr();
|
||||
const emptySummary = { total: 0, counts: { Success: 0, Failure: 0 }, success_rate: null };
|
||||
const statuses = [
|
||||
makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }, null, emptySummary),
|
||||
];
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />);
|
||||
fireEvent.click(screen.getByTestId(`day-${dateStr}`));
|
||||
await waitFor(() => screen.getByRole('dialog'));
|
||||
expect(screen.queryByTestId('bar-chart')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows bar chart for multiple animals each with their own analysis_summary', async () => {
|
||||
const dateStr = todayStr();
|
||||
const summaryA = { total: 18, counts: { Success: 5, Failure: 13 }, success_rate: 0.28 };
|
||||
const summaryB = { total: 10, counts: { Success: 7, Failure: 3 }, success_rate: 0.7 };
|
||||
const statuses = [
|
||||
makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }, null, summaryA),
|
||||
makeStatus('a2000000-0000-0000-0000-000000000002', dateStr, { 'ww000000-0000-0000-0000-000000000099': '310' }, null, summaryB),
|
||||
];
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />);
|
||||
fireEvent.click(screen.getByTestId(`day-${dateStr}`));
|
||||
await waitFor(() => {
|
||||
expect(within(screen.getByRole('dialog')).getByTestId('bar-chart')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('excludes from chart animals whose field value is empty on that day, even with analysis_summary', async () => {
|
||||
const dateStr = todayStr();
|
||||
const summary = { total: 10, counts: { Success: 7, Failure: 3 }, success_rate: 0.7 };
|
||||
const statuses = [
|
||||
// Rat A: has weight AND analysis_summary → appears in chart
|
||||
makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }, null, summary),
|
||||
// Rat B: has analysis_summary but NO weight → not in day view at all, not in chart
|
||||
makeStatus('a2000000-0000-0000-0000-000000000002', dateStr, {}, null, summary),
|
||||
];
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={statuses} animals={ANIMALS} />);
|
||||
fireEvent.click(screen.getByTestId(`day-${dateStr}`));
|
||||
await waitFor(() => screen.getByRole('dialog'));
|
||||
// Chart shows because Rat A has data
|
||||
expect(screen.getByTestId('bar-chart')).toBeInTheDocument();
|
||||
// Rat B not shown in day view (no weight value)
|
||||
const dialog = screen.getByRole('dialog');
|
||||
expect(within(dialog).queryAllByText('Rat B')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('navigates with no field param when selectedField is null (no active fields)', () => {
|
||||
const emptyTemplate = [];
|
||||
render(<ExperimentCalendar experimentId={EXP_ID} template={emptyTemplate} allStatuses={[]} />);
|
||||
// No days have data with empty template, so nothing to click — just verify no crash
|
||||
expect(screen.queryByTestId(`count-${todayStr()}`)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user