@openmrs/esm-stock-management-app 3.0.1-pre.845 → 3.0.1-pre.853

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 (126) hide show
  1. package/.husky/pre-commit +4 -1
  2. package/__mocks__/react-i18next.js +8 -9
  3. package/dist/10.js +1 -1
  4. package/dist/10.js.map +1 -1
  5. package/dist/119.js +1 -1
  6. package/dist/119.js.map +1 -1
  7. package/dist/14.js +1 -1
  8. package/dist/14.js.map +1 -1
  9. package/dist/172.js +1 -1
  10. package/dist/172.js.map +1 -1
  11. package/dist/20.js +1 -1
  12. package/dist/20.js.map +1 -1
  13. package/dist/290.js +1 -1
  14. package/dist/290.js.map +1 -1
  15. package/dist/33.js +1 -0
  16. package/dist/33.js.map +1 -0
  17. package/dist/467.js +1 -1
  18. package/dist/467.js.map +1 -1
  19. package/dist/574.js +1 -1
  20. package/dist/606.js +1 -1
  21. package/dist/606.js.map +1 -1
  22. package/dist/642.js +1 -1
  23. package/dist/642.js.map +1 -1
  24. package/dist/675.js +1 -1
  25. package/dist/675.js.map +1 -1
  26. package/dist/727.js +1 -1
  27. package/dist/727.js.map +1 -1
  28. package/dist/842.js +1 -1
  29. package/dist/842.js.map +1 -1
  30. package/dist/93.js +1 -1
  31. package/dist/93.js.map +1 -1
  32. package/dist/main.js +1 -1
  33. package/dist/main.js.map +1 -1
  34. package/dist/openmrs-esm-stock-management-app.js.buildmanifest.json +70 -70
  35. package/dist/routes.json +1 -1
  36. package/jest.config.js +6 -3
  37. package/package.json +1 -1
  38. package/src/core/components/overlay/overlay.scss +1 -1
  39. package/src/core/components/privilages-component/privilages.scss +1 -1
  40. package/src/core/components/table/table.component.tsx +2 -2
  41. package/src/core/components/table/table.scss +1 -1
  42. package/src/core/components/tabs/vertical-tabs.scss +1 -1
  43. package/src/index.ts +5 -5
  44. package/src/stock-items/add-bulk-stock-item/add-stock-items-bulk-import-action-button.component.tsx +3 -3
  45. package/src/stock-items/add-bulk-stock-item/{stock-items-bulk-import.component.tsx → stock-items-bulk-import.modal.tsx} +20 -19
  46. package/src/stock-items/add-bulk-stock-item/stock-items-bulk-import.resource.ts +1 -1
  47. package/src/stock-items/add-bulk-stock-item/stock-items-bulk-import.test.tsx +59 -59
  48. package/src/stock-items/add-stock-item/add-stock-action-button.component.tsx +6 -6
  49. package/src/stock-items/add-stock-item/add-stock-item.component.tsx +6 -4
  50. package/src/stock-items/add-stock-item/add-stock-item.scss +5 -0
  51. package/src/stock-items/add-stock-item/add-stock-item.test.tsx +28 -43
  52. package/src/stock-items/add-stock-item/packaging-units/packaging-units-delete-modal.component.tsx +3 -4
  53. package/src/stock-items/add-stock-item/packaging-units/packaging-units.component.tsx +9 -10
  54. package/src/stock-items/add-stock-item/packaging-units/packaging-units.scss +4 -4
  55. package/src/stock-items/add-stock-item/stock-item-details/stock-item-details.component.tsx +27 -19
  56. package/src/stock-items/add-stock-item/stock-item-details/stock-item-details.scss +1 -1
  57. package/src/stock-items/add-stock-item/stock-item-references/stock-item-references.scss +4 -4
  58. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.component.tsx +15 -9
  59. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.scss +1 -0
  60. package/src/stock-items/add-stock-item/stock-item-rules/delete-stock-rule-modal.component.tsx +2 -1
  61. package/src/stock-items/add-stock-item/stock-item-rules/stock-item-rules.component.tsx +14 -16
  62. package/src/stock-items/add-stock-item/stock-item-rules/stock-item-rules.scss +7 -3
  63. package/src/stock-items/add-stock-item/transactions/printout/transactions-print-bincard-preview.modal.tsx +14 -6
  64. package/src/stock-items/add-stock-item/transactions/printout/transactions-print-stockcard-preview.modal.tsx +14 -8
  65. package/src/stock-items/edit-stock-item/edit-stock-item-action-menu.component.tsx +2 -2
  66. package/src/stock-items/stock-item.utils.tsx +3 -5
  67. package/src/stock-items/stock-items-table.component.tsx +47 -45
  68. package/src/stock-items/stock-items-table.resource.ts +2 -2
  69. package/src/stock-items/stock-items-table.scss +9 -8
  70. package/src/stock-items/stock-items-table.test.tsx +106 -65
  71. package/src/stock-items/stock-items.component.tsx +1 -1
  72. package/src/stock-locations/location-admin-form.component.tsx +5 -4
  73. package/src/stock-locations/stock-locations-table.component.tsx +10 -8
  74. package/src/stock-locations/stock-locations.component.tsx +1 -1
  75. package/src/stock-lookups/stock-lookups.resource.ts +3 -2
  76. package/src/stock-management-header/stock-management-header.scss +1 -1
  77. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operation-expanded-row.scss +1 -1
  78. package/src/stock-operations/stock-operations-dialog/stock-operations-dialog.component.tsx +2 -2
  79. package/src/stock-operations/stock-operations-filters.component.tsx +5 -5
  80. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.component.tsx +11 -11
  81. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.test.tsx +115 -25
  82. package/src/stock-operations/stock-operations-forms/input-components/qty-uim-selector.test.tsx +107 -65
  83. package/src/stock-operations/stock-operations-forms/input-components/quantity-uom-selector.component.tsx +9 -9
  84. package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.test.tsx +35 -153
  85. package/src/stock-operations/stock-operations-forms/input-components/user-selector.test.tsx +82 -29
  86. package/src/stock-operations/stock-operations-forms/step1.test.tsx +204 -69
  87. package/src/stock-operations/stock-operations-forms/step2.test.tsx +140 -63
  88. package/src/stock-operations/stock-operations-forms/step3.test.tsx +79 -60
  89. package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.component.tsx +6 -5
  90. package/src/stock-operations/stock-operations-forms/steps/stock-operation-submission-form-step.component.tsx +12 -11
  91. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.scss +1 -0
  92. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.workspace.tsx +20 -12
  93. package/src/stock-operations/stock-operations-forms/stock-operation-form.scss +1 -0
  94. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stepper.scss +1 -3
  95. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stock-operation-stepper.component.tsx +2 -1
  96. package/src/stock-operations/stock-operations-table.component.tsx +66 -73
  97. package/src/stock-operations/stock-operations-table.scss +18 -9
  98. package/src/stock-operations/stock-operations.component.tsx +1 -1
  99. package/src/stock-reports/generate-report/create-stock-report.scss +3 -2
  100. package/src/stock-reports/generate-report/create-stock-report.workspace.tsx +32 -25
  101. package/src/stock-reports/report-list/stock-report-parameters.component.tsx +1 -1
  102. package/src/stock-reports/report-list/stock-report-status.component.tsx +1 -1
  103. package/src/stock-reports/report-list/stock-reports.component.tsx +24 -25
  104. package/src/stock-reports/report-list/stock-reports.scss +11 -3
  105. package/src/stock-sources/add-stock-sources/add-stock-sources.scss +12 -5
  106. package/src/stock-sources/add-stock-sources/add-stock-sources.test.tsx +38 -36
  107. package/src/stock-sources/add-stock-sources/add-stock-sources.workspace.tsx +35 -30
  108. package/src/stock-sources/delete-stock-modal.component.tsx +2 -1
  109. package/src/stock-sources/delete-stock-modal.scss +1 -1
  110. package/src/stock-sources/stock-sources-delete/stock-sources-delete.test.tsx +27 -36
  111. package/src/stock-sources/stock-sources-filter/stock-sources-filter.component.tsx +33 -21
  112. package/src/stock-sources/stock-sources-items-table.component.tsx +16 -17
  113. package/src/stock-sources/stock-sources-items-table.resource.ts +8 -6
  114. package/src/stock-sources/stock-sources-items-table.test.tsx +60 -37
  115. package/src/stock-sources/stock-sources.component.tsx +1 -1
  116. package/src/stock-sources/stock-sources.scss +6 -2
  117. package/src/stock-user-role-scopes/add-stock-user-scope/add-stock-user-role-scope.scss +5 -13
  118. package/src/stock-user-role-scopes/add-stock-user-scope/add-stock-user-role-scope.workspace.tsx +2 -2
  119. package/src/stock-user-role-scopes/delete-stock-user-scope-modal.component.tsx +2 -1
  120. package/src/stock-user-role-scopes/delete-stock-user-scope-modal.scss +1 -1
  121. package/src/stock-user-role-scopes/stock-user-role-scopes.component.tsx +1 -1
  122. package/src/stock-user-role-scopes/stock-user-role-scopes.scss +1 -1
  123. package/translations/en.json +5 -6
  124. package/tsconfig.json +4 -0
  125. package/dist/627.js +0 -1
  126. package/dist/627.js.map +0 -1
