@openmrs/esm-billing-app 1.0.2-pre.78 → 1.0.2-pre.786

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 (203) hide show
  1. package/.eslintrc +16 -2
  2. package/README.md +54 -9
  3. package/__mocks__/bills.mock.ts +12 -0
  4. package/__mocks__/react-i18next.js +6 -5
  5. package/dist/1119.js +1 -1
  6. package/dist/1146.js +1 -2
  7. package/dist/1146.js.map +1 -1
  8. package/dist/1197.js +1 -1
  9. package/dist/1856.js +1 -0
  10. package/dist/1856.js.map +1 -0
  11. package/dist/2146.js +1 -1
  12. package/dist/2177.js +2 -0
  13. package/dist/2177.js.LICENSE.txt +9 -0
  14. package/dist/2177.js.map +1 -0
  15. package/dist/2524.js +1 -0
  16. package/dist/2524.js.map +1 -0
  17. package/dist/2690.js +1 -1
  18. package/dist/3041.js +1 -0
  19. package/dist/3041.js.map +1 -0
  20. package/dist/3099.js +1 -1
  21. package/dist/3584.js +1 -1
  22. package/dist/4055.js +1 -1
  23. package/dist/4132.js +1 -1
  24. package/dist/4225.js +1 -0
  25. package/dist/4225.js.map +1 -0
  26. package/dist/4300.js +1 -1
  27. package/dist/4335.js +1 -1
  28. package/dist/4618.js +1 -1
  29. package/dist/4652.js +1 -1
  30. package/dist/4724.js +1 -0
  31. package/dist/4724.js.map +1 -0
  32. package/dist/4739.js +1 -1
  33. package/dist/4739.js.map +1 -1
  34. package/dist/4944.js +1 -1
  35. package/dist/5173.js +1 -1
  36. package/dist/5241.js +1 -1
  37. package/dist/5422.js +1 -0
  38. package/dist/5422.js.map +1 -0
  39. package/dist/5442.js +1 -1
  40. package/dist/5661.js +1 -1
  41. package/dist/6022.js +1 -1
  42. package/dist/6468.js +1 -1
  43. package/dist/6540.js +1 -1
  44. package/dist/6540.js.map +1 -1
  45. package/dist/6606.js +1 -0
  46. package/dist/6606.js.map +1 -0
  47. package/dist/6679.js +1 -1
  48. package/dist/6840.js +1 -1
  49. package/dist/6859.js +1 -1
  50. package/dist/7097.js +1 -1
  51. package/dist/7159.js +1 -1
  52. package/dist/723.js +1 -1
  53. package/dist/7452.js +2 -0
  54. package/dist/7452.js.map +1 -0
  55. package/dist/7617.js +1 -1
  56. package/dist/795.js +1 -1
  57. package/dist/8163.js +1 -1
  58. package/dist/8349.js +1 -1
  59. package/dist/8618.js +1 -1
  60. package/dist/890.js +1 -1
  61. package/dist/8930.js +2 -0
  62. package/dist/{6525.js.LICENSE.txt → 8930.js.LICENSE.txt} +16 -4
  63. package/dist/8930.js.map +1 -0
  64. package/dist/9214.js +1 -1
  65. package/dist/942.js +1 -0
  66. package/dist/942.js.map +1 -0
  67. package/dist/9538.js +1 -1
  68. package/dist/9569.js +1 -1
  69. package/dist/961.js +1 -1
  70. package/dist/961.js.map +1 -1
  71. package/dist/986.js +1 -1
  72. package/dist/9879.js +1 -1
  73. package/dist/9895.js +1 -1
  74. package/dist/9900.js +1 -1
  75. package/dist/9913.js +1 -1
  76. package/dist/main.js +1 -1
  77. package/dist/main.js.map +1 -1
  78. package/dist/openmrs-esm-billing-app.js +1 -1
  79. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +368 -262
  80. package/dist/openmrs-esm-billing-app.js.map +1 -1
  81. package/dist/routes.json +1 -1
  82. package/e2e/README.md +19 -18
  83. package/e2e/core/test.ts +1 -1
  84. package/e2e/fixtures/api.ts +1 -1
  85. package/e2e/specs/sample-test.spec.ts +0 -1
  86. package/e2e/support/github/Dockerfile +1 -1
  87. package/package.json +13 -10
  88. package/src/bill-history/bill-history.component.tsx +17 -25
  89. package/src/bill-history/bill-history.scss +4 -94
  90. package/src/bill-history/bill-history.test.tsx +37 -78
  91. package/src/bill-item-actions/bill-item-actions.scss +0 -4
  92. package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +100 -78
  93. package/src/bill-item-actions/edit-bill-item.test.tsx +116 -31
  94. package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
  95. package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
  96. package/src/billable-services/billable-service.resource.ts +17 -9
  97. package/src/billable-services/billable-services-home.component.tsx +1 -1
  98. package/src/billable-services/billable-services.component.tsx +142 -145
  99. package/src/billable-services/billable-services.scss +3 -0
  100. package/src/billable-services/billable-services.test.tsx +2 -45
  101. package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
  102. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +18 -192
  103. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  104. package/src/billable-services/create-edit/add-billable-service.component.tsx +345 -298
  105. package/src/billable-services/create-edit/add-billable-service.scss +5 -6
  106. package/src/billable-services/create-edit/add-billable-service.test.tsx +37 -36
  107. package/src/billable-services/create-edit/edit-billable-service.modal.tsx +51 -0
  108. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  109. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
  110. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  111. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  112. package/src/billing-form/billing-checkin-form.component.tsx +2 -3
  113. package/src/billing-form/billing-checkin-form.test.tsx +97 -24
  114. package/src/billing-form/billing-form.component.tsx +216 -269
  115. package/src/billing-form/billing-form.scss +143 -0
  116. package/src/billing.resource.ts +16 -19
  117. package/src/bills-table/bills-table.test.tsx +98 -54
  118. package/src/config-schema.ts +52 -24
  119. package/src/dashboard.meta.ts +4 -2
  120. package/src/helpers/functions.ts +5 -4
  121. package/src/index.ts +17 -6
  122. package/src/invoice/invoice-table.component.tsx +35 -69
  123. package/src/invoice/invoice-table.scss +1 -5
  124. package/src/invoice/invoice-table.test.tsx +273 -62
  125. package/src/invoice/invoice.component.tsx +36 -29
  126. package/src/invoice/invoice.scss +7 -4
  127. package/src/invoice/invoice.test.tsx +324 -120
  128. package/src/invoice/payments/payment-form/payment-form.component.tsx +31 -29
  129. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  130. package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
  131. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  132. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  133. package/src/invoice/payments/payments.component.tsx +53 -65
  134. package/src/invoice/payments/payments.test.tsx +282 -0
  135. package/src/invoice/payments/utils.ts +5 -23
  136. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  137. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  138. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  139. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  140. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  141. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  142. package/src/invoice/printable-invoice/printable-invoice.component.tsx +19 -33
  143. package/src/left-panel-link.test.tsx +1 -4
  144. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  145. package/src/modal/require-payment-modal.test.tsx +27 -22
  146. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
  147. package/src/routes.json +22 -2
  148. package/src/types/index.ts +26 -17
  149. package/translations/am.json +70 -21
  150. package/translations/ar.json +70 -21
  151. package/translations/ar_SY.json +70 -21
  152. package/translations/bn.json +75 -26
  153. package/translations/de.json +70 -21
  154. package/translations/en.json +70 -21
  155. package/translations/en_US.json +70 -21
  156. package/translations/es.json +70 -21
  157. package/translations/es_MX.json +70 -21
  158. package/translations/fr.json +83 -34
  159. package/translations/he.json +70 -21
  160. package/translations/hi.json +70 -21
  161. package/translations/hi_IN.json +70 -21
  162. package/translations/id.json +70 -21
  163. package/translations/it.json +105 -56
  164. package/translations/ka.json +70 -21
  165. package/translations/km.json +70 -21
  166. package/translations/ku.json +70 -21
  167. package/translations/ky.json +70 -21
  168. package/translations/lg.json +70 -21
  169. package/translations/ne.json +70 -21
  170. package/translations/pl.json +70 -21
  171. package/translations/pt.json +70 -21
  172. package/translations/pt_BR.json +70 -21
  173. package/translations/qu.json +70 -21
  174. package/translations/ro_RO.json +214 -165
  175. package/translations/ru_RU.json +70 -21
  176. package/translations/si.json +70 -21
  177. package/translations/sw.json +70 -21
  178. package/translations/sw_KE.json +70 -21
  179. package/translations/tr.json +70 -21
  180. package/translations/tr_TR.json +70 -21
  181. package/translations/uk.json +70 -21
  182. package/translations/uz.json +70 -21
  183. package/translations/uz@Latn.json +70 -21
  184. package/translations/uz_UZ.json +70 -21
  185. package/translations/vi.json +70 -21
  186. package/translations/zh.json +70 -21
  187. package/translations/zh_CN.json +125 -76
  188. package/dist/1146.js.LICENSE.txt +0 -21
  189. package/dist/2352.js +0 -1
  190. package/dist/2352.js.map +0 -1
  191. package/dist/246.js +0 -1
  192. package/dist/246.js.map +0 -1
  193. package/dist/6525.js +0 -2
  194. package/dist/6525.js.map +0 -1
  195. package/dist/8556.js +0 -2
  196. package/dist/8556.js.map +0 -1
  197. package/dist/8638.js +0 -1
  198. package/dist/8638.js.map +0 -1
  199. package/dist/9968.js +0 -1
  200. package/dist/9968.js.map +0 -1
  201. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  202. package/src/invoice/payments/payments.component.test.tsx +0 -121
  203. /package/dist/{8556.js.LICENSE.txt → 7452.js.LICENSE.txt} +0 -0
