@openmrs/esm-stock-management-app 1.0.1-pre.684 → 1.0.1-pre.691

Sign up to get free protection for your applications and to get access to all the features.
@@ -134,10 +134,10 @@
134
134
  "initial": true,
135
135
  "entry": true,
136
136
  "recorded": false,
137
- "size": 5407714,
137
+ "size": 5407793,
138
138
  "sizes": {
139
139
  "consume-shared": 252,
140
- "javascript": 5385765,
140
+ "javascript": 5385844,
141
141
  "share-init": 252,
142
142
  "runtime": 21445
143
143
  },
@@ -154,7 +154,7 @@
154
154
  "auxiliaryFiles": [
155
155
  "main.js.map"
156
156
  ],
157
- "hash": "7584bde8978b83b7",
157
+ "hash": "b6ba4ea06ac17137",
158
158
  "childrenByOrder": {}
159
159
  },
160
160
  {
@@ -607,9 +607,9 @@
607
607
  "initial": false,
608
608
  "entry": false,
609
609
  "recorded": false,
610
- "size": 1748842,
610
+ "size": 1748921,
611
611
  "sizes": {
612
- "javascript": 1748632,
612
+ "javascript": 1748711,
613
613
  "consume-shared": 210
614
614
  },
615
615
  "names": [],
@@ -623,7 +623,7 @@
623
623
  "auxiliaryFiles": [
624
624
  "973.js.map"
625
625
  ],
626
- "hash": "b0ca032dbaa177cd",
626
+ "hash": "f060b14379d6e58f",
627
627
  "childrenByOrder": {}
628
628
  }