@@ -1,44 +1,27 @@
1
1
  import React from 'react';
2
- import { render, waitFor, screen, fireEvent } from '@testing-library/react';
3
- import { showSnackbar, useConfig, ErrorState, launchWorkspace } from '@openmrs/esm-framework';
4
- import { useStockOperationTypes, useUser } from '../../stock-lookups/stock-lookups.resource';
5
- import { getStockOperationLinks, useStockOperations } from '../stock-operations.resource';
6
- import { StockOperationDTO } from '../../core/api/types/stockOperation/StockOperationDTO';
7
- import { StockOperationType } from '../../core/api/types/stockOperation/StockOperationType';
8
- import { closeOverlay } from '../../core/components/overlay/hook';
9
- import StockOperationForm from './stock-operation-form.component';
10
- import useParties from './hooks/useParties';
11
2
  import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { useFormContext, type UseFormReturn } from 'react-hook-form';
5
+ import { useConfig, useSession } from '@openmrs/esm-framework';
6
+ import { formatForDatePicker } from '../../constants';
7
+ import { receiptOperationTypeMock } from '@mocks';
8
+ import { type BaseStockOperationItemFormData } from '../validation-schema';
12
9
  import { type StockItemDTO } from '../../core/api/types/stockItem/StockItem';
13
- import { useStockItem, useStockItems, useStockBatches } from '../../stock-items/stock-items.resource';
14
- import { initialStockOperationValue } from '../../core/utils/utils';
15
- import { useForm, useFormContext, Controller, FormProvider } from 'react-hook-form';
16
- import { type BaseStockOperationItemFormData, StockOperationItemFormData } from '../validation-schema';
17
- import { useStockItemBatchInformationHook } from '../../stock-items/add-stock-item/batch-information/batch-information.resource';
18
10
  import { useFilterableStockItems } from './hooks/useFilterableStockItems';
19
- import { formatForDatePicker } from '../../constants';
20
- import { receiptOperationTypeMock } from '../../../__mocks__';
21
- jest.mock('react-i18next', () => ({
22
- useTranslation: jest.fn().mockReturnValue({ t: (key) => key }),
23
- }));
11
+ import { useStockItem } from '../../stock-items/stock-items.resource';
12
+ import { useStockOperations } from '../stock-operations.resource';
13
+ import { useStockOperationTypes } from '../../stock-lookups/stock-lookups.resource';
14
+ import useParties from './hooks/useParties';
15
+ import StockOperationForm from './stock-operation-form.component';
24
16
 
25
- jest.mock('@openmrs/esm-framework', () => ({
26
- ActionMenu: jest.fn(() => null),
27
- showSnackbar: jest.fn(),
28
- useDebounce: jest.fn((x) => x),
29
- getGlobalStore: jest.fn(() => ({
30
- getState: jest.fn(),
31
- subscribe: jest.fn(),
32
- setState: jest.fn(),
33
- })),
34
- parseDate: jest.fn((date) => new Date(date)),
35
- showNotification: jest.fn(),
36
- usePagination: jest.fn(() => ({ currentPage: 1, setPage: jest.fn() })),
37
- useSession: jest.fn(() => ({ user: { display: 'Test User' } })),
38
- useConfig: jest.fn(),
39
- ErrorState: jest.fn(({ error }: { error: any }) => <div>{error}</div>),
40
- launchWorkspace: jest.fn(),
41
- }));
17
+ const mockUseConfig = jest.mocked(useConfig);
18
+ const mockUseFilterableStockItems = jest.mocked(useFilterableStockItems);
19
+ const mockUseFormContext = jest.mocked(useFormContext);
20
+ const mockUseParties = jest.mocked(useParties);
21
+ const mockUseSession = jest.mocked(useSession);
22
+ const mockUseStockItem = jest.mocked(useStockItem);
23
+ const mockUseStockOperations = jest.mocked(useStockOperations);
24
+ const mockUseStockOperationTypes = jest.mocked(useStockOperationTypes);
42
25
 
