2026-04-18 12:36:29 +09:00
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
|
|
|
import userEvent from '@testing-library/user-event';
|
2026-04-26 18:44:59 +09:00
|
|
|
import type React from 'react';
|
2026-04-18 12:36:29 +09:00
|
|
|
|
|
|
|
|
const { mockUseApi } = vi.hoisted(() => ({ mockUseApi: vi.fn() }));
|
|
|
|
|
|
|
|
|
|
const { mockRecordPrice, mockGetPriceHistory, mockCompareStores } = vi.hoisted(() => ({
|
|
|
|
|
mockRecordPrice: vi.fn(),
|
|
|
|
|
mockGetPriceHistory: vi.fn(),
|
|
|
|
|
mockCompareStores: vi.fn(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const { mockListMedicines, mockListMedicineProducts } = vi.hoisted(() => ({
|
|
|
|
|
mockListMedicines: vi.fn(),
|
|
|
|
|
mockListMedicineProducts: vi.fn(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const { mockListStores } = vi.hoisted(() => ({ mockListStores: vi.fn() }));
|
|
|
|
|
|
|
|
|
|
vi.mock('@/lib/useApi', () => ({ useApi: mockUseApi }));
|
|
|
|
|
vi.mock('@/services/medicine-prices', () => ({
|
|
|
|
|
recordPrice: mockRecordPrice,
|
|
|
|
|
getPriceHistory: mockGetPriceHistory,
|
|
|
|
|
compareStores: mockCompareStores,
|
|
|
|
|
}));
|
|
|
|
|
vi.mock('@/services/medicines', () => ({
|
|
|
|
|
listMedicines: mockListMedicines,
|
|
|
|
|
listMedicineProducts: mockListMedicineProducts,
|
|
|
|
|
}));
|
|
|
|
|
vi.mock('@/services/stores', () => ({ listStores: mockListStores }));
|
2026-04-26 18:44:59 +09:00
|
|
|
vi.mock('next/link', () => ({
|
|
|
|
|
default: (props: { href?: string; children: React.ReactNode }) => props.children,
|
|
|
|
|
}));
|
2026-04-18 12:36:29 +09:00
|
|
|
|
2026-05-19 11:06:03 +09:00
|
|
|
import MedicinePricesPage from '../../../../src/app/(dashboard)/medicine-prices/page';
|
2026-04-18 12:36:29 +09:00
|
|
|
|
|
|
|
|
const emptyResponse = { data: [], pagination: { cursor: null, hasMore: false } };
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
mockListMedicines.mockResolvedValue(emptyResponse);
|
|
|
|
|
mockListStores.mockResolvedValue(emptyResponse);
|
|
|
|
|
mockGetPriceHistory.mockResolvedValue(emptyResponse);
|
|
|
|
|
mockCompareStores.mockResolvedValue({ data: [] });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('MedicinePricesPage', () => {
|
|
|
|
|
it('shows loading skeleton when session loading', () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: null, isLoading: true });
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
expect(screen.getByText('Medicine Prices')).toBeInTheDocument();
|
|
|
|
|
expect(screen.queryByText('Record Price')).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows household prompt when no householdId', () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: null, isLoading: false });
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
expect(screen.getByText(/create or join a household/)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders Medicine Prices heading when householdId exists', () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
expect(screen.getByText('Medicine Prices')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText('Record Price')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows empty prompt to select a medicine', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
await waitFor(() =>
|
|
|
|
|
expect(screen.getByText('Select a medicine to view price history.')).toBeInTheDocument(),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('loads price history when medicine selected', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
mockListMedicines.mockResolvedValue({
|
2026-04-26 18:44:59 +09:00
|
|
|
data: [
|
|
|
|
|
{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg', form: 'tablet' },
|
|
|
|
|
],
|
2026-04-18 12:36:29 +09:00
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
mockGetPriceHistory.mockResolvedValue({
|
|
|
|
|
data: [
|
|
|
|
|
{
|
|
|
|
|
_id: 'pr-1',
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Metformin',
|
|
|
|
|
storeId: 'st-1',
|
|
|
|
|
storeName: 'Walgreens',
|
|
|
|
|
price: 15.99,
|
|
|
|
|
pricePerUnit: 0.18,
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
quantity: 90,
|
|
|
|
|
unit: 'tablet',
|
|
|
|
|
date: '2026-01-01T00:00:00.000Z',
|
|
|
|
|
createdAt: '2026-01-01T00:00:00.000Z',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByText('Select a medicine'));
|
|
|
|
|
fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
|
|
|
|
|
|
2026-04-26 18:44:59 +09:00
|
|
|
await waitFor(() =>
|
|
|
|
|
expect(mockGetPriceHistory).toHaveBeenCalledWith('hh1', 'med-1', expect.any(Object)),
|
|
|
|
|
);
|
2026-04-18 12:36:29 +09:00
|
|
|
// Wait for price records to render
|
|
|
|
|
await waitFor(() => expect(screen.getByText('Walgreens')).toBeInTheDocument());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows validation error in form when required fields missing', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
|
|
|
|
|
await userEvent.click(screen.getByText('Record Price'));
|
|
|
|
|
await waitFor(() => screen.getByRole('button', { name: 'Record Price', hidden: false }));
|
|
|
|
|
|
|
|
|
|
// The submit button inside the form also has text 'Record Price'
|
2026-04-26 18:44:59 +09:00
|
|
|
const submitBtn = screen
|
|
|
|
|
.getAllByRole('button', { name: 'Record Price' })
|
|
|
|
|
.find((b) => b.getAttribute('type') === 'submit');
|
2026-04-18 12:36:29 +09:00
|
|
|
if (submitBtn) {
|
|
|
|
|
fireEvent.submit(submitBtn.closest('form')!);
|
|
|
|
|
await waitFor(() =>
|
2026-04-26 18:44:59 +09:00
|
|
|
expect(
|
|
|
|
|
screen.getByText('Please select a medicine, a product, and a store.'),
|
|
|
|
|
).toBeInTheDocument(),
|
2026-04-18 12:36:29 +09:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows error when price history fails', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
mockListMedicines.mockResolvedValue({
|
2026-04-26 18:44:59 +09:00
|
|
|
data: [
|
|
|
|
|
{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg', form: 'tablet' },
|
|
|
|
|
],
|
2026-04-18 12:36:29 +09:00
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
mockGetPriceHistory.mockRejectedValue(new Error('History load failed'));
|
|
|
|
|
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByText('Select a medicine'));
|
|
|
|
|
fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => expect(screen.getByText('History load failed')).toBeInTheDocument());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('records a price successfully', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
mockListMedicines.mockResolvedValue({
|
2026-04-26 18:44:59 +09:00
|
|
|
data: [
|
|
|
|
|
{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg', form: 'tablet' },
|
|
|
|
|
],
|
2026-04-18 12:36:29 +09:00
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
mockListStores.mockResolvedValue({
|
2026-04-26 18:44:59 +09:00
|
|
|
data: [
|
|
|
|
|
{
|
|
|
|
|
_id: 'st-1',
|
|
|
|
|
name: 'Walgreens',
|
|
|
|
|
tags: [],
|
|
|
|
|
isActive: true,
|
|
|
|
|
createdAt: '2026-01-01T00:00:00.000Z',
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-04-18 12:36:29 +09:00
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
mockListMedicineProducts.mockResolvedValue({
|
|
|
|
|
data: [{ _id: 'prod-1', brand: 'Generic', packageSize: 90, packageUnit: 'tablet' }],
|
|
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
mockRecordPrice.mockResolvedValue({});
|
|
|
|
|
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByText('Record Price'));
|
|
|
|
|
await userEvent.click(screen.getByText('Record Price'));
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByText('Record Price', { selector: 'h2' }));
|
|
|
|
|
|
|
|
|
|
// Select store
|
|
|
|
|
fireEvent.change(screen.getByDisplayValue('Select store'), { target: { value: 'st-1' } });
|
|
|
|
|
// Select medicine
|
|
|
|
|
fireEvent.change(screen.getByDisplayValue('Select medicine'), { target: { value: 'med-1' } });
|
|
|
|
|
|
|
|
|
|
// Wait for products to load and select product
|
|
|
|
|
await waitFor(() => screen.getByText(/Generic.*90/));
|
|
|
|
|
fireEvent.change(screen.getByDisplayValue('Select product'), { target: { value: 'prod-1' } });
|
|
|
|
|
|
|
|
|
|
// Submit form
|
2026-04-26 18:44:59 +09:00
|
|
|
fireEvent.submit(
|
|
|
|
|
screen.getByRole('button', { name: 'Record Price', hidden: true }).closest('form')!,
|
|
|
|
|
);
|
2026-04-18 12:36:29 +09:00
|
|
|
|
2026-04-26 18:44:59 +09:00
|
|
|
await waitFor(() =>
|
|
|
|
|
expect(mockRecordPrice).toHaveBeenCalledWith(
|
|
|
|
|
'hh1',
|
|
|
|
|
expect.objectContaining({ medicineId: 'med-1' }),
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-04-18 12:36:29 +09:00
|
|
|
// Form should close after success
|
2026-04-26 18:44:59 +09:00
|
|
|
await waitFor(() =>
|
|
|
|
|
expect(screen.queryByText('Record Price', { selector: 'h2' })).not.toBeInTheDocument(),
|
|
|
|
|
);
|
2026-04-18 12:36:29 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows Load more button in price history', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
mockListMedicines.mockResolvedValue({
|
2026-04-26 18:44:59 +09:00
|
|
|
data: [
|
|
|
|
|
{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg', form: 'tablet' },
|
|
|
|
|
],
|
2026-04-18 12:36:29 +09:00
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
mockGetPriceHistory.mockResolvedValue({
|
|
|
|
|
data: [
|
|
|
|
|
{
|
|
|
|
|
_id: 'pr-1',
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Metformin',
|
|
|
|
|
storeId: 'st-1',
|
|
|
|
|
storeName: 'Walgreens',
|
|
|
|
|
price: 15.99,
|
|
|
|
|
pricePerUnit: 0.18,
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
quantity: 90,
|
|
|
|
|
unit: 'tablet',
|
|
|
|
|
date: '2026-01-01T00:00:00.000Z',
|
|
|
|
|
createdAt: '2026-01-01T00:00:00.000Z',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
pagination: { cursor: 'cur1', hasMore: true },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByText('Select a medicine'));
|
|
|
|
|
fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => expect(screen.getByText('Load more')).toBeInTheDocument());
|
|
|
|
|
await userEvent.click(screen.getByText('Load more'));
|
|
|
|
|
|
|
|
|
|
expect(mockGetPriceHistory).toHaveBeenCalledTimes(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows store filter when medicine selected and changes it', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
mockListMedicines.mockResolvedValue({
|
2026-04-26 18:44:59 +09:00
|
|
|
data: [
|
|
|
|
|
{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg', form: 'tablet' },
|
|
|
|
|
],
|
2026-04-18 12:36:29 +09:00
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
mockListStores.mockResolvedValue({
|
2026-04-26 18:44:59 +09:00
|
|
|
data: [
|
|
|
|
|
{
|
|
|
|
|
_id: 'st-1',
|
|
|
|
|
name: 'Walgreens',
|
|
|
|
|
tags: [],
|
|
|
|
|
isActive: true,
|
|
|
|
|
createdAt: '2026-01-01T00:00:00.000Z',
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-04-18 12:36:29 +09:00
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByText('Select a medicine'));
|
|
|
|
|
fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByDisplayValue('All stores'));
|
|
|
|
|
fireEvent.change(screen.getByDisplayValue('All stores'), { target: { value: 'st-1' } });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => expect(mockGetPriceHistory).toHaveBeenCalledTimes(2));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('dismisses price history error', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
mockListMedicines.mockResolvedValue({
|
2026-04-26 18:44:59 +09:00
|
|
|
data: [
|
|
|
|
|
{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg', form: 'tablet' },
|
|
|
|
|
],
|
2026-04-18 12:36:29 +09:00
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
mockGetPriceHistory.mockRejectedValue(new Error('History load failed'));
|
|
|
|
|
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByText('Select a medicine'));
|
|
|
|
|
fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByText('History load failed'));
|
|
|
|
|
await userEvent.click(screen.getByText('Dismiss'));
|
|
|
|
|
expect(screen.queryByText('History load failed')).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows store comparison table', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
mockListMedicines.mockResolvedValue({
|
2026-04-26 18:44:59 +09:00
|
|
|
data: [
|
|
|
|
|
{ _id: 'med-1', name: 'Metformin', strength: 500, strengthUnit: 'mg', form: 'tablet' },
|
|
|
|
|
],
|
2026-04-18 12:36:29 +09:00
|
|
|
pagination: { cursor: null, hasMore: false },
|
|
|
|
|
});
|
|
|
|
|
mockCompareStores.mockResolvedValue({
|
|
|
|
|
data: [
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
storeId: 'st-1',
|
|
|
|
|
storeName: 'Walgreens',
|
|
|
|
|
latestPrice: 12.99,
|
|
|
|
|
latestPricePerUnit: 0.14,
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
date: '2026-01-01T00:00:00.000Z',
|
|
|
|
|
isInsurancePrice: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
storeId: 'st-2',
|
|
|
|
|
storeName: 'CVS',
|
|
|
|
|
latestPrice: 14.99,
|
|
|
|
|
latestPricePerUnit: 0.17,
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
date: '2026-01-01T00:00:00.000Z',
|
|
|
|
|
isInsurancePrice: false,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByText('Select a medicine'));
|
|
|
|
|
fireEvent.change(screen.getAllByRole('combobox')[0]!, { target: { value: 'med-1' } });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => expect(screen.getByText('Store comparison')).toBeInTheDocument());
|
|
|
|
|
expect(screen.getByText('Walgreens')).toBeInTheDocument();
|
|
|
|
|
// First row (cheapest) should be highlighted green
|
|
|
|
|
expect(screen.getByText('cheapest')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('changes price, currency, quantity, and unit fields in form', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
|
|
|
|
|
await userEvent.click(screen.getByText('Record Price'));
|
|
|
|
|
await waitFor(() => screen.getByPlaceholderText('9.99'));
|
|
|
|
|
|
|
|
|
|
fireEvent.change(screen.getByPlaceholderText('9.99'), { target: { value: '15.99' } });
|
|
|
|
|
fireEvent.change(screen.getByPlaceholderText('USD'), { target: { value: 'EUR' } });
|
|
|
|
|
fireEvent.change(screen.getByPlaceholderText('90'), { target: { value: '30' } });
|
|
|
|
|
fireEvent.change(screen.getByDisplayValue('tablet'), { target: { value: 'capsule' } });
|
|
|
|
|
|
|
|
|
|
expect(screen.getByPlaceholderText('9.99')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('changes medicine search, notes and insurance price fields in form', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
|
|
|
|
|
await userEvent.click(screen.getByText('Record Price'));
|
|
|
|
|
await waitFor(() => screen.getByPlaceholderText('Search medicines...'));
|
|
|
|
|
|
2026-04-26 18:44:59 +09:00
|
|
|
fireEvent.change(screen.getByPlaceholderText('Search medicines...'), {
|
|
|
|
|
target: { value: 'Met' },
|
|
|
|
|
});
|
2026-04-18 12:36:29 +09:00
|
|
|
|
|
|
|
|
// Notes field (no placeholder, but maxLength 1000)
|
|
|
|
|
const notesInput = document.querySelector('input[maxLength="1000"]') as HTMLElement;
|
|
|
|
|
if (notesInput) {
|
|
|
|
|
fireEvent.change(notesInput, { target: { value: 'Some notes' } });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Insurance price checkbox
|
|
|
|
|
fireEvent.click(screen.getByLabelText('Insurance price'));
|
|
|
|
|
expect(screen.getByLabelText('Insurance price')).toBeChecked();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('toggles Record Price form', async () => {
|
|
|
|
|
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
|
|
|
|
|
render(<MedicinePricesPage />);
|
|
|
|
|
await userEvent.click(screen.getByText('Record Price'));
|
|
|
|
|
// Both the header toggle and the form show "Cancel" when open
|
|
|
|
|
expect(screen.getAllByRole('button', { name: 'Cancel' }).length).toBeGreaterThan(0);
|
|
|
|
|
await userEvent.click(screen.getAllByRole('button', { name: 'Cancel' })[0]);
|
|
|
|
|
expect(screen.queryByRole('button', { name: 'Cancel' })).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|