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

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 (108) hide show
  1. package/.husky/pre-commit +1 -4
  2. package/__mocks__/react-i18next.js +9 -8
  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/467.js +1 -1
  16. package/dist/467.js.map +1 -1
  17. package/dist/574.js +1 -1
  18. package/dist/606.js +1 -1
  19. package/dist/606.js.map +1 -1
  20. package/dist/627.js +1 -0
  21. package/dist/627.js.map +1 -0
  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 +3 -6
  37. package/package.json +1 -1
  38. package/src/core/components/table/table.component.tsx +2 -2
  39. package/src/index.ts +5 -5
  40. package/src/stock-items/add-bulk-stock-item/add-stock-items-bulk-import-action-button.component.tsx +3 -3
  41. package/src/stock-items/add-bulk-stock-item/{stock-items-bulk-import.modal.tsx → stock-items-bulk-import.component.tsx} +19 -20
  42. package/src/stock-items/add-bulk-stock-item/stock-items-bulk-import.resource.ts +1 -1
  43. package/src/stock-items/add-bulk-stock-item/stock-items-bulk-import.test.tsx +59 -59
  44. package/src/stock-items/add-stock-item/add-stock-action-button.component.tsx +6 -6
  45. package/src/stock-items/add-stock-item/add-stock-item.component.tsx +4 -6
  46. package/src/stock-items/add-stock-item/add-stock-item.scss +0 -5
  47. package/src/stock-items/add-stock-item/add-stock-item.test.tsx +43 -28
  48. package/src/stock-items/add-stock-item/packaging-units/packaging-units-delete-modal.component.tsx +4 -3
  49. package/src/stock-items/add-stock-item/packaging-units/packaging-units.component.tsx +10 -9
  50. package/src/stock-items/add-stock-item/packaging-units/packaging-units.scss +4 -4
  51. package/src/stock-items/add-stock-item/stock-item-details/stock-item-details.component.tsx +19 -27
  52. package/src/stock-items/add-stock-item/stock-item-references/stock-item-references.scss +4 -4
  53. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.component.tsx +9 -15
  54. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.scss +0 -1
  55. package/src/stock-items/add-stock-item/stock-item-rules/delete-stock-rule-modal.component.tsx +1 -2
  56. package/src/stock-items/add-stock-item/stock-item-rules/stock-item-rules.component.tsx +16 -14
  57. package/src/stock-items/add-stock-item/stock-item-rules/stock-item-rules.scss +3 -7
  58. package/src/stock-items/add-stock-item/transactions/printout/transactions-print-bincard-preview.modal.tsx +6 -14
  59. package/src/stock-items/add-stock-item/transactions/printout/transactions-print-stockcard-preview.modal.tsx +8 -14
  60. package/src/stock-items/edit-stock-item/edit-stock-item-action-menu.component.tsx +2 -2
  61. package/src/stock-items/stock-item.utils.tsx +5 -3
  62. package/src/stock-items/stock-items-table.component.tsx +45 -47
  63. package/src/stock-items/stock-items-table.resource.ts +2 -2
  64. package/src/stock-items/stock-items-table.scss +1 -5
  65. package/src/stock-items/stock-items-table.test.tsx +65 -106
  66. package/src/stock-locations/location-admin-form.component.tsx +4 -5
  67. package/src/stock-locations/stock-locations-table.component.tsx +8 -10
  68. package/src/stock-lookups/stock-lookups.resource.ts +2 -3
  69. package/src/stock-operations/stock-operations-dialog/stock-operations-dialog.component.tsx +2 -2
  70. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.component.tsx +11 -11
  71. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.test.tsx +25 -115
  72. package/src/stock-operations/stock-operations-forms/input-components/qty-uim-selector.test.tsx +65 -107
  73. package/src/stock-operations/stock-operations-forms/input-components/quantity-uom-selector.component.tsx +9 -9
  74. package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.test.tsx +153 -35
  75. package/src/stock-operations/stock-operations-forms/input-components/user-selector.test.tsx +29 -82
  76. package/src/stock-operations/stock-operations-forms/step1.test.tsx +69 -204
  77. package/src/stock-operations/stock-operations-forms/step2.test.tsx +63 -140
  78. package/src/stock-operations/stock-operations-forms/step3.test.tsx +60 -79
  79. package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.component.tsx +5 -6
  80. package/src/stock-operations/stock-operations-forms/steps/stock-operation-submission-form-step.component.tsx +11 -12
  81. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.scss +0 -1
  82. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.workspace.tsx +12 -20
  83. package/src/stock-operations/stock-operations-forms/stock-operation-form.scss +0 -1
  84. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stepper.scss +3 -1
  85. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stock-operation-stepper.component.tsx +1 -2
  86. package/src/stock-reports/generate-report/create-stock-report.scss +2 -3
  87. package/src/stock-reports/generate-report/create-stock-report.workspace.tsx +25 -32
  88. package/src/stock-reports/report-list/stock-report-parameters.component.tsx +1 -1
  89. package/src/stock-reports/report-list/stock-report-status.component.tsx +1 -1
  90. package/src/stock-reports/report-list/stock-reports.component.tsx +25 -24
  91. package/src/stock-reports/report-list/stock-reports.scss +2 -10
  92. package/src/stock-sources/add-stock-sources/add-stock-sources.scss +4 -11
  93. package/src/stock-sources/add-stock-sources/add-stock-sources.test.tsx +36 -38
  94. package/src/stock-sources/add-stock-sources/add-stock-sources.workspace.tsx +30 -35
  95. package/src/stock-sources/delete-stock-modal.component.tsx +1 -2
  96. package/src/stock-sources/stock-sources-delete/stock-sources-delete.test.tsx +36 -27
  97. package/src/stock-sources/stock-sources-filter/stock-sources-filter.component.tsx +21 -33
  98. package/src/stock-sources/stock-sources-items-table.component.tsx +17 -16
  99. package/src/stock-sources/stock-sources-items-table.resource.ts +6 -8
  100. package/src/stock-sources/stock-sources-items-table.test.tsx +37 -60
  101. package/src/stock-sources/stock-sources.scss +2 -6
  102. package/src/stock-user-role-scopes/add-stock-user-scope/add-stock-user-role-scope.scss +13 -5
  103. package/src/stock-user-role-scopes/add-stock-user-scope/add-stock-user-role-scope.workspace.tsx +2 -2
  104. package/src/stock-user-role-scopes/delete-stock-user-scope-modal.component.tsx +1 -2
  105. package/translations/en.json +6 -5
  106. package/tsconfig.json +0 -4
  107. package/dist/33.js +0 -1
  108. package/dist/33.js.map +0 -1