629
629
  ]
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"extensions":[{"name":"stock-nav-menu","slot":"stock-sidebar-slot","component":"stockNavMenu","online":true,"offline":true},{"name":"overview-db-link","slot":"stock-page-dashboard-slot","component":"stockOverviewLink","meta":{"name":"overview","slot":"overview-dashboard-slot","title":"overview"},"order":0,"online":true,"offline":true},{"name":"stock-overview-db","slot":"overview-dashboard-slot","component":"stockOverview"},{"name":"operations-db-link","slot":"stock-page-dashboard-slot","component":"stockOperationsLink","meta":{"name":"operations","slot":"operations-dashboard-slot","title":"operations"},"order":2,"online":true,"offline":true},{"name":"stock-operations-db","slot":"operations-dashboard-slot","component":"stockOperations"},{"name":"items-db-link","slot":"stock-page-dashboard-slot","component":"stockItemsLink","meta":{"name":"items","slot":"items-dashboard-slot","title":"items"},"order":1,"online":true,"offline":true},{"name":"stock-items-db","slot":"items-dashboard-slot","component":"stockItems"},{"name":"user-scopes-db-link","slot":"stock-page-dashboard-slot","component":"stockUserScopesLink","meta":{"name":"user-scopes","slot":"user-scopes-dashboard-slot","title":"user-scopes"},"order":3,"online":true,"offline":true},{"name":"stock-user-scopes-db","slot":"user-scopes-dashboard-slot","component":"stockUserScopes"},{"name":"sources-db-link","slot":"stock-page-dashboard-slot","component":"stockSourcesLink","meta":{"name":"sources","slot":"sources-dashboard-slot","title":"Sources"},"order":2,"online":true,"offline":true},{"name":"stock-sources-db","slot":"sources-dashboard-slot","component":"stockSources"},{"name":"locations-db-link","slot":"stock-page-dashboard-slot","component":"stockLocationsLink","meta":{"name":"locations","slot":"locations-dashboard-slot","title":"Locations"},"order":4,"online":true,"offline":true},{"name":"stock-locations-db","slot":"locations-dashboard-slot","component":"stockLocations"},{"name":"reports-db-link","slot":"stock-page-dashboard-slot","component":"stockReportsLink","meta":{"name":"reports","slot":"reports-dashboard-slot","title":"Reports"},"order":5,"online":true,"offline":true},{"name":"stock-reports-db","slot":"reports-dashboard-slot","component":"stockReports"},{"name":"settings-db-link","slot":"stock-page-dashboard-slot","component":"stockSettingsLink","meta":{"name":"settings","slot":"settings-dashboard-slot","title":"Settings"},"order":6,"online":true,"offline":true},{"name":"stock-settings-db","slot":"settings-dashboard-slot","component":"stockSettings"},{"name":"stock-management-admin-card-link","slot":"system-admin-page-card-link-slot","component":"stockManagementAdminCardLink"},{"name":"stock-management-app-menu-item","component":"stockManagementAppMenuItem","slot":"app-menu-item-slot","meta":{"name":" Stock Management"}},{"name":"delete-packaging-unit-button","component":"deletePackagingUnitButton"}],"modals":[{"name":"delete-stock-modal","component":"deleteStockModal"},{"name":"delete-stock-user-scope-modal","component":"deleteUserScopeModal"},{"name":"delete-stock-rule-modal","component":"deleteStockRuleModal"},{"name":"delete-packaging-unit-modal","component":"deletePackagingUnitModal"},{"name":"import-bulk-stock-items","component":"importBulkStockItemsModal"},{"name":"stock-operation-dialog","component":"stockOperationModal"}],"pages":[{"component":"root","route":"stock-management"}],"version":"1.0.1-pre.684"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"extensions":[{"name":"stock-nav-menu","slot":"stock-sidebar-slot","component":"stockNavMenu","online":true,"offline":true},{"name":"overview-db-link","slot":"stock-page-dashboard-slot","component":"stockOverviewLink","meta":{"name":"overview","slot":"overview-dashboard-slot","title":"overview"},"order":0,"online":true,"offline":true},{"name":"stock-overview-db","slot":"overview-dashboard-slot","component":"stockOverview"},{"name":"operations-db-link","slot":"stock-page-dashboard-slot","component":"stockOperationsLink","meta":{"name":"operations","slot":"operations-dashboard-slot","title":"operations"},"order":2,"online":true,"offline":true},{"name":"stock-operations-db","slot":"operations-dashboard-slot","component":"stockOperations"},{"name":"items-db-link","slot":"stock-page-dashboard-slot","component":"stockItemsLink","meta":{"name":"items","slot":"items-dashboard-slot","title":"items"},"order":1,"online":true,"offline":true},{"name":"stock-items-db","slot":"items-dashboard-slot","component":"stockItems"},{"name":"user-scopes-db-link","slot":"stock-page-dashboard-slot","component":"stockUserScopesLink","meta":{"name":"user-scopes","slot":"user-scopes-dashboard-slot","title":"user-scopes"},"order":3,"online":true,"offline":true},{"name":"stock-user-scopes-db","slot":"user-scopes-dashboard-slot","component":"stockUserScopes"},{"name":"sources-db-link","slot":"stock-page-dashboard-slot","component":"stockSourcesLink","meta":{"name":"sources","slot":"sources-dashboard-slot","title":"Sources"},"order":2,"online":true,"offline":true},{"name":"stock-sources-db","slot":"sources-dashboard-slot","component":"stockSources"},{"name":"locations-db-link","slot":"stock-page-dashboard-slot","component":"stockLocationsLink","meta":{"name":"locations","slot":"locations-dashboard-slot","title":"Locations"},"order":4,"online":true,"offline":true},{"name":"stock-locations-db","slot":"locations-dashboard-slot","component":"stockLocations"},{"name":"reports-db-link","slot":"stock-page-dashboard-slot","component":"stockReportsLink","meta":{"name":"reports","slot":"reports-dashboard-slot","title":"Reports"},"order":5,"online":true,"offline":true},{"name":"stock-reports-db","slot":"reports-dashboard-slot","component":"stockReports"},{"name":"settings-db-link","slot":"stock-page-dashboard-slot","component":"stockSettingsLink","meta":{"name":"settings","slot":"settings-dashboard-slot","title":"Settings"},"order":6,"online":true,"offline":true},{"name":"stock-settings-db","slot":"settings-dashboard-slot","component":"stockSettings"},{"name":"stock-management-admin-card-link","slot":"system-admin-page-card-link-slot","component":"stockManagementAdminCardLink"},{"name":"stock-management-app-menu-item","component":"stockManagementAppMenuItem","slot":"app-menu-item-slot","meta":{"name":" Stock Management"}},{"name":"delete-packaging-unit-button","component":"deletePackagingUnitButton"}],"modals":[{"name":"delete-stock-modal","component":"deleteStockModal"},{"name":"delete-stock-user-scope-modal","component":"deleteUserScopeModal"},{"name":"delete-stock-rule-modal","component":"deleteStockRuleModal"},{"name":"delete-packaging-unit-modal","component":"deletePackagingUnitModal"},{"name":"import-bulk-stock-items","component":"importBulkStockItemsModal"},{"name":"stock-operation-dialog","component":"stockOperationModal"}],"pages":[{"component":"root","route":"stock-management"}],"version":"1.0.1-pre.691"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-stock-management-app",
3
- "version": "1.0.1-pre.684",
3
+ "version": "1.0.1-pre.691",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Stock management microfrontend for OpenMRS 3.x",
6
6
  "browser": "dist/openmrs-esm-stock-management-app.js",
@@ -0,0 +1,192 @@
1
+ import React from 'react';
2
+ import { render, waitFor, screen, fireEvent } from '@testing-library/react';
3
+ import { showSnackbar } from '@openmrs/esm-framework';
4
+ import AddStockOperation from './add-stock-operation.component';
5
+ import { useInitializeStockOperations } from './add-stock-operation.resource';
6
+ import { useStockOperationTypes } from '../../stock-lookups/stock-lookups.resource';
7
+ import { getStockOperationLinks } from '../stock-operations.resource';
8
+ import { StockOperationDTO } from '../../core/api/types/stockOperation/StockOperationDTO';
9
+ import { StockOperationType } from '../../core/api/types/stockOperation/StockOperationType';
10
+ import { InitializeResult } from './types';
11
+ import { useStockOperations } from '../stock-operations.resource';
12
+ import { closeOverlay } from '../../core/components/overlay/hook';
13
+
14
+ jest.mock('react-i18next', () => ({
15
+ useTranslation: jest.fn().mockReturnValue({ t: (key) => key }),
16
+ }));
17
+
18
+ jest.mock('@openmrs/esm-framework', () => ({
19
+ ActionMenu: jest.fn(() => null),
20
+ showSnackbar: jest.fn(),
21
+ useDebounce: jest.fn((x) => x),
22
+ getGlobalStore: jest.fn(() => ({
23
+ getState: jest.fn(),
24
+ subscribe: jest.fn(),
25
+ setState: jest.fn(),
26
+ })),
27
+ parseDate: jest.fn((date) => new Date(date)),
28
+ showNotification: jest.fn(),
29
+ usePagination: jest.fn(() => ({ currentPage: 1, setPage: jest.fn() })),
30
+ useSession: jest.fn(() => ({ user: { display: 'Test User' } })),
31
+ }));
32
+
33
+ jest.mock('./add-stock-operation.resource', () => ({
34
+ useInitializeStockOperations: jest.fn(),
35
+ }));
36
+
37
+ jest.mock('../../stock-lookups/stock-lookups.resource', () => ({
38
+ useStockOperationTypes: jest.fn(),
39
+ useUsers: jest.fn().mockReturnValue({ items: { results: [] }, isLoading: false }),
40
+ }));
41
+
42
+ jest.mock('../stock-operations.resource', () => ({
43
+ operationStatusColor: jest.fn(() => 'some-color'),
44
+ getStockOperationLinks: jest.fn(),
45
+ useStockOperations: jest.fn().mockReturnValue({
46
+ items: { results: [] },
47
+ isLoading: false,
48
+ error: null,
49
+ }),
50
+ }));
51
+
52
+ jest.mock('../../core/components/overlay/hook', () => ({
53
+ closeOverlay: jest.fn(),
54
+ }));
55
+
56
+ jest.mock('../../stock-items/stock-items.resource', () => ({
57
+ useStockItems: jest.fn().mockReturnValue({
58
+ isLoading: false,
59
+ error: null,
60
+ items: {
61
+ results: [{ uuid: 'mock-uuid', packagingUomName: 'Mock Unit', factor: 1 }],
62
+ },
63
+ }),
64
+ useStockItem: jest.fn(),
65
+ }));
66
+
67
+ const mockOnGoBack = jest.fn();
68
+ const mockOnSave = jest.fn();
69
+ const mockOnComplete = jest.fn();
70
+ const mockOnSubmit = jest.fn();
71
+ const mockOnDispatch = jest.fn();
72
+
73
+ const mockProps = {
74
+ stockOperations: { results: [] },
75
+ uuid: 'some-mock-uuid',
76
+ isEditing: false,
77
+ canPrint: false,
78
+ canEdit: true,
79
+ locked: false,
80
+ model: {
81
+ approvalRequired: null,
82
+ uuid: 'mock-uuid',
83
+ operationType: 'mock-operation-type',
84
+ status: 'COMPLETED',
85
+ dateCreated: '2023-01-01',
86
+ creatorFamilyName: 'Doe',
87
+ creatorGivenName: 'John',
88
+ } as unknown as StockOperationDTO,
89
+ operation: {
90
+ name: 'Stock Issue',
91
+ description: 'Issuing stock',
92
+ operationType: 'stockissue',
93
+ hasSource: true,
94
+ sourceType: {},
95
+ hasDestination: true,
96
+ destinationType: {},
97
+ hasRecipient: false,
98
+ recipientRequired: false,
99
+ availableWhenReserved: false,
100
+ allowExpiredBatchNumbers: false,
101
+ stockOperationTypeLocationScopes: [],
102
+ } as StockOperationType,
103
+ setup: {
104
+ isNegativeQuantityAllowed: false,
105
+ requiresBatchUuid: false,
106
+ requiresActualBatchInfo: false,
107
+ isQuantityOptional: false,
108
+ } as InitializeResult,
109
+ actions: {
110
+ onGoBack: mockOnGoBack,
111
+ onSave: mockOnSave,
112
+ onComplete: mockOnComplete,
113
+ onSubmit: mockOnSubmit,
114
+ onDispatch: mockOnDispatch,
115
+ },
116
+ dto: {
117
+ results: [],
118
+ },
119
+ };
120
+
121
+ describe('AddStockOperation', () => {
122
+ beforeEach(() => {
123
+ (useInitializeStockOperations as jest.Mock).mockReturnValue({
124
+ isLoading: true,
125
+ error: null,
126
+ item: {},
127
+ result: {
128
+ uuid: 'some-valid-uuid-value',
129
+ },
130
+ });
131
+ const mockStockOperationTypes = { results: [] };
132
+ (useStockOperationTypes as jest.Mock).mockReturnValue(mockStockOperationTypes);
133
+ (getStockOperationLinks as jest.Mock).mockResolvedValue({ data: { results: [] } });
134
+ (useStockOperations as jest.Mock).mockReturnValue({ items: { results: [] }, isLoading: false, error: null });
135
+ });
136
+
137
+ it('displays error state correctly', async () => {
138
+ (useInitializeStockOperations as jest.Mock).mockReturnValue({
139
+ isLoading: false,
140
+ error: true,
141
+ result: null,
142
+ });
143
+ const { result } = useInitializeStockOperations(mockProps);
144
+ if (!result) {
145
+ return null;
146
+ }
147
+ render(<AddStockOperation {...mockProps} />);
148
+ expect(showSnackbar).toHaveBeenCalledWith(
149
+ expect.objectContaining({
150
+ kind: 'error',
151
+ title: 'error',
152
+ }),
153
+ );
154
+ });
155
+
156
+ it('displays loading state correctly', async () => {
157
+ (useInitializeStockOperations as jest.Mock).mockReturnValue({
158
+ isLoading: true,
159
+ error: null,
160
+ result: null,
161
+ });
162
+ render(<AddStockOperation {...mockProps} />);
163
+ expect(showSnackbar).not.toHaveBeenCalled();
164
+ });
165
+
166
+ it('displays error state and shows snackbar', () => {
167
+ (useInitializeStockOperations as jest.Mock).mockReturnValue({ isLoading: false, error: true, result: null });
168
+ render(<AddStockOperation {...mockProps} />);
169
+ expect(showSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'error' }));
170
+ });
171
+
172
+ it('calls external utilities correctly', async () => {
173
+ (useInitializeStockOperations as jest.Mock).mockReturnValue({
174
+ isLoading: false,
175
+ error: true,
176
+ result: null,
177
+ });
178
+
179
+ render(<AddStockOperation {...mockProps} />);
180
+ await waitFor(() => {
181
+ expect(closeOverlay).toHaveBeenCalled();
182
+ });
183
+ await waitFor(() => {
184
+ expect(showSnackbar).toHaveBeenCalledWith(
185
+ expect.objectContaining({
186
+ kind: 'error',
187
+ title: 'error',
188
+ }),
189
+ );
190
+ });
191
+ });
192
+ });
@@ -65,7 +65,7 @@ const BatchNoSelector = <T,>(props: BatchNoSelectorProps<T>) => {
65
65
  }
66
66
  }, [isLoading, stockItemBatchNos, props.selectedItem, filteredBatches]);
67
67
 
68
- if (isLoading) return <InlineLoading status="active" />;
68
+ if (isLoading) return <InlineLoading status="active" data-testid="loading" />;
69
69
 
70
70
  return (
71
71
  <div style={{ display: 'flex', flexDirection: 'column' }}>
@@ -88,9 +88,7 @@ const BatchNoSelector = <T,>(props: BatchNoSelectorProps<T>) => {
88
88
  onChange(data.selectedItem?.uuid);
89
89
  }}
90
90
  initialSelectedItem={initialSelectedItem}
91
- itemToString={(s: StockBatchDTO) =>
92
- s?.batchNo ? `${s?.batchNo} | Qty: ${s?.quantity ?? "Unknown"}` : ""
93
- }
91
+ itemToString={(s: StockBatchDTO) => (s?.batchNo ? `${s?.batchNo} | Qty: ${s?.quantity ?? 'Unknown'}` : '')}
94
92
  placeholder={props.placeholder}
95
93
  invalid={props.invalid}
96
94
  invalidText={props.invalidText}
@@ -99,7 +97,11 @@ const BatchNoSelector = <T,>(props: BatchNoSelectorProps<T>) => {
99
97
  )}
