@openmrs/esm-billing-app 1.0.2-pre.613 → 1.0.2-pre.619

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +12 -6
  2. package/__mocks__/react-i18next.js +6 -5
  3. package/dist/2352.js +1 -1
  4. package/dist/2352.js.map +1 -1
  5. package/dist/4300.js +1 -1
  6. package/dist/4739.js +1 -1
  7. package/dist/4739.js.map +1 -1
  8. package/dist/7239.js +1 -1
  9. package/dist/7239.js.map +1 -1
  10. package/dist/8638.js +1 -1
  11. package/dist/8638.js.map +1 -1
  12. package/dist/main.js +1 -1
  13. package/dist/main.js.map +1 -1
  14. package/dist/openmrs-esm-billing-app.js +1 -1
  15. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +19 -19
  16. package/dist/routes.json +1 -1
  17. package/e2e/README.md +19 -18
  18. package/package.json +2 -2
  19. package/src/bill-history/bill-history.test.tsx +37 -77
  20. package/src/bill-item-actions/edit-bill-item.component.tsx +7 -3
  21. package/src/bill-item-actions/edit-bill-item.test.tsx +17 -19
  22. package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
  23. package/src/billable-services/billable-service.resource.ts +4 -3
  24. package/src/billable-services/billable-services.component.tsx +5 -4
  25. package/src/billable-services/billable-services.test.tsx +3 -44
  26. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +2 -2
  27. package/src/billable-services/create-edit/add-billable-service.component.tsx +2 -2
  28. package/src/billable-services/create-edit/add-billable-service.test.tsx +6 -6
  29. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +1 -1
  30. package/src/billing-form/billing-checkin-form.component.tsx +1 -2
  31. package/src/billing-form/billing-checkin-form.test.tsx +0 -2
  32. package/src/billing-form/billing-form.component.tsx +8 -4
  33. package/src/billing.resource.ts +13 -9
  34. package/src/bills-table/bills-table.test.tsx +97 -53
  35. package/src/config-schema.ts +52 -18
  36. package/src/invoice/invoice-table.component.tsx +9 -9
  37. package/src/invoice/invoice-table.scss +1 -5
  38. package/src/invoice/invoice-table.test.tsx +24 -39
  39. package/src/invoice/invoice.component.tsx +8 -6
  40. package/src/invoice/invoice.scss +7 -4
  41. package/src/invoice/invoice.test.tsx +12 -47
  42. package/src/invoice/payments/payment-form/payment-form.test.tsx +8 -10
  43. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  44. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  45. package/src/invoice/payments/payments.component.test.tsx +20 -6
  46. package/src/invoice/printable-invoice/print-receipt.component.tsx +1 -1
  47. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  48. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  49. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  50. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +5 -4
  51. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +11 -11
  52. package/src/invoice/printable-invoice/printable-invoice.component.tsx +11 -11
  53. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  54. package/src/modal/require-payment-modal.test.tsx +24 -19
  55. package/translations/en.json +18 -0
@@ -65,13 +65,13 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
65
65
  }, [debouncedSearchTerm, lineItems]);
66
66
 
67
67
  const tableHeaders = [
68
- { header: 'No', key: 'no', width: 7 }, // Width as a percentage
69
- { header: 'Bill item', key: 'billItem', width: 25 },
70
- { header: 'Bill code', key: 'billCode', width: 20 },
71
- { header: 'Status', key: 'status', width: 25 },
72
- { header: 'Quantity', key: 'quantity', width: 15 },
73
- { header: 'Price', key: 'price', width: 24 },
74
- { header: 'Total', key: 'total', width: 15 },
68
+ { header: t('number', 'No'), key: 'no', width: 7 }, // Width as a percentage
69
+ { header: t('billItem', 'Bill item'), key: 'billItem', width: 25 },
70
+ { header: t('billCode', 'Bill code'), key: 'billCode', width: 20 },
71
+ { header: t('status', 'Status'), key: 'status', width: 25 },
72
+ { header: t('quantity', 'Quantity'), key: 'quantity', width: 15 },
73
+ { header: t('price', 'Price'), key: 'price', width: 24 },
74
+ { header: t('total', 'Total'), key: 'total', width: 15 },
75
75
  { header: t('actions', 'Actions'), key: 'actionButton' },
76
76
  ];