@@ -1,13 +1,10 @@
1
1
  import React from 'react';
2
+ import { render, screen, waitFor, within } from '@testing-library/react';
2
3
  import userEvent from '@testing-library/user-event';
3
- import { render, screen, waitFor } from '@testing-library/react';
4
- import { type StockItemDTO } from '../core/api/types/stockItem/StockItem';
5
- import { handleMutate } from '../utils';
6
- import { launchAddOrEditStockItemWorkspace } from './stock-item.utils';
7
- import { useStockItemsPages } from './stock-items-table.resource';
8
4
  import StockItemsTableComponent from './stock-items-table.component';
9
-
10
- const mockUseStockItemsPages = jest.mocked(useStockItemsPages);
5
+ import { useStockItemsPages } from './stock-items-table.resource';
6
+ import { handleMutate } from '../utils';
7
+ import { launchAddOrStockItemWorkspace } from './stock-item.utils';
11
8
 
12
9
  jest.mock('./stock-items-table.resource', () => ({
13
10
  useStockItemsPages: jest.fn(),
@@ -18,146 +15,109 @@ jest.mock('../utils', () => ({
18
15
  }));
19
16
 
20
17
  jest.mock('./stock-item.utils', () => ({
21
- launchAddOrEditStockItemWorkspace: jest.fn(),
18
+ launchAddOrStockItemWorkspace: jest.fn(),
19
+ }));
20
+
21
+ jest.mock('react-i18next', () => ({
22
+ useTranslation: () => ({
23
+ t: (key: string) => key,
24
+ }),
22
25
  }));
23
26
 
24
27
  describe('StockItemsTableComponent', () => {
28
+ const mockUseStockItemsPages = {
29
+ isLoading: false,
30
+ items: Array(25)
31
+ .fill(null)
32
+ .map((_, index) => ({
33
+ uuid: `item-${index}`,
34
+ commonName: `Test Item ${index}`,
35
+ drugUuid: index % 2 === 0 ? `drug-${index}` : null,
36
+ conceptName: `Concept ${index}`,
37
+ dispensingUnitName: `Unit ${index}`,
38
+ defaultStockOperationsUoMName: `UoM ${index}`,
39
+ reorderLevel: index * 10,
40
+ reorderLevelUoMName: 'Units',
41
+ })),
42
+ totalCount: 25,
43
+ currentPageSize: 10,
44
+ setPageSize: jest.fn(),
45
+ pageSizes: [10, 20, 30],
46
+ currentPage: 1,
47
+ setCurrentPage: jest.fn(),
48
+ isDrug: '',
49
+ setDrug: jest.fn(),
50
+ setSearchString: jest.fn(),
51
+ };
52
+
25
53
  beforeEach(() => {
26
- mockUseStockItemsPages.mockReturnValue({
27
- isLoading: false,
28
- items: Array(25)
29
- .fill(null)
30
- .map((_, index) => ({
31
- uuid: `item-${index}`,
32
- commonName: `Test Item ${index}`,
33
- drugUuid: index % 2 === 0 ? `drug-${index}` : null,
34
- conceptName: `Concept ${index}`,
35
- dispensingUnitName: `Unit ${index}`,
36
- defaultStockOperationsUoMName: `UoM ${index}`,
37
- reorderLevel: index * 10,
38
- reorderLevelUoMName: 'Units',
39
- })) as StockItemDTO[],
40
- pagination: {
41
- results: [],
42
- totalPages: 3,
43
- currentPage: 1,
44
- paginated: true,
45
- showNextButton: true,
46
- showPreviousButton: false,
47
- goTo: jest.fn(),
48
- goToNext: jest.fn(),
49
- goToPrevious: jest.fn(),
50
- },
51
- error: null,
52
- totalCount: 25,
53
- currentPageSize: 10,
54
- setPageSize: jest.fn(),
55
- pageSizes: [10, 20, 30],
56
- currentPage: 1,
57
- setCurrentPage: jest.fn(),
58
- isDrug: '',
59
- setDrug: jest.fn(),
60
- setSearchString: jest.fn(),
61
- });
54
+ jest.clearAllMocks();
55
+ (useStockItemsPages as jest.Mock).mockReturnValue(mockUseStockItemsPages);
62
56
  });
63
57
 
64
58
  it('renders initial state and UI elements correctly', async () => {
59
+ render(<StockItemsTableComponent />);
60
+ expect(screen.getByText('panelDescription')).toBeInTheDocument();
61
+ expect(screen.getByRole('searchbox')).toBeInTheDocument();
62
+
65
63
  const user = userEvent.setup();
64
+ const menuButton = screen.getByTestId('stock-items-menu');
65
+ await user.click(menuButton);
66
66
 
67
- render(<StockItemsTableComponent />);
67
+ await screen.findByText('Refresh');
68
68
 
69
- expect(screen.getByRole('radio', { name: /all/i })).toBeInTheDocument();
70
- expect(screen.getByRole('radio', { name: /^pharmaceuticals$/i })).toBeInTheDocument();
71
- expect(screen.getByRole('radio', { name: /non pharmaceuticals/i })).toBeInTheDocument();
72
- expect(screen.getByRole('button', { name: /import/i })).toBeInTheDocument();
73
- expect(screen.getByRole('button', { name: /settings/i })).toBeInTheDocument();
74
- expect(screen.getByRole('searchbox', { name: /filter table/i })).toBeInTheDocument();
75
-
76
- const settingsMenuButton = screen.getByRole('button', { name: /settings/i });
77
- await user.click(settingsMenuButton);
78
- await screen.findByText(/refresh/i);
79
-
80
- expect(screen.getByRole('button', { name: /generic name/i })).toBeInTheDocument();
81
- expect(screen.getByRole('button', { name: /common name/i })).toBeInTheDocument();
82
- expect(screen.getByRole('button', { name: /dispensing uom/i })).toBeInTheDocument();
83
- expect(screen.getByRole('button', { name: /bulk packaging/i })).toBeInTheDocument();
84
- expect(screen.getByRole('button', { name: /reorder level/i })).toBeInTheDocument();
69
+ expect(screen.getByText('type')).toBeInTheDocument();
70
+ expect(screen.getByText('genericName')).toBeInTheDocument();
71
+ expect(screen.getByText('commonName')).toBeInTheDocument();
85
72
  });
86
73
 
87
74
  it('displays skeleton loader when isLoading is true', () => {
88
- mockUseStockItemsPages.mockReturnValue({
89
- ...mockUseStockItemsPages,
90
- isLoading: true,
91
- items: [],
92
- pagination: undefined,
93
- totalCount: 0,
94
- currentPageSize: 0,
95
- currentPage: 0,
96
- setCurrentPage: function (value: React.SetStateAction<number>): void {
97
- throw new Error('Function not implemented.');
98
- },
99
- setPageSize: function (value: React.SetStateAction<number>): void {
100
- throw new Error('Function not implemented.');
101
- },
102
- pageSizes: [],
103
- error: undefined,
104
- isDrug: '',
105
- setDrug: undefined,
106
- setSearchString: function (value: any): void {
107
- throw new Error('Function not implemented.');
108
- },
109
- });
110
-
75
+ (useStockItemsPages as jest.Mock).mockReturnValue({ ...mockUseStockItemsPages, isLoading: true });
111
76
  render(<StockItemsTableComponent />);
112
-
113
77
  expect(screen.getByRole('progressbar')).toBeInTheDocument();
114
78
  });
115
79
 
116
80
  it('renders table rows correctly based on items prop', () => {
117
81
  render(<StockItemsTableComponent />);
118
-
119
- expect(screen.getByRole('cell', { name: /test item 0/i })).toBeInTheDocument();
120
- expect(screen.getAllByRole('cell', { name: /drug/i }).length).toBeGreaterThan(0);
121
- expect(screen.getAllByRole('cell', { name: /other/i }).length).toBeGreaterThan(0);
82
+ expect(screen.getByText('Test Item 0')).toBeInTheDocument();
83
+ expect(screen.getAllByText('drug').length).toBeGreaterThan(0);
84
+ expect(screen.getAllByText('other').length).toBeGreaterThan(0);
122
85
  });
123
86
 
124
87
  it('updates search input and triggers search function', async () => {
125
- const user = userEvent.setup();
126
-
127
88
  render(<StockItemsTableComponent />);
128
-
89
+ const user = userEvent.setup();
129
90
  const searchInput = screen.getByRole('searchbox');
130
91
  await user.type(searchInput, 'test search');
131
92
  await waitFor(
132
93
  () => {
133
- expect(mockUseStockItemsPages().setSearchString).toHaveBeenCalledWith('test search');
94
+ expect(mockUseStockItemsPages.setSearchString).toHaveBeenCalledWith('test search');
134
95
  },
135
96
  { timeout: 2000 },
136
97
  );
137
98
  });
138
99
 
139
100
  it('updates pagination when page or page size changes', async () => {
140
- const user = userEvent.setup();
141
101
  render(<StockItemsTableComponent />);
142
-
143
- const nextPageButton = screen.getByLabelText(/next page/i);
102
+ const user = userEvent.setup();
103
+ const nextPageButton = screen.getByLabelText('Next page');
144
104
  await user.click(nextPageButton);
145
- expect(mockUseStockItemsPages().setCurrentPage).toHaveBeenCalled();
105
+ expect(mockUseStockItemsPages.setCurrentPage).toHaveBeenCalled();
146
106
 
147
- const pageSizeSelect = screen.getByLabelText(/items per page/i);
107
+ const pageSizeSelect = screen.getByLabelText('Items per page:');
148
108
  await user.selectOptions(pageSizeSelect, '20');
149
- expect(mockUseStockItemsPages().setPageSize).toHaveBeenCalledWith(20);
109
+ expect(mockUseStockItemsPages.setPageSize).toHaveBeenCalledWith(20);
150
110
  });
151
111
 
152
112
  it('triggers handleRefresh when refresh button is clicked', async () => {
153
- const user = userEvent.setup();
154
113
  render(<StockItemsTableComponent />);
155
114
 
115
+ const user = userEvent.setup();
156
116
  const menuButton = screen.getByTestId('stock-items-menu');
157
117
  expect(menuButton).toBeInTheDocument();
158
118
  await user.click(menuButton);
159
119
 
160
- const refreshButton = await screen.findByText(/refresh/i);
120
+ const refreshButton = await screen.findByText('Refresh');
161
121
  expect(refreshButton).toBeInTheDocument();
162
122
  await user.click(refreshButton);
163
123
  await waitFor(() => {
@@ -165,14 +125,13 @@ describe('StockItemsTableComponent', () => {
165
125
  });
166
126
  });
167
127
 
168
- it('triggers launchAddOrEditStockItemWorkspace when edit button is clicked', async () => {
169
- const user = userEvent.setup();
128
+ it('triggers launchAddOrEditDialog when edit button is clicked', async () => {
170
129
  render(<StockItemsTableComponent />);
171
-
172
- const editButtons = screen.getAllByLabelText(/edit stock item/i);
130
+ const user = userEvent.setup();
131
+ const editButtons = screen.getAllByLabelText('Edit Stock Item');
173
132
  await user.click(editButtons[0]);
174
133
 
175
- expect(launchAddOrEditStockItemWorkspace).toHaveBeenCalledWith(
134
+ expect(launchAddOrStockItemWorkspace).toHaveBeenCalledWith(
176
135
  expect.any(Function),
177
136
  expect.objectContaining({ uuid: 'item-0' }),
178
137
  );
@@ -5,20 +5,19 @@ import { zodResolver } from '@hookform/resolvers/zod';
5
5
  import {
6
6
  Button,
7
7
  ComposedModal,
8
- FilterableMultiSelect,
9
8
  FormGroup,
10
- InlineNotification,
11
9
  ModalBody,
12
10
  ModalFooter,
13
11
  ModalHeader,
14
12
  Stack,
15
13
  TextInput,
14
+ InlineNotification,
15
+ FilterableMultiSelect,
16
16
  } from '@carbon/react';
17
- import { getCoreTranslation } from '@openmrs/esm-framework';
18
17
  import { useTranslation } from 'react-i18next';
19
18
  import { type locationData } from '../stock-items/types';
20
- import { useLocationTags } from './stock-locations-table.resource';
21
19
  import styles from './stock-locations-table.scss';
20
+ import { useLocationTags } from './stock-locations-table.resource';
22
21
 
23
22
  const LocationAdministrationSchema = z.object({
24
23
  name: z.string().max(255),
@@ -147,7 +146,7 @@ const LocationAdministrationForm: React.FC<LocationAdministrationFormProps> = ({
147
146
  </ModalBody>
148
147
  <ModalFooter>
149
148
  <Button onClick={() => onModalChange(false)} kind="secondary">
150
- {getCoreTranslation('cancel')}
149
+ {t('cancel', 'Cancel')}
151
150
  </Button>
152
151
  <Button disabled={!isDirty} type="submit">
153
152
  <span>{t('save', 'Save')}</span>
@@ -1,4 +1,5 @@
1
1
  import React, { useState } from 'react';
2
+ import { useStockLocationPages } from './stock-locations-table.resource';
2
3
  import {
3
4
  Button,
4
5
  DataTableSkeleton,
@@ -7,16 +8,15 @@ import {
7
8
  TableToolbarSearch,
8
9
  Tile,
9
10
  } from '@carbon/react';
10
- import { Add } from '@carbon/react/icons';
11
- import { useTranslation } from 'react-i18next';
12
- import { mutate } from 'swr';
13
- import { restBaseUrl } from '@openmrs/esm-framework';
14
- import { handleMutate } from '../utils';
11
+ import styles from '../stock-items/stock-items-table.scss';
15
12
  import { ResourceRepresentation } from '../core/api/api';
16
- import { useStockLocationPages } from './stock-locations-table.resource';
17
13
  import DataList from '../core/components/table/table.component';
14
+ import { useTranslation } from 'react-i18next';
15
+ import { mutate } from 'swr';
18
16
  import NewLocationForm from './add-locations-form.workspace';
19
- import styles from '../stock-items/stock-items-table.scss';
17
+ import { Add } from '@carbon/react/icons';
18
+ import { handleMutate } from '../utils';
19
+ import { restBaseUrl } from '@openmrs/esm-framework';
20
20
 
21
21
  interface StockLocationsTableProps {
22
22
  status?: string;
@@ -46,9 +46,7 @@ const StockLocationsItems: React.FC<StockLocationsTableProps> = () => {
46
46
  <>
47
47
  <TableToolbarSearch persistent onChange={onInputChange} />
48
48
  <TableToolbarMenu>
49
- <TableToolbarAction className={styles.toolbarMenuAction} onClick={handleRefresh}>
50
- {t('refresh', 'Refresh')}
51
- </TableToolbarAction>
49
+ <TableToolbarAction onClick={handleRefresh}>Refresh</TableToolbarAction>
52
50
  </TableToolbarMenu>
53
51
  {showLocationModal ? (
54
52
  <NewLocationForm onModalChange={setAddLocationModal} showModal={showLocationModal} mutate={mutate} />
@@ -190,10 +190,9 @@ export function useConcept(conceptUuid: string) {
190
190
  data: Concept;
191
191
  },
192
192
  Error
193
- >(conceptUuid ? apiUrl : null, openmrsFetch);
194
-
193
+ >(apiUrl, openmrsFetch);
195
194
  return {
196
- items: data?.data,
195
+ items: data?.data || <Concept>{},
197
196
  isLoading,
198
197
  error,
199
198
  };
@@ -8,7 +8,7 @@ import {
8
8
  type StopOperationActionType,
9
9
  } from '../../core/api/types/stockOperation/StockOperationAction';
10
10
  import { executeStockOperationAction } from '../stock-operations.resource';
11
- import { getCoreTranslation, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
11
+ import { restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
12
12
  import { closeOverlay } from '../../core/components/overlay/hook';
13
13
  import { extractErrorMessagesFromResponse } from '../../constants';
14
14
  import { handleMutate } from '../../utils';
@@ -135,7 +135,7 @@ const StockOperationDialog: React.FC<StockOperationDialogProps> = ({ title, requ
135
135
  </ModalBody>
136
136
  <ModalFooter>
137
137
  <Button kind="secondary" onClick={closeModal}>
138
- {getCoreTranslation('cancel')}
138
+ {t('cancel', 'Cancel')}
139
139
  </Button>
140
140
  {isApproving ? <InlineLoading /> : <Button type="submit">{t('submit', 'Submit')}</Button>}
141
141
  </ModalFooter>
@@ -1,10 +1,10 @@
1
+ import { ComboBox, SelectSkeleton } from '@carbon/react';
1
2
  import React, { useEffect, useMemo } from 'react';
2
3
  import { useTranslation } from 'react-i18next';
3
- import { ComboBox, SelectSkeleton } from '@carbon/react';
4
4
  import { type StockBatchDTO } from '../../../core/api/types/stockItem/StockBatchDTO';
5
- import { formatForDatePicker } from '../../../constants';
6
5
  import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
7
6
  import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
7
+ import { formatForDatePicker } from '../../../constants';
8
8
 
9
9
  interface BatchNoSelectorProps {
10
10
  stockItemUuid: string;
@@ -48,23 +48,23 @@ const BatchNoSelector: React.FC<BatchNoSelectorProps> = ({ stockItemUuid, error,
48
48
 
49
49
  return (
50
50
  <ComboBox
51
+ style={{ flexGrow: '1' }}
52
+ titleText={t('batchNo', 'Batch')}
53
+ name={'stockBatchUuid'}
51
54
  id={'stockBatchUuid'}
52
- invalid={!!error}
53
- invalidText={error}
54
55
  items={filteredBatches || []}
56
+ onChange={(data: { selectedItem?: StockBatchDTO }) => {
57
+ onValueChange(data.selectedItem?.uuid);
58
+ }}
59
+ selectedItem={initialSelectedItem}
55
60
  itemToString={(s: StockBatchDTO) =>
56
61
  s?.batchNo
57
62
  ? `${s?.batchNo} | Qty: ${s?.quantity ?? 'Unknown'} | Expiry: ${formatForDatePicker(s.expiration)}`
58
63
  : ''
59
64
  }
60
- name={'stockBatchUuid'}
61
- onChange={(data: { selectedItem?: StockBatchDTO }) => {
62
- onValueChange(data.selectedItem?.uuid);
63
- }}
64
65
  placeholder={t('filter', "'Filter") + '...'}
65
- selectedItem={initialSelectedItem}
66
- style={{ flexGrow: '1' }}
67
- titleText={t('batchNo', 'Batch no.')}
66
+ invalid={error}
67
+ invalidText={error}
68
68
  />
69
69
  );
70
70
  };
@@ -1,127 +1,64 @@
1
- import React from 'react';
2
- import userEvent from '@testing-library/user-event';
3
1
  import { render, screen } from '@testing-library/react';
4
- import { type StockBatchDTO } from '../../../core/api/types/stockItem/StockBatchDTO';
2
+ import userEvent from '@testing-library/user-event';
3
+ import React from 'react';
5
4
  import { type StockItemInventory } from '../../../core/api/types/stockItem/StockItemInventory';
6
- import { formatForDatePicker } from '../../../constants';
7
5
  import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
8
6
  import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
9
7
  import BatchNoSelector from './batch-no-selector.component';
8
+ import { formatForDatePicker } from '../../../constants';
10
9
 
11
- jest.mock('../hooks/useStockItemBatchNumbers', () => ({
12
- useStockItemBatchNumbers: jest.fn(),
13
- }));
14
-
15
- jest.mock('../../../stock-items/add-stock-item/batch-information/batch-information.resource', () => ({
16
- useStockItemBatchInformationHook: jest.fn(),
10
+ jest.mock('../hooks/useStockItemBatchNumbers');
11
+ jest.mock('../../../stock-items/add-stock-item/batch-information/batch-information.resource');
12
+ jest.mock('react-i18next', () => ({
13
+ useTranslation: () => ({ t: (key: string) => key }),
17
14
  }));
18
15
 
19
- const mockUseStockItemBatchNumbers = jest.mocked(useStockItemBatchNumbers);
20
- const mockUseStockItemBatchInformationHook = jest.mocked(useStockItemBatchInformationHook);
16
+ const mockUseStockItemBatchNumbers = useStockItemBatchNumbers as jest.Mock;
17
+ const mockUseStockItemBatchInformationHook = useStockItemBatchInformationHook as jest.Mock;
21
18
 
22
19
  describe('BatchNoSelector', () => {
23
20
  const mockOnValueChange = jest.fn();
24
21
  const mockStockItemUuid = 'test-uuid';
25
22
  const mockExpiration = new Date();
26
-
27
23
  beforeEach(() => {
24
+ jest.clearAllMocks();
25
+
28
26
  mockUseStockItemBatchNumbers.mockReturnValue({
29
27
  isLoading: false,
30
28
  stockItemBatchNos: [
31
- {
32
- uuid: '1',
33
- batchNo: 'BATCH-001',
34
- quantity: '10',
35
- expiration: mockExpiration,
36
- stockItemUuid: 'item1',
37
- voided: false,
38
- },
39
- {
40
- uuid: '2',
41
- batchNo: 'BATCH-002',
42
- quantity: '20',
43
- expiration: mockExpiration,
44
- stockItemUuid: 'item2',
45
- voided: false,
46
- },
29
+ { uuid: '1', batchNo: 'BATCH-001', quantity: 10, expiration: mockExpiration },
30
+ { uuid: '2', batchNo: 'BATCH-002', quantity: 20, expiration: mockExpiration },
47
31
  ],
48
- setLimit: jest.fn(),
49
- setRepresentation: jest.fn(),
50
- setSearchString: jest.fn(),
51
32
  });
52
-
53
33
  mockUseStockItemBatchInformationHook.mockReturnValue({
54
34
  items: [
55
35
  { batchNumber: 'BATCH-001', quantity: 10 },
56
36
  { batchNumber: 'BATCH-002', quantity: 20 },
57
37
  ] as StockItemInventory[],
58
- totalCount: 2,
59
- currentPage: 1,
60
- currentPageSize: 10,
61
- setCurrentPage: jest.fn(),
62
- setStockBatchUuid: jest.fn(),
63
38
  setStockItemUuid: jest.fn(),
64
- setPageSize: jest.fn(),
65
- pageSizes: [10, 20, 50],
66
- isLoading: false,
67
- error: null,
68
- setLocationUuid: jest.fn(),
69
- setPartyUuid: jest.fn(),
70
- setSearchString: jest.fn(),
71
39
  });
72
40
  });
73
41
 
74
- it('should render a loading skeleton when "isLoading" is true', () => {
75
- mockUseStockItemBatchNumbers.mockReturnValue({
76
- isLoading: true,
77
- stockItemBatchNos: [] as StockBatchDTO[],
78
- setLimit: jest.fn(),
79
- setRepresentation: jest.fn(),
80
- setSearchString: jest.fn(),
81
- });
82
-
83
- mockUseStockItemBatchInformationHook.mockReturnValue({
84
- isLoading: true,
85
- items: [],
86
- setStockItemUuid: jest.fn(),
87
- totalCount: 0,
88
- currentPage: 1,
89
- currentPageSize: 10,
90
- setCurrentPage: jest.fn(),
91
- setStockBatchUuid: jest.fn(),
92
- setPageSize: jest.fn(),
93
- pageSizes: [10, 20, 50],
94
- error: null,
95
- setLocationUuid: jest.fn(),
96
- setPartyUuid: jest.fn(),
97
- setSearchString: jest.fn(),
98
- });
99
-
42
+ it('should render loading skeleton when isLoading is true', () => {
43
+ mockUseStockItemBatchNumbers.mockReturnValue({ isLoading: true, stockItemBatchNos: [] });
44
+ mockUseStockItemBatchInformationHook.mockReturnValue({ isLoading: true, items: [], setStockItemUuid: jest.fn() });
100
45
  render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
101
-
102
46
  expect(screen.getByRole('progressbar')).toBeInTheDocument();
103
47
  });
104
48
 
105
- it('should render a combobox with batch numbers', async () => {
49
+ it('should render combobox with batch numbers', async () => {
106
50
  render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
107
-
108
51
  expect(screen.getByRole('combobox')).toBeInTheDocument();
109
- expect(screen.getByText(/batch no./i)).toBeInTheDocument();
52
+ expect(screen.getByText('batchNo')).toBeInTheDocument();
110
53
  });
111
54
 
112
55
  it('should handle batch selection', async () => {
113
- const user = userEvent.setup();
114
-
115
56
  render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
116
-
117
57
  const combobox = screen.getByRole('combobox');
118
- await user.click(combobox);
119
- await user.type(combobox, 'BATCH-001');
120
-
58
+ await userEvent.click(combobox);
59
+ await userEvent.type(combobox, 'BATCH-001');
121
60
  const option = screen.getByText(`BATCH-001 | Qty: 10 | Expiry: ${formatForDatePicker(mockExpiration)}`);
122
-
123
- await user.click(option);
124
-
61
+ await userEvent.click(option);
125
62
  expect(mockOnValueChange).toHaveBeenCalledWith('1');
126
63
  });
127
64
 
@@ -134,45 +71,18 @@ describe('BatchNoSelector', () => {
134
71
  });
135
72
 
136
73
  it('should filter out batches with zero or undefined quantity', async () => {
137
- const user = userEvent.setup();
138
-
139
74
  mockUseStockItemBatchNumbers.mockReturnValue({
140
75
  isLoading: false,
141
76
  stockItemBatchNos: [
142
- {
143
- uuid: '1',
144
- batchNo: 'BATCH-001',
145
- quantity: '10',
146
- expiration: mockExpiration,
147
- stockItemUuid: 'item1',
148
- voided: false,
149
- },
150
- {
151
- uuid: '2',
152
- batchNo: 'BATCH-002',
153
- quantity: '0',
154
- expiration: mockExpiration,
155
- stockItemUuid: 'item2',
156
- voided: false,
157
- },
158
- {
159
- uuid: '3',
160
- batchNo: 'BATCH-003',
161
- quantity: undefined,
162
- expiration: mockExpiration,
163
- stockItemUuid: 'item3',
164
- voided: false,
165
- },
77
+ { uuid: '1', batchNo: 'BATCH-001', quantity: 10 },
78
+ { uuid: '2', batchNo: 'BATCH-002', quantity: 0 },
79
+ { uuid: '3', batchNo: 'BATCH-003', quantity: undefined },
166
80
  ],
167
- setLimit: jest.fn(),
168
- setRepresentation: jest.fn(),
169
- setSearchString: jest.fn(),
170
81
  });
171
82
 
172
83
  render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
173
-
174
84
  const combobox = screen.getByRole('combobox');
175
- await user.click(combobox);
85
+ await userEvent.click(combobox);
176
86
 
177
87
  expect(screen.queryByText('BATCH-002')).not.toBeInTheDocument();
178
88
  expect(screen.queryByText('BATCH-003')).not.toBeInTheDocument();