foreman_remote_execution 7.2.1 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_ci.yml +2 -2
  3. data/app/controllers/ui_job_wizard_controller.rb +15 -0
  4. data/app/helpers/remote_execution_helper.rb +1 -1
  5. data/app/models/job_invocation.rb +2 -4
  6. data/app/models/job_invocation_composer.rb +5 -2
  7. data/app/views/templates/script/package_action.erb +8 -3
  8. data/config/routes.rb +3 -1
  9. data/lib/foreman_remote_execution/engine.rb +5 -5
  10. data/lib/foreman_remote_execution/version.rb +1 -1
  11. data/test/functional/api/v2/job_invocations_controller_test.rb +8 -0
  12. data/test/helpers/remote_execution_helper_test.rb +4 -0
  13. data/test/unit/job_invocation_report_template_test.rb +1 -1
  14. data/test/unit/job_invocation_test.rb +1 -2
  15. data/webpack/JobWizard/JobWizard.js +154 -20
  16. data/webpack/JobWizard/JobWizard.scss +43 -1
  17. data/webpack/JobWizard/JobWizardConstants.js +11 -1
  18. data/webpack/JobWizard/JobWizardPageRerun.js +112 -0
  19. data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +79 -0
  20. data/webpack/JobWizard/__tests__/fixtures.js +73 -0
  21. data/webpack/JobWizard/__tests__/integration.test.js +17 -3
  22. data/webpack/JobWizard/autofill.js +8 -1
  23. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +36 -17
  24. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -3
  25. data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -3
  26. data/webpack/JobWizard/steps/Schedule/PurposeField.js +1 -3
  27. data/webpack/JobWizard/steps/Schedule/QueryType.js +33 -40
  28. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +55 -16
  29. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +19 -56
  30. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
  31. data/webpack/JobWizard/steps/Schedule/ScheduleFuture.js +126 -0
  32. data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +287 -0
  33. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +88 -20
  34. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +206 -186
  35. data/webpack/JobWizard/steps/form/DateTimePicker.js +23 -6
  36. data/webpack/JobWizard/steps/form/Formatter.js +7 -8
  37. data/webpack/JobWizard/submit.js +8 -3
  38. data/webpack/Routes/routes.js +8 -2
  39. data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +1 -0
  40. data/webpack/react_app/components/HostKebab/KebabItems.js +0 -1
  41. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +0 -5
  42. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +59 -51
  43. data/webpack/react_app/extend/Fills.js +4 -4
  44. metadata +8 -6
  45. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -106
  46. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -32
  47. data/webpack/JobWizard/steps/Schedule/index.js +0 -178
@@ -1,28 +1,96 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { FormGroup, Radio } from '@patternfly/react-core';
3
+ import { Form, FormGroup, Radio, Divider } from '@patternfly/react-core';
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
+ import {
6
+ WIZARD_TITLES,
7
+ SCHEDULE_TYPES,
8
+ repeatTypes,
9
+ } from '../../JobWizardConstants';
10
+ import { WizardTitle } from '../form/WizardTitle';
11
+ import { QueryType } from './QueryType';
5
12
 
6
- export const ScheduleType = ({ isFuture, setIsFuture }) => (
7
- <FormGroup label={__('Schedule type')} fieldId="schedule-type">
8
- <Radio
9
- isChecked={!isFuture}
10
- name="schedule-type"
11
- onChange={() => setIsFuture(false)}
12
- id="schedule-type-now"
13
- label={__('Execute now')}
14
- />
15
- <Radio
16
- isChecked={isFuture}
17
- name="schedule-type"
18
- onChange={() => setIsFuture(true)}
19
- id="schedule-type-future"
20
- label={__('Schedule for future execution')}
21
- />
22
- </FormGroup>
13
+ export const ScheduleType = ({
14
+ scheduleType,
15
+ isTypeStatic,
16
+ setScheduleValue,
17
+ setValid,
18
+ }) => (
19
+ <div className="schedule-tab">
20
+ <WizardTitle title={WIZARD_TITLES.schedule} />
21
+ <Form>
22
+ <FormGroup
23
+ fieldId="schedule-type-now"
24
+ label={__('Select the type of execution')}
25
+ >
26
+ <Radio
27
+ isChecked={scheduleType === SCHEDULE_TYPES.NOW}
28
+ name="schedule-type-now"
29
+ id="schedule-type-now"
30
+ onChange={() => {
31
+ setScheduleValue(current => ({
32
+ ...current,
33
+ scheduleType: SCHEDULE_TYPES.NOW,
34
+ repeatType: repeatTypes.noRepeat,
35
+ startsAt: null,
36
+ startsBefore: null,
37
+ }));
38
+ setValid(true);
39
+ }}
40
+ label={__('Immediate execution')}
41
+ body={__('Execute the job now.')}
42
+ />
43
+ </FormGroup>
44
+ <FormGroup fieldId="schedule-type-future">
45
+ <Radio
46
+ isChecked={scheduleType === SCHEDULE_TYPES.FUTURE}
47
+ onChange={() => {
48
+ setScheduleValue(current => ({
49
+ ...current,
50
+ startsAt: new Date().toISOString(),
51
+ scheduleType: SCHEDULE_TYPES.FUTURE,
52
+ repeatType: repeatTypes.noRepeat,
53
+ }));
54
+ setValid(true);
55
+ }}
56
+ name="schedule-type-future"
57
+ id="schedule-type-future"
58
+ label={__('Future execution')}
59
+ body={__('Execute the job later, at a scheduled time.')}
60
+ />
61
+ </FormGroup>
62
+ <FormGroup fieldId="schedule-type-recurring">
63
+ <Radio
64
+ isChecked={scheduleType === SCHEDULE_TYPES.RECURRING}
65
+ onChange={() => {
66
+ setScheduleValue(current => ({
67
+ ...current,
68
+ scheduleType: SCHEDULE_TYPES.RECURRING,
69
+ repeatType: repeatTypes.daily,
70
+ repeatData: { at: '12:00' },
71
+ }));
72
+ setValid(true);
73
+ }}
74
+ name="schedule-type-recurring"
75
+ id="schedule-type-recurring"
76
+ label={__('Recurring execution')}
77
+ body={__('Execute the job on a repeating schedule.')}
78
+ />
79
+ </FormGroup>
80
+ <Divider component="div" />
81
+ <QueryType
82
+ isTypeStatic={isTypeStatic}
83
+ setIsTypeStatic={newValue => {
84
+ setScheduleValue(current => ({ ...current, isTypeStatic: newValue }));
85
+ }}
86
+ />
87
+ </Form>
88
+ </div>
23
89
  );
24
90
 
25
91
  ScheduleType.propTypes = {
26
- isFuture: PropTypes.bool.isRequired,
27
- setIsFuture: PropTypes.func.isRequired,
92
+ isTypeStatic: PropTypes.bool.isRequired,
93
+ scheduleType: PropTypes.string.isRequired,
94
+ setScheduleValue: PropTypes.func.isRequired,
95
+ setValid: PropTypes.func.isRequired,
28
96
  };
@@ -3,6 +3,7 @@ import React from 'react';
3
3
  import { Provider } from 'react-redux';
4
4
  import configureMockStore from 'redux-mock-store';
5
5
  import { fireEvent, screen, render, act } from '@testing-library/react';
6
+ import '@testing-library/jest-dom';
6
7
  import * as api from 'foremanReact/redux/API';
7
8
  import { JobWizard } from '../../../JobWizard';
8
9
  import * as selectors from '../../../JobWizardSelectors';
@@ -47,166 +48,232 @@ const store = mockStore({});
47
48
  jest.useFakeTimers();
48
49
 
49
50
  describe('Schedule', () => {
50
- it('should save date time between steps ', async () => {
51
+ it('sub steps appear', () => {
51
52
  render(
52
53
  <Provider store={store}>
53
54
  <JobWizard />
54
55
  </Provider>
55
56
  );
56
- await act(async () => {
57
- fireEvent.click(screen.getByText('Schedule'));
58
- });
59
- const newStartDate = '2020/03/12';
60
- const newStartTime = '12:03';
61
- const newEndsDate = '2030/03/12';
62
- const newEndsTime = '17:34';
63
- const startsDateField = screen.getByLabelText('starts at datepicker');
64
- const endsDateField = screen.getByLabelText('ends datepicker');
65
-
66
- const startsTimeField = screen.getByLabelText('starts at timepicker');
67
- const endsTimeField = screen.getByLabelText('ends timepicker');
68
-
69
- const staticQuery = screen.getByLabelText('Static query');
70
- const dynamicQuery = screen.getByLabelText('Dynamic query');
71
- const purpose = screen.getByLabelText('purpose');
72
- const newPurposeLabel = 'some fun text';
73
- expect(staticQuery.checked).toBeTruthy();
74
- await act(async () => {
75
- await fireEvent.change(startsDateField, {
76
- target: { value: newStartDate },
77
- });
78
- await fireEvent.change(startsTimeField, {
79
- target: { value: newStartTime },
80
- });
81
- await fireEvent.change(purpose, {
82
- target: { value: newPurposeLabel },
83
- });
84
- await fireEvent.change(endsDateField, { target: { value: newEndsDate } });
85
- await fireEvent.change(endsTimeField, { target: { value: newEndsTime } });
86
-
87
- await fireEvent.click(dynamicQuery);
88
- jest.runAllTimers(); // to handle pf4 date picker popover useTimer
57
+ act(() => {
58
+ fireEvent.click(screen.getByText('Type of execution'));
89
59
  });
90
- await act(async () => {
91
- fireEvent.click(screen.getByText('Category and Template'));
92
- });
93
- expect(screen.getAllByText('Category and Template')).toHaveLength(3);
60
+ expect(screen.getAllByText('Future execution')).toHaveLength(1);
61
+ expect(screen.getAllByText('Recurring execution')).toHaveLength(1);
94
62
 
95
- await act(async () => {
96
- fireEvent.click(screen.getByText('Schedule'));
97
- jest.runAllTimers();
63
+ act(() => {
64
+ fireEvent.click(screen.getByText('Future execution'));
98
65
  });
99
- expect(startsDateField.value).toBe(newStartDate);
100
- expect(startsTimeField.value).toBe(newStartTime);
101
- expect(endsDateField.value).toBe(newEndsDate);
102
- expect(endsTimeField.value).toBe(newEndsTime);
103
- expect(dynamicQuery.checked).toBeTruthy();
104
- expect(purpose.value).toBe(newPurposeLabel);
66
+ expect(screen.getAllByText('Future execution')).toHaveLength(2);
67
+ expect(screen.getAllByText('Recurring execution')).toHaveLength(1);
68
+ act(() => {
69
+ fireEvent.click(screen.getByText('Recurring execution'));
70
+ });
71
+ expect(screen.getAllByText('Future execution')).toHaveLength(1);
72
+ expect(screen.getAllByText('Recurring execution')).toHaveLength(2);
105
73
  });
106
- it('should remove start date time on execute now', async () => {
74
+ it('Future execution', async () => {
107
75
  render(
108
76
  <Provider store={store}>
109
77
  <JobWizard />
110
78
  </Provider>
111
79
  );
112
- await act(async () => {
113
- fireEvent.click(screen.getByText('Schedule'));
80
+ act(() => {
81
+ fireEvent.click(screen.getByText('Type of execution'));
114
82
  });
115
- const executeNow = screen.getByLabelText('Execute now');
116
- const executeFuture = screen.getByLabelText(
117
- 'Schedule for future execution'
118
- );
119
- expect(executeNow.checked).toBeTruthy();
120
- const newStartDate = '2020/03/12';
121
- const newStartTime = '12:03';
122
- const startsDateField = screen.getByLabelText('starts at datepicker');
123
- const startsTimeField = screen.getByLabelText('starts at timepicker');
83
+ act(() => {
84
+ fireEvent.click(screen.getByText('Future execution'));
85
+ });
86
+ act(() => {
87
+ fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
88
+ jest.runAllTimers(); // to handle pf4 date picker popover useTimer
89
+ });
90
+
91
+ const newStartAtDate = '2030/03/12';
92
+ const newStartBeforeDate = '2030/05/22';
93
+ const newStartAtTime = '12:46';
94
+ const newStartBeforeTime = '14:27';
95
+ const startsAtDateField = () =>
96
+ screen.getByLabelText('starts at datepicker');
97
+ const startsAtTimeField = () =>
98
+ screen.getByLabelText('starts at timepicker');
99
+
100
+ const startsBeforeDateField = () =>
101
+ screen.getByLabelText('starts before datepicker');
102
+ const startsBeforeTimeField = () =>
103
+ screen.getByLabelText('starts before timepicker');
104
+
124
105
  await act(async () => {
125
- await fireEvent.change(startsDateField, {
126
- target: { value: newStartDate },
106
+ await fireEvent.change(startsAtDateField(), {
107
+ target: { value: newStartAtDate },
127
108
  });
128
- await fireEvent.change(startsTimeField, {
129
- target: { value: newStartTime },
109
+ fireEvent.change(startsAtTimeField(), {
110
+ target: { value: newStartAtTime },
111
+ });
112
+ await fireEvent.change(startsBeforeDateField(), {
113
+ target: { value: newStartBeforeDate },
114
+ });
115
+ fireEvent.change(startsBeforeTimeField(), {
116
+ target: { value: newStartBeforeTime },
117
+ });
118
+ jest.runOnlyPendingTimers();
119
+ });
120
+
121
+ act(() => {
122
+ fireEvent.click(screen.getByText('Category and Template'));
123
+ });
124
+ act(() => {
125
+ fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
126
+ jest.runAllTimers(); // to handle pf4 date picker popover useTimer
127
+ });
128
+ expect(startsAtDateField().value).toBe(newStartAtDate);
129
+ expect(startsAtTimeField().value).toBe(newStartAtTime);
130
+ expect(startsBeforeDateField().value).toBe(newStartBeforeDate);
131
+ expect(startsBeforeTimeField().value).toBe(newStartBeforeTime);
132
+
133
+ expect(
134
+ screen.queryAllByText(
135
+ "'Starts before' date must be after 'Starts at' date"
136
+ )
137
+ ).toHaveLength(0);
138
+ await act(async () => {
139
+ await fireEvent.change(startsBeforeDateField(), {
140
+ target: { value: '2030/03/11' },
130
141
  });
142
+ await fireEvent.click(startsBeforeTimeField());
131
143
  await jest.runOnlyPendingTimers();
132
144
  });
133
- expect(startsDateField.value).toBe(newStartDate);
134
- expect(startsTimeField.value).toBe(newStartTime);
135
- expect(executeFuture.checked).toBeTruthy();
145
+ expect(startsBeforeDateField().value).toBe('2030/03/11');
146
+ expect(
147
+ screen.getAllByText("'Starts before' date must be after 'Starts at' date")
148
+ ).toHaveLength(1);
149
+
150
+ expect(
151
+ screen.queryAllByText("'Starts before' date must in the future")
152
+ ).toHaveLength(0);
136
153
  await act(async () => {
137
- await fireEvent.click(executeNow);
138
- jest.runAllTimers();
154
+ await fireEvent.change(startsBeforeDateField(), {
155
+ target: { value: '2019/03/11' },
156
+ });
157
+ await fireEvent.change(startsAtDateField(), {
158
+ target: { value: '' },
159
+ });
160
+ jest.runOnlyPendingTimers();
139
161
  });
140
- expect(executeNow.checked).toBeTruthy();
141
- expect(startsDateField.value).toBe('');
142
- expect(startsTimeField.value).toBe('');
162
+
163
+ expect(startsBeforeDateField().value).toBe('2019/03/11');
164
+ expect(
165
+ screen.getAllByText("'Starts before' date must in the future")
166
+ ).toHaveLength(1);
143
167
  });
144
168
 
145
- it('should disable end date on never ends', async () => {
169
+ it('Recurring execution - date pickers', async () => {
146
170
  render(
147
171
  <Provider store={store}>
148
172
  <JobWizard />
149
173
  </Provider>
150
174
  );
151
- await act(async () => {
152
- await fireEvent.click(screen.getByText('Schedule'));
153
- jest.runAllTimers();
175
+ act(() => {
176
+ fireEvent.click(screen.getByText('Type of execution'));
177
+ });
178
+ act(() => {
179
+ fireEvent.click(screen.getByText('Recurring execution'));
180
+ });
181
+ act(() => {
182
+ fireEvent.click(
183
+ screen.getByRole('button', { name: 'Recurring execution' })
184
+ );
185
+ jest.runAllTimers(); // to handle pf4 date picker popover useTimer
154
186
  });
155
- const neverEnds = screen.getByLabelText('Never ends');
156
- expect(neverEnds.checked).toBeFalsy();
157
187
 
158
- const endsDateField = screen.getByLabelText('ends datepicker');
159
- const endsTimeField = screen.getByLabelText('ends timepicker');
160
- fireEvent.click(
161
- screen.getByLabelText('Does not repeat', { selector: 'button' })
162
- );
188
+ const newStartAtDate = '2030/03/12';
189
+ const newStartAtTime = '12:46';
190
+ const startsAtDateField = () =>
191
+ screen.getByLabelText('starts at datepicker');
192
+ const startsAtTimeField = () =>
193
+ screen.getByLabelText('starts at timepicker');
194
+
195
+ const endsAtDateField = () => screen.getByLabelText('ends on datepicker');
196
+ const endsAtTimeField = () => screen.getByLabelText('ends on timepicker');
197
+
198
+ expect(startsAtDateField().disabled).toBeTruthy();
199
+ act(() => {
200
+ fireEvent.click(screen.getAllByText('At')[0]);
201
+ });
202
+ expect(startsAtDateField().disabled).toBeFalsy();
163
203
  await act(async () => {
164
- fireEvent.click(screen.getByText('Cronline'));
204
+ await fireEvent.change(startsAtDateField(), {
205
+ target: { value: newStartAtDate },
206
+ });
207
+ fireEvent.change(startsAtTimeField(), {
208
+ target: { value: newStartAtTime },
209
+ });
210
+ jest.runOnlyPendingTimers();
211
+ });
212
+
213
+ expect(endsAtDateField().disabled).toBeTruthy();
214
+ act(() => {
215
+ fireEvent.click(screen.getByText('On'));
165
216
  });
166
- expect(endsDateField.disabled).toBeFalsy();
167
- expect(endsTimeField.disabled).toBeFalsy();
217
+ expect(endsAtDateField().disabled).toBeFalsy();
168
218
  await act(async () => {
169
- fireEvent.click(neverEnds);
219
+ await fireEvent.change(endsAtDateField(), {
220
+ target: { value: newStartAtDate },
221
+ });
222
+ fireEvent.change(endsAtTimeField(), {
223
+ target: { value: newStartAtTime },
224
+ });
225
+ jest.runOnlyPendingTimers();
170
226
  });
171
- expect(neverEnds.checked).toBeTruthy();
172
- expect(endsDateField.disabled).toBeTruthy();
173
- expect(endsTimeField.disabled).toBeTruthy();
174
- });
175
227
 
176
- it('should change between repeat on states', async () => {
228
+ act(() => {
229
+ fireEvent.click(screen.getByText('Category and Template'));
230
+ });
231
+ act(() => {
232
+ fireEvent.click(
233
+ screen.getByRole('button', { name: 'Recurring execution' })
234
+ );
235
+ jest.runAllTimers(); // to handle pf4 date picker popover useTimer
236
+ });
237
+ expect(startsAtDateField().value).toBe(newStartAtDate);
238
+ expect(startsAtTimeField().value).toBe(newStartAtTime);
239
+ expect(endsAtDateField().value).toBe(newStartAtDate);
240
+ expect(endsAtTimeField().value).toBe(newStartAtTime);
241
+
242
+ act(() => {
243
+ fireEvent.click(screen.getByText('Now'));
244
+ fireEvent.click(screen.getByText('After'));
245
+ });
246
+ expect(startsAtDateField().disabled).toBeTruthy();
247
+ expect(endsAtDateField().disabled).toBeTruthy();
248
+ expect(startsAtDateField().value).toBe('');
249
+ expect(startsAtTimeField().value).toBe('');
250
+ expect(endsAtDateField().value).toBe('');
251
+ expect(endsAtTimeField().value).toBe('');
252
+ });
253
+ it('Recurring execution - repeat', async () => {
177
254
  render(
178
255
  <Provider store={store}>
179
256
  <JobWizard />
180
257
  </Provider>
181
258
  );
182
- await act(async () => {
183
- fireEvent.click(screen.getByText('Schedule'));
184
- jest.runAllTimers(); // to handle pf4 date picker popover useTimer
259
+ act(() => {
260
+ fireEvent.click(screen.getByText('Type of execution'));
185
261
  });
186
- expect(
187
- screen.getByPlaceholderText('Repeat N times').hasAttribute('disabled')
188
- ).toBeTruthy();
189
- expect(screen.getByText('Review details').disabled).toBeFalsy();
190
- await act(async () => {
262
+ act(() => {
263
+ fireEvent.click(screen.getByText('Recurring execution'));
264
+ });
265
+ act(() => {
191
266
  fireEvent.click(
192
- screen.getByLabelText('Does not repeat', { selector: 'button' })
267
+ screen.getByRole('button', { name: 'Recurring execution' })
193
268
  );
269
+ jest.runAllTimers(); // to handle pf4 date picker popover useTimer
194
270
  });
195
-
196
271
  await act(async () => {
197
- fireEvent.click(screen.getByText('Cronline'));
272
+ fireEvent.click(screen.getByLabelText('Daily', { selector: 'button' }));
198
273
  });
199
- expect(screen.getByText('Review details').disabled).toBeTruthy();
200
- const newRepeatTimes = '3';
201
- const repeatNTimes = screen.getByPlaceholderText('Repeat N times');
202
- expect(repeatNTimes.value).toBe('');
203
274
  await act(async () => {
204
- fireEvent.change(repeatNTimes, {
205
- target: { value: newRepeatTimes },
206
- });
275
+ fireEvent.click(screen.getByText('Cronline'));
207
276
  });
208
- expect(repeatNTimes.value).toBe(newRepeatTimes);
209
-
210
277
  const newCronline = '1 2';
211
278
  const cronline = screen.getByLabelText('cronline');
212
279
  expect(cronline.value).toBe('');
@@ -224,11 +291,12 @@ describe('Schedule', () => {
224
291
  expect(screen.getAllByText('Category and Template')).toHaveLength(3);
225
292
 
226
293
  await act(async () => {
227
- fireEvent.click(screen.getByText('Schedule'));
294
+ fireEvent.click(
295
+ screen.getByRole('button', { name: 'Recurring execution' })
296
+ );
228
297
  jest.runAllTimers();
229
298
  });
230
- expect(screen.queryAllByText('Schedule')).toHaveLength(3);
231
- expect(repeatNTimes.value).toBe(newRepeatTimes);
299
+ expect(screen.queryAllByText('Recurring execution')).toHaveLength(3);
232
300
  expect(cronline.value).toBe(newCronline);
233
301
 
234
302
  fireEvent.click(screen.getByText('Cronline'));
@@ -244,7 +312,6 @@ describe('Schedule', () => {
244
312
  fireEvent.change(days, {
245
313
  target: { value: newDays },
246
314
  });
247
- fireEvent.click(repeatNTimes);
248
315
  });
249
316
  expect(days.value).toBe(newDays);
250
317
 
@@ -295,12 +362,12 @@ describe('Schedule', () => {
295
362
 
296
363
  expect(screen.getByText('Review details').disabled).toBeFalsy();
297
364
  fireEvent.click(screen.getByText('Weekly'));
298
- await act(async () => {
365
+ act(() => {
299
366
  fireEvent.click(screen.getByText('Daily'));
300
367
  });
301
368
 
302
369
  expect(screen.getByText('Review details').disabled).toBeFalsy();
303
- await act(async () => {
370
+ act(() => {
304
371
  fireEvent.change(at(), {
305
372
  target: { value: '' },
306
373
  });
@@ -308,7 +375,7 @@ describe('Schedule', () => {
308
375
  expect(screen.getByText('Review details').disabled).toBeTruthy();
309
376
  const newAtDaily = '17:07';
310
377
  expect(at().value).toBe('');
311
- await act(async () => {
378
+ act(() => {
312
379
  fireEvent.change(at(), {
313
380
  target: { value: newAtDaily },
314
381
  });
@@ -317,86 +384,39 @@ describe('Schedule', () => {
317
384
  expect(screen.getByText('Review details').disabled).toBeFalsy();
318
385
 
319
386
  fireEvent.click(screen.getByText('Daily'));
320
- await act(async () => {
387
+ act(() => {
321
388
  fireEvent.click(screen.getByText('Hourly'));
322
389
  });
323
390
 
324
- expect(screen.getByText('Review details').disabled).toBeTruthy();
325
- const newMinutes = '6';
326
- const atHourly = screen.getByLabelText('repeat-at-minute-typeahead');
327
- expect(atHourly.value).toBe('');
328
- await act(async () => {
329
- fireEvent.click(screen.getByLabelText('select minute toggle'));
330
- });
331
- await act(async () => {
332
- fireEvent.click(screen.getByText(newMinutes));
333
- });
334
391
  expect(screen.getByText('Review details').disabled).toBeFalsy();
335
- expect(atHourly.value).toBe(newMinutes);
336
- });
337
- it('should show invalid error on start date after end', async () => {
338
- render(
339
- <Provider store={store}>
340
- <JobWizard />
341
- </Provider>
342
- );
343
- await act(async () => {
344
- await fireEvent.click(screen.getByText('Schedule'));
345
- jest.runAllTimers();
392
+ const newMinutes = '6';
393
+ const atHourly = () => screen.getByLabelText('repeat-at-minute-typeahead');
394
+ expect(atHourly().value).toBe('0');
395
+ act(() => {
396
+ fireEvent.change(atHourly(), {
397
+ target: { value: '62' },
398
+ });
346
399
  });
347
- const neverEnds = screen.getByLabelText('Never ends');
348
- expect(neverEnds.checked).toBeFalsy();
349
-
350
- const startsDateField = screen.getByLabelText('starts at datepicker');
351
- const endsDateField = screen.getByLabelText('ends datepicker');
352
-
353
- expect(
354
- screen.queryAllByText('End time needs to be after start time')
355
- ).toHaveLength(0);
356
- expect(screen.getByText('Review details').disabled).toBeFalsy();
357
400
  await act(async () => {
358
- await fireEvent.change(startsDateField, {
359
- target: { value: '2020/10/15' },
360
- });
361
- await fireEvent.change(endsDateField, {
362
- target: { value: '2020/10/14' },
363
- });
364
- await jest.runOnlyPendingTimers();
401
+ await fireEvent.click(screen.getByText('Create "62"'));
365
402
  });
366
-
403
+ expect(atHourly().value).toBe('0');
367
404
  expect(
368
- screen.queryAllByText('End time needs to be after start time')
405
+ screen.queryAllByText('Minute can only be a number between 0-59')
369
406
  ).toHaveLength(1);
370
407
 
371
- expect(screen.getByText('Review details').disabled).toBeTruthy();
372
- });
373
- it('purpose and ends should be disabled when no reaccurence ', async () => {
374
- render(
375
- <Provider store={store}>
376
- <JobWizard />
377
- </Provider>
378
- );
379
- await act(async () => {
380
- await fireEvent.click(screen.getByText('Schedule'));
381
- jest.runAllTimers();
382
- });
383
-
384
- const endsDateField = screen.getByLabelText('ends datepicker');
385
- const endsTimeField = screen.getByLabelText('ends timepicker');
386
- const purpose = screen.getByLabelText('purpose');
387
- expect(endsDateField.disabled).toBeTruthy();
388
- expect(endsTimeField.disabled).toBeTruthy();
389
- expect(purpose.disabled).toBeTruthy();
390
- await act(async () => {
391
- fireEvent.click(
392
- screen.getByLabelText('Does not repeat', { selector: 'button' })
393
- );
408
+ act(() => {
409
+ fireEvent.change(atHourly(), {
410
+ target: { value: newMinutes },
411
+ });
394
412
  });
395
413
  await act(async () => {
396
- fireEvent.click(screen.getByText('Cronline'));
414
+ await fireEvent.click(screen.getByText(`Create "${newMinutes}"`));
397
415
  });
398
- expect(endsDateField.disabled).toBeFalsy();
399
- expect(endsTimeField.disabled).toBeFalsy();
400
- expect(purpose.disabled).toBeFalsy();
416
+ expect(
417
+ screen.queryAllByText('Minute can only be a number between 0-59')
418
+ ).toHaveLength(0);
419
+ expect(screen.getByText('Review details').disabled).toBeFalsy();
420
+ expect(atHourly().value).toBe(newMinutes);
401
421
  });
402
422
  });