77
77
 
@@ -149,7 +149,7 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
149
149
  };
150
150
 
151
151
  return (
152
- <div className={styles.invoiceContainer}>
152
+ <>
153
153
  <DataTable headers={tableHeaders} isSortable rows={tableRows} size={responsiveSize} useZebraStyles>
154
154
  {({ rows, headers, getRowProps, getSelectionProps, getTableProps, getToolbarProps }) => (
155
155
  <TableContainer
@@ -221,7 +221,7 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
221
221
  </Layer>
222
222
  </div>
223
223
  )}
224
- </div>
224
+ </>
225
225
  );
226
226
  };
227
227
 
@@ -29,11 +29,7 @@
29
29
 
30
30
  .headerContainer {
31
31
  background-color: colors.$gray-10;
32
- }
33
-
34
- .invoiceContainer {
35
- border: 1px solid $ui-03;
36
- }
32
+ }c
37
33
 
38
34
  .searchbox {
39
35
  input:focus {
@@ -1,43 +1,24 @@
1
1
  import React from 'react';
2
- import { useTranslation } from 'react-i18next';
3
- import { render, screen, fireEvent, act } from '@testing-library/react';
4
- import { showModal } from '@openmrs/esm-framework';
5
- import InvoiceTable from './invoice-table.component';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen, act } from '@testing-library/react';
4
+ import { getDefaultsFromConfigSchema, showModal, useConfig } from '@openmrs/esm-framework';
6
5
  import { type MappedBill } from '../types';
6
+ import { configSchema, type BillingConfig } from '../config-schema';
7
+ import InvoiceTable from './invoice-table.component';
7
8
 
8
- // Mocking dependencies
9
- jest.mock('react-i18next', () => ({
10
- useTranslation: jest.fn(() => ({
11
- t: jest.fn((key, fallback) => fallback || key),
12
- i18n: { language: 'en' },
13
- })),
14
- }));
15
-
16
- jest.mock('@openmrs/esm-framework', () => ({
17
- showModal: jest.fn(),
18
- useConfig: jest.fn(() => ({
19
- defaultCurrency: 'USD',
20
- showEditBillButton: true,
21
- })),
22
- useDebounce: jest.fn((value) => value),
23
- useLayoutType: jest.fn(() => 'desktop'),
24
- isDesktop: jest.fn(() => true),
25
- }));
9
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
26
10
 
27
11
  jest.mock('../helpers', () => ({
28
12
  convertToCurrency: jest.fn((price) => `USD ${price}`),
29
13
  }));
30
14
 
31
15
  describe('InvoiceTable', () => {
32
- const mockT = jest.fn((key) => key);
33
-
34
16
  beforeEach(() => {
35
- (useTranslation as jest.Mock).mockReturnValue({ t: mockT, i18n: { language: 'en' } });
36
- jest.useFakeTimers();
37
- });
38
-
39
- afterEach(() => {
40
- jest.useRealTimers();
17
+ mockUseConfig.mockReturnValue({
18
+ ...getDefaultsFromConfigSchema(configSchema),
19
+ defaultCurrency: 'USD',
20
+ showEditBillButton: true,
21
+ });
41
22
  });
42
23
 
43
24
  const bill: MappedBill = {
@@ -103,11 +84,12 @@ describe('InvoiceTable', () => {
103
84
  expect(screen.getByTestId('receipt-number-0')).toHaveTextContent('12345');
104
85
  });
105
86
 
106
- it('renders the edit button and calls showModal when clicked', () => {
87
+ it('renders the edit button and calls showModal when clicked', async () => {
88
+ const user = userEvent.setup();
107
89
  render(<InvoiceTable bill={bill} />);
108
90
 
109
91
  const editButton = screen.getByTestId('edit-button-1');
110
- fireEvent.click(editButton);
92
+ await user.click(editButton);
111
93
  expect(showModal).toHaveBeenCalledWith('edit-bill-line-item-dialog', expect.anything());
112
94
  });
113
95
 
@@ -117,31 +99,34 @@ describe('InvoiceTable', () => {
117
99
  expect(screen.getByTestId('loader')).toBeInTheDocument();
118
100
  });
119
101
 
120
- it('filters line items based on the search term', () => {
102
+ it('filters line items based on the search term', async () => {
103
+ const user = userEvent.setup();
121
104
  render(<InvoiceTable bill={bill} />);
122
- const searchInput = screen.getByPlaceholderText('searchThisTable'); //
105
+ const searchInput = screen.getByPlaceholderText(/search this table/i);
123
106
 
124
- fireEvent.change(searchInput, { target: { value: 'Item 2' } });
107
+ await user.type(searchInput, 'Item 2');
125
108
 
126
109
  expect(screen.queryByText('Item 1')).not.toBeInTheDocument();
127
110
  expect(screen.getByText('Item 2')).toBeInTheDocument();
128
111
  });
129
112
 
130
- it('correctly handles row selection', () => {
113
+ it('correctly handles row selection', async () => {
114
+ const user = userEvent.setup();
131
115
  const onSelectItem = jest.fn();
132
116
  render(<InvoiceTable bill={bill} onSelectItem={onSelectItem} />);
133
117
 
134
118
  const checkboxes = screen.getAllByLabelText('Select row');
135
- fireEvent.click(checkboxes[0]);
119
+ await user.click(checkboxes[0]);
136
120
 
137
121
  expect(onSelectItem).toHaveBeenCalledWith([bill.lineItems[0]]);
138
122
  });
139
123
 
140
- it('resets isRedirecting to false after timeout', () => {
124
+ it('resets isRedirecting to false after timeout', async () => {
125
+ const user = userEvent.setup();
141
126
  render(<InvoiceTable bill={bill} />);
142
127
 
143
128
  const button = screen.getByTestId('edit-button-1');
144
- fireEvent.click(button);
129
+ await user.click(button);
145
130
  act(() => {
146
131
  jest.advanceTimersByTime(1000);
147
132
  });
@@ -4,7 +4,7 @@ import { Printer } from '@carbon/react/icons';
4
4
  import { useParams } from 'react-router-dom';
5
5
  import { useReactToPrint } from 'react-to-print';
6
6
  import { useTranslation } from 'react-i18next';
7
- import { ExtensionSlot, useConfig, usePatient } from '@openmrs/esm-framework';
7
+ import { ExtensionSlot, showSnackbar, useConfig, usePatient } from '@openmrs/esm-framework';
8
8
  import { ErrorState } from '@openmrs/esm-patient-common-lib';
9
9
  import { convertToCurrency } from '../helpers';
10
10
  import { type LineItem } from '../types';
@@ -13,6 +13,7 @@ import InvoiceTable from './invoice-table.component';
13
13
  import Payments from './payments/payments.component';
14
14
  import PrintReceipt from './printable-invoice/print-receipt.component';
15
15
  import PrintableInvoice from './printable-invoice/printable-invoice.component';
16
+ import type { BillingConfig } from '../config-schema';
16
17
  import styles from './invoice.scss';
17
18
 
18
19
  interface InvoiceDetailsProps {
@@ -29,7 +30,7 @@ const Invoice: React.FC = () => {
29
30
  const [selectedLineItems, setSelectedLineItems] = useState<LineItem[]>([]);
30
31
  const componentRef = useRef<HTMLDivElement>(null);
31
32
  const onBeforeGetContentResolve = useRef<(() => void) | null>(null);
32
- const { defaultCurrency } = useConfig();
33
+ const { defaultCurrency } = useConfig<BillingConfig>();
33
34
  const handleSelectItem = (lineItems: LineItem[]) => {
34
35
  setSelectedLineItems(lineItems);
35
36
  };
@@ -51,11 +52,12 @@ const Invoice: React.FC = () => {
51
52
  }, [bill, patient]);
52
53
 
53
54
  const handlePrint = useReactToPrint({
54
- content: reactToPrintContent,
55
55
  documentTitle: `Invoice ${bill?.receiptNumber} - ${patient?.name?.[0]?.given?.join(' ')} ${patient?.name?.[0].family}`,
56
- onBeforeGetContent: handleOnBeforeGetContent,
56
+ onBeforePrint: handleOnBeforeGetContent,
57
57
  onAfterPrint: handleAfterPrint,
58
- removeAfterPrint: true,
58
+ preserveAfterPrint: false,
59
+ onPrintError: (_, error) =>
60
+ showSnackbar({ title: t('printError', 'Error printing invoice'), kind: 'error', subtitle: error.message }),
59
61
  });
60
62
 
61
63
  useEffect(() => {
@@ -110,7 +112,7 @@ const Invoice: React.FC = () => {
110
112
  <div>
111
113
  <Button
112
114
  disabled={isPrinting}
113
- onClick={handlePrint}
115
+ onClick={() => handlePrint(reactToPrintContent)}
114
116
  renderIcon={(props) => <Printer size={24} {...props} />}
115
117
  iconDescription="Print bill"
116
118
  size="md">
@@ -78,16 +78,19 @@
78
78
  color: colors.$cool-gray-90;
79
79
  }
80
80
 
81
- @media screen {
82
- .printContainer {
83
- background-color: colors.$white;
84
- display: none;
81
+ .printContainer {
82
+ background-color: colors.$white;
83
+ display: none;
84
+
85
+ @media print {
86
+ display: block !important;
85
87
  }
86
88
  }
87
89
 
88
90
  @media print {
89
91
  html,
90
92
  body {
93
+ display: block !important;
91
94
  background-color: colors.$white !important;
92
95
  }
93
96
  }
@@ -1,24 +1,21 @@
1
1
  import React from 'react';
2
- import { screen, render } from '@testing-library/react';
3
2
  import userEvent from '@testing-library/user-event';
3
+ import { screen, render } from '@testing-library/react';
4
4
  import { useReactToPrint } from 'react-to-print';
5
+ import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
6
+ import { configSchema, type BillingConfig } from '../config-schema';
5
7
  import { mockBill } from '../../__mocks__/bills.mock';
6
8
  import { useBill, processBillPayment } from '../billing.resource';
7
9
  import { usePaymentModes } from './payments/payment.resource';
8
10
  import Invoice from './invoice.component';
9
11
 
12
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
13
+
10
14
  // Mock convertToCurrency
11
15
  jest.mock('../helpers/functions', () => ({
12
16
  convertToCurrency: jest.fn((amount) => `USD ${amount}`),
13
17
  }));
14
18
 
15
- // Mock i18next
16
- jest.mock('react-i18next', () => ({
17
- useTranslation: () => ({
18
- t: (key: string) => key,
19
- }),
20
- }));
21
-
22
19
  // Set window.i18next
23
20
  window.i18next = {
24
21
  language: 'en',
@@ -78,38 +75,6 @@ jest.mock('react-to-print', () => ({
78
75
  useReactToPrint: jest.fn(),
79
76
  }));
80
77
 
81
- // Mock OpenMRS framework
82
- jest.mock('@openmrs/esm-framework', () => ({
83
- showSnackbar: jest.fn(),
84
- useLayoutType: jest.fn(() => 'desktop'),
85
- isDesktop: jest.fn(() => true),
86
- useConfig: jest.fn(() => ({
87
- defaultCurrency: 'USD',
88
- })),
89
- formatDate: jest.fn((date) => date?.toString() ?? ''),
90
- ExtensionSlot: jest.fn(({ children }) => <div data-testid="extension-slot">{children}</div>),
91
- usePatient: jest.fn().mockReturnValue({
92
- patient: {
93
- id: 'b2fcf02b-7ee3-4d16-a48f-576be2b103aa',
94
- name: [{ given: ['John'], family: 'Doe' }],
95
- },
96
- patientUuid: 'b2fcf02b-7ee3-4d16-a48f-576be2b103aa',
97
- isLoading: false,
98
- error: null,
99
- }),
100
- createGlobalStore: jest.fn(),
101
- getGlobalStore: jest.fn(() => ({
102
- subscribe: jest.fn(),
103
- getState: jest.fn(),
104
- setState: jest.fn(),
105
- })),
106
- }));
107
-
108
- // Mock patient common lib
109
- jest.mock('@openmrs/esm-patient-common-lib', () => ({
110
- ErrorState: jest.fn(({ error }) => <div data-testid="error-state">Error: {error?.message || error}</div>),
111
- }));
112
-
113
78
  describe('Invoice', () => {
114
79
  const mockedBill = useBill as jest.Mock;
115
80
  const mockedProcessBillPayment = processBillPayment as jest.Mock;
@@ -154,15 +119,13 @@ describe('Invoice', () => {
154
119
  mutate: jest.fn(),
155
120
  });
156
121
 
122
+ mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
123
+
157
124
  // Setup print handler mock
158
125
  const printHandler = jest.fn();
159
126
  mockedUseReactToPrint.mockReturnValue(printHandler);
160
127
  });
161
128
 
162
- afterEach(() => {
163
- jest.clearAllMocks();
164
- });
165
-
166
129
  it('should render error state correctly', () => {
167
130
  mockedBill.mockReturnValue({
168
131
  bill: null,
@@ -173,8 +136,8 @@ describe('Invoice', () => {
173
136
  });
174
137
 
175
138
  render(<Invoice />);
176
- expect(screen.getByTestId('error-state')).toBeInTheDocument();
177
- expect(screen.getByText(/Test error/i)).toBeInTheDocument();
139
+ expect(screen.getByText(/Invoice error/i)).toBeInTheDocument();
140
+ expect(screen.getByText(/Error/)).toBeInTheDocument();
178
141
  });
179
142
 
180
143
  it('should render invoice details correctly', () => {
@@ -265,7 +228,9 @@ describe('Invoice', () => {
265
228
 
266
229
  it('should show patient information correctly', () => {
267
230
  render(<Invoice />);
268
- expect(screen.getByTestId('extension-slot')).toBeInTheDocument();
231
+ // Check that the invoice details are rendered
232
+ expect(screen.getByText('Invoice Number')).toBeInTheDocument();
233
+ expect(screen.getByText('RCPT-001')).toBeInTheDocument();
269
234
  });
270
235
 
271
236
  // Add more test cases as needed for specific features or edge cases
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen, waitFor } from '@testing-library/react';
3
4
  import { FormProvider, useForm } from 'react-hook-form';
4
5
  import type { PaymentFormValue } from '../payments.component';
5
6
  import PaymentForm from './payment-form.component';
6
7
 
7
- // Mock the payment resource
8
8
  jest.mock('../payment.resource', () => ({
9
9
  usePaymentModes: jest.fn(),
10
10
  }));
@@ -21,10 +21,6 @@ const Wrapper: React.FC<WrapperProps> = ({ children }) => {
21
21
  };
22
22
 
23
23
  describe('PaymentForm Component', () => {
24
- beforeEach(() => {
25
- jest.clearAllMocks();
26
- });
27
-
28
24
  test('should render skeleton while loading payment modes', () => {
29
25
  usePaymentModes.mockReturnValue({
30
26
  paymentModes: [],
@@ -96,7 +92,8 @@ describe('PaymentForm Component', () => {
96
92
  expect(screen.getByPlaceholderText(/enter amount/i)).toBeInTheDocument();
97
93
  });
98
94
 
99
- test('should append a payment field when add payment option button is clicked', () => {
95
+ test('should append a payment field when add payment option button is clicked', async () => {
96
+ const user = userEvent.setup();
100
97
  usePaymentModes.mockReturnValue({
101
98
  paymentModes: [{ uuid: '1', name: 'Credit Card' }],
102
99
  isLoading: false,
@@ -116,7 +113,7 @@ describe('PaymentForm Component', () => {
116
113
  );
117
114
 
118
115
  const addButton = screen.getByText(/add payment option/i);
119
- fireEvent.click(addButton);
116
+ await user.click(addButton);
120
117
  const paymentMethodElements = screen.getAllByLabelText(/payment method/i);
121
118
  expect(paymentMethodElements).toHaveLength(2);
122
119
  });
@@ -144,6 +141,7 @@ describe('PaymentForm Component', () => {
144
141
  });
145
142
 
146
143
  test('should remove payment field when trash can icon is clicked', async () => {
144
+ const user = userEvent.setup();
147
145
  usePaymentModes.mockReturnValue({
148
146
  paymentModes: [{ uuid: '1', name: 'Credit Card' }],
149
147
  isLoading: false,
@@ -162,10 +160,10 @@ describe('PaymentForm Component', () => {
162
160
  </Wrapper>,
163
161
  );
164
162
 
165
- fireEvent.click(screen.getByText(/add payment option/i));
163
+ await user.click(screen.getByText(/add payment option/i));
166
164
 
167
165
  const trashCanIcon = screen.getByTestId('trash-can-icon');
168
- fireEvent.click(trashCanIcon);
166
+ await user.click(trashCanIcon);
169
167
 
170
168
  await waitFor(() => {
171
169
  expect(screen.queryByPlaceholderText(/enter amount/i)).not.toBeInTheDocument();
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { DataTable, Table, TableHead, TableRow, TableHeader, TableBody, TableCell } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
3
4
  import { type MappedBill } from '../../../types';
4
5
  import { formatDate, useConfig } from '@openmrs/esm-framework';
5
6
  import { convertToCurrency } from '../../../helpers';
@@ -9,23 +10,24 @@ type PaymentHistoryProps = {
9
10
  };
10
11
 
11
12
  const PaymentHistory: React.FC<PaymentHistoryProps> = ({ bill }) => {
13
+ const { t } = useTranslation();
12
14
  const { defaultCurrency } = useConfig();
13
15
  const headers = [
14
16
  {
15
17
  key: 'dateCreated',
16
- header: 'Date of payment',
18
+ header: t('dateOfPayment', 'Date of payment'),
17
19
  },
18
20
  {
19
21
  key: 'amount',
20
- header: 'Bill amount',
22
+ header: t('billAmount', 'Bill amount'),
21
23
  },
22
24
  {
23
25
  key: 'amountTendered',
24
- header: 'Amount tendered',
26
+ header: t('amountTendered', 'Amount tendered'),
25
27
  },
26
28
  {
27
29
  key: 'paymentMethod',
28
- header: 'Payment method',
30
+ header: t('paymentMethod', 'Payment method'),
29
31
  },
30
32
  ];
31
33
  const rows = bill?.payments?.map((payment, index) => {
@@ -1,24 +1,19 @@
1
1
  import React from 'react';
2
2
  import { render, screen } from '@testing-library/react';
3
- import { useConfig } from '@openmrs/esm-framework';
4
- import PaymentHistory from './payment-history.component';
3
+ import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
4
+ import { type BillingConfig, configSchema } from '../../../config-schema';
5
5
  import { type MappedBill } from '../../../types';
6
-
7
- // Mocking useConfig to return a default currency
8
- jest.mock('@openmrs/esm-framework', () => ({
9
- useConfig: jest.fn(),
10
- formatDate: jest.fn((date) => date.toISOString().split('T')[0]),
11
- }));
6
+ import PaymentHistory from './payment-history.component';
12
7
 
13
8
  jest.mock('../../../helpers', () => ({
14
9
  convertToCurrency: jest.fn((amount, currency) => `${currency} ${amount.toFixed(2)}`),
15
10
  }));
16
11
 
12
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
13
+
17
14
  describe('PaymentHistory Component', () => {
18
15
  beforeEach(() => {
19
- (useConfig as jest.Mock).mockReturnValue({
20
- defaultCurrency: 'USD',
21
- });
16
+ mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
22
17
  });
23
18
 
24
19
  const mockBill: MappedBill = {
@@ -129,12 +124,12 @@ describe('PaymentHistory Component', () => {
129
124
  test('renders correct data in the rows', () => {
130
125
  render(<PaymentHistory bill={mockBill} />);
131
126
 
132
- expect(screen.getByText('2023-09-01')).toBeInTheDocument();
127
+ expect(screen.getByText('01-Sept-2023')).toBeInTheDocument();
133
128
  expect(screen.getByText('USD 80.00')).toBeInTheDocument();
134
129
  expect(screen.getByText('USD 100.00')).toBeInTheDocument();
135
130
  expect(screen.getByText('Credit Card')).toBeInTheDocument();
136
131
 
137
- expect(screen.getByText('2023-09-05')).toBeInTheDocument();
132
+ expect(screen.getByText('05-Sept-2023')).toBeInTheDocument();
138
133
  expect(screen.getByText('USD 180.00')).toBeInTheDocument();
139
134
  expect(screen.getByText('USD 200.00')).toBeInTheDocument();
140
135
  expect(screen.getByText('Cash')).toBeInTheDocument();
@@ -153,7 +148,7 @@ describe('PaymentHistory Component', () => {
153
148
  test('formats dates and converts amounts correctly', () => {
154
149
  render(<PaymentHistory bill={mockBill} />);
155
150
 
156
- expect(screen.getByText('2023-09-01')).toBeInTheDocument();
151
+ expect(screen.getByText('01-Sept-2023')).toBeInTheDocument();
157
152
  expect(screen.getByText('USD 80.00')).toBeInTheDocument();
158
153
  expect(screen.getByText('USD 100.00')).toBeInTheDocument();
159
154
  });
@@ -1,12 +1,21 @@
1
1
  import React from 'react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { render, screen } from '@testing-library/react';
4
- import { useVisit, useConfig, navigate } from '@openmrs/esm-framework';
4
+ import {
5
+ useVisit,
6
+ useConfig,
7
+ navigate,
8
+ getDefaultsFromConfigSchema,
9
+ type VisitReturnType,
10
+ } from '@openmrs/esm-framework';
5
11
  import { useBillableServices } from '../../billable-services/billable-service.resource';
6
12
  import { type MappedBill, type LineItem } from '../../types';
13
+ import { configSchema, type BillingConfig } from '../../config-schema';
7
14
  import Payments from './payments.component';
8
15
 
9
- // Add this mock for currency formatting
16
+ const mockUseVisit = jest.mocked(useVisit);
17
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
18
+ const mockUseBillableServices = jest.mocked(useBillableServices);
10
19
  const mockFormatToParts = jest.fn().mockReturnValue([{ type: 'integer', value: '1000' }]);
11
20
  const mockFormat = jest.fn().mockReturnValue('$1000.00');
12
21
  global.Intl.NumberFormat = jest.fn().mockImplementation(() => ({
@@ -87,10 +96,15 @@ describe('Payments', () => {
87
96
  const mockSelectedLineItems: LineItem[] = [];
88
97
 
89
98
  beforeEach(() => {
90
- jest.clearAllMocks();
91
- (useVisit as jest.Mock).mockReturnValue({ currentVisit: null });
92
- (useConfig as jest.Mock).mockReturnValue({ defaultCurrency: 'USD' });
93
- (useBillableServices as jest.Mock).mockReturnValue({ billableServices: [], isLoading: false });
99
+ mockUseVisit.mockReturnValue({ currentVisit: null } as unknown as VisitReturnType);
100
+ mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
101
+ mockUseBillableServices.mockReturnValue({
102
+ billableServices: [],
103
+ isLoading: false,
104
+ isValidating: false,
105
+ error: null,
106
+ mutate: jest.fn(),
107
+ });
94
108
  });
95
109
 
96
110
  it('renders payment form and history', () => {
@@ -2,8 +2,8 @@ import React, { useState } from 'react';
2
2
  import { Button } from '@carbon/react';
3
3
  import { Printer } from '@carbon/react/icons';
4
4
  import { useTranslation } from 'react-i18next';
5
- import styles from './print-receipt.scss';
6
5
  import { apiBasePath } from '../../constants';
6
+ import styles from './print-receipt.scss';
7
7
 
8
8
  interface PrintReceiptProps {
9
9
  billId: number;
@@ -1,50 +1,39 @@
1
1
  import React from 'react';
2
- import { render, screen, fireEvent } from '@testing-library/react';
3
- import { useTranslation } from 'react-i18next';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/react';
4
4
  import PrintReceipt from './print-receipt.component';
5
5
 
6
- jest.mock('react-i18next', () => ({
7
- useTranslation: jest.fn(),
8
- }));
9
-
10
- jest.mock('@carbon/react/icons', () => ({
11
- Printer: jest.fn(() => <div data-testid="printer-icon" />),
12
- }));
13
-
14
6
  describe('PrintReceipt', () => {
15
- const mockT = jest.fn((key) => key);
16
-
17
7
  beforeEach(() => {
18
- (useTranslation as jest.Mock).mockReturnValue({ t: mockT });
19
- jest.useFakeTimers();
20
8
  window.URL.createObjectURL = jest.fn();
21
9
  });
22
10
 
23
- afterEach(() => {
24
- jest.useRealTimers();
25
- });
26
-
27
11
  it('renders button with correct text and icon', () => {
28
12
  render(<PrintReceipt billId={123} />);
29
- expect(screen.getByText('printReceipt')).toBeInTheDocument();
30
- expect(screen.getByTestId('printer-icon')).toBeInTheDocument();
13
+ expect(screen.getByText('Print receipt')).toBeInTheDocument();
31
14
  });
32
15
 
33
- it('displays "Loading" and disables button when isRedirecting is true', () => {
16
+ it('displays "Loading" and disables button when isRedirecting is true', async () => {
17
+ const user = userEvent.setup();
18
+
34
19
  render(<PrintReceipt billId={123} />);
20
+
35
21
  const button = screen.getByRole('button');
36
- fireEvent.click(button);
37
- expect(screen.getByText('loading')).toBeInTheDocument();
22
+
23
+ await user.click(button);
24
+ expect(screen.getByText(/loading/i)).toBeInTheDocument();
38
25
  expect(button).toBeDisabled();
39
26
  });
40
27
 
41
- it('applies correct CSS class to button', () => {
28
+ it('applies correct CSS class to button', async () => {
29
+ const user = userEvent.setup();
30
+
42
31
  render(<PrintReceipt billId={123} />);
43
32
  expect(screen.getByRole('button')).toHaveClass('button');
44
33
  });
45
34
 
46
35
  it('translates button text correctly', () => {
47
36
  render(<PrintReceipt billId={123} />);
48
- expect(mockT).toHaveBeenCalledWith('printReceipt', 'Print receipt');
37
+ expect(screen.getByText('Print receipt')).toBeInTheDocument();
49
38
  });
50
39
  });
@@ -3,9 +3,9 @@ import { useDefaultFacility } from '../../billing.resource';
3
3
  import styles from './printable-footer.scss';
4
4
 
5
5
  const PrintableFooter = () => {
6
- const { data, isLoading } = useDefaultFacility();
6
+ const { data } = useDefaultFacility();
7
7
 
8
- if (isLoading) {
8
+ if (!data) {
9
9
  return <div>--</div>;
10
10
  }
11
11