foreman-tasks 11.0.6 → 11.0.7

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/foreman_tasks/version.rb +1 -1
  3. data/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js +37 -9
  4. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js +19 -16
  5. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js +197 -71
  6. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap +24 -40
  7. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap +2 -16
  8. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalSelectors.js +0 -16
  9. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/GenericConfirmModal.js +70 -0
  10. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalSelectors.test.js +25 -14
  11. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalSelectors.test.js.snap +8 -7
  12. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/index.test.js +409 -0
  13. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/createBulkTaskModal.js +67 -0
  14. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/createTaskModal.js +51 -0
  15. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/index.js +73 -23
  16. data/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +5 -2
  17. data/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js +1 -1
  18. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +67 -11
  19. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +86 -6
  20. data/webpack/ForemanTasks/Components/common/ClickConfirmation/ClickConfirmation.test.js +225 -39
  21. data/webpack/ForemanTasks/Components/common/ClickConfirmation/index.js +136 -37
  22. metadata +6 -19
  23. data/webpack/ForemanTasks/Components/TaskActions/UnlockModals.js +0 -60
  24. data/webpack/ForemanTasks/Components/TaskActions/UnlockModals.test.js +0 -14
  25. data/webpack/ForemanTasks/Components/TaskActions/__snapshots__/UnlockModals.test.js.snap +0 -25
  26. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskButtons.test.js.snap +0 -212
  27. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModal.js +0 -83
  28. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalActions.js +0 -106
  29. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalReducer.js +0 -38
  30. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModal.test.js +0 -36
  31. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalActions.test.js +0 -205
  32. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalReducer.test.js +0 -27
  33. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModal.test.js.snap +0 -41
  34. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalReducer.test.js.snap +0 -19
  35. data/webpack/ForemanTasks/Components/common/ClickConfirmation/ClickConfirmation.scss +0 -9
  36. data/webpack/ForemanTasks/Components/common/ClickConfirmation/__snapshots__/ClickConfirmation.test.js.snap +0 -52
  37. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js +0 -2
  38. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js +0 -10
  39. data/webpack/__mocks__/foremanReact/components/ForemanModal/index.js +0 -18
@@ -1,4 +1,3 @@
1
- import { selectForemanTasks } from '../../../../ForemanTasksSelectors';
2
1
  import {
3
2
  selectTasksTableQuery,
4
3
  selectResults,
@@ -6,15 +5,7 @@ import {
6
5
  selectItemCount,
7
6
  selectAllRowsSelected,
8
7
  } from '../../TasksTableSelectors';
9
- import { RESUME_MODAL, CANCEL_MODAL } from '../../TasksTableConstants';
10
- import { FORCE_UNLOCK_MODAL } from '../../../TaskActions/TaskActionsConstants';
11
8
 
12
- export const selectCofirmModal = state =>
13
- selectForemanTasks(state).confirmModal || {};
14
-
15
- export const selectActionType = state => selectCofirmModal(state).actionType;
16
- export const selectActionText = state => selectCofirmModal(state).actionText;
17
- export const selectActionState = state => selectCofirmModal(state).actionState;
18
9
  export const selectClicked = state =>
19
10
  selectTasksTableQuery(state).clicked || {};
20
11
 
@@ -32,13 +23,6 @@ export const selectSelectedTasks = state => {
32
23
  };
33
24
 
34
25
  export const selectSelectedRowsLen = state => {
35
- if (
36
- [CANCEL_MODAL, RESUME_MODAL, FORCE_UNLOCK_MODAL].includes(
37
- selectActionType(state)
38
- )
39
- ) {
40
- return 1;
41
- }
42
26
  if (selectAllRowsSelected(state)) {
43
27
  return selectItemCount(state);
44
28
  }
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useDispatch } from 'react-redux';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import { Button, Modal, ModalVariant } from '@patternfly/react-core';
6
+
7
+ export const GenericConfirmModal = ({
8
+ isModalOpen,
9
+ setIsModalOpen,
10
+ title,
11
+ message,
12
+ onConfirm,
13
+ confirmButtonVariant,
14
+ ouiaIdPrefix,
15
+ }) => {
16
+ const dispatch = useDispatch();
17
+
18
+ const handleConfirm = () => {
19
+ const action = onConfirm();
20
+ if (action) {
21
+ dispatch(action);
22
+ }
23
+ setIsModalOpen(false);
24
+ };
25
+
26
+ return (
27
+ <Modal
28
+ title={title}
29
+ variant={ModalVariant.small}
30
+ isOpen={isModalOpen}
31
+ onClose={() => setIsModalOpen(false)}
32
+ ouiaId={`${ouiaIdPrefix}-modal`}
33
+ actions={[
34
+ <Button
35
+ ouiaId={`${ouiaIdPrefix}-modal-confirm-button`}
36
+ key="confirm"
37
+ className="confirm-button"
38
+ variant={confirmButtonVariant}
39
+ onClick={handleConfirm}
40
+ >
41
+ {__('Yes')}
42
+ </Button>,
43
+ <Button
44
+ ouiaId={`${ouiaIdPrefix}-modal-cancel-button`}
45
+ key="cancel"
46
+ variant="secondary"
47
+ onClick={() => setIsModalOpen(false)}
48
+ >
49
+ {__('No')}
50
+ </Button>,
51
+ ]}
52
+ >
53
+ {message}
54
+ </Modal>
55
+ );
56
+ };
57
+
58
+ GenericConfirmModal.propTypes = {
59
+ isModalOpen: PropTypes.bool.isRequired,
60
+ setIsModalOpen: PropTypes.func.isRequired,
61
+ title: PropTypes.string.isRequired,
62
+ message: PropTypes.string.isRequired,
63
+ onConfirm: PropTypes.func.isRequired,
64
+ confirmButtonVariant: PropTypes.string,
65
+ ouiaIdPrefix: PropTypes.string.isRequired,
66
+ };
67
+
68
+ GenericConfirmModal.defaultProps = {
69
+ confirmButtonVariant: 'primary',
70
+ };
@@ -1,8 +1,6 @@
1
1
  import { testSelectorsSnapshotWithFixtures } from '@theforeman/test';
2
2
  import {
3
- selectActionText,
4
- selectActionState,
5
- selectActionType,
3
+ selectClicked,
6
4
  selectSelectedTasks,
7
5
  selectSelectedRowsLen,
8
6
  } from '../ConfirmModalSelectors';
@@ -10,11 +8,6 @@ import { CANCEL_MODAL } from '../../../TasksTableConstants';
10
8
 
11
9
  const state = {
12
10
  foremanTasks: {
13
- confirmModal: {
14
- actionText: 'some-text',
15
- actionState: 'some-state',
16
- actionType: 'some-type',
17
- },
18
11
  tasksTable: {
19
12
  tasksTableContent: {
20
13
  results: [
@@ -28,26 +21,44 @@ const state = {
28
21
  ],
29
22
  itemCount: 10,
30
23
  },
31
- tasksTableQuery: { selectedRows: [1, 2, 3] },
24
+ tasksTableQuery: {
25
+ selectedRows: [1, 2, 3],
26
+ clicked: { taskId: '1', taskName: 'test-task' },
27
+ allRowsSelected: false,
28
+ },
32
29
  },
33
30
  },
34
31
  };
35
32
 
36
33
  const fixtures = {
37
- 'should select actionText': () => selectActionText(state),
38
- 'should select actionState': () => selectActionState(state),
39
- 'should select actionType': () => selectActionType(state),
34
+ 'should select clicked': () => selectClicked(state),
40
35
  'should select selectedTasks': () => selectSelectedTasks(state),
41
36
  'should select selectedRowsLen 1': () =>
42
37
  selectSelectedRowsLen({
43
38
  ...state,
44
- foremanTasks: { confirmModal: { actionType: CANCEL_MODAL } },
39
+ foremanTasks: {
40
+ tasksTable: {
41
+ ...state.foremanTasks.tasksTable,
42
+ tasksTableQuery: {
43
+ ...state.foremanTasks.tasksTable.tasksTableQuery,
44
+ clicked: { modalType: CANCEL_MODAL },
45
+ },
46
+ },
47
+ },
45
48
  }),
46
49
  'should select selectedRowsLen all': () => selectSelectedRowsLen(state),
47
50
  'should select selectedRowsLen some': () =>
48
51
  selectSelectedRowsLen({
49
52
  ...state,
50
- tasksTable: { tasksTableQuery: { allRowsSelected: true } },
53
+ foremanTasks: {
54
+ tasksTable: {
55
+ ...state.foremanTasks.tasksTable,
56
+ tasksTableQuery: {
57
+ ...state.foremanTasks.tasksTable.tasksTableQuery,
58
+ allRowsSelected: true,
59
+ },
60
+ },
61
+ },
51
62
  }),
52
63
  };
53
64
 
@@ -1,16 +1,17 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`TasksDashboard - Selectors should select actionState 1`] = `"some-state"`;
4
-
5
- exports[`TasksDashboard - Selectors should select actionText 1`] = `"some-text"`;
6
-
7
- exports[`TasksDashboard - Selectors should select actionType 1`] = `"some-type"`;
3
+ exports[`TasksDashboard - Selectors should select clicked 1`] = `
4
+ Object {
5
+ "taskId": "1",
6
+ "taskName": "test-task",
7
+ }
8
+ `;
8
9
 
9
- exports[`TasksDashboard - Selectors should select selectedRowsLen 1 1`] = `1`;
10
+ exports[`TasksDashboard - Selectors should select selectedRowsLen 1 1`] = `3`;
10
11
 
11
12
  exports[`TasksDashboard - Selectors should select selectedRowsLen all 1`] = `3`;
12
13
 
13
- exports[`TasksDashboard - Selectors should select selectedRowsLen some 1`] = `3`;
14
+ exports[`TasksDashboard - Selectors should select selectedRowsLen some 1`] = `0`;
14
15
 
15
16
  exports[`TasksDashboard - Selectors should select selectedTasks 1`] = `
16
17
  Array [
@@ -0,0 +1,409 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { Provider } from 'react-redux';
5
+ import { configureStore } from '@reduxjs/toolkit';
6
+ import {
7
+ CancelModal,
8
+ ResumeModal,
9
+ CancelSelectedModal,
10
+ ResumeSelectedModal,
11
+ ForceUnlockModal,
12
+ ForceUnlockSelectedModal,
13
+ } from '../index';
14
+
15
+ // Mock the action creators
16
+ jest.mock('../../../TasksTableActions', () => ({
17
+ cancelTask: jest.fn(() => ({ type: 'CANCEL_TASK' })),
18
+ resumeTask: jest.fn(() => ({ type: 'RESUME_TASK' })),
19
+ }));
20
+
21
+ jest.mock('../../../TasksBulkActions', () => ({
22
+ bulkCancelBySearch: jest.fn(() => ({ type: 'BULK_CANCEL_BY_SEARCH' })),
23
+ bulkCancelById: jest.fn(() => ({ type: 'BULK_CANCEL_BY_ID' })),
24
+ bulkResumeBySearch: jest.fn(() => ({ type: 'BULK_RESUME_BY_SEARCH' })),
25
+ bulkResumeById: jest.fn(() => ({ type: 'BULK_RESUME_BY_ID' })),
26
+ bulkForceUnlockBySearch: jest.fn(() => ({
27
+ type: 'BULK_FORCE_UNLOCK_BY_SEARCH',
28
+ })),
29
+ bulkForceUnlockById: jest.fn(() => ({ type: 'BULK_FORCE_UNLOCK_BY_ID' })),
30
+ }));
31
+
32
+ // Mock the selectors
33
+ jest.mock('../ConfirmModalSelectors', () => ({
34
+ selectClicked: jest.fn(() => ({ taskId: '123', taskName: 'Test Task' })),
35
+ selectSelectedTasks: jest.fn(() => [
36
+ { id: 1, name: 'Task 1' },
37
+ { id: 2, name: 'Task 2' },
38
+ ]),
39
+ selectSelectedRowsLen: jest.fn(() => 2),
40
+ }));
41
+
42
+ jest.mock('../../../TasksTableSelectors', () => ({
43
+ selectAllRowsSelected: jest.fn(() => false),
44
+ }));
45
+
46
+ // Create a mock store
47
+ const createMockStore = (initialState = {}) => {
48
+ return configureStore({
49
+ reducer: {
50
+ foremanTasks: (state = initialState, action) => state,
51
+ },
52
+ preloadedState: {
53
+ foremanTasks: initialState,
54
+ },
55
+ });
56
+ };
57
+
58
+ // Test wrapper component
59
+ const TestWrapper = ({ children, store }) => (
60
+ <Provider store={store}>{children}</Provider>
61
+ );
62
+
63
+ describe('ConfirmModal Components', () => {
64
+ const defaultProps = {
65
+ isModalOpen: true,
66
+ setIsModalOpen: jest.fn(),
67
+ url: '/api/tasks',
68
+ parentTaskID: 'parent-123',
69
+ };
70
+
71
+ const mockStore = createMockStore();
72
+
73
+ beforeEach(() => {
74
+ jest.clearAllMocks();
75
+ });
76
+
77
+ describe('CancelModal', () => {
78
+ it('renders with correct title and content', () => {
79
+ render(
80
+ <TestWrapper store={mockStore}>
81
+ <CancelModal {...defaultProps} />
82
+ </TestWrapper>
83
+ );
84
+
85
+ expect(screen.getByText('Cancel Task')).toBeInTheDocument();
86
+ expect(
87
+ screen.getByText(
88
+ /This will cancel task "Test Task", putting it in the stopped state/
89
+ )
90
+ ).toBeInTheDocument();
91
+ expect(screen.getByText('No')).toBeInTheDocument();
92
+ expect(screen.getByText('Yes')).toBeInTheDocument();
93
+ });
94
+
95
+ it('calls setIsModalOpen when cancel button is clicked', () => {
96
+ const setIsModalOpen = jest.fn();
97
+
98
+ render(
99
+ <TestWrapper store={mockStore}>
100
+ <CancelModal {...defaultProps} setIsModalOpen={setIsModalOpen} />
101
+ </TestWrapper>
102
+ );
103
+
104
+ const cancelButton = screen.getByRole('button', { name: 'No' });
105
+ fireEvent.click(cancelButton);
106
+
107
+ expect(setIsModalOpen).toHaveBeenCalledWith(false);
108
+ });
109
+
110
+ it('calls setIsModalOpen when confirm button is clicked', () => {
111
+ const setIsModalOpen = jest.fn();
112
+
113
+ render(
114
+ <TestWrapper store={mockStore}>
115
+ <CancelModal {...defaultProps} setIsModalOpen={setIsModalOpen} />
116
+ </TestWrapper>
117
+ );
118
+
119
+ const confirmButton = screen.getByRole('button', { name: 'Yes' });
120
+ fireEvent.click(confirmButton);
121
+
122
+ expect(setIsModalOpen).toHaveBeenCalledWith(false);
123
+ });
124
+
125
+ it('does not render when isModalOpen is false', () => {
126
+ render(
127
+ <TestWrapper store={mockStore}>
128
+ <CancelModal {...defaultProps} isModalOpen={false} />
129
+ </TestWrapper>
130
+ );
131
+
132
+ expect(screen.queryByText('Cancel Task')).not.toBeInTheDocument();
133
+ });
134
+ });
135
+
136
+ describe('ResumeModal', () => {
137
+ it('renders with correct title and content', () => {
138
+ render(
139
+ <TestWrapper store={mockStore}>
140
+ <ResumeModal {...defaultProps} />
141
+ </TestWrapper>
142
+ );
143
+
144
+ expect(screen.getByText('Resume Task')).toBeInTheDocument();
145
+ expect(
146
+ screen.getByText(
147
+ /This will resume task "Test Task", putting it in the running state/
148
+ )
149
+ ).toBeInTheDocument();
150
+ expect(screen.getByText('No')).toBeInTheDocument();
151
+ expect(screen.getByText('Yes')).toBeInTheDocument();
152
+ });
153
+
154
+ it('calls setIsModalOpen when cancel button is clicked', () => {
155
+ const setIsModalOpen = jest.fn();
156
+
157
+ render(
158
+ <TestWrapper store={mockStore}>
159
+ <ResumeModal {...defaultProps} setIsModalOpen={setIsModalOpen} />
160
+ </TestWrapper>
161
+ );
162
+
163
+ const cancelButton = screen.getByRole('button', { name: 'No' });
164
+ fireEvent.click(cancelButton);
165
+
166
+ expect(setIsModalOpen).toHaveBeenCalledWith(false);
167
+ });
168
+
169
+ it('calls setIsModalOpen when confirm button is clicked', () => {
170
+ const setIsModalOpen = jest.fn();
171
+
172
+ render(
173
+ <TestWrapper store={mockStore}>
174
+ <ResumeModal {...defaultProps} setIsModalOpen={setIsModalOpen} />
175
+ </TestWrapper>
176
+ );
177
+
178
+ const confirmButton = screen.getByRole('button', { name: 'Yes' });
179
+ fireEvent.click(confirmButton);
180
+
181
+ expect(setIsModalOpen).toHaveBeenCalledWith(false);
182
+ });
183
+ });
184
+
185
+ describe('CancelSelectedModal', () => {
186
+ const selectedProps = {
187
+ ...defaultProps,
188
+ uriQuery: { search: 'test' },
189
+ };
190
+
191
+ it('renders with correct title and content', () => {
192
+ render(
193
+ <TestWrapper store={mockStore}>
194
+ <CancelSelectedModal {...selectedProps} />
195
+ </TestWrapper>
196
+ );
197
+
198
+ expect(screen.getByText('Cancel Selected Tasks')).toBeInTheDocument();
199
+ expect(
200
+ screen.getByText(
201
+ /This will cancel 2 task\(s\), putting them in the stopped state/
202
+ )
203
+ ).toBeInTheDocument();
204
+ expect(screen.getByText('No')).toBeInTheDocument();
205
+ expect(screen.getByText('Yes')).toBeInTheDocument();
206
+ });
207
+
208
+ it('calls setIsModalOpen when cancel button is clicked', () => {
209
+ const setIsModalOpen = jest.fn();
210
+
211
+ render(
212
+ <TestWrapper store={mockStore}>
213
+ <CancelSelectedModal
214
+ {...selectedProps}
215
+ setIsModalOpen={setIsModalOpen}
216
+ />
217
+ </TestWrapper>
218
+ );
219
+
220
+ const cancelButton = screen.getByRole('button', { name: 'No' });
221
+ fireEvent.click(cancelButton);
222
+
223
+ expect(setIsModalOpen).toHaveBeenCalledWith(false);
224
+ });
225
+
226
+ it('calls setIsModalOpen when confirm button is clicked', () => {
227
+ const setIsModalOpen = jest.fn();
228
+
229
+ render(
230
+ <TestWrapper store={mockStore}>
231
+ <CancelSelectedModal
232
+ {...selectedProps}
233
+ setIsModalOpen={setIsModalOpen}
234
+ />
235
+ </TestWrapper>
236
+ );
237
+
238
+ const confirmButton = screen.getByRole('button', { name: 'Yes' });
239
+ fireEvent.click(confirmButton);
240
+
241
+ expect(setIsModalOpen).toHaveBeenCalledWith(false);
242
+ });
243
+ });
244
+
245
+ describe('ResumeSelectedModal', () => {
246
+ const selectedProps = {
247
+ ...defaultProps,
248
+ uriQuery: { search: 'test' },
249
+ };
250
+
251
+ it('renders with correct title and content', () => {
252
+ render(
253
+ <TestWrapper store={mockStore}>
254
+ <ResumeSelectedModal {...selectedProps} />
255
+ </TestWrapper>
256
+ );
257
+
258
+ expect(screen.getByText('Resume Selected Tasks')).toBeInTheDocument();
259
+ expect(
260
+ screen.getByText(
261
+ /This will resume 2 task\(s\), putting them in the running state/
262
+ )
263
+ ).toBeInTheDocument();
264
+ expect(screen.getByText('No')).toBeInTheDocument();
265
+ expect(screen.getByText('Yes')).toBeInTheDocument();
266
+ });
267
+
268
+ it('calls setIsModalOpen when cancel button is clicked', () => {
269
+ const setIsModalOpen = jest.fn();
270
+
271
+ render(
272
+ <TestWrapper store={mockStore}>
273
+ <ResumeSelectedModal
274
+ {...selectedProps}
275
+ setIsModalOpen={setIsModalOpen}
276
+ />
277
+ </TestWrapper>
278
+ );
279
+
280
+ const cancelButton = screen.getByRole('button', { name: 'No' });
281
+ fireEvent.click(cancelButton);
282
+
283
+ expect(setIsModalOpen).toHaveBeenCalledWith(false);
284
+ });
285
+ });
286
+
287
+ describe('ForceUnlockModal', () => {
288
+ it('renders with correct title and content', () => {
289
+ render(
290
+ <TestWrapper store={mockStore}>
291
+ <ForceUnlockModal {...defaultProps} />
292
+ </TestWrapper>
293
+ );
294
+
295
+ expect(screen.getByText('Force Unlock Task')).toBeInTheDocument();
296
+ expect(
297
+ screen.getByText(
298
+ /This will force unlock task "Test Task". This may cause harm and should be used with caution/
299
+ )
300
+ ).toBeInTheDocument();
301
+ expect(screen.getByText('No')).toBeInTheDocument();
302
+ expect(screen.getByText('Yes')).toBeInTheDocument();
303
+ });
304
+
305
+ it('calls setIsModalOpen when cancel button is clicked', () => {
306
+ const setIsModalOpen = jest.fn();
307
+
308
+ render(
309
+ <TestWrapper store={mockStore}>
310
+ <ForceUnlockModal {...defaultProps} setIsModalOpen={setIsModalOpen} />
311
+ </TestWrapper>
312
+ );
313
+
314
+ const cancelButton = screen.getByRole('button', { name: 'No' });
315
+ fireEvent.click(cancelButton);
316
+
317
+ expect(setIsModalOpen).toHaveBeenCalledWith(false);
318
+ });
319
+ });
320
+
321
+ describe('ForceUnlockSelectedModal', () => {
322
+ const selectedProps = {
323
+ ...defaultProps,
324
+ uriQuery: { search: 'test' },
325
+ };
326
+
327
+ it('renders with correct title and content', () => {
328
+ render(
329
+ <TestWrapper store={mockStore}>
330
+ <ForceUnlockSelectedModal {...selectedProps} />
331
+ </TestWrapper>
332
+ );
333
+
334
+ expect(
335
+ screen.getByText('Force Unlock Selected Tasks')
336
+ ).toBeInTheDocument();
337
+ expect(
338
+ screen.getByText(
339
+ /This will force unlock 2 task\(s\). This may cause harm and should be used with caution/
340
+ )
341
+ ).toBeInTheDocument();
342
+ expect(screen.getByText('No')).toBeInTheDocument();
343
+ expect(screen.getByText('Yes')).toBeInTheDocument();
344
+ });
345
+
346
+ it('calls setIsModalOpen when cancel button is clicked', () => {
347
+ const setIsModalOpen = jest.fn();
348
+
349
+ render(
350
+ <TestWrapper store={mockStore}>
351
+ <ForceUnlockSelectedModal
352
+ {...selectedProps}
353
+ setIsModalOpen={setIsModalOpen}
354
+ />
355
+ </TestWrapper>
356
+ );
357
+
358
+ const cancelButton = screen.getByRole('button', { name: 'No' });
359
+ fireEvent.click(cancelButton);
360
+
361
+ expect(setIsModalOpen).toHaveBeenCalledWith(false);
362
+ });
363
+ });
364
+
365
+ describe('Accessibility', () => {
366
+ it('has proper ARIA attributes for all modals', () => {
367
+ const { rerender } = render(
368
+ <TestWrapper store={mockStore}>
369
+ <CancelModal {...defaultProps} />
370
+ </TestWrapper>
371
+ );
372
+
373
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
374
+ expect(screen.getByRole('button', { name: 'No' })).toBeInTheDocument();
375
+ expect(screen.getByRole('button', { name: 'Yes' })).toBeInTheDocument();
376
+
377
+ // Test other modals
378
+ rerender(
379
+ <TestWrapper store={mockStore}>
380
+ <ResumeModal {...defaultProps} />
381
+ </TestWrapper>
382
+ );
383
+
384
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
385
+ expect(screen.getByRole('button', { name: 'No' })).toBeInTheDocument();
386
+ expect(screen.getByRole('button', { name: 'Yes' })).toBeInTheDocument();
387
+ });
388
+ });
389
+
390
+ describe('Modal Visibility', () => {
391
+ it('handles modal visibility correctly for all components', () => {
392
+ const { rerender } = render(
393
+ <TestWrapper store={mockStore}>
394
+ <CancelModal {...defaultProps} isModalOpen={false} />
395
+ </TestWrapper>
396
+ );
397
+
398
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
399
+
400
+ rerender(
401
+ <TestWrapper store={mockStore}>
402
+ <ResumeModal {...defaultProps} isModalOpen={false} />
403
+ </TestWrapper>
404
+ );
405
+
406
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
407
+ });
408
+ });
409
+ });
@@ -0,0 +1,67 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useSelector } from 'react-redux';
4
+ import { sprintf } from 'foremanReact/common/I18n';
5
+ import {
6
+ selectSelectedTasks,
7
+ selectSelectedRowsLen,
8
+ } from './ConfirmModalSelectors';
9
+ import { selectAllRowsSelected } from '../../TasksTableSelectors';
10
+ import { GenericConfirmModal } from './GenericConfirmModal';
11
+
12
+ export const createBulkTaskModal = ({
13
+ bulkActionBySearch,
14
+ bulkActionById,
15
+ title,
16
+ messageTemplate,
17
+ confirmButtonVariant = 'primary',
18
+ ouiaIdPrefix,
19
+ }) => {
20
+ const BulkTaskModal = ({
21
+ isModalOpen,
22
+ setIsModalOpen,
23
+ url,
24
+ uriQuery,
25
+ parentTaskID,
26
+ }) => {
27
+ const allRowsSelected = useSelector(selectAllRowsSelected);
28
+ const selectedTasks = useSelector(selectSelectedTasks);
29
+ const selectedRowsLen = useSelector(selectSelectedRowsLen);
30
+
31
+ const handleConfirm = () =>
32
+ allRowsSelected
33
+ ? bulkActionBySearch({ query: uriQuery, parentTaskID })
34
+ : bulkActionById({
35
+ selected: selectedTasks,
36
+ url,
37
+ parentTaskID,
38
+ });
39
+
40
+ return (
41
+ <GenericConfirmModal
42
+ isModalOpen={isModalOpen}
43
+ setIsModalOpen={setIsModalOpen}
44
+ title={title}
45
+ message={sprintf(messageTemplate, { number: selectedRowsLen })}
46
+ onConfirm={handleConfirm}
47
+ confirmButtonVariant={confirmButtonVariant}
48
+ ouiaIdPrefix={ouiaIdPrefix}
49
+ />
50
+ );
51
+ };
52
+
53
+ BulkTaskModal.propTypes = {
54
+ isModalOpen: PropTypes.bool.isRequired,
55
+ setIsModalOpen: PropTypes.func.isRequired,
56
+ url: PropTypes.string.isRequired,
57
+ uriQuery: PropTypes.object,
58
+ parentTaskID: PropTypes.string,
59
+ };
60
+
61
+ BulkTaskModal.defaultProps = {
62
+ uriQuery: {},
63
+ parentTaskID: null,
64
+ };
65
+
66
+ return BulkTaskModal;
67
+ };