43
26
  jest.mock('../../stock-lookups/stock-lookups.resource', () => ({
44
27
  useStockOperationTypes: jest.fn(),
@@ -50,7 +33,11 @@ jest.mock('../stock-operations.resource', () => ({
50
33
  operationStatusColor: jest.fn(() => 'some-color'),
51
34
  getStockOperationLinks: jest.fn(),
52
35
  useStockOperations: jest.fn().mockReturnValue({
53
- items: { results: [] },
36
+ items: {
37
+ results: [],
38
+ links: [],
39
+ totalCount: 0,
40
+ },
54
41
  isLoading: false,
55
42
  error: null,
56
43
  }),
@@ -78,6 +65,7 @@ jest.mock('../../stock-items/stock-items.resource', () => ({
78
65
  items: {},
79
66
  }),
80
67
  }));
68
+
81
69
  jest.mock('./hooks/useFilterableStockItems', () => ({
82
70
  useFilterableStockItems: jest.fn().mockReturnValue({
83
71
  stockItemsList: [],
@@ -87,7 +75,9 @@ jest.mock('./hooks/useFilterableStockItems', () => ({
87
75
  isLoading: false,
88
76
  }),
89
77
  }));
78
+
90
79
  jest.mock('./hooks/useParties', () => jest.fn());
80
+
91
81
  jest.mock('react-hook-form', () => ({
92
82
  useForm: jest.fn().mockReturnValue({
93
83
  watch: jest.fn(),
@@ -103,6 +93,17 @@ jest.mock('react-hook-form', () => ({
103
93
  watch: jest.fn(),
104
94
  formState: {
105
95
  errors: {},
96
+ isDirty: false,
97
+ isLoading: false,
98
+ isSubmitted: false,
99
+ isSubmitSuccessful: false,
100
+ isSubmitting: false,
101
+ isValid: true,
102
+ isValidating: false,
103
+ submitCount: 0,
104
+ touchedFields: {},
105
+ dirtyFields: {},
106
+ disabled: false,
106
107
  },
107
108
  resetField: jest.fn(),
108
109
  getValues: jest.fn(),
@@ -134,21 +135,71 @@ jest.mock('../../stock-items/add-stock-item/batch-information/batch-information.
134
135
 
135
136
  describe('Stock Operation step 2 (stock operation items details)', () => {
136
137
  beforeEach(() => {
137
- const mockStockOperationTypes = { results: [] };
138
- (useStockOperationTypes as jest.Mock).mockReturnValue(mockStockOperationTypes);
139
- (useStockOperations as jest.Mock).mockReturnValue({ items: { results: [] }, isLoading: false, error: null });
140
- (useConfig as jest.Mock).mockReturnValue({ autoPopulateResponsiblePerson: true });
141
- (useParties as jest.Mock).mockReturnValue({
142
- destinationParties: [],
143
- sourceParties: [],
138
+ const mockStockOperationTypes = {
139
+ types: {
140
+ results: [],
141
+ links: [],
142
+ totalCount: 0,
143
+ },
144
+ isLoading: false,
145
+ error: null,
146
+ };
147
+
148
+ mockUseStockOperationTypes.mockReturnValue(mockStockOperationTypes);
149
+
150
+ mockUseStockOperations.mockReturnValue({
151
+ items: {
152
+ results: [],
153
+ links: [],
154
+ totalCount: 0,
155
+ },
144
156
  isLoading: false,
157
+ error: null,
158
+ });
159
+
160
+ mockUseConfig.mockReturnValue({ autoPopulateResponsiblePerson: true });
161
+
162
+ mockUseParties.mockReturnValue({
163
+ destinationParties: [],
164
+ destinationPartiesFilter: jest.fn(),
165
+ destinationTags: [],
145
166
  error: undefined,
167
+ isLoading: false,
168
+ mutate: jest.fn(),
169
+ parties: [],
170
+ sourceParties: [],
171
+ sourcePartiesFilter: jest.fn(),
146
172
  sourceTags: [],
147
- destinationTags: [],
173
+ });
174
+
175
+ mockUseSession.mockReturnValue({
176
+ authenticated: true,
177
+ sessionId: 'test-session-id',
178
+ user: {
179
+ uuid: 'test-user-uuid',
180
+ display: 'Test User',
181
+ username: 'testuser',
182
+ systemId: 'test-system-id',
183
+ userProperties: {},
184
+ person: { uuid: 'test-person-uuid' },
185
+ privileges: [],
186
+ roles: [],
187
+ retired: false,
188
+ links: [],
189
+ locale: 'en',
190
+ allowedLocales: ['en'],
191
+ },
192
+ sessionLocation: {
193
+ uuid: 'test-location-uuid',
194
+ display: 'Test Location',
195
+ links: [],
196
+ },
148
197
  });
149
198
  });
150
199
 
151
200
  it('should have both previous and next btns', async () => {
201
+ const user = userEvent.setup();
202
+
152
203
  render(
153
204
  <StockOperationForm
154
205
  stockOperationType={receiptOperationTypeMock as any}
@@ -159,13 +210,15 @@ describe('Stock Operation step 2 (stock operation items details)', () => {
159
210
  />,
160
211
  );
161
212
  // MOVE TO STEP 2
162
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
213
+ await user.click(screen.getByRole('button', { name: /Next/i }));
163
214
 
164
215
  expect(screen.getByRole('button', { name: /Next/i })).toBeInTheDocument();
165
216
  expect(screen.getByTestId('previous-btn')).toBeInTheDocument();
166
217
  });
167
218
 
168
219
  it('should render stock operation items table with item search component', async () => {
220
+ const user = userEvent.setup();
221
+
169
222
  render(
170
223
  <StockOperationForm
171
224
  stockOperationType={receiptOperationTypeMock as any}
@@ -175,16 +228,17 @@ describe('Stock Operation step 2 (stock operation items details)', () => {
175
228
  promptBeforeClosing={jest.fn()}
176
229
  />,
177
230
  );
231
+
178
232
  const nextButton = screen.getByRole('button', { name: /Next/i });
179
233
  expect(nextButton).toBeInTheDocument();
180
- await userEvent.click(nextButton);
234
+ await user.click(nextButton);
181
235
  expect(screen.getByRole('table')).toBeInTheDocument();
182
236
  expect(
183
237
  screen.getByRole('searchbox', {
184
238
  name(accessibleName, element) {
185
239
  return (
186
240
  element.getAttribute('id') === 'search-stock-operation-item' &&
187
- element.getAttribute('placeholder') === 'findItems' &&
241
+ element.getAttribute('placeholder') === 'Find your items' &&
188
242
  element.getAttribute('name') === 'search-stock-operation-item'
189
243
  );
190
244
  },
@@ -193,8 +247,10 @@ describe('Stock Operation step 2 (stock operation items details)', () => {
193
247
  });
194
248
 
195
249
  it('should search stock operation item and render results', async () => {
250
+ const user = userEvent.setup();
251
+
196
252
  const mocksetSearchString = jest.fn();
197
- (useFilterableStockItems as jest.Mock).mockReturnValue({
253
+ mockUseFilterableStockItems.mockReturnValue({
198
254
  stockItemsList: [
199
255
  { uuid: 'mock-uuid', commonName: 'mock-common-name', drugName: 'mock-common-name' },
200
256
  ] as Array<StockItemDTO>,
@@ -203,6 +259,7 @@ describe('Stock Operation step 2 (stock operation items details)', () => {
203
259
  isLoading: false,
204
260
  setSearchString: mocksetSearchString,
205
261
  });
262
+
206
263
  render(
207
264
  <StockOperationForm
208
265
  stockOperationType={receiptOperationTypeMock as any}
@@ -212,21 +269,24 @@ describe('Stock Operation step 2 (stock operation items details)', () => {
212
269
  promptBeforeClosing={jest.fn()}
213
270
  />,
214
271
  );
272
+
215
273
  // ----- CLICK NEXT TO MOVE TO STEP 2 ---------
216
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
274
+ await user.click(screen.getByRole('button', { name: /Next/i }));
217
275
  // -------------------------------
218
276
  const searchInput = screen.getByRole('searchbox', {
219
- name: (_, element) => element.getAttribute('id') === 'search-stock-operation-item',
277
+ name: /search/i,
220
278
  });
221
279
  expect(searchInput).toBeInTheDocument();
222
- await userEvent.click(searchInput);
223
- await userEvent.type(searchInput, 'stock');
280
+ await user.click(searchInput);
281
+ await user.type(searchInput, 'stock');
224
282
  expect(mocksetSearchString).toHaveBeenCalledWith('stock');
225
283
  expect(screen.getByText('mock-common-name'));
226
284
  });
227
285
 
228
286
  it('should properly handle stock operation item selection', async () => {
229
- (useFilterableStockItems as jest.Mock).mockReturnValue({
287
+ const user = userEvent.setup();
288
+
289
+ mockUseFilterableStockItems.mockReturnValue({
230
290
  stockItemsList: [
231
291
  { uuid: 'mock-uuid', commonName: 'mock-common-name', drugName: 'mock-common-name' },
232
292
  ] as Array<StockItemDTO>,
@@ -235,6 +295,7 @@ describe('Stock Operation step 2 (stock operation items details)', () => {
235
295
  isLoading: false,
236
296
  setSearchString: jest.fn(),
237
297
  });
298
+
238
299
  render(
239
300
  <StockOperationForm
240
301
  stockOperationType={receiptOperationTypeMock as any}
@@ -245,27 +306,30 @@ describe('Stock Operation step 2 (stock operation items details)', () => {
245
306
  />,
246
307
  );
247
308
  // ----- CLICK NEXT TO MOVE TO STEP 2 ---------
248
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
309
+ await user.click(screen.getByRole('button', { name: /Next/i }));
249
310
  // -------------------------------
250
311
  const searchInput = screen.getByRole('searchbox', {
251
312
  name: (_, element) => element.getAttribute('id') === 'search-stock-operation-item',
252
313
  });
253
- await userEvent.click(searchInput);
254
- await userEvent.type(searchInput, 'stock');
255
- await userEvent.click(screen.getByText('mock-common-name'));
314
+ await user.click(searchInput);
315
+ await user.type(searchInput, 'stock');
316
+ await user.click(screen.getByText('mock-common-name'));
256
317
  // Look for common name at the top of workspace
257
- expect(screen.getByText(/noDrugNameAvailable|noCommonNameAvailable|mock-common-name/i)).toBeInTheDocument();
318
+ expect(screen.getByText(/no drug name available|no common name available|mock-common-name/i)).toBeInTheDocument();
258
319
  });
259
320
 
260
321
  it('should render stock operation items in data table', async () => {
261
- (useStockItem as jest.Mock).mockReturnValue({
322
+ const user = userEvent.setup();
323
+
324
+ mockUseStockItem.mockReturnValue({
262
325
  isLoading: false,
263
326
  error: null,
264
327
  item: { commonName: 'mock-stock-item-common name', uuid: 'mock-uuid' } as StockItemDTO,
265
328
  });
329
+
266
330
  const mockQuantity = 99999;
267
331
  const mockExpiration = new Date();
268
- (useFormContext as jest.Mock).mockReturnValue({
332
+ mockUseFormContext.mockReturnValue({
269
333
  watch: jest.fn().mockReturnValue([
270
334
  {
271
335
  quantity: mockQuantity,
@@ -275,11 +339,23 @@ describe('Stock Operation step 2 (stock operation items details)', () => {
275
339
  resetField: jest.fn(),
276
340
  formState: {
277
341
  errors: {},
342
+ isDirty: false,
343
+ isLoading: false,
344
+ isSubmitted: false,
345
+ isSubmitSuccessful: false,
346
+ isSubmitting: false,
347
+ isValid: true,
348
+ isValidating: false,
349
+ submitCount: 0,
350
+ touchedFields: {},
351
+ dirtyFields: {},
352
+ disabled: false,
278
353
  },
279
354
  getValues: jest.fn(),
280
355
  setValue: jest.fn(),
281
356
  handleSubmit: jest.fn(),
282
- });
357
+ } as unknown as UseFormReturn<BaseStockOperationItemFormData>);
358
+
283
359
  render(
284
360
  <StockOperationForm
285
361
  stockOperationType={receiptOperationTypeMock as any}
@@ -289,8 +365,9 @@ describe('Stock Operation step 2 (stock operation items details)', () => {
289
365
  promptBeforeClosing={jest.fn()}
290
366
  />,
291
367
  );
368
+
292
369
  // ----- CLICK NEXT TO MOVE TO STEP 2 ---------
293
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
370
+ await user.click(screen.getByRole('button', { name: /Next/i }));
294
371
  // -------------------------------
295
372
  expect(screen.getByText(mockQuantity.toLocaleString())).toBeInTheDocument(); //Find by quentity
296
373
  expect(screen.getByText(formatForDatePicker(mockExpiration))).toBeInTheDocument(); //Find by quentity
@@ -1,44 +1,18 @@
1
- import { render, waitFor, screen, fireEvent } from '@testing-library/react';
2
- import { showSnackbar, useConfig, ErrorState, launchWorkspace } from '@openmrs/esm-framework';
3
- import { useStockOperationTypes, useUser } from '../../stock-lookups/stock-lookups.resource';
4
- import { getStockOperationLinks, useStockOperation, useStockOperations } from '../stock-operations.resource';
5
- import { StockOperationDTO } from '../../core/api/types/stockOperation/StockOperationDTO';
6
- import { StockOperationType } from '../../core/api/types/stockOperation/StockOperationType';
7
- import { closeOverlay } from '../../core/components/overlay/hook';
8
- import StockOperationForm from './stock-operation-form.component';
9
- import useParties from './hooks/useParties';
10
- import userEvent from '@testing-library/user-event';
11
- import { StockItemDTO } from '../../core/api/types/stockItem/StockItem';
12
- import { useStockItem, useStockItems } from '../../stock-items/stock-items.resource';
13
- import { initialStockOperationValue } from '../../core/utils/utils';
14
- import { useForm, useFormContext, Controller, FormProvider } from 'react-hook-form';
15
- import { BaseStockOperationItemFormData, StockOperationItemFormData } from '../validation-schema';
16
- import { useStockItemBatchInformationHook } from '../../stock-items/add-stock-item/batch-information/batch-information.resource';
17
- import { useFilterableStockItems } from './hooks/useFilterableStockItems';
18
- import { formatForDatePicker } from '../../constants';
19
1
  import React from 'react';
20
- import { receiptOperationTypeMock, returnOperationTypeMock, stockIssueOperationtypeMock } from '../../../__mocks__';
21
- jest.mock('react-i18next', () => ({
22
- useTranslation: jest.fn().mockReturnValue({ t: (key) => key }),
23
- }));
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { useConfig, useSession } from '@openmrs/esm-framework';
5
+ import { receiptOperationTypeMock, returnOperationTypeMock, stockIssueOperationtypeMock } from '@mocks';
6
+ import { useStockOperations } from '../stock-operations.resource';
7
+ import { useStockOperationTypes } from '../../stock-lookups/stock-lookups.resource';
8
+ import useParties from './hooks/useParties';
9
+ import StockOperationForm from './stock-operation-form.component';
24
10
 
25
- jest.mock('@openmrs/esm-framework', () => ({
26
- ActionMenu: jest.fn(() => null),
27
- showSnackbar: jest.fn(),
28
- useDebounce: jest.fn((x) => x),
29
- getGlobalStore: jest.fn(() => ({
30
- getState: jest.fn(),
31
- subscribe: jest.fn(),
32
- setState: jest.fn(),
33
- })),
34
- parseDate: jest.fn((date) => new Date(date)),
35
- showNotification: jest.fn(),
36
- usePagination: jest.fn(() => ({ currentPage: 1, setPage: jest.fn() })),
37
- useSession: jest.fn(() => ({ user: { display: 'Test User' } })),
38
- useConfig: jest.fn(),
39
- ErrorState: jest.fn(({ error }: { error: any }) => <div>{error}</div>),
40
- launchWorkspace: jest.fn(),
41
- }));
11
+ const mockUseParties = jest.mocked(useParties);
12
+ const mockUseStockOperationTypes = jest.mocked(useStockOperationTypes);
13
+ const mockUseStockOperations = jest.mocked(useStockOperations);
14
+ const mockUseConfig = jest.mocked(useConfig);
15
+ const mockUseSession = jest.mocked(useSession);
42
16
 
43
17
  jest.mock('../../stock-lookups/stock-lookups.resource', () => ({
44
18
  useStockOperationTypes: jest.fn(),
@@ -78,6 +52,7 @@ jest.mock('../../stock-items/stock-items.resource', () => ({
78
52
  items: {},
79
53
  }),
80
54
  }));
55
+
81
56
  jest.mock('./hooks/useFilterableStockItems', () => ({
82
57
  useFilterableStockItems: jest.fn().mockReturnValue({
83
58
  stockItemsList: [],
@@ -87,7 +62,9 @@ jest.mock('./hooks/useFilterableStockItems', () => ({
87
62
  isLoading: false,
88
63
  }),
89
64
  }));
65
+
90
66
  jest.mock('./hooks/useParties', () => jest.fn());
67
+
91
68
  jest.mock('react-hook-form', () => ({
92
69
  useForm: jest.fn().mockReturnValue({
93
70
  watch: jest.fn(),
@@ -134,17 +111,55 @@ jest.mock('../../stock-items/add-stock-item/batch-information/batch-information.
134
111
 
135
112
  describe('Stock Operation form step 3 (stock submision)', () => {
136
113
  beforeEach(() => {
137
- const mockStockOperationTypes = { results: [] };
138
- (useStockOperationTypes as jest.Mock).mockReturnValue(mockStockOperationTypes);
139
- (useStockOperations as jest.Mock).mockReturnValue({ items: { results: [] }, isLoading: false, error: null });
140
- (useConfig as jest.Mock).mockReturnValue({ autoPopulateResponsiblePerson: true });
141
- (useParties as jest.Mock).mockReturnValue({
114
+ mockUseStockOperationTypes.mockReturnValue({
115
+ types: { results: [], links: [], totalCount: 0 },
116
+ isLoading: false,
117
+ error: null,
118
+ });
119
+
120
+ mockUseStockOperations.mockReturnValue({
121
+ items: { results: [], links: [], totalCount: 0 },
122
+ isLoading: false,
123
+ error: null,
124
+ });
125
+
126
+ mockUseConfig.mockReturnValue({ autoPopulateResponsiblePerson: true });
127
+
128
+ mockUseParties.mockReturnValue({
142
129
  destinationParties: [],
143
130
  sourceParties: [],
144
131
  isLoading: false,
145
132
  error: undefined,
146
133
  sourceTags: [],
147
134
  destinationTags: [],
135
+ parties: [],
136
+ mutate: jest.fn(),
137
+ sourcePartiesFilter: () => true,
138
+ destinationPartiesFilter: () => true,
139
+ });
140
+
141
+ mockUseSession.mockReturnValue({
142
+ authenticated: true,
143
+ sessionId: 'test-session-id',
144
+ user: {
145
+ uuid: 'test-user-uuid',
146
+ display: 'Test User',
147
+ username: 'testuser',
148
+ systemId: 'test-system-id',
149
+ userProperties: {},
150
+ person: { uuid: 'test-person-uuid' },
151
+ privileges: [],
152
+ roles: [],
153
+ retired: false,
154
+ links: [],
155
+ locale: 'en',
156
+ allowedLocales: ['en'],
157
+ },
158
+ sessionLocation: {
159
+ uuid: 'test-location-uuid',
160
+ display: 'Test Location',
161
+ links: [],
162
+ },
148
163
  });
149
164
  });
150
165
 
@@ -159,11 +174,11 @@ describe('Stock Operation form step 3 (stock submision)', () => {
159
174
  />,
160
175
  );
161
176
  // MOVE TO STEP 2
162
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
177
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
163
178
  // MOVE TO STEP3
164
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
179
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
165
180
 
166
- expect(screen.queryByRole('button', { name: /Next/i })).not.toBeInTheDocument();
181
+ expect(screen.queryByRole('button', { name: /next/i })).not.toBeInTheDocument();
167
182
  expect(screen.getByTestId('previous-btn')).toBeInTheDocument();
168
183
  });
169
184
 
@@ -178,14 +193,15 @@ describe('Stock Operation form step 3 (stock submision)', () => {
178
193
  />,
179
194
  );
180
195
  // MOVE TO STEP 2
181
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
196
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
182
197
  // MOVE TO STEP3
183
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
198
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
184
199
 
185
200
  // Shouls have save button
186
201
  expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
187
202
  expect(screen.getAllByRole('radio', { name: /yes|no/i })).toHaveLength(2);
188
203
  });
204
+
189
205
  it('should render submitForReview button when require aprroval radion button is checked yes', async () => {
190
206
  render(
191
207
  <StockOperationForm
@@ -197,18 +213,19 @@ describe('Stock Operation form step 3 (stock submision)', () => {
197
213
  />,
198
214
  );
199
215
  // MOVE TO STEP 2
200
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
216
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
201
217
  // MOVE TO STEP3
202
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
218
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
203
219
 
204
220
  const yesRadioButton = screen.getByRole('radio', { name: /yes/i });
205
221
  expect(yesRadioButton).toBeInTheDocument();
206
222
  // Submit for review shouldnt be in doc
207
- expect(screen.queryByRole('button', { name: /submitForReview/i })).not.toBeInTheDocument();
223
+ expect(screen.queryByRole('button', { name: /submit for review/i })).not.toBeInTheDocument();
208
224
  await userEvent.click(yesRadioButton);
209
225
  // On require aprooval should now show
210
- expect(screen.getByRole('button', { name: /submitForReview/i })).toBeInTheDocument();
226
+ expect(screen.getByRole('button', { name: /submit for review/i })).toBeInTheDocument();
211
227
  });
228
+
212
229
  it('should render complete button when require aprroval radion button is checked no', async () => {
213
230
  render(
214
231
  <StockOperationForm
@@ -220,9 +237,9 @@ describe('Stock Operation form step 3 (stock submision)', () => {
220
237
  />,
221
238
  );
222
239
  // MOVE TO STEP 2
223
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
240
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
224
241
  // MOVE TO STEP3
225
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
242
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
226
243
 
227
244
  const noRadioButton = screen.getByRole('radio', { name: /no/i });
228
245
  expect(noRadioButton).toBeInTheDocument();
@@ -230,6 +247,7 @@ describe('Stock Operation form step 3 (stock submision)', () => {
230
247
  // On require aprooval should now show complete btn
231
248
  expect(screen.getByTestId('complete-button')).toBeInTheDocument();
232
249
  });
250
+
233
251
  it('should render dispatch btn for stock return operation and dont require aproval', async () => {
234
252
  render(
235
253
  <StockOperationForm
@@ -241,9 +259,9 @@ describe('Stock Operation form step 3 (stock submision)', () => {
241
259
  />,
242
260
  );
243
261
  // MOVE TO STEP 2
244
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
262
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
245
263
  // MOVE TO STEP3
246
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
264
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
247
265
 
248
266
  const noRadioButton = screen.getByRole('radio', { name: /no/i });
249
267
  expect(noRadioButton).toBeInTheDocument();
@@ -251,6 +269,7 @@ describe('Stock Operation form step 3 (stock submision)', () => {
251
269
  // On require aprooval should now show complete btn
252
270
  expect(screen.getByTestId('dipatch-button')).toBeInTheDocument();
253
271
  });
272
+
254
273
  it('should render dispatch btn for stock issue operation and dont require aproval', async () => {
255
274
  render(
256
275
  <StockOperationForm
@@ -262,14 +281,14 @@ describe('Stock Operation form step 3 (stock submision)', () => {
262
281
  />,
263
282
  );
264
283
  // MOVE TO STEP 2
265
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
284
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
266
285
  // MOVE TO STEP3
267
- await userEvent.click(screen.getByRole('button', { name: /Next/i }));
286
+ await userEvent.click(screen.getByRole('button', { name: /next/i }));
268
287
 
269
288
  const noRadioButton = screen.getByRole('radio', { name: /no/i });
270
289
  expect(noRadioButton).toBeInTheDocument();
271
290
  await userEvent.click(noRadioButton);
272
- // On require aprooval should now show complete btn
291
+ // On require approval should now show complete btn
273
292
  expect(screen.getByTestId('dipatch-button')).toBeInTheDocument();
274
293
  });
275
294
  });
@@ -1,3 +1,5 @@
1
+ import React, { useCallback, useId, useMemo } from 'react';
2
+ import { ArrowLeft, ArrowRight, Edit, TrashCan } from '@carbon/react/icons';
1
3
  import {
2
4
  Button,
3
5
  DataTable,
@@ -9,8 +11,6 @@ import {
9
11
  TableHeader,
10
12
  TableRow,
11
13
  } from '@carbon/react';
12
- import { ArrowLeft, ArrowRight, Edit, TrashCan } from '@carbon/react/icons';
13
- import React, { useCallback, useMemo } from 'react';
14
14
  import { useFormContext } from 'react-hook-form';
15
15
  import { useTranslation } from 'react-i18next';
16
16
  import { type StockOperationDTO } from '../../../core/api/types/stockOperation/StockOperationDTO';
@@ -42,6 +42,7 @@ const StockOperationItemsFormStep: React.FC<StockOperationItemsFormStepProps> =
42
42
  }) => {
43
43
  const { t } = useTranslation();
44
44
  const operationTypePermision = useOperationTypePermisions(stockOperationType);
45
+ const uniqueId = useId();
45
46
 
46
47
  const form = useFormContext<StockOperationItemDtoSchema>();
47
48
  const observableOperationItems = form.watch('stockOperationItems');
@@ -111,7 +112,7 @@ const StockOperationItemsFormStep: React.FC<StockOperationItemsFormStepProps> =
111
112
  );
112
113
 
113
114
  const tableRows = useMemo(() => {
114
- return observableOperationItems?.map((item) => {
115
+ return observableOperationItems?.map((item, index) => {
115
116
  const {
116
117
  batchNo,
117
118
  expiration,
@@ -124,7 +125,7 @@ const StockOperationItemsFormStep: React.FC<StockOperationItemsFormStepProps> =
124
125
  } = item;
125
126
 
126
127
  return {
127
- id: uuid,
128
+ id: uuid || `${uniqueId}-${index}`,
128
129
  item: stockItemUuid ? <StockOperationItemCell stockItemUuid={stockItemUuid} /> : '--',
129
130
  itemDetails: stockItemUuid ? <StockAvailability stockItemUuid={stockItemUuid} /> : '--',
130
131
  batch: (
@@ -178,7 +179,7 @@ const StockOperationItemsFormStep: React.FC<StockOperationItemsFormStepProps> =
178
179
  ),
179
180
  };
180
181
  });
181
- }, [observableOperationItems, onLaunchItemsForm, stockOperationType]);
182
+ }, [observableOperationItems, onLaunchItemsForm, stockOperationType, uniqueId]);
182
183
 
183
184
  const headerTitle = t('stockoperationItems', 'Stock operation items');
184
185