@@ -1,71 +1,51 @@
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 { render, screen, waitFor } from '@testing-library/react';
4
4
  import { useReactToPrint } from 'react-to-print';
5
- import { mockBill } from '../../__mocks__/bills.mock';
6
- import { useBill, processBillPayment } from '../billing.resource';
5
+ import { getDefaultsFromConfigSchema, useConfig, usePatient } from '@openmrs/esm-framework';
6
+ import { configSchema, type BillingConfig } from '../config-schema';
7
+ import { mockBill, mockPatient } from '../../__mocks__/bills.mock';
8
+ import { useBill } from '../billing.resource';
7
9
  import { usePaymentModes } from './payments/payment.resource';
8
10
  import Invoice from './invoice.component';
9
11
 
10
- // Mock convertToCurrency
12
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
13
+ const mockUseBill = jest.mocked(useBill);
14
+ const mockUsePatient = jest.mocked(usePatient);
15
+ const mockUsePaymentModes = jest.mocked(usePaymentModes);
16
+ const mockUseReactToPrint = jest.mocked(useReactToPrint);
17
+
11
18
  jest.mock('../helpers/functions', () => ({
12
19
  convertToCurrency: jest.fn((amount) => `USD ${amount}`),
13
20
  }));
14
21
 
15
- // Mock i18next
16
- jest.mock('react-i18next', () => ({
17
- useTranslation: () => ({
18
- t: (key: string) => key,
19
- }),
20
- }));
21
-
22
- // Set window.i18next
23
22
  window.i18next = {
24
23
  language: 'en',
25
24
  } as any;
26
25
 
27
- // Mock InvoiceTable component
28
- jest.mock('./invoice-table.component', () =>
29
- jest.fn(({ bill }) => <div data-testid="mock-invoice-table">Invoice Table Mock</div>),
30
- );
31
-
32
- // Mock payments component
33
- jest.mock('./payments/payments.component', () =>
34
- jest.fn(({ bill, mutate, selectedLineItems }) => (
35
- <div data-testid="mock-payments">
36
- <h2>Payments</h2>
37
- <button>Add payment option</button>
38
- </div>
39
- )),
40
- );
41
-
42
- // Mock PrintReceipt component
43
26
  jest.mock('./printable-invoice/print-receipt.component', () =>
44
- jest.fn(({ billId }) => <div data-testid="mock-print-receipt">Print Receipt Mock</div>),
27
+ jest.fn(() => <div data-testid="mock-print-receipt">Print Receipt Mock</div>),
45
28
  );
46
29
 
47
- // Mock PrintableInvoice component
48
30
  jest.mock('./printable-invoice/printable-invoice.component', () =>
49
- jest.fn(({ bill, patient }) => <div data-testid="mock-printable-invoice">Printable Invoice Mock</div>),
31
+ jest.fn(() => <div data-testid="mock-printable-invoice">Printable Invoice Mock</div>),
50
32
  );
51
33
 
52
- // Mock payment resource
53
34
  jest.mock('./payments/payment.resource', () => ({
54
35
  usePaymentModes: jest.fn(),
55
36
  updateBillVisitAttribute: jest.fn(),
56
37
  }));
57
38
 
58
- // Mock billing resource
59
39
  jest.mock('../billing.resource', () => ({
60
40
  useBill: jest.fn(),
61
- processBillPayment: jest.fn(),
62
41
  useDefaultFacility: jest.fn().mockReturnValue({
63
- uuid: '54065383-b4d4-42d2-af4d-d250a1fd2590',
64
- display: 'MTRH',
42
+ data: {
43
+ uuid: '54065383-b4d4-42d2-af4d-d250a1fd2590',
44
+ display: 'MTRH',
45
+ },
65
46
  }),
66
47
  }));
67
48
 
68
- // Mock react-router-dom
69
49
  jest.mock('react-router-dom', () => ({
70
50
  useParams: jest.fn().mockReturnValue({
71
51
  patientUuid: 'patientUuid',
@@ -73,49 +53,11 @@ jest.mock('react-router-dom', () => ({
73
53
  }),
74
54
  }));
75
55
 
76
- // Mock react-to-print
77
56
  jest.mock('react-to-print', () => ({
78
57
  useReactToPrint: jest.fn(),
79
58
  }));
80
59
 
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
60
  describe('Invoice', () => {
114
- const mockedBill = useBill as jest.Mock;
115
- const mockedProcessBillPayment = processBillPayment as jest.Mock;
116
- const mockedUsePaymentModes = usePaymentModes as jest.Mock;
117
- const mockedUseReactToPrint = useReactToPrint as jest.Mock;
118
-
119
61
  const defaultBillData = {
120
62
  ...mockBill,
121
63
  uuid: 'test-uuid',
@@ -131,12 +73,20 @@ describe('Invoice', () => {
131
73
  quantity: 1,
132
74
  price: 1000,
133
75
  paymentStatus: 'PENDING',
76
+ billableService: 'Test Service',
77
+ display: '',
78
+ voided: false,
79
+ voidReason: '',
80
+ priceName: '',
81
+ priceUuid: '',
82
+ lineItemOrder: 0,
83
+ resourceVersion: '',
134
84
  },
135
85
  ],
136
86
  };
137
87
 
138
88
  beforeEach(() => {
139
- mockedBill.mockReturnValue({
89
+ mockUseBill.mockReturnValue({
140
90
  bill: defaultBillData,
141
91
  isLoading: false,
142
92
  error: null,
@@ -144,7 +94,14 @@ describe('Invoice', () => {
144
94
  mutate: jest.fn(),
145
95
  });
146
96
 
147
- mockedUsePaymentModes.mockReturnValue({
97
+ mockUsePatient.mockReturnValue({
98
+ patient: mockPatient as any,
99
+ isLoading: false,
100
+ error: null,
101
+ patientUuid: 'patientUuid',
102
+ });
103
+
104
+ mockUsePaymentModes.mockReturnValue({
148
105
  paymentModes: [
149
106
  { uuid: 'cash-uuid', name: 'Cash', description: 'Cash Method', retired: false },
150
107
  { uuid: 'mpesa-uuid', name: 'MPESA', description: 'MPESA Method', retired: false },
@@ -154,46 +111,77 @@ describe('Invoice', () => {
154
111
  mutate: jest.fn(),
155
112
  });
156
113
 
157
- // Setup print handler mock
114
+ mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
115
+
158
116
  const printHandler = jest.fn();
159
- mockedUseReactToPrint.mockReturnValue(printHandler);
117
+ mockUseReactToPrint.mockReturnValue(printHandler);
118
+ });
119
+
120
+ it('should render loading state when bill is loading', () => {
121
+ mockUseBill.mockReturnValue({
122
+ bill: null,
123
+ isLoading: true,
124
+ error: null,
125
+ isValidating: false,
126
+ mutate: jest.fn(),
127
+ });
128
+
129
+ render(<Invoice />);
130
+ expect(screen.getByText(/loading bill information/i)).toBeInTheDocument();
160
131
  });
161
132
 
162
- afterEach(() => {
163
- jest.clearAllMocks();
133
+ it('should render loading state when patient is loading', () => {
134
+ mockUsePatient.mockReturnValue({
135
+ patient: null as any,
136
+ isLoading: true,
137
+ error: null,
138
+ patientUuid: 'patientUuid',
139
+ });
140
+
141
+ render(<Invoice />);
142
+ expect(screen.getByText(/loading bill information/i)).toBeInTheDocument();
164
143
  });
165
144
 
166
- it('should render error state correctly', () => {
167
- mockedBill.mockReturnValue({
145
+ it('should render error state when bill fails to load', () => {
146
+ mockUseBill.mockReturnValue({
168
147
  bill: null,
169
148
  isLoading: false,
170
- error: new Error('Test error'),
149
+ error: new Error('Failed to load bill'),
171
150
  isValidating: false,
172
151
  mutate: jest.fn(),
173
152
  });
174
153
 
175
154
  render(<Invoice />);
176
- expect(screen.getByTestId('error-state')).toBeInTheDocument();
177
- expect(screen.getByText(/Test error/i)).toBeInTheDocument();
155
+ expect(screen.getByText(/invoice error/i)).toBeInTheDocument();
178
156
  });
179
157
 
180
158
  it('should render invoice details correctly', () => {
181
159
  render(<Invoice />);
182
160
 
183
- // Check invoice details
184
- expect(screen.getByText(/Total Amount/i)).toBeInTheDocument();
185
- expect(screen.getByText(/Amount Tendered/i)).toBeInTheDocument();
186
- expect(screen.getByText(/Invoice Number/i)).toBeInTheDocument();
187
- expect(screen.getByText(/Date And Time/i)).toBeInTheDocument();
188
- expect(screen.getByText(/Invoice Status/i)).toBeInTheDocument();
161
+ expect(screen.getAllByText(/total amount/i).length).toBeGreaterThan(0);
162
+ expect(screen.getAllByText(/amount tendered/i).length).toBeGreaterThan(0);
163
+ expect(screen.getByText(/invoice number/i)).toBeInTheDocument();
164
+ expect(screen.getByText(/date and time/i)).toBeInTheDocument();
165
+ expect(screen.getByText(/invoice status/i)).toBeInTheDocument();
166
+ expect(screen.getAllByText('RCPT-001').length).toBeGreaterThan(0);
167
+ expect(screen.getAllByText('PENDING').length).toBeGreaterThan(0);
168
+ });
189
169
 
190
- // Check mock components
191
- expect(screen.getByTestId('mock-invoice-table')).toBeInTheDocument();
192
- expect(screen.getByTestId('mock-payments')).toBeInTheDocument();
170
+ it('should render invoice table with line items', () => {
171
+ render(<Invoice />);
172
+
173
+ expect(screen.getByText(/line items/i)).toBeInTheDocument();
174
+ expect(screen.getByText('Test Service')).toBeInTheDocument();
175
+ });
176
+
177
+ it('should render payments section', () => {
178
+ render(<Invoice />);
179
+
180
+ expect(screen.getByText(/payments/i)).toBeInTheDocument();
193
181
  });
194
182
 
195
183
  it('should show print receipt button for paid bills', () => {
196
- mockedBill.mockReturnValue({
184
+ mockUseBill.mockReturnValue({
197
185
  bill: {
198
186
  ...defaultBillData,
199
187
  status: 'PAID',
@@ -209,64 +197,280 @@ describe('Invoice', () => {
209
197
  expect(screen.getByTestId('mock-print-receipt')).toBeInTheDocument();
210
198
  });
211
199
 
212
- it('should handle bill payment processing', async () => {
213
- const user = userEvent.setup();
214
- const mockMutate = jest.fn();
215
-
216
- mockedBill.mockReturnValue({
217
- bill: defaultBillData,
200
+ it('should show print receipt button for bills with tendered amount', () => {
201
+ mockUseBill.mockReturnValue({
202
+ bill: {
203
+ ...defaultBillData,
204
+ status: 'PENDING',
205
+ tenderedAmount: 500,
206
+ },
218
207
  isLoading: false,
219
208
  error: null,
220
209
  isValidating: false,
221
- mutate: mockMutate,
210
+ mutate: jest.fn(),
222
211
  });
223
212
 
224
- mockedProcessBillPayment.mockResolvedValue({});
213
+ render(<Invoice />);
214
+ expect(screen.getByTestId('mock-print-receipt')).toBeInTheDocument();
215
+ });
216
+
217
+ it('should not show print receipt button for unpaid bills', () => {
218
+ render(<Invoice />);
219
+ expect(screen.queryByTestId('mock-print-receipt')).not.toBeInTheDocument();
220
+ });
221
+
222
+ it('should handle print button click', async () => {
223
+ const handlePrintMock = jest.fn();
224
+ const user = userEvent.setup();
225
+ mockUseReactToPrint.mockReturnValue(handlePrintMock);
225
226
 
226
227
  render(<Invoice />);
227
228
 
228
- // Add payment flow would go here
229
- // Note: Detailed payment interaction testing should be in the Payments component tests
229
+ const printButton = screen.getByRole('button', { name: /print bill/i });
230
+ await user.click(printButton);
230
231
 
231
- expect(screen.getByText(/Payments/i)).toBeInTheDocument();
232
+ await waitFor(() => {
233
+ expect(handlePrintMock).toHaveBeenCalled();
234
+ });
232
235
  });
233
236
 
234
- it('should update line items when bill data changes', () => {
235
- const { rerender } = render(<Invoice />);
237
+ it('should disable print button while printing', () => {
238
+ render(<Invoice />);
236
239
 
237
- // Update bill with new line items
238
- const updatedBill = {
240
+ const printButton = screen.getByRole('button', { name: /print bill/i });
241
+ expect(printButton).toBeEnabled();
242
+ });
243
+
244
+ it('should render patient header when patient data is available', () => {
245
+ render(<Invoice />);
246
+
247
+ // Patient header is rendered via ExtensionSlot
248
+ expect(screen.getByText(/line items/i)).toBeInTheDocument();
249
+ });
250
+
251
+ it('should search and filter line items in the table', async () => {
252
+ const billWithMultipleItems = {
239
253
  ...defaultBillData,
240
254
  lineItems: [
241
- ...defaultBillData.lineItems,
255
+ {
256
+ uuid: 'item-1',
257
+ item: 'Lab Test',
258
+ quantity: 1,
259
+ price: 500,
260
+ paymentStatus: 'PENDING',
261
+ billableService: 'Lab Test',
262
+ display: '',
263
+ voided: false,
264
+ voidReason: '',
265
+ priceName: '',
266
+ priceUuid: '',
267
+ lineItemOrder: 0,
268
+ resourceVersion: '',
269
+ },
242
270
  {
243
271
  uuid: 'item-2',
244
- item: 'New Service',
272
+ item: 'X-Ray',
245
273
  quantity: 1,
246
274
  price: 500,
247
275
  paymentStatus: 'PENDING',
276
+ billableService: 'X-Ray',
277
+ display: '',
278
+ voided: false,
279
+ voidReason: '',
280
+ priceName: '',
281
+ priceUuid: '',
282
+ lineItemOrder: 1,
283
+ resourceVersion: '',
248
284
  },
249
285
  ],
250
286
  };
251
287
 
252
- mockedBill.mockReturnValue({
253
- bill: updatedBill,
288
+ mockUseBill.mockReturnValue({
289
+ bill: billWithMultipleItems,
254
290
  isLoading: false,
255
291
  error: null,
256
292
  isValidating: false,
257
293
  mutate: jest.fn(),
258
294
  });
259
295
 
296
+ const user = userEvent.setup();
297
+ render(<Invoice />);
298
+
299
+ expect(screen.getByText('Lab Test')).toBeInTheDocument();
300
+ expect(screen.getByText('X-Ray')).toBeInTheDocument();
301
+
302
+ const searchInput = screen.getByPlaceholderText(/search this table/i);
303
+ await user.type(searchInput, 'Lab Test');
304
+
305
+ await waitFor(() => {
306
+ expect(screen.getByText('Lab Test')).toBeInTheDocument();
307
+ expect(screen.queryByText('X-Ray')).not.toBeInTheDocument();
308
+ });
309
+ });
310
+
311
+ it('should handle bill data updates via mutate', () => {
312
+ const mockMutate = jest.fn();
313
+ mockUseBill.mockReturnValue({
314
+ bill: defaultBillData,
315
+ isLoading: false,
316
+ error: null,
317
+ isValidating: false,
318
+ mutate: mockMutate,
319
+ });
320
+
321
+ const { rerender } = render(<Invoice />);
322
+
323
+ const updatedBill = {
324
+ ...defaultBillData,
325
+ status: 'PAID',
326
+ tenderedAmount: 1000,
327
+ };
328
+
329
+ mockUseBill.mockReturnValue({
330
+ bill: updatedBill,
331
+ isLoading: false,
332
+ error: null,
333
+ isValidating: false,
334
+ mutate: mockMutate,
335
+ });
336
+
260
337
  rerender(<Invoice />);
261
338
 
262
- // The mock invoice table should receive updated props
263
- expect(screen.getByTestId('mock-invoice-table')).toBeInTheDocument();
339
+ expect(screen.getByText('PAID')).toBeInTheDocument();
340
+ });
341
+
342
+ it('should display correct currency formatting', () => {
343
+ render(<Invoice />);
344
+
345
+ // convertToCurrency is mocked to return "USD ${amount}"
346
+ expect(screen.getAllByText('USD 1000').length).toBeGreaterThan(0);
347
+ expect(screen.getAllByText('USD 0').length).toBeGreaterThan(0);
348
+ });
349
+
350
+ it('should disable print button when isPrinting state is true', () => {
351
+ // Mock isPrinting state by checking the button's disabled state when loading
352
+ mockUseBill.mockReturnValue({
353
+ bill: defaultBillData,
354
+ isLoading: true,
355
+ error: null,
356
+ isValidating: false,
357
+ mutate: jest.fn(),
358
+ });
359
+
360
+ render(<Invoice />);
361
+ // When bill is loading, component shows loading state, not the button
362
+ expect(screen.getByText(/loading bill information/i)).toBeInTheDocument();
363
+ });
364
+
365
+ it('should not render PrintableInvoice when bill is missing', () => {
366
+ mockUseBill.mockReturnValue({
367
+ bill: null,
368
+ isLoading: false,
369
+ error: null,
370
+ isValidating: false,
371
+ mutate: jest.fn(),
372
+ });
373
+
374
+ mockUsePatient.mockReturnValue({
375
+ patient: mockPatient as any,
376
+ isLoading: false,
377
+ error: null,
378
+ patientUuid: 'patientUuid',
379
+ });
380
+
381
+ render(<Invoice />);
382
+
383
+ // PrintableInvoice should not be rendered when bill is null
384
+ // Since it's in a hidden div, we can't easily assert its absence
385
+ // but we can verify the main content doesn't have the print container
386
+ expect(screen.queryByTestId('mock-printable-invoice')).not.toBeInTheDocument();
387
+ });
388
+
389
+ it('should not render PrintableInvoice when patient is missing', () => {
390
+ mockUsePatient.mockReturnValue({
391
+ patient: null as any,
392
+ isLoading: false,
393
+ error: null,
394
+ patientUuid: 'patientUuid',
395
+ });
396
+
397
+ render(<Invoice />);
398
+
399
+ // PrintableInvoice requires both bill and patient
400
+ expect(screen.queryByTestId('mock-printable-invoice')).not.toBeInTheDocument();
401
+ });
402
+
403
+ it('should render PrintableInvoice when both bill and patient exist', () => {
404
+ render(<Invoice />);
405
+
406
+ // PrintableInvoice should be rendered with both bill and patient
407
+ expect(screen.getByTestId('mock-printable-invoice')).toBeInTheDocument();
408
+ });
409
+
410
+ it('should pass correct props to InvoiceTable', () => {
411
+ render(<Invoice />);
412
+
413
+ // Verify InvoiceTable is rendered with line items
414
+ expect(screen.getByText('Test Service')).toBeInTheDocument();
415
+ expect(screen.getByText(/line items/i)).toBeInTheDocument();
416
+ });
417
+
418
+ it('should pass mutate function to Payments component', () => {
419
+ const mockMutate = jest.fn();
420
+ mockUseBill.mockReturnValue({
421
+ bill: defaultBillData,
422
+ isLoading: false,
423
+ error: null,
424
+ isValidating: false,
425
+ mutate: mockMutate,
426
+ });
427
+
428
+ render(<Invoice />);
429
+
430
+ // Payments component should be rendered
431
+ expect(screen.getByText(/payments/i)).toBeInTheDocument();
432
+ });
433
+
434
+ it('should show print receipt for bills with partial payment', () => {
435
+ mockUseBill.mockReturnValue({
436
+ bill: {
437
+ ...defaultBillData,
438
+ status: 'PENDING',
439
+ totalAmount: 1000,
440
+ tenderedAmount: 500, // Partial payment
441
+ },
442
+ isLoading: false,
443
+ error: null,
444
+ isValidating: false,
445
+ mutate: jest.fn(),
446
+ });
447
+
448
+ render(<Invoice />);
449
+ expect(screen.getByTestId('mock-print-receipt')).toBeInTheDocument();
264
450
  });
265
451
 
266
- it('should show patient information correctly', () => {
452
+ it('should render ExtensionSlot when patient and patientUuid exist', () => {
267
453
  render(<Invoice />);
268
- expect(screen.getByTestId('extension-slot')).toBeInTheDocument();
454
+
455
+ // The component renders, which includes the ExtensionSlot
456
+ // We can verify this indirectly by checking the main content is present
457
+ expect(screen.getByText(/invoice number/i)).toBeInTheDocument();
269
458
  });
270
459
 
271
- // Add more test cases as needed for specific features or edge cases
460
+ it('should not show print receipt for bills with zero tendered amount', () => {
461
+ mockUseBill.mockReturnValue({
462
+ bill: {
463
+ ...defaultBillData,
464
+ status: 'PENDING',
465
+ tenderedAmount: 0,
466
+ },
467
+ isLoading: false,
468
+ error: null,
469
+ isValidating: false,
470
+ mutate: jest.fn(),
471
+ });
472
+
473
+ render(<Invoice />);
474
+ expect(screen.queryByTestId('mock-print-receipt')).not.toBeInTheDocument();
475
+ });
272
476
  });