Tests refactor
This commit is contained in:
parent
245520fb50
commit
99134d8556
165 changed files with 911 additions and 531 deletions
684
packages/web/tests/app/(dashboard)/medicines/CabinetTab.test.tsx
Normal file
684
packages/web/tests/app/(dashboard)/medicines/CabinetTab.test.tsx
Normal file
|
|
@ -0,0 +1,684 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { SWRConfig } from 'swr';
|
||||
import type React from 'react';
|
||||
|
||||
const {
|
||||
mockListCabinetItems,
|
||||
mockGetCabinetSummary,
|
||||
mockCreateCabinetItem,
|
||||
mockAdjustCabinetItemQuantity,
|
||||
mockDeleteCabinetItem,
|
||||
} = vi.hoisted(() => ({
|
||||
mockListCabinetItems: vi.fn(),
|
||||
mockGetCabinetSummary: vi.fn(),
|
||||
mockCreateCabinetItem: vi.fn(),
|
||||
mockAdjustCabinetItemQuantity: vi.fn(),
|
||||
mockDeleteCabinetItem: vi.fn(),
|
||||
}));
|
||||
|
||||
const { mockListMedicines } = vi.hoisted(() => ({ mockListMedicines: vi.fn() }));
|
||||
|
||||
vi.mock('@/services/cabinet', () => ({
|
||||
listCabinetItems: mockListCabinetItems,
|
||||
getCabinetSummary: mockGetCabinetSummary,
|
||||
createCabinetItem: mockCreateCabinetItem,
|
||||
adjustCabinetItemQuantity: mockAdjustCabinetItemQuantity,
|
||||
deleteCabinetItem: mockDeleteCabinetItem,
|
||||
}));
|
||||
|
||||
vi.mock('@/services/medicines', () => ({ listMedicines: mockListMedicines }));
|
||||
|
||||
vi.mock('next/link', () => ({
|
||||
default: (props: { href?: string; children: React.ReactNode }) => (
|
||||
<a href={props.href}>{props.children}</a>
|
||||
),
|
||||
}));
|
||||
|
||||
import { CabinetTab } from '../../../../src/app/(dashboard)/medicines/CabinetTab';
|
||||
|
||||
const emptySummary = { data: [] };
|
||||
const emptyItems = { data: [], pagination: { cursor: null, hasMore: false } };
|
||||
const emptyMeds = { data: [], pagination: { cursor: null, hasMore: false } };
|
||||
|
||||
const cabinetItem = {
|
||||
_id: 'ci-1',
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
quantity: 10,
|
||||
unit: 'tablet',
|
||||
status: 'active',
|
||||
expirationDate: null,
|
||||
unitPrice: null,
|
||||
totalPrice: null,
|
||||
storeId: null,
|
||||
notes: null,
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockGetCabinetSummary.mockResolvedValue(emptySummary);
|
||||
mockListCabinetItems.mockResolvedValue(emptyItems);
|
||||
mockListMedicines.mockResolvedValue(emptyMeds);
|
||||
});
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<SWRConfig value={{ provider: () => new Map(), dedupingInterval: 0 }}>{children}</SWRConfig>
|
||||
);
|
||||
|
||||
describe('CabinetTab', () => {
|
||||
it('shows empty state when no items', async () => {
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/cabinet is empty/i)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows error when list fails', async () => {
|
||||
mockGetCabinetSummary.mockRejectedValue(new Error('Server error'));
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Server error')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('dismisses error on Dismiss click', async () => {
|
||||
mockGetCabinetSummary.mockRejectedValue(new Error('Server error'));
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Server error'));
|
||||
await userEvent.click(screen.getByText('Dismiss'));
|
||||
expect(screen.queryByText('Server error')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('toggles Add to Cabinet form', async () => {
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Add to Cabinet'));
|
||||
await userEvent.click(screen.getByText('Add to Cabinet'));
|
||||
expect(screen.getByPlaceholderText('Search medicines...')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getAllByText('Cancel')[0]);
|
||||
expect(screen.queryByPlaceholderText('Search medicines...')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('switches between Summary and All Items views', async () => {
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
|
||||
expect(mockListCabinetItems).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders cabinet items in detail view', async () => {
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [cabinetItem],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Metformin')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('adjusts item quantity', async () => {
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [cabinetItem],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockAdjustCabinetItemQuantity.mockResolvedValue({ ...cabinetItem, quantity: 11 });
|
||||
mockGetCabinetSummary.mockResolvedValue(emptySummary);
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
|
||||
await waitFor(() => screen.getByTitle('Add 1'));
|
||||
await userEvent.click(screen.getByTitle('Add 1'));
|
||||
|
||||
expect(mockAdjustCabinetItemQuantity).toHaveBeenCalledWith('hh1', 'ci-1', { delta: 1 });
|
||||
});
|
||||
|
||||
it('shows summary view with medicine data', async () => {
|
||||
mockGetCabinetSummary.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
medicineStrength: 500,
|
||||
medicineStrengthUnit: 'mg',
|
||||
medicineForm: 'tablet',
|
||||
totalQuantity: 30,
|
||||
unit: 'tablet',
|
||||
itemCount: 2,
|
||||
earliestExpiry: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Metformin')).toBeInTheDocument());
|
||||
expect(screen.getByText(/2 items/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('expands medicine in summary view', async () => {
|
||||
mockGetCabinetSummary.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
medicineStrength: 500,
|
||||
medicineStrengthUnit: 'mg',
|
||||
medicineForm: 'tablet',
|
||||
totalQuantity: 30,
|
||||
unit: 'tablet',
|
||||
itemCount: 1,
|
||||
earliestExpiry: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [cabinetItem],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Metformin'));
|
||||
await userEvent.click(screen.getByText('Metformin').closest('button')!);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockListCabinetItems).toHaveBeenCalledWith(
|
||||
'hh1',
|
||||
expect.objectContaining({ medicineId: 'med-1' }),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('submits AddToCabinetForm with medicine selection validation', async () => {
|
||||
mockCreateCabinetItem.mockResolvedValue(cabinetItem);
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Add to Cabinet'));
|
||||
await userEvent.click(screen.getByText('Add to Cabinet'));
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
|
||||
|
||||
// Submit without selecting a medicine - should show error
|
||||
fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Please select a medicine')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('submits AddToCabinetForm successfully', async () => {
|
||||
mockListMedicines.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'med-1',
|
||||
name: 'Metformin',
|
||||
form: 'tablet',
|
||||
strength: 500,
|
||||
strengthUnit: 'mg',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockCreateCabinetItem.mockResolvedValue(cabinetItem);
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Add to Cabinet'));
|
||||
await userEvent.click(screen.getByText('Add to Cabinet'));
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
|
||||
|
||||
// Select a medicine (first combobox is the medicine select)
|
||||
fireEvent.change(screen.getAllByRole('combobox')[0]!, {
|
||||
target: { value: 'med-1' },
|
||||
});
|
||||
|
||||
fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockCreateCabinetItem).toHaveBeenCalledWith(
|
||||
'hh1',
|
||||
expect.objectContaining({ medicineId: 'med-1' }),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('submits AddToCabinetForm with expiration date and notes', async () => {
|
||||
mockListMedicines.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'med-1',
|
||||
name: 'Metformin',
|
||||
form: 'tablet',
|
||||
strength: 500,
|
||||
strengthUnit: 'mg',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockCreateCabinetItem.mockResolvedValue(cabinetItem);
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Add to Cabinet'));
|
||||
await userEvent.click(screen.getByText('Add to Cabinet'));
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
|
||||
|
||||
// Select a medicine
|
||||
fireEvent.change(screen.getAllByRole('combobox')[0]!, {
|
||||
target: { value: 'med-1' },
|
||||
});
|
||||
|
||||
// Set expiration date (type="date" input, no label association)
|
||||
const dateInput = document.querySelector('input[type="date"]') as HTMLElement;
|
||||
if (dateInput) fireEvent.change(dateInput, { target: { value: '2027-12-31' } });
|
||||
|
||||
// Set notes
|
||||
fireEvent.change(screen.getByPlaceholderText('Any notes about this item'), {
|
||||
target: { value: 'Store in fridge' },
|
||||
});
|
||||
|
||||
fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockCreateCabinetItem).toHaveBeenCalledWith(
|
||||
'hh1',
|
||||
expect.objectContaining({
|
||||
medicineId: 'med-1',
|
||||
notes: 'Store in fridge',
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('changes quantity and unit in AddToCabinetForm', async () => {
|
||||
mockListMedicines.mockResolvedValue({
|
||||
data: [
|
||||
{ _id: 'med-1', name: 'Metformin', form: 'tablet', strength: 500, strengthUnit: 'mg' },
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Add to Cabinet'));
|
||||
await userEvent.click(screen.getByText('Add to Cabinet'));
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
|
||||
|
||||
// Search for medicine
|
||||
fireEvent.change(screen.getByPlaceholderText('Search medicines...'), {
|
||||
target: { value: 'Met' },
|
||||
});
|
||||
|
||||
// Change quantity
|
||||
fireEvent.change(screen.getByPlaceholderText('30'), { target: { value: '60' } });
|
||||
|
||||
// Change unit
|
||||
fireEvent.change(screen.getAllByRole('combobox')[1]!, { target: { value: 'capsule' } });
|
||||
|
||||
expect(screen.getByPlaceholderText('Search medicines...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('switches from detail view back to summary view', async () => {
|
||||
mockGetCabinetSummary.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
medicineStrength: 500,
|
||||
medicineStrengthUnit: 'mg',
|
||||
medicineForm: 'tablet',
|
||||
totalQuantity: 10,
|
||||
unit: 'tablet',
|
||||
itemCount: 1,
|
||||
earliestExpiry: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
// Now in detail view
|
||||
await waitFor(() => screen.getByText('Summary'));
|
||||
await userEvent.click(screen.getByText('Summary'));
|
||||
|
||||
// Back in summary view — Metformin should show
|
||||
await waitFor(() => expect(screen.getByText('Metformin')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows expiry date for cabinet items', async () => {
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
...cabinetItem,
|
||||
expirationDate: '2099-12-31T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
|
||||
await waitFor(() => screen.getByText('Metformin'));
|
||||
expect(screen.getByText(/days\)/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('filters cabinet items by status in detail view', async () => {
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [cabinetItem],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
|
||||
await waitFor(() => screen.getByDisplayValue('All Statuses'));
|
||||
fireEvent.change(screen.getByDisplayValue('All Statuses'), { target: { value: 'active' } });
|
||||
|
||||
await waitFor(() => expect(mockListCabinetItems).toHaveBeenCalledTimes(2));
|
||||
});
|
||||
|
||||
it('takes 1 from cabinet item', async () => {
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [cabinetItem],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockAdjustCabinetItemQuantity.mockResolvedValue({ ...cabinetItem, quantity: 9 });
|
||||
mockGetCabinetSummary.mockResolvedValue(emptySummary);
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
|
||||
await waitFor(() => screen.getByTitle('Take 1'));
|
||||
await userEvent.click(screen.getByTitle('Take 1'));
|
||||
|
||||
expect(mockAdjustCabinetItemQuantity).toHaveBeenCalledWith('hh1', 'ci-1', { delta: -1 });
|
||||
});
|
||||
|
||||
it('collapses expanded medicine when it returns 0 items', async () => {
|
||||
mockGetCabinetSummary.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
medicineStrength: 500,
|
||||
medicineStrengthUnit: 'mg',
|
||||
medicineForm: 'tablet',
|
||||
totalQuantity: 0,
|
||||
unit: 'tablet',
|
||||
itemCount: 0,
|
||||
earliestExpiry: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Metformin'));
|
||||
await userEvent.click(screen.getByText('Metformin').closest('button')!);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockListCabinetItems).toHaveBeenCalledWith(
|
||||
'hh1',
|
||||
expect.objectContaining({ medicineId: 'med-1' }),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('shows error when createCabinetItem fails', async () => {
|
||||
mockCreateCabinetItem.mockRejectedValue(new Error('Create failed'));
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Add to Cabinet'));
|
||||
await userEvent.click(screen.getByText('Add to Cabinet'));
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
|
||||
fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);
|
||||
|
||||
// Should show 'Please select a medicine' validation first (since no medicine selected)
|
||||
await waitFor(() => expect(screen.getByText('Please select a medicine')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('deletes cabinet item after confirmation', async () => {
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [cabinetItem],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockDeleteCabinetItem.mockResolvedValue({});
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(true);
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
|
||||
await waitFor(() => screen.getByTitle('Delete'));
|
||||
await userEvent.click(screen.getByTitle('Delete'));
|
||||
|
||||
expect(mockDeleteCabinetItem).toHaveBeenCalledWith('hh1', 'ci-1');
|
||||
});
|
||||
|
||||
it('shows error when adjust fails', async () => {
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [cabinetItem],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockAdjustCabinetItemQuantity.mockRejectedValue(new Error('Adjust failed'));
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
|
||||
await waitFor(() => screen.getByTitle('Add 1'));
|
||||
await userEvent.click(screen.getByTitle('Add 1'));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Adjust failed')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows error when delete fails', async () => {
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [cabinetItem],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockDeleteCabinetItem.mockRejectedValue(new Error('Delete failed'));
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(true);
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
|
||||
await waitFor(() => screen.getByTitle('Delete'));
|
||||
await userEvent.click(screen.getByTitle('Delete'));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Delete failed')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('cancels AddToCabinetForm with internal Cancel button', async () => {
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Add to Cabinet'));
|
||||
await userEvent.click(screen.getByText('Add to Cabinet'));
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
|
||||
// Click the Cancel button inside the form (not the toggle button)
|
||||
const cancelButtons = screen.getAllByText('Cancel');
|
||||
await userEvent.click(cancelButtons[cancelButtons.length - 1]!);
|
||||
|
||||
expect(screen.queryByPlaceholderText('Search medicines...')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('collapses expanded medicine when clicked again', async () => {
|
||||
const summary = {
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
medicineStrength: 500,
|
||||
medicineStrengthUnit: 'mg',
|
||||
medicineForm: 'tablet',
|
||||
totalQuantity: 10,
|
||||
unit: 'tablet',
|
||||
itemCount: 1,
|
||||
earliestExpiry: null,
|
||||
};
|
||||
mockGetCabinetSummary.mockResolvedValue({ data: [summary] });
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [cabinetItem],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Metformin'));
|
||||
// First click expands — handleExpand calls listCabinetItems
|
||||
await userEvent.click(screen.getByText('Metformin').closest('button')!);
|
||||
await waitFor(() => expect(mockListCabinetItems).toHaveBeenCalledTimes(1));
|
||||
const callsAfterExpand = mockListCabinetItems.mock.calls.length;
|
||||
|
||||
// Second click collapses — no additional listCabinetItems calls
|
||||
await userEvent.click(screen.getByText('Metformin').closest('button')!);
|
||||
await waitFor(() => expect(screen.queryByTitle('Take 1')).not.toBeInTheDocument());
|
||||
|
||||
expect(mockListCabinetItems.mock.calls.length).toBe(callsAfterExpand);
|
||||
});
|
||||
|
||||
it('shows empty items when expand fails', async () => {
|
||||
mockGetCabinetSummary.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
medicineId: 'med-1',
|
||||
medicineName: 'Metformin',
|
||||
medicineStrength: 500,
|
||||
medicineStrengthUnit: 'mg',
|
||||
medicineForm: 'tablet',
|
||||
totalQuantity: 10,
|
||||
unit: 'tablet',
|
||||
itemCount: 1,
|
||||
earliestExpiry: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
mockListCabinetItems.mockRejectedValue(new Error('Expand failed'));
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Metformin'));
|
||||
await userEvent.click(screen.getByText('Metformin').closest('button')!);
|
||||
|
||||
await waitFor(() => expect(mockListCabinetItems).toHaveBeenCalledTimes(1));
|
||||
// No items should be shown (empty after error)
|
||||
expect(screen.queryByTitle('Take 1')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows create error when medicine is selected and create fails', async () => {
|
||||
mockListMedicines.mockResolvedValue({
|
||||
data: [
|
||||
{ _id: 'med-1', name: 'Metformin', form: 'tablet', strength: 500, strengthUnit: 'mg' },
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockCreateCabinetItem.mockRejectedValue(new Error('Create failed'));
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Add to Cabinet'));
|
||||
await userEvent.click(screen.getByText('Add to Cabinet'));
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
|
||||
fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
|
||||
fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Create failed')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows item notes in detail view', async () => {
|
||||
mockListCabinetItems.mockResolvedValue({
|
||||
data: [{ ...cabinetItem, notes: 'Store in fridge' }],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('All Items'));
|
||||
await userEvent.click(screen.getByText('All Items'));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Store in fridge')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('waits for medicines to load then selects medicine in form', async () => {
|
||||
mockListMedicines.mockResolvedValue({
|
||||
data: [
|
||||
{ _id: 'med-1', name: 'Metformin', form: 'tablet', strength: 500, strengthUnit: 'mg' },
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
await userEvent.click(screen.getByText('Add to Cabinet'));
|
||||
|
||||
// Wait for medicine options to load (covers medicines.map callback)
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
(screen.getAllByRole('combobox')[0] as HTMLSelectElement).options.length,
|
||||
).toBeGreaterThan(1),
|
||||
);
|
||||
|
||||
// Select a valid medicine (true branch: medForm is set → defaultUnitForForm called)
|
||||
fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
|
||||
|
||||
// Select empty value (false branch: selectedMed undefined → DosageUnit.TABLET fallback)
|
||||
fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: '' } });
|
||||
|
||||
expect(screen.getByPlaceholderText('Search medicines...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows fallback error when non-Error is thrown during create', async () => {
|
||||
mockListMedicines.mockResolvedValue({
|
||||
data: [
|
||||
{ _id: 'med-1', name: 'Metformin', form: 'tablet', strength: 500, strengthUnit: 'mg' },
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockCreateCabinetItem.mockRejectedValue('unexpected');
|
||||
|
||||
render(<CabinetTab householdId="hh1" />, { wrapper });
|
||||
|
||||
await waitFor(() => screen.getByText('Add to Cabinet'));
|
||||
await userEvent.click(screen.getByText('Add to Cabinet'));
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
|
||||
fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
|
||||
fireEvent.submit(screen.getByPlaceholderText('Search medicines...').closest('form')!);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Failed to add item')).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue