Tests refactor
This commit is contained in:
parent
245520fb50
commit
99134d8556
165 changed files with 911 additions and 531 deletions
572
packages/web/tests/app/(dashboard)/purchases/page.test.tsx
Normal file
572
packages/web/tests/app/(dashboard)/purchases/page.test.tsx
Normal file
|
|
@ -0,0 +1,572 @@
|
|||
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 type React from 'react';
|
||||
|
||||
const { mockUseApi } = vi.hoisted(() => ({ mockUseApi: vi.fn() }));
|
||||
|
||||
const { mockListPurchases, mockCreatePurchase, mockReceivePurchase, mockDeletePurchase } =
|
||||
vi.hoisted(() => ({
|
||||
mockListPurchases: vi.fn(),
|
||||
mockCreatePurchase: vi.fn(),
|
||||
mockReceivePurchase: vi.fn(),
|
||||
mockDeletePurchase: vi.fn(),
|
||||
}));
|
||||
|
||||
const { mockListStores } = vi.hoisted(() => ({ mockListStores: vi.fn() }));
|
||||
const { mockListMedicines, mockListMedicineProducts } = vi.hoisted(() => ({
|
||||
mockListMedicines: vi.fn(),
|
||||
mockListMedicineProducts: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/useApi', () => ({ useApi: mockUseApi }));
|
||||
vi.mock('@/services/purchases', () => ({
|
||||
listPurchases: mockListPurchases,
|
||||
createPurchase: mockCreatePurchase,
|
||||
receivePurchase: mockReceivePurchase,
|
||||
deletePurchase: mockDeletePurchase,
|
||||
}));
|
||||
vi.mock('@/services/stores', () => ({ listStores: mockListStores }));
|
||||
vi.mock('@/services/medicines', () => ({
|
||||
listMedicines: mockListMedicines,
|
||||
listMedicineProducts: mockListMedicineProducts,
|
||||
}));
|
||||
vi.mock('next/link', () => ({
|
||||
default: (props: { href?: string; children: React.ReactNode }) => props.children,
|
||||
}));
|
||||
|
||||
import PurchasesPage from '../../../../src/app/(dashboard)/purchases/page';
|
||||
|
||||
const emptyResponse = { data: [], pagination: { cursor: null, hasMore: false } };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockListStores.mockResolvedValue(emptyResponse);
|
||||
mockListMedicines.mockResolvedValue(emptyResponse);
|
||||
});
|
||||
|
||||
describe('PurchasesPage', () => {
|
||||
it('shows loading skeleton when session loading', () => {
|
||||
mockUseApi.mockReturnValue({ householdId: null, isLoading: true });
|
||||
render(<PurchasesPage />);
|
||||
expect(screen.getByText('Purchases')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Record Purchase')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows household prompt when no householdId', () => {
|
||||
mockUseApi.mockReturnValue({ householdId: null, isLoading: false });
|
||||
render(<PurchasesPage />);
|
||||
expect(screen.getByText(/create or join a household/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows empty state when no purchases', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/No purchases recorded yet/)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows Record Purchase button', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Record Purchase')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows error when purchases fail to load', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockRejectedValue(new Error('Network failure'));
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Network failure')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('renders ordered and received sections', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'p1',
|
||||
status: 'ordered',
|
||||
purchasedAt: '2026-01-01T00:00:00.000Z',
|
||||
items: [],
|
||||
householdId: 'hh1',
|
||||
createdBy: 'u1',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
{
|
||||
_id: 'p2',
|
||||
status: 'in_cabinet',
|
||||
purchasedAt: '2026-01-02T00:00:00.000Z',
|
||||
items: [],
|
||||
householdId: 'hh1',
|
||||
createdBy: 'u1',
|
||||
createdAt: '2026-01-02T00:00:00.000Z',
|
||||
updatedAt: '2026-01-02T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Pending arrival')).toBeInTheDocument());
|
||||
// "Received" appears as a section heading (h2) — use getAllByText since PurchaseCard may also render it
|
||||
expect(screen.getAllByText('Received').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('toggles the create form', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
// After clicking, both the header toggle and the form show "Cancel"
|
||||
expect(screen.getAllByRole('button', { name: 'Cancel' }).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('shows Load more button when hasMore', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue({
|
||||
data: [],
|
||||
pagination: { cursor: 'cur1', hasMore: true },
|
||||
});
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Load more')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows validation error when no store selected', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
mockListStores.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'st-1',
|
||||
name: 'Walgreens',
|
||||
tags: [],
|
||||
isActive: true,
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => screen.getByText('Save Purchase'));
|
||||
// Submit without selecting a store
|
||||
fireEvent.submit(screen.getByRole('button', { name: 'Save Purchase' }).closest('form')!);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Please select a store.')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows validation error when no items', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
mockListStores.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'st-1',
|
||||
name: 'Walgreens',
|
||||
tags: [],
|
||||
isActive: true,
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => screen.getByText('Save Purchase'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select store'), { target: { value: 'st-1' } });
|
||||
fireEvent.submit(screen.getByRole('button', { name: 'Save Purchase' }).closest('form')!);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText('Add at least one item with a name and quantity.'),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it('receives a purchase after confirmation', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'p1',
|
||||
status: 'ordered',
|
||||
purchasedAt: '2026-01-01T00:00:00.000Z',
|
||||
items: [],
|
||||
householdId: 'hh1',
|
||||
createdBy: 'u1',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockReceivePurchase.mockResolvedValue({});
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Mark as received'));
|
||||
await userEvent.click(screen.getByText('Mark as received'));
|
||||
|
||||
expect(mockReceivePurchase).toHaveBeenCalledWith('hh1', 'p1');
|
||||
});
|
||||
|
||||
it('deletes a purchase after confirmation', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'p1',
|
||||
status: 'ordered',
|
||||
purchasedAt: '2026-01-01T00:00:00.000Z',
|
||||
items: [],
|
||||
householdId: 'hh1',
|
||||
createdBy: 'u1',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockDeletePurchase.mockResolvedValue({});
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Cancel order'));
|
||||
await userEvent.click(screen.getByText('Cancel order'));
|
||||
|
||||
expect(mockDeletePurchase).toHaveBeenCalledWith('hh1', 'p1');
|
||||
});
|
||||
|
||||
it('shows error when receive fails', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'p1',
|
||||
status: 'ordered',
|
||||
purchasedAt: '2026-01-01T00:00:00.000Z',
|
||||
items: [],
|
||||
householdId: 'hh1',
|
||||
createdBy: 'u1',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockReceivePurchase.mockRejectedValue(new Error('Receive failed'));
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Mark as received'));
|
||||
await userEvent.click(screen.getByText('Mark as received'));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Receive failed')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('shows error when delete fails', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'p1',
|
||||
status: 'ordered',
|
||||
purchasedAt: '2026-01-01T00:00:00.000Z',
|
||||
items: [],
|
||||
householdId: 'hh1',
|
||||
createdBy: 'u1',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockDeletePurchase.mockRejectedValue(new Error('Delete failed'));
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Cancel order'));
|
||||
await userEvent.click(screen.getByText('Cancel order'));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Delete failed')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('creates a purchase successfully', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
mockListMedicineProducts.mockResolvedValue(emptyResponse);
|
||||
mockListStores.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'st-1',
|
||||
name: 'Walgreens',
|
||||
tags: [],
|
||||
isActive: true,
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockCreatePurchase.mockResolvedValue({
|
||||
_id: 'p-new',
|
||||
status: 'in_cabinet',
|
||||
purchasedAt: '2026-01-01T00:00:00.000Z',
|
||||
items: [],
|
||||
householdId: 'hh1',
|
||||
createdBy: 'u1',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
});
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => screen.getByText('Save Purchase'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select store'), { target: { value: 'st-1' } });
|
||||
fireEvent.change(screen.getByPlaceholderText('Brand / product name'), {
|
||||
target: { value: 'Aspirin' },
|
||||
});
|
||||
fireEvent.change(screen.getByPlaceholderText('90'), { target: { value: '30' } });
|
||||
fireEvent.submit(screen.getByRole('button', { name: 'Save Purchase' }).closest('form')!);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockCreatePurchase).toHaveBeenCalledWith(
|
||||
'hh1',
|
||||
expect.objectContaining({ storeId: 'st-1' }),
|
||||
),
|
||||
);
|
||||
// Form hides after success
|
||||
await waitFor(() => expect(screen.queryByText('Save Purchase')).not.toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('cancels the create form', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => screen.getAllByRole('button', { name: 'Cancel' }));
|
||||
// Click the form's Cancel button (inside the form)
|
||||
const cancelBtns = screen.getAllByRole('button', { name: 'Cancel' });
|
||||
await userEvent.click(cancelBtns[cancelBtns.length - 1]!);
|
||||
|
||||
expect(screen.queryByText('Save Purchase')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders purchase items with price data', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
_id: 'p1',
|
||||
status: 'in_cabinet',
|
||||
storeName: 'Walgreens',
|
||||
purchasedAt: '2026-01-01T00:00:00.000Z',
|
||||
items: [
|
||||
{
|
||||
name: 'Aspirin',
|
||||
quantity: 30,
|
||||
unit: 'tablet',
|
||||
actualPrice: 5.99,
|
||||
currency: 'USD',
|
||||
},
|
||||
],
|
||||
householdId: 'hh1',
|
||||
createdBy: 'u1',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Aspirin')).toBeInTheDocument());
|
||||
expect(screen.getByText(/5.99/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('loads more purchases when Load more is clicked', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue({
|
||||
data: [],
|
||||
pagination: { cursor: 'cur1', hasMore: true },
|
||||
});
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Load more'));
|
||||
await userEvent.click(screen.getByText('Load more'));
|
||||
|
||||
expect(mockListPurchases).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('toggles online order checkbox and changes notes', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => screen.getByLabelText('Online order (pending arrival)'));
|
||||
fireEvent.click(screen.getByLabelText('Online order (pending arrival)'));
|
||||
fireEvent.change(document.querySelector('input[maxLength="1000"]') as HTMLElement, {
|
||||
target: { value: 'Some notes' },
|
||||
});
|
||||
|
||||
expect(screen.getByLabelText('Online order (pending arrival)')).toBeChecked();
|
||||
});
|
||||
|
||||
it('adds and removes purchase items', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => screen.getByText('Save Purchase'));
|
||||
const addItemBtn = screen.getByRole('button', { name: 'Add item' });
|
||||
await userEvent.click(addItemBtn);
|
||||
|
||||
// Now 2 items — remove button shows
|
||||
await waitFor(() => screen.getAllByText('Remove').length > 0);
|
||||
await userEvent.click(screen.getAllByText('Remove')[0]!);
|
||||
|
||||
expect(screen.queryByText('Remove')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('changes item price and currency fields', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('9.99'));
|
||||
fireEvent.change(screen.getByPlaceholderText('9.99'), { target: { value: '5.99' } });
|
||||
fireEvent.change(screen.getByPlaceholderText('USD'), { target: { value: 'EUR' } });
|
||||
|
||||
expect(screen.getByPlaceholderText('9.99')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('changes item name and unit fields', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText('Brand / product name'));
|
||||
fireEvent.change(screen.getByPlaceholderText('Brand / product name'), {
|
||||
target: { value: 'Aspirin' },
|
||||
});
|
||||
fireEvent.change(screen.getByPlaceholderText('90'), { target: { value: '30' } });
|
||||
fireEvent.change(screen.getByPlaceholderText('tablet'), { target: { value: 'capsule' } });
|
||||
|
||||
expect(screen.getByPlaceholderText('Brand / product name')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('selects a medicine and product in the form', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
mockListMedicines.mockResolvedValue({
|
||||
data: [{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg' }],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockListMedicineProducts.mockResolvedValue({
|
||||
data: [{ _id: 'prod-1', brand: 'Glucophage', packageSize: 60, packageUnit: 'tablet' }],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => screen.getByText('Save Purchase'));
|
||||
// Select a medicine
|
||||
fireEvent.change(screen.getByDisplayValue('Select medicine'), { target: { value: 'med-1' } });
|
||||
|
||||
// Wait for products to load
|
||||
await waitFor(() => screen.getByDisplayValue('Select product'));
|
||||
|
||||
// Select a product
|
||||
fireEvent.change(screen.getByDisplayValue('Select product'), { target: { value: 'prod-1' } });
|
||||
|
||||
// Product details should auto-fill the form
|
||||
expect(screen.getByDisplayValue('60')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles product list failure gracefully', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
mockListMedicines.mockResolvedValue({
|
||||
data: [{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg' }],
|
||||
pagination: { cursor: null, hasMore: false },
|
||||
});
|
||||
mockListMedicineProducts.mockRejectedValue(new Error('Products failed'));
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => screen.getByText('Save Purchase'));
|
||||
fireEvent.change(screen.getByDisplayValue('Select medicine'), { target: { value: 'med-1' } });
|
||||
|
||||
// Wait for products loading to finish (error is swallowed)
|
||||
await waitFor(() => expect(mockListMedicineProducts).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('shows no stores message when store list is empty in form', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockResolvedValue(emptyResponse);
|
||||
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
|
||||
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Record Purchase'));
|
||||
await userEvent.click(screen.getByText('Record Purchase'));
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/No stores yet/)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('dismisses error', async () => {
|
||||
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
||||
mockListPurchases.mockRejectedValue(new Error('Oops'));
|
||||
render(<PurchasesPage />);
|
||||
|
||||
await waitFor(() => screen.getByText('Oops'));
|
||||
await userEvent.click(screen.getByText('Dismiss'));
|
||||
expect(screen.queryByText('Oops')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue