@openmrs/esm-billing-app 1.0.2-pre.764 → 1.0.2-pre.768
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.
- package/.eslintrc +16 -2
- package/dist/942.js +1 -1
- package/dist/942.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +6 -6
- package/dist/routes.json +1 -1
- package/e2e/core/test.ts +1 -1
- package/e2e/fixtures/api.ts +1 -1
- package/e2e/support/github/Dockerfile +1 -1
- package/package.json +4 -1
- package/src/bills-table/bills-table.test.tsx +1 -1
- package/src/invoice/invoice-table.component.tsx +1 -1
- package/src/invoice/invoice-table.test.tsx +267 -29
- package/src/invoice/invoice.test.tsx +315 -85
- package/src/invoice/payments/payment-form/payment-form.test.tsx +4 -2
- package/src/invoice/payments/payments.test.tsx +2 -2
- package/src/left-panel-link.test.tsx +1 -4
|
@@ -71,9 +71,9 @@
|
|
|
71
71
|
"initial": false,
|
|
72
72
|
"entry": false,
|
|
73
73
|
"recorded": false,
|
|
74
|
-
"size":
|
|
74
|
+
"size": 1174566,
|
|
75
75
|
"sizes": {
|
|
76
|
-
"javascript":
|
|
76
|
+
"javascript": 1174524,
|
|
77
77
|
"consume-shared": 42
|
|
78
78
|
},
|
|
79
79
|
"names": [],
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"auxiliaryFiles": [
|
|
88
88
|
"942.js.map"
|
|
89
89
|
],
|
|
90
|
-
"hash": "
|
|
90
|
+
"hash": "4c3e935e456ec877",
|
|
91
91
|
"childrenByOrder": {}
|
|
92
92
|
},
|
|
93
93
|
{
|
|
@@ -1209,10 +1209,10 @@
|
|
|
1209
1209
|
"initial": true,
|
|
1210
1210
|
"entry": true,
|
|
1211
1211
|
"recorded": false,
|
|
1212
|
-
"size":
|
|
1212
|
+
"size": 5472396,
|
|
1213
1213
|
"sizes": {
|
|
1214
1214
|
"consume-shared": 210,
|
|
1215
|
-
"javascript":
|
|
1215
|
+
"javascript": 5449741,
|
|
1216
1216
|
"share-init": 336,
|
|
1217
1217
|
"runtime": 22109
|
|
1218
1218
|
},
|
|
@@ -1229,7 +1229,7 @@
|
|
|
1229
1229
|
"auxiliaryFiles": [
|
|
1230
1230
|
"main.js.map"
|
|
1231
1231
|
],
|
|
1232
|
-
"hash": "
|
|
1232
|
+
"hash": "5253e749f65b670a",
|
|
1233
1233
|
"childrenByOrder": {}
|
|
1234
1234
|
},
|
|
1235
1235
|
{
|
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.24.0","fhir2":">=1.2"},"pages":[{"component":"billableServicesHome","route":"billable-services"}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"},"featureFlag":"billing"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing history"},"featureFlag":"billing"},{"name":"billable-services-app-menu-item","component":"billableServicesAppMenuItem","slot":"app-menu-item-slot","meta":{"name":"Billable Services"}},{"name":"billing-checkin-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","featureFlag":"billing"},{"slot":"system-admin-page-card-link-slot","component":"billableServicesCardLink","name":"billable-services-admin-card-link"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"billing-home-tiles-ext","slot":"billing-home-tiles-slot","component":"serviceMetrics"},{"name":"edit-bill-line-item-dialog","component":"editBillLineItemModal","online":true,"offline":true}],"modals":[{"name":"add-cash-point-modal","component":"addCashPointModal"},{"name":"add-payment-mode-modal","component":"addPaymentModeModal"},{"name":"delete-payment-mode-modal","component":"deletePaymentModeModal"},{"name":"edit-bill-item-modal","component":"editBillLineItemModal"},{"name":"edit-billable-service-modal","component":"editBillableServiceModal"},{"name":"require-billing-modal","component":"requirePaymentModal"}],"workspaces":[{"name":"billing-form-workspace","title":"billingForm","component":"billingFormWorkspace","type":"form"}],"featureFlags":[{"flagName":"billing","label":"Billing module","description":"This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"}],"version":"1.0.2-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.24.0","fhir2":">=1.2"},"pages":[{"component":"billableServicesHome","route":"billable-services"}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"},"featureFlag":"billing"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing history"},"featureFlag":"billing"},{"name":"billable-services-app-menu-item","component":"billableServicesAppMenuItem","slot":"app-menu-item-slot","meta":{"name":"Billable Services"}},{"name":"billing-checkin-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","featureFlag":"billing"},{"slot":"system-admin-page-card-link-slot","component":"billableServicesCardLink","name":"billable-services-admin-card-link"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"billing-home-tiles-ext","slot":"billing-home-tiles-slot","component":"serviceMetrics"},{"name":"edit-bill-line-item-dialog","component":"editBillLineItemModal","online":true,"offline":true}],"modals":[{"name":"add-cash-point-modal","component":"addCashPointModal"},{"name":"add-payment-mode-modal","component":"addPaymentModeModal"},{"name":"delete-payment-mode-modal","component":"deletePaymentModeModal"},{"name":"edit-bill-item-modal","component":"editBillLineItemModal"},{"name":"edit-billable-service-modal","component":"editBillableServiceModal"},{"name":"require-billing-modal","component":"requirePaymentModal"}],"workspaces":[{"name":"billing-form-workspace","title":"billingForm","component":"billingFormWorkspace","type":"form"}],"featureFlags":[{"flagName":"billing","label":"Billing module","description":"This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"}],"version":"1.0.2-pre.768"}
|
package/e2e/core/test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { APIRequestContext, Page, test as base } from '@playwright/test';
|
|
1
|
+
import { type APIRequestContext, type Page, test as base } from '@playwright/test';
|
|
2
2
|
import { api } from '../fixtures';
|
|
3
3
|
|
|
4
4
|
// This file sets up our custom test harness using the custom fixtures.
|
package/e2e/fixtures/api.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
-
import { APIRequestContext, PlaywrightWorkerArgs, WorkerFixture } from '@playwright/test';
|
|
2
|
+
import { type APIRequestContext, type PlaywrightWorkerArgs, type WorkerFixture } from '@playwright/test';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A fixture which initializes an [`APIRequestContext`](https://playwright.dev/docs/api/class-apirequestcontext)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-billing-app",
|
|
3
|
-
"version": "1.0.2-pre.
|
|
3
|
+
"version": "1.0.2-pre.768",
|
|
4
4
|
"description": "O3 frontend module for handling billing concerns in healthcare settings",
|
|
5
5
|
"browser": "dist/openmrs-esm-billing-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -86,7 +86,10 @@
|
|
|
86
86
|
"dotenv": "^16.4.1",
|
|
87
87
|
"eslint": "^8.56.0",
|
|
88
88
|
"eslint-plugin-import": "^2.31.0",
|
|
89
|
+
"eslint-plugin-jest-dom": "^5.5.0",
|
|
90
|
+
"eslint-plugin-playwright": "^2.2.2",
|
|
89
91
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
92
|
+
"eslint-plugin-testing-library": "^7.13.1",
|
|
90
93
|
"husky": "^9.0.6",
|
|
91
94
|
"i18next": "^23.7.20",
|
|
92
95
|
"i18next-parser": "^8.12.0",
|
|
@@ -196,7 +196,7 @@ describe('BillsTable', () => {
|
|
|
196
196
|
const patientNameLink = screen.getByRole('link', { name: 'John Doe' });
|
|
197
197
|
expect(patientNameLink).toBeInTheDocument();
|
|
198
198
|
|
|
199
|
-
expect(patientNameLink.
|
|
199
|
+
expect(patientNameLink).toHaveAttribute('href', '/home/billing/patient/uuid1/1');
|
|
200
200
|
});
|
|
201
201
|
|
|
202
202
|
test('should filter bills by payment status', async () => {
|
|
@@ -93,7 +93,7 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isLoadingBill }) => {
|
|
|
93
93
|
status: item.paymentStatus,
|
|
94
94
|
quantity: item.quantity,
|
|
95
95
|
price: convertToCurrency(item.price, defaultCurrency),
|
|
96
|
-
total: item.price * item.quantity,
|
|
96
|
+
total: convertToCurrency(item.price * item.quantity, defaultCurrency),
|
|
97
97
|
actionButton: (
|
|
98
98
|
<span>
|
|
99
99
|
<Button
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import { render, screen,
|
|
3
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
4
4
|
import { getDefaultsFromConfigSchema, showModal, useConfig } from '@openmrs/esm-framework';
|
|
5
5
|
import { type MappedBill } from '../types';
|
|
6
6
|
import { configSchema, type BillingConfig } from '../config-schema';
|
|
7
7
|
import InvoiceTable from './invoice-table.component';
|
|
8
8
|
|
|
9
9
|
const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
|
|
10
|
+
const mockShowModal = jest.mocked(showModal);
|
|
10
11
|
|
|
11
12
|
jest.mock('../helpers', () => ({
|
|
12
13
|
convertToCurrency: jest.fn((price) => `USD ${price}`),
|
|
13
14
|
}));
|
|
14
15
|
|
|
15
16
|
describe('InvoiceTable', () => {
|
|
16
|
-
|
|
17
|
-
mockUseConfig.mockReturnValue({
|
|
18
|
-
...getDefaultsFromConfigSchema(configSchema),
|
|
19
|
-
defaultCurrency: 'USD',
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const bill: MappedBill = {
|
|
17
|
+
const defaultBill: MappedBill = {
|
|
24
18
|
uuid: 'bill-uuid',
|
|
25
19
|
id: 123,
|
|
26
20
|
patientUuid: 'patient-uuid',
|
|
@@ -75,50 +69,294 @@ describe('InvoiceTable', () => {
|
|
|
75
69
|
tenderedAmount: 300,
|
|
76
70
|
};
|
|
77
71
|
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
mockUseConfig.mockReturnValue({
|
|
74
|
+
...getDefaultsFromConfigSchema(configSchema),
|
|
75
|
+
defaultCurrency: 'USD',
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should render table headers correctly', () => {
|
|
80
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
81
|
+
|
|
82
|
+
expect(screen.getByText(/line items/i)).toBeInTheDocument();
|
|
83
|
+
expect(screen.getByText(/items to be billed/i)).toBeInTheDocument();
|
|
84
|
+
expect(screen.getByRole('columnheader', { name: /no/i })).toBeInTheDocument();
|
|
85
|
+
expect(screen.getByRole('columnheader', { name: /bill item/i })).toBeInTheDocument();
|
|
86
|
+
expect(screen.getByRole('columnheader', { name: /bill code/i })).toBeInTheDocument();
|
|
87
|
+
expect(screen.getByRole('columnheader', { name: /status/i })).toBeInTheDocument();
|
|
88
|
+
expect(screen.getByRole('columnheader', { name: /quantity/i })).toBeInTheDocument();
|
|
89
|
+
expect(screen.getByRole('columnheader', { name: /price/i })).toBeInTheDocument();
|
|
90
|
+
expect(screen.getByRole('columnheader', { name: /total/i })).toBeInTheDocument();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should render line items correctly', () => {
|
|
94
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
80
95
|
|
|
81
96
|
expect(screen.getByText('Item 1')).toBeInTheDocument();
|
|
82
97
|
expect(screen.getByText('Item 2')).toBeInTheDocument();
|
|
83
98
|
expect(screen.getByTestId('receipt-number-0')).toHaveTextContent('12345');
|
|
99
|
+
expect(screen.getByTestId('receipt-number-1')).toHaveTextContent('12345');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should display loading skeleton when bill is loading', () => {
|
|
103
|
+
render(<InvoiceTable bill={defaultBill} isLoadingBill={true} />);
|
|
104
|
+
|
|
105
|
+
expect(screen.getByTestId('loader')).toBeInTheDocument();
|
|
106
|
+
expect(screen.queryByText(/line items/i)).not.toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should display payment status for each line item', () => {
|
|
110
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
111
|
+
|
|
112
|
+
expect(screen.getByText('PAID')).toBeInTheDocument();
|
|
113
|
+
expect(screen.getByText('PENDING')).toBeInTheDocument();
|
|
84
114
|
});
|
|
85
115
|
|
|
86
|
-
it('
|
|
116
|
+
it('should display correct quantities', () => {
|
|
117
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
118
|
+
|
|
119
|
+
// Item 1 has quantity 1, Item 2 has quantity 2
|
|
120
|
+
const rows = screen.getAllByRole('row');
|
|
121
|
+
expect(rows).toHaveLength(3); // Header row + 2 data rows
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should calculate and display line item totals correctly', () => {
|
|
125
|
+
const billWithCalculation: MappedBill = {
|
|
126
|
+
...defaultBill,
|
|
127
|
+
lineItems: [
|
|
128
|
+
{
|
|
129
|
+
uuid: '1',
|
|
130
|
+
item: 'Service A',
|
|
131
|
+
paymentStatus: 'PENDING',
|
|
132
|
+
quantity: 3,
|
|
133
|
+
price: 100,
|
|
134
|
+
display: '',
|
|
135
|
+
voided: false,
|
|
136
|
+
voidReason: '',
|
|
137
|
+
billableService: 'Service A',
|
|
138
|
+
priceName: '',
|
|
139
|
+
priceUuid: '',
|
|
140
|
+
lineItemOrder: 0,
|
|
141
|
+
resourceVersion: '',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
render(<InvoiceTable bill={billWithCalculation} />);
|
|
147
|
+
|
|
148
|
+
// Total should be 3 * 100 = 300
|
|
149
|
+
expect(screen.getByText('USD 300')).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should render edit buttons for all line items', () => {
|
|
153
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
154
|
+
|
|
155
|
+
const editButton1 = screen.getByTestId('edit-button-1');
|
|
156
|
+
const editButton2 = screen.getByTestId('edit-button-2');
|
|
157
|
+
|
|
158
|
+
expect(editButton1).toBeInTheDocument();
|
|
159
|
+
expect(editButton2).toBeInTheDocument();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should open edit modal when edit button is clicked', async () => {
|
|
87
163
|
const user = userEvent.setup();
|
|
88
|
-
render(<InvoiceTable bill={
|
|
164
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
89
165
|
|
|
90
166
|
const editButton = screen.getByTestId('edit-button-1');
|
|
91
167
|
await user.click(editButton);
|
|
92
|
-
|
|
168
|
+
|
|
169
|
+
expect(mockShowModal).toHaveBeenCalledTimes(1);
|
|
170
|
+
expect(mockShowModal).toHaveBeenCalledWith(
|
|
171
|
+
'edit-bill-line-item-dialog',
|
|
172
|
+
expect.objectContaining({
|
|
173
|
+
bill: defaultBill,
|
|
174
|
+
item: expect.objectContaining({ uuid: '1' }),
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
93
177
|
});
|
|
94
178
|
|
|
95
|
-
it('
|
|
96
|
-
|
|
179
|
+
it('should filter line items based on search term', async () => {
|
|
180
|
+
const user = userEvent.setup();
|
|
181
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
97
182
|
|
|
98
|
-
|
|
183
|
+
const searchInput = screen.getByPlaceholderText(/search this table/i);
|
|
184
|
+
await user.type(searchInput, 'Item 2');
|
|
185
|
+
|
|
186
|
+
await waitFor(() => {
|
|
187
|
+
expect(screen.queryByText('Item 1')).not.toBeInTheDocument();
|
|
188
|
+
expect(screen.getByText('Item 2')).toBeInTheDocument();
|
|
189
|
+
});
|
|
99
190
|
});
|
|
100
191
|
|
|
101
|
-
it('
|
|
192
|
+
it('should show all items when search is cleared', async () => {
|
|
102
193
|
const user = userEvent.setup();
|
|
103
|
-
render(<InvoiceTable bill={
|
|
194
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
195
|
+
|
|
104
196
|
const searchInput = screen.getByPlaceholderText(/search this table/i);
|
|
105
197
|
|
|
106
|
-
|
|
198
|
+
// Search for Item 1
|
|
199
|
+
await user.type(searchInput, 'Item 1');
|
|
107
200
|
|
|
108
|
-
|
|
109
|
-
|
|
201
|
+
await waitFor(() => {
|
|
202
|
+
expect(screen.queryByText('Item 2')).not.toBeInTheDocument();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Clear search
|
|
206
|
+
await user.clear(searchInput);
|
|
207
|
+
|
|
208
|
+
await waitFor(() => {
|
|
209
|
+
expect(screen.getByText('Item 1')).toBeInTheDocument();
|
|
210
|
+
expect(screen.getByText('Item 2')).toBeInTheDocument();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should display empty state when no line items exist', () => {
|
|
215
|
+
const emptyBill: MappedBill = {
|
|
216
|
+
...defaultBill,
|
|
217
|
+
lineItems: [],
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
render(<InvoiceTable bill={emptyBill} />);
|
|
221
|
+
|
|
222
|
+
expect(screen.getByText(/no matching items to display/i)).toBeInTheDocument();
|
|
223
|
+
expect(screen.getByText(/check the filters above/i)).toBeInTheDocument();
|
|
110
224
|
});
|
|
111
225
|
|
|
112
|
-
it('
|
|
226
|
+
it('should show empty state when search has no results', async () => {
|
|
113
227
|
const user = userEvent.setup();
|
|
114
|
-
render(<InvoiceTable bill={
|
|
228
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
115
229
|
|
|
116
|
-
const
|
|
117
|
-
await user.
|
|
118
|
-
|
|
119
|
-
|
|
230
|
+
const searchInput = screen.getByPlaceholderText(/search this table/i);
|
|
231
|
+
await user.type(searchInput, 'NonexistentItem');
|
|
232
|
+
|
|
233
|
+
await waitFor(() => {
|
|
234
|
+
expect(screen.getByText(/no matching items to display/i)).toBeInTheDocument();
|
|
235
|
+
expect(screen.getByText(/check the filters above/i)).toBeInTheDocument();
|
|
120
236
|
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should handle line items with zero price', () => {
|
|
240
|
+
const billWithZeroPrice: MappedBill = {
|
|
241
|
+
...defaultBill,
|
|
242
|
+
lineItems: [
|
|
243
|
+
{
|
|
244
|
+
uuid: '1',
|
|
245
|
+
item: 'Free Service',
|
|
246
|
+
paymentStatus: 'PAID',
|
|
247
|
+
quantity: 1,
|
|
248
|
+
price: 0,
|
|
249
|
+
display: '',
|
|
250
|
+
voided: false,
|
|
251
|
+
voidReason: '',
|
|
252
|
+
billableService: 'Free Service',
|
|
253
|
+
priceName: '',
|
|
254
|
+
priceUuid: '',
|
|
255
|
+
lineItemOrder: 0,
|
|
256
|
+
resourceVersion: '',
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
};
|
|
121
260
|
|
|
122
|
-
|
|
261
|
+
render(<InvoiceTable bill={billWithZeroPrice} />);
|
|
262
|
+
|
|
263
|
+
// USD 0 appears for both price and total
|
|
264
|
+
expect(screen.getAllByText('USD 0').length).toBeGreaterThan(0);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should handle line items with zero quantity', () => {
|
|
268
|
+
const billWithZeroQuantity: MappedBill = {
|
|
269
|
+
...defaultBill,
|
|
270
|
+
lineItems: [
|
|
271
|
+
{
|
|
272
|
+
uuid: '1',
|
|
273
|
+
item: 'Service',
|
|
274
|
+
paymentStatus: 'PENDING',
|
|
275
|
+
quantity: 0,
|
|
276
|
+
price: 100,
|
|
277
|
+
display: '',
|
|
278
|
+
voided: false,
|
|
279
|
+
voidReason: '',
|
|
280
|
+
billableService: 'Service',
|
|
281
|
+
priceName: '',
|
|
282
|
+
priceUuid: '',
|
|
283
|
+
lineItemOrder: 0,
|
|
284
|
+
resourceVersion: '',
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
render(<InvoiceTable bill={billWithZeroQuantity} />);
|
|
290
|
+
|
|
291
|
+
// Total should be 0 * 100 = 0
|
|
292
|
+
expect(screen.getByText('USD 0')).toBeInTheDocument();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should use billableService name when available, otherwise use item name', () => {
|
|
296
|
+
const billWithBillableService: MappedBill = {
|
|
297
|
+
...defaultBill,
|
|
298
|
+
lineItems: [
|
|
299
|
+
{
|
|
300
|
+
uuid: '1',
|
|
301
|
+
item: 'Item Name',
|
|
302
|
+
billableService: 'Billable Service Name',
|
|
303
|
+
paymentStatus: 'PAID',
|
|
304
|
+
quantity: 1,
|
|
305
|
+
price: 100,
|
|
306
|
+
display: '',
|
|
307
|
+
voided: false,
|
|
308
|
+
voidReason: '',
|
|
309
|
+
priceName: '',
|
|
310
|
+
priceUuid: '',
|
|
311
|
+
lineItemOrder: 0,
|
|
312
|
+
resourceVersion: '',
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
uuid: '2',
|
|
316
|
+
item: 'Item Without Billable',
|
|
317
|
+
billableService: '',
|
|
318
|
+
paymentStatus: 'PENDING',
|
|
319
|
+
quantity: 1,
|
|
320
|
+
price: 200,
|
|
321
|
+
display: '',
|
|
322
|
+
voided: false,
|
|
323
|
+
voidReason: '',
|
|
324
|
+
priceName: '',
|
|
325
|
+
priceUuid: '',
|
|
326
|
+
lineItemOrder: 1,
|
|
327
|
+
resourceVersion: '',
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
render(<InvoiceTable bill={billWithBillableService} />);
|
|
333
|
+
|
|
334
|
+
expect(screen.getByText('Billable Service Name')).toBeInTheDocument();
|
|
335
|
+
expect(screen.getByText('Item Without Billable')).toBeInTheDocument();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should display line item numbers starting from 1', () => {
|
|
339
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
340
|
+
|
|
341
|
+
// Check the table body for numbered rows
|
|
342
|
+
const rows = screen.getAllByRole('row');
|
|
343
|
+
// First row is header, so data rows start at index 1
|
|
344
|
+
expect(rows.length).toBeGreaterThan(2);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should pass correct currency to convertToCurrency helper', () => {
|
|
348
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
349
|
+
|
|
350
|
+
// Verify prices are formatted with USD - multiple occurrences expected
|
|
351
|
+
expect(screen.getAllByText('USD 100').length).toBeGreaterThan(0);
|
|
352
|
+
expect(screen.getAllByText('USD 200').length).toBeGreaterThan(0);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should render search input in expanded state', () => {
|
|
356
|
+
render(<InvoiceTable bill={defaultBill} />);
|
|
357
|
+
|
|
358
|
+
const searchInput = screen.getByPlaceholderText(/search this table/i);
|
|
359
|
+
expect(searchInput).toBeInTheDocument();
|
|
360
|
+
expect(searchInput).toBeVisible();
|
|
123
361
|
});
|
|
124
362
|
});
|