Tests refactor
This commit is contained in:
parent
245520fb50
commit
99134d8556
165 changed files with 911 additions and 531 deletions
|
|
@ -0,0 +1,524 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const { mockListFills, mockPreviewFill, mockExecuteFill, mockUndoFill } = vi.hoisted(() => ({
|
||||
mockListFills: vi.fn(),
|
||||
mockPreviewFill: vi.fn(),
|
||||
mockExecuteFill: vi.fn(),
|
||||
mockUndoFill: vi.fn(),
|
||||
}));
|
||||
|
||||
const { mockListRegimens } = vi.hoisted(() => ({ mockListRegimens: vi.fn() }));
|
||||
|
||||
vi.mock('@/services/organizer', () => ({
|
||||
listFills: mockListFills,
|
||||
previewFill: mockPreviewFill,
|
||||
executeFill: mockExecuteFill,
|
||||
undoFill: mockUndoFill,
|
||||
}));
|
||||
|
||||
vi.mock('@/services/regimens', () => ({ listRegimens: mockListRegimens }));
|
||||
|
||||
import { OrganizerTab } from '../../../../src/app/(dashboard)/medicines/OrganizerTab';
|
||||
|
||||
const emptyRegimens = { data: [], pagination: { cursor: null, hasMore: false } };
|
||||
const emptyFills = { data: [], pagination: { cursor: null, hasMore: false } };
|
||||
|
||||
const activeRegimen = {
|
||||
_id: 'reg-1',
|
||||
name: 'Morning Routine',
|
||||
isActive: true,
|
||||
medications: [],
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockListRegimens.mockResolvedValue(emptyRegimens);
|
||||
mockListFills.mockResolvedValue(emptyFills);
|
||||
});
|
||||
|
||||
describe('OrganizerTab', () => {
|
||||
it('shows fill organizer and fill history sections', async () => {
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Fill Pill Organizer')).toBeInTheDocument());
|
||||
expect(screen.getByText('Fill History')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows no active regimens message when none exist', async () => {
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/No active regimens found/)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows fill form when active regimens exist', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Preview fill')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows empty fill history', async () => {
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('No fills recorded yet.')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('renders fill history entries', async () => {
|
||||
mockListFills.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'fill-1',
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
status: 'completed',
|
||||
numberOfDays: 7,
|
||||
items: [],
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Morning Routine')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('previews fill and shows preview result', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockPreviewFill.mockResolvedValue({
|
||||
regimenId: 'reg-1',
|
||||
numberOfDays: 7,
|
||||
hasShortages: false,
|
||||
items: [],
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Preview fill'));
|
||||
|
||||
fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
|
||||
target: { value: 'reg-1' },
|
||||
});
|
||||
fireEvent.change(screen.getByDisplayValue('7'), { target: { value: '14' } });
|
||||
fireEvent.submit(screen.getByText('Preview fill').closest('form')!);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockPreviewFill).toHaveBeenCalledWith(
|
||||
'hh1',
|
||||
expect.objectContaining({ regimenId: 'reg-1' }),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('shows preview result and executes fill', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockPreviewFill.mockResolvedValue({
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
numberOfDays: 7,
|
||||
hasShortages: false,
|
||||
items: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
quantityNeeded: 7,
|
||||
quantityAvailable: 30,
|
||||
isShort: false,
|
||||
shortage: 0,
|
||||
cabinetBreakdown: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
mockExecuteFill.mockResolvedValue({ _id: 'fill-new', status: 'completed' });
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Preview fill'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
|
||||
target: { value: 'reg-1' },
|
||||
});
|
||||
fireEvent.submit(screen.getByText('Preview fill').closest('form')!);
|
||||
|
||||
await waitFor(() => screen.getByText('Confirm fill'));
|
||||
expect(screen.getByText('Metformin')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByText('Confirm fill'));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockExecuteFill).toHaveBeenCalledWith(
|
||||
'hh1',
|
||||
expect.objectContaining({ regimenId: 'reg-1' }),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('shows shortage warning in preview', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockPreviewFill.mockResolvedValue({
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
numberOfDays: 7,
|
||||
hasShortages: true,
|
||||
items: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
quantityNeeded: 7,
|
||||
quantityAvailable: 3,
|
||||
isShort: true,
|
||||
shortage: 4,
|
||||
cabinetBreakdown: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Preview fill'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
|
||||
target: { value: 'reg-1' },
|
||||
});
|
||||
fireEvent.submit(screen.getByText('Preview fill').closest('form')!);
|
||||
|
||||
await waitFor(() => screen.getByText('Shortages detected'));
|
||||
expect(screen.getByText('Allow partial fill (fill what is available)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows undo error when undo fails', async () => {
|
||||
mockListFills.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'fill-1',
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
status: 'completed',
|
||||
numberOfDays: 7,
|
||||
items: [],
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockUndoFill.mockRejectedValue(new Error('Undo failed'));
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(true);
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Undo'));
|
||||
await userEvent.click(screen.getByText('Undo'));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Undo failed')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('filters fill history by status', async () => {
|
||||
mockListFills.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'fill-1',
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
status: 'completed',
|
||||
numberOfDays: 7,
|
||||
items: [],
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByDisplayValue('All statuses'));
|
||||
fireEvent.change(screen.getByDisplayValue('All statuses'), { target: { value: 'completed' } });
|
||||
|
||||
await waitFor(() => expect(mockListFills).toHaveBeenCalledTimes(2));
|
||||
});
|
||||
|
||||
it('shows Ready to fill when no shortages', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockPreviewFill.mockResolvedValue({
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
numberOfDays: 7,
|
||||
hasShortages: false,
|
||||
items: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
quantityNeeded: 7,
|
||||
quantityAvailable: 30,
|
||||
isShort: false,
|
||||
shortage: 0,
|
||||
cabinetBreakdown: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Preview fill'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
|
||||
target: { value: 'reg-1' },
|
||||
});
|
||||
fireEvent.submit(screen.getByText('Preview fill').closest('form')!);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Ready to fill')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('renders fill items with short status', async () => {
|
||||
mockListFills.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'fill-1',
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
status: 'partial',
|
||||
numberOfDays: 7,
|
||||
fillDate: '2026-01-01T00:00:00.000Z',
|
||||
items: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
quantityNeeded: 7,
|
||||
quantityTaken: 3,
|
||||
wasShort: true,
|
||||
shortage: 4,
|
||||
deductions: [],
|
||||
},
|
||||
],
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/Metformin.*short/)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('dismisses fill history error', async () => {
|
||||
mockListFills.mockRejectedValue(new Error('History error'));
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('History error'));
|
||||
await userEvent.click(screen.getByText('Dismiss'));
|
||||
expect(screen.queryByText('History error')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('undoes a fill after confirmation', async () => {
|
||||
mockListFills.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'fill-1',
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
status: 'completed',
|
||||
numberOfDays: 7,
|
||||
items: [],
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockUndoFill.mockResolvedValue({});
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(true);
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Undo'));
|
||||
await userEvent.click(screen.getByText('Undo'));
|
||||
|
||||
expect(mockUndoFill).toHaveBeenCalledWith('hh1', 'fill-1');
|
||||
});
|
||||
|
||||
it('cancels from preview screen', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockPreviewFill.mockResolvedValue({
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
numberOfDays: 7,
|
||||
hasShortages: false,
|
||||
items: [],
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Preview fill'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
|
||||
target: { value: 'reg-1' },
|
||||
});
|
||||
fireEvent.submit(screen.getByText('Preview fill').closest('form')!);
|
||||
|
||||
await waitFor(() => screen.getByText('Confirm fill'));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Back' }));
|
||||
|
||||
// Should return to the form
|
||||
await waitFor(() => expect(screen.getByText('Preview fill')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows cabinet breakdown in preview items', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockPreviewFill.mockResolvedValue({
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
numberOfDays: 7,
|
||||
hasShortages: false,
|
||||
items: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
quantityNeeded: 7,
|
||||
quantityAvailable: 30,
|
||||
isShort: false,
|
||||
shortage: 0,
|
||||
cabinetBreakdown: [
|
||||
{
|
||||
cabinetItemId: 'ci-1',
|
||||
quantityToTake: 7,
|
||||
expirationDate: '2027-06-01T00:00:00.000Z',
|
||||
},
|
||||
{ cabinetItemId: 'ci-2', quantityToTake: 3 },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Preview fill'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
|
||||
target: { value: 'reg-1' },
|
||||
});
|
||||
fireEvent.submit(screen.getByText('Preview fill').closest('form')!);
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/7 units/)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows fallback error when non-Error thrown during preview', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockPreviewFill.mockRejectedValue('preview failed');
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Preview fill'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
|
||||
target: { value: 'reg-1' },
|
||||
});
|
||||
fireEvent.submit(screen.getByText('Preview fill').closest('form')!);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Failed to generate preview')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows fallback error when non-Error thrown during fill', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockPreviewFill.mockResolvedValue({
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
numberOfDays: 7,
|
||||
hasShortages: false,
|
||||
items: [],
|
||||
});
|
||||
mockExecuteFill.mockRejectedValue('fill failed');
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Preview fill'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
|
||||
target: { value: 'reg-1' },
|
||||
});
|
||||
fireEvent.submit(screen.getByText('Preview fill').closest('form')!);
|
||||
|
||||
await waitFor(() => screen.getByText('Confirm fill'));
|
||||
await userEvent.click(screen.getByText('Confirm fill'));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Fill failed')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('toggles allow partial checkbox in shortage preview', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockPreviewFill.mockResolvedValue({
|
||||
regimenId: 'reg-1',
|
||||
regimenName: 'Morning Routine',
|
||||
numberOfDays: 7,
|
||||
hasShortages: true,
|
||||
items: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
quantityNeeded: 7,
|
||||
quantityAvailable: 3,
|
||||
isShort: true,
|
||||
shortage: 4,
|
||||
cabinetBreakdown: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByText('Preview fill'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select regimen...').closest('select')!, {
|
||||
target: { value: 'reg-1' },
|
||||
});
|
||||
fireEvent.submit(screen.getByText('Preview fill').closest('form')!);
|
||||
|
||||
await waitFor(() => screen.getByText('Allow partial fill (fill what is available)'));
|
||||
fireEvent.click(screen.getByLabelText('Allow partial fill (fill what is available)'));
|
||||
|
||||
expect(screen.getByLabelText('Allow partial fill (fill what is available)')).toBeChecked();
|
||||
});
|
||||
|
||||
it('changes notes field in the organizer form', async () => {
|
||||
mockListRegimens.mockResolvedValue({
|
||||
data: [activeRegimen],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<OrganizerTab householdId="hh1" />);
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('Any notes for this fill'));
|
||||
fireEvent.change(screen.getByPlaceholderText('Any notes for this fill'), {
|
||||
target: { value: 'Fill for next week' },
|
||||
});
|
||||
|
||||
expect(screen.getByDisplayValue('Fill for next week')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue