@openmrs/esm-stock-management-app 1.0.1-pre.635 → 1.0.1-pre.652

Sign up to get free protection for your applications and to get access to all the features.
@@ -85,9 +85,9 @@
85
85
  "initial": false,
86
86
  "entry": false,
87
87
  "recorded": false,
88
- "size": 1841242,
88
+ "size": 1841303,
89
89
  "sizes": {
90
- "javascript": 1841032,
90
+ "javascript": 1841093,
91
91
  "consume-shared": 210
92
92
  },
93
93
  "names": [],
@@ -101,7 +101,7 @@
101
101
  "auxiliaryFiles": [
102
102
  "173.js.map"
103
103
  ],
104
- "hash": "f2850dca772388be",
104
+ "hash": "be71504bcfe9203c",
105
105
  "childrenByOrder": {}
106
106
  },
107
107
  {
@@ -109,10 +109,10 @@
109
109
  "initial": true,
110
110
  "entry": true,
111
111
  "recorded": false,
112
- "size": 5509669,
112
+ "size": 5509730,
113
113
  "sizes": {
114
114
  "consume-shared": 252,
115
- "javascript": 5487719,
115
+ "javascript": 5487780,
116
116
  "share-init": 252,
117
117
  "runtime": 21446
118
118
  },
@@ -129,7 +129,7 @@
129
129
  "auxiliaryFiles": [
130
130
  "main.js.map"
131
131
  ],
132
- "hash": "c7d23e1193b201e4",
132
+ "hash": "05641415f286ec13",
133
133
  "childrenByOrder": {}
134
134
  },
135
135
  {
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-operation-dialog","component":"stockOperationDialog"},{"name":"import-bulk-stock-items","component":"importBulkStockItemsDialog"},{"name":"delete-stock-modal","component":"deleteStockModal"},{"name":"delete-stock-user-scope-modal","component":"deleteUserScopeModal"},{"name":"stock-management-app-menu-item","component":"stockManagementAppMenuItem","slot":"app-menu-item-slot","meta":{"name":" Stock Management"}},{"name":"delete-stock-rule-modal","component":"deleteStockRuleModal"},{"name":"delete-packaging-unit-modal","component":"deletePackagingUnitModal"},{"name":"delete-packaging-unit-button","component":"deletePackagingUnitButton"}],"pages":[{"component":"root","route":"stock-management"}],"version":"1.0.1-pre.635"}
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-operation-dialog","component":"stockOperationDialog"},{"name":"import-bulk-stock-items","component":"importBulkStockItemsDialog"},{"name":"delete-stock-modal","component":"deleteStockModal"},{"name":"delete-stock-user-scope-modal","component":"deleteUserScopeModal"},{"name":"stock-management-app-menu-item","component":"stockManagementAppMenuItem","slot":"app-menu-item-slot","meta":{"name":" Stock Management"}},{"name":"delete-stock-rule-modal","component":"deleteStockRuleModal"},{"name":"delete-packaging-unit-modal","component":"deletePackagingUnitModal"},{"name":"delete-packaging-unit-button","component":"deletePackagingUnitButton"}],"pages":[{"component":"root","route":"stock-management"}],"version":"1.0.1-pre.652"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-stock-management-app",
3
- "version": "1.0.1-pre.635",
3
+ "version": "1.0.1-pre.652",
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",
@@ -187,7 +187,7 @@ const StockItemsTableComponent: React.FC<StockItemsTableProps> = () => {
187
187
 
188
188
  <FilterStockItems filterType={isDrug} changeFilterType={setDrug} />
189
189
  <AddStockItemsBulktImportActionButton />
190
- <TableToolbarMenu>
190
+ <TableToolbarMenu data-testid="stock-items-menu">
191
191
  <TableToolbarAction onClick={handleRefresh}>Refresh</TableToolbarAction>
192
192
  </TableToolbarMenu>
193
193
  <AddStockItemActionButton />
@@ -0,0 +1,134 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor, within } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import StockItemsTableComponent from './stock-items-table.component';
5
+ import { useStockItemsPages } from './stock-items-table.resource';
6
+ import { handleMutate } from '../utils';
7
+ import { launchAddOrEditDialog } from './stock-item.utils';
8
+
9
+ jest.mock('./stock-items-table.resource', () => ({
10
+ useStockItemsPages: jest.fn(),
11
+ }));
12
+
13
+ jest.mock('../utils', () => ({
14
+ handleMutate: jest.fn(),
15
+ }));
16
+
17
+ jest.mock('./stock-item.utils', () => ({
18
+ launchAddOrEditDialog: jest.fn(),
19
+ }));
20
+
21
+ jest.mock('react-i18next', () => ({
22
+ useTranslation: () => ({
23
+ t: (key: string) => key,
24
+ }),
25
+ }));
26
+
27
+ describe('StockItemsTableComponent', () => {
28
+ const mockUseStockItemsPages = {
29
+ isLoading: false,
30
+ items: Array(25).fill(null).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
+ })),
40
+ totalCount: 25,
41
+ currentPageSize: 10,
42
+ setPageSize: jest.fn(),
43
+ pageSizes: [10, 20, 30],
44
+ currentPage: 1,
45
+ setCurrentPage: jest.fn(),
46
+ isDrug: '',
47
+ setDrug: jest.fn(),
48
+ setSearchString: jest.fn(),
49
+ };
50
+
51
+ beforeEach(() => {
52
+ jest.clearAllMocks();
53
+ (useStockItemsPages as jest.Mock).mockReturnValue(mockUseStockItemsPages);
54
+ });
55
+
56
+ it('renders initial state and UI elements correctly', async () => {
57
+ render(<StockItemsTableComponent />);
58
+ expect(screen.getByText('panelDescription')).toBeInTheDocument();
59
+ expect(screen.getByRole('searchbox')).toBeInTheDocument();
60
+
61
+ const user = userEvent.setup();
62
+ const menuButton = screen.getByTestId('stock-items-menu');
63
+ await user.click(menuButton);
64
+
65
+ await screen.findByText('Refresh');
66
+
67
+ expect(screen.getByText('type')).toBeInTheDocument();
68
+ expect(screen.getByText('genericName')).toBeInTheDocument();
69
+ expect(screen.getByText('commonName')).toBeInTheDocument();
70
+ });
71
+
72
+ it('displays skeleton loader when isLoading is true', () => {
73
+ (useStockItemsPages as jest.Mock).mockReturnValue({ ...mockUseStockItemsPages, isLoading: true });
74
+ render(<StockItemsTableComponent />);
75
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
76
+ });
77
+
78
+ it('renders table rows correctly based on items prop', () => {
79
+ render(<StockItemsTableComponent />);
80
+ expect(screen.getByText('Test Item 0')).toBeInTheDocument();
81
+ expect(screen.getAllByText('drug').length).toBeGreaterThan(0);
82
+ expect(screen.getAllByText('other').length).toBeGreaterThan(0);
83
+ });
84
+
85
+ it('updates search input and triggers search function', async () => {
86
+ render(<StockItemsTableComponent />);
87
+ const user = userEvent.setup();
88
+ const searchInput = screen.getByRole('searchbox');
89
+ await user.type(searchInput, 'test search');
90
+ await waitFor(() => {
91
+ expect(mockUseStockItemsPages.setSearchString).toHaveBeenCalledWith('test search');
92
+ }, { timeout: 2000 });
93
+ });
94
+
95
+ it('updates pagination when page or page size changes', async () => {
96
+ render(<StockItemsTableComponent />);
97
+ const user = userEvent.setup();
98
+ const nextPageButton = screen.getByLabelText('Next page');
99
+ await user.click(nextPageButton);
100
+ expect(mockUseStockItemsPages.setCurrentPage).toHaveBeenCalled();
101
+
102
+ const pageSizeSelect = screen.getByLabelText('Items per page:');
103
+ await user.selectOptions(pageSizeSelect, '20');
104
+ expect(mockUseStockItemsPages.setPageSize).toHaveBeenCalledWith(20);
105
+ });
106
+
107
+ it('triggers handleRefresh when refresh button is clicked', async () => {
108
+ render(<StockItemsTableComponent />);
109
+
110
+ const user = userEvent.setup();
111
+ const menuButton = screen.getByTestId('stock-items-menu');
112
+ expect(menuButton).toBeInTheDocument();
113
+ await user.click(menuButton);
114
+
115
+ const refreshButton = await screen.findByText('Refresh');
116
+ expect(refreshButton).toBeInTheDocument();
117
+ await user.click(refreshButton);
118
+ await waitFor(() => {
119
+ expect(handleMutate).toHaveBeenCalledWith(expect.stringContaining('/stockmanagement/stockitem'));
120
+ });
121
+ });
122
+
123
+ it('triggers launchAddOrEditDialog when edit button is clicked', async () => {
124
+ render(<StockItemsTableComponent />);
125
+ const user = userEvent.setup();
126
+ const editButtons = screen.getAllByLabelText('Edit Stock Item');
127
+ await user.click(editButtons[0]);
128
+ expect(launchAddOrEditDialog).toHaveBeenCalledWith(
129
+ expect.any(Function),
130
+ expect.objectContaining({ uuid: 'item-0' }),
131
+ true
132
+ );
133
+ });
134
+ });
@@ -0,0 +1,138 @@
1
+ import React from 'react';
2
+ import { render, waitFor, screen } from '@testing-library/react';
3
+ import StockOperationSubmission from './stock-operation-submission.component';
4
+ import { StockOperationDTO } from '../../core/api/types/stockOperation/StockOperationDTO';
5
+ import { StockOperationType } from '../../core/api/types/stockOperation/StockOperationType';
6
+ import { InitializeResult } from './types';
7
+ import userEvent from '@testing-library/user-event';
8
+
9
+ const mockOnGoBack = jest.fn();
10
+ const mockOnSave = jest.fn();
11
+ const mockOnComplete = jest.fn();
12
+ const mockOnSubmit = jest.fn();
13
+ const mockOnDispatch = jest.fn();
14
+ const defaultProps = {
15
+ canEdit: true,
16
+ locked: false,
17
+ model: { approvalRequired: null } as StockOperationDTO,
18
+ operation: {
19
+ name: 'Stock Issue',
20
+ description: 'Issuing stock',
21
+ operationType: 'stockissue',
22
+ hasSource: true,
23
+ sourceType: {},
24
+ hasDestination: true,
25
+ destinationType: {},
26
+ hasRecipient: false,
27
+ recipientRequired: false,
28
+ availableWhenReserved: false,
29
+ allowExpiredBatchNumbers: false,
30
+ stockOperationTypeLocationScopes: [],
31
+ } as StockOperationType,
32
+ setup: {
33
+ isNegativeQuantityAllowed: false,
34
+ requiresBatchUuid: false,
35
+ requiresActualBatchInfo: false,
36
+ isQuantityOptional: false,
37
+ } as InitializeResult,
38
+ actions: {
39
+ onGoBack: mockOnGoBack,
40
+ onSave: mockOnSave,
41
+ onComplete: mockOnComplete,
42
+ onSubmit: mockOnSubmit,
43
+ onDispatch: mockOnDispatch,
44
+ },
45
+ };
46
+
47
+ describe('StockOperationSubmission', () => {
48
+ let user;
49
+
50
+ beforeEach(() => {
51
+ jest.clearAllMocks();
52
+ user = userEvent.setup();
53
+ });
54
+
55
+ it('renders without crashing', () => {
56
+ render(<StockOperationSubmission {...defaultProps} />);
57
+ expect(screen.getByText(/does the transaction require approval/i)).toBeInTheDocument();
58
+ });
59
+
60
+ it('allows approval required to be selected', async () => {
61
+ render(<StockOperationSubmission {...defaultProps} />);
62
+ await user.click(screen.getByLabelText(/yes/i));
63
+ const yesRadioButton = screen.getByLabelText(/yes/i) as HTMLInputElement;
64
+ expect(yesRadioButton.checked).toBe(true);
65
+ });
66
+
67
+ it('allows approval not required to be selected', async () => {
68
+ render(<StockOperationSubmission {...defaultProps} />);
69
+ await user.click(screen.getByLabelText(/no/i));
70
+ const noRadioButton = screen.getByLabelText(/no/i) as HTMLInputElement;
71
+ expect(noRadioButton.checked).toBe(true);
72
+ });
73
+
74
+ it('calls onGoBack when Go Back button is clicked', async () => {
75
+ render(<StockOperationSubmission {...defaultProps} />);
76
+ await user.click(screen.getByText(/go back/i));
77
+ expect(mockOnGoBack).toHaveBeenCalled();
78
+ });
79
+
80
+ it('shows loading indicator when saving', async () => {
81
+ render(<StockOperationSubmission {...defaultProps} />);
82
+ await user.click(screen.getByText(/save/i));
83
+ await waitFor(() => {
84
+ expect(mockOnSave).toHaveBeenCalled();
85
+ });
86
+ });
87
+
88
+ it('calls onSave when save button is clicked', async () => {
89
+ render(<StockOperationSubmission {...defaultProps} />);
90
+ await user.click(screen.getByText(/save/i));
91
+ await waitFor(() => {
92
+ expect(mockOnSave).toHaveBeenCalled();
93
+ });
94
+ });
95
+
96
+ it('disables the save button while saving', async () => {
97
+ const mockOnSave = jest.fn<Promise<void>, [StockOperationDTO]>(
98
+ (model) => new Promise((resolve) => setTimeout(resolve, 500)),
99
+ );
100
+
101
+ const testProps = {
102
+ ...defaultProps,
103
+ actions: {
104
+ ...defaultProps.actions,
105
+ onSave: mockOnSave,
106
+ },
107
+ };
108
+
109
+ render(<StockOperationSubmission {...testProps} />);
110
+ const saveButton = screen.getByRole('button', { name: 'Save' });
111
+ await user.click(saveButton);
112
+
113
+ expect(saveButton).toBeDisabled();
114
+
115
+ await waitFor(() => {
116
+ expect(saveButton).not.toBeDisabled();
117
+ });
118
+ });
119
+
120
+ it('calls onComplete when Complete button is clicked', async () => {
121
+ render(<StockOperationSubmission {...defaultProps} />);
122
+ await user.click(screen.getByText(/complete/i));
123
+ await waitFor(() => {
124
+ expect(mockOnComplete).toHaveBeenCalled();
125
+ });
126
+ });
127
+
128
+ it('does not render buttons when locked', () => {
129
+ const lockedProps = {
130
+ ...defaultProps,
131
+ locked: true,
132
+ };
133
+
134
+ render(<StockOperationSubmission {...lockedProps} />);
135
+ expect(screen.queryByRole('button', { name: /save/i })).not.toBeInTheDocument();
136
+ expect(screen.queryByRole('button', { name: /complete/i })).not.toBeInTheDocument();
137
+ });
138
+ });
@@ -41,7 +41,7 @@ describe('StockSourcesAddOrUpdate', () => {
41
41
 
42
42
  it('renders correctly with model prop', () => {
43
43
  const model: StockSource = {
44
- uuid: '123', // Add this
44
+ uuid: '123',
45
45
  name: 'Test Source',
46
46
  acronym: 'TS',
47
47
  sourceType: {
@@ -72,7 +72,6 @@ describe('StockSourcesAddOrUpdate', () => {
72
72
  retiredBy: undefined,
73
73
  retireReason: '',
74
74
  },
75
- // Add these properties from BaseOpenmrsData
76
75
  creator: {
77
76
  uuid: 'creator-uuid',
78
77
  display: 'Creator Name',
@@ -115,13 +114,14 @@ describe('StockSourcesAddOrUpdate', () => {
115
114
 
116
115
  await user.type(screen.getByLabelText('Full Name'), 'New Source');
117
116
  await user.type(screen.getByLabelText('Acronym/Code'), 'NS');
118
- await user.type(screen.getByLabelText('Source Type'), 'type2');
117
+ await user.selectOptions(screen.getByLabelText('Source Type'), 'type2');
119
118
  await user.click(screen.getByText('Save'));
120
119
 
121
120
  expect(createOrUpdateStockSource).toHaveBeenCalledWith(
122
121
  expect.objectContaining({
123
122
  name: 'New Source',
124
123
  acronym: 'NS',
124
+ sourceType: expect.objectContaining({ uuid: 'type2' }),
125
125
  }),
126
126
  );
127
127
  });
@@ -168,4 +168,4 @@ describe('StockSourcesAddOrUpdate', () => {
168
168
 
169
169
  expect(closeOverlay).toHaveBeenCalled();
170
170
  });
171
- });
171
+ });