100
98
  />
101
99
  {isLoading && <InlineLoading status="active" />}
102
- {validationMessage && <div style={{ color: 'red', marginTop: '8px' }}>{t(validationMessage)}</div>}
100
+ {validationMessage && (
101
+ <div data-testid="validation-message" style={{ color: 'red', marginTop: '8px' }}>
102
+ {t(validationMessage)}
103
+ </div>
104
+ )}
103
105
  </div>
104
106
  );
105
107
  };
@@ -1,10 +1,101 @@
1
1
  import React from 'react';
2
-
3
- import { render } from '@testing-library/react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { useForm, FormProvider } from 'react-hook-form';
4
+ import { useStockItemBatchNos } from './batch-no-selector.resource';
5
+ import { useStockItemBatchInformationHook } from '../../stock-items/add-stock-item/batch-information/batch-information.resource';
4
6
  import BatchNoSelector from './batch-no-selector.component';
7
+ import { StockBatchDTO } from '../../core/api/types/stockItem/StockBatchDTO';
8
+ import userEvent from '@testing-library/user-event';
9
+ import { waitFor } from '@testing-library/react';
10
+
11
+ // Mock hooks
12
+ jest.mock('./batch-no-selector.resource');
13
+ jest.mock('../../stock-items/add-stock-item/batch-information/batch-information.resource');
14
+ jest.mock('react-i18next', () => ({
15
+ useTranslation: () => ({ t: (key: string) => key }),
16
+ }));
17
+
18
+ const mockUseStockItemBatchNos = useStockItemBatchNos as jest.Mock;
19
+ const mockUseStockItemBatchInformationHook = useStockItemBatchInformationHook as jest.Mock;
20
+
21
+ describe('BatchNoSelector Component', () => {
22
+ const stockItemUuid = 'test-uuid';
23
+ const batchUuid = 'batch-uuid';
24
+ const mockStockItemBatchNos: StockBatchDTO[] = [
25
+ { uuid: '1', batchNo: 'batch1', quantity: '10', expiration: new Date(), stockItemUuid: '', voided: false },
26
+ { uuid: '2', batchNo: 'batch2', quantity: '20', expiration: new Date(), stockItemUuid: '', voided: false },
27
+ ];
28
+ const mockBatchInformation = [
29
+ { batchNumber: 'batch1', quantity: '10' },
30
+ { batchNumber: 'batch2', quantity: '20' },
31
+ ];
32
+
33
+ beforeEach(() => {
34
+ mockUseStockItemBatchNos.mockReturnValue({
35
+ isLoading: false,
36
+ stockItemBatchNos: mockStockItemBatchNos,
37
+ });
38
+ mockUseStockItemBatchInformationHook.mockReturnValue({
39
+ items: mockBatchInformation,
40
+ setStockItemUuid: jest.fn(),
41
+ });
42
+ });
43
+
44
+ const renderComponent = () => {
45
+ const Wrapper = () => {
46
+ const methods = useForm();
47
+ return (
48
+ <FormProvider {...methods}>
49
+ <BatchNoSelector
50
+ stockItemUuid={stockItemUuid}
51
+ batchUuid={batchUuid}
52
+ controllerName="batchNo"
53
+ name="batchNo"
54
+ control={methods.control}
55
+ />
56
+ </FormProvider>
57
+ );
58
+ };
59
+ render(<Wrapper />);
60
+ };
61
+
62
+ test('should render without crashing', () => {
63
+ renderComponent();
64
+ expect(screen.getByRole('combobox')).toBeInTheDocument();
65
+ });
66
+
67
+ test('should display loading state when isLoading is true', () => {
68
+ mockUseStockItemBatchNos.mockReturnValueOnce({
69
+ isLoading: true,
70
+ stockItemBatchNos: [],
71
+ });
72
+ renderComponent();
73
+ expect(screen.getByTestId('loading')).toBeInTheDocument();
74
+ });
75
+
76
+ test('should call onBatchNoChanged when a batch is selected', async () => {
77
+ const onBatchNoChanged = jest.fn();
78
+ const Wrapper = () => {
79
+ const methods = useForm();
80
+ return (
81
+ <FormProvider {...methods}>
82
+ <BatchNoSelector
83
+ stockItemUuid={stockItemUuid}
84
+ batchUuid={batchUuid}
85
+ controllerName="batchNo"
86
+ name="batchNo"
87
+ control={methods.control}
88
+ onBatchNoChanged={onBatchNoChanged}
89
+ />
90
+ </FormProvider>
91
+ );
92
+ };
93
+ render(<Wrapper />);
94
+
95
+ const combobox = screen.getByRole('combobox');
96
+ await userEvent.click(combobox);
97
+ await userEvent.click(screen.getByText((content) => content.includes('batch1')));
5
98
 
6
- describe('Test the batch no selector', () => {
7
- it(`renders without dying`, () => {
8
- // render(<BatchNoSelector />);
99
+ expect(onBatchNoChanged).toHaveBeenCalledWith(mockStockItemBatchNos[0]);
9
100
  });
10
101
  });