foreman_remote_execution 4.7.0 → 4.8.0
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +1 -0
- data/app/controllers/api/v2/job_invocations_controller.rb +7 -1
- data/app/lib/actions/remote_execution/run_host_job.rb +2 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +57 -3
- data/app/mailers/rex_job_mailer.rb +15 -0
- data/app/models/job_invocation.rb +4 -0
- data/app/models/job_invocation_composer.rb +20 -12
- data/app/models/remote_execution_provider.rb +18 -2
- data/app/models/rex_mail_notification.rb +13 -0
- data/app/models/setting/remote_execution.rb +7 -1
- data/app/services/ui_notifications/remote_execution_jobs/base_job_finish.rb +2 -1
- data/app/views/dashboard/_latest-jobs.html.erb +21 -0
- data/app/views/rex_job_mailer/job_finished.html.erb +24 -0
- data/app/views/rex_job_mailer/job_finished.text.erb +9 -0
- data/app/views/template_invocations/show.html.erb +2 -1
- data/db/seeds.d/50-notification_blueprints.rb +14 -0
- data/db/seeds.d/95-mail_notifications.rb +24 -0
- data/foreman_remote_execution.gemspec +1 -1
- data/lib/foreman_remote_execution/engine.rb +1 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +6 -6
- data/test/functional/api/v2/job_invocations_controller_test.rb +10 -0
- data/test/unit/actions/run_hosts_job_test.rb +99 -4
- data/test/unit/job_invocation_report_template_test.rb +15 -12
- data/test/unit/remote_execution_provider_test.rb +46 -0
- data/webpack/JobWizard/JobWizard.js +53 -20
- data/webpack/JobWizard/JobWizard.scss +33 -4
- data/webpack/JobWizard/JobWizardConstants.js +17 -0
- data/webpack/JobWizard/__tests__/fixtures.js +8 -0
- data/webpack/JobWizard/__tests__/integration.test.js +3 -7
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +16 -5
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +48 -1
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +29 -14
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +4 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +3 -2
- data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +25 -0
- data/webpack/JobWizard/steps/HostsAndInputs/TemplateInputs.js +23 -0
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/SelectedChips.test.js +37 -0
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +50 -0
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +66 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +24 -21
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +36 -21
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +155 -0
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +9 -8
- data/webpack/JobWizard/steps/Schedule/index.js +89 -28
- data/webpack/JobWizard/steps/form/DateTimePicker.js +93 -0
- data/webpack/JobWizard/steps/form/Formatter.js +10 -9
- data/webpack/JobWizard/steps/form/NumberInput.js +2 -0
- data/webpack/JobWizard/steps/form/WizardTitle.js +14 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +2 -1
- metadata +18 -4
@@ -0,0 +1,37 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Provider } from 'react-redux';
|
3
|
+
import { fireEvent, screen, render, act } from '@testing-library/react';
|
4
|
+
import * as api from 'foremanReact/redux/API';
|
5
|
+
import { JobWizard } from '../../../JobWizard';
|
6
|
+
import * as selectors from '../../../JobWizardSelectors';
|
7
|
+
import { testSetup, mockApi } from '../../../__tests__/fixtures';
|
8
|
+
|
9
|
+
const store = testSetup(selectors, api);
|
10
|
+
mockApi(api);
|
11
|
+
|
12
|
+
describe('TemplateInputs', () => {
|
13
|
+
it('should save data between steps for template input fields', async () => {
|
14
|
+
render(
|
15
|
+
<Provider store={store}>
|
16
|
+
<JobWizard advancedValues={{}} setAdvancedValues={jest.fn()} />
|
17
|
+
</Provider>
|
18
|
+
);
|
19
|
+
await act(async () => {
|
20
|
+
await fireEvent.click(
|
21
|
+
screen.getByText('Target hosts and inputs', { selector: 'button' })
|
22
|
+
);
|
23
|
+
});
|
24
|
+
|
25
|
+
expect(
|
26
|
+
screen.getAllByLabelText('host2', { selector: 'button' })
|
27
|
+
).toHaveLength(1);
|
28
|
+
const chip1 = screen.getByLabelText('host1', { selector: 'button' });
|
29
|
+
fireEvent.click(chip1);
|
30
|
+
expect(
|
31
|
+
screen.queryAllByLabelText('host1', { selector: 'button' })
|
32
|
+
).toHaveLength(0);
|
33
|
+
expect(
|
34
|
+
screen.queryAllByLabelText('host2', { selector: 'button' })
|
35
|
+
).toHaveLength(1);
|
36
|
+
});
|
37
|
+
});
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Provider } from 'react-redux';
|
3
|
+
import { fireEvent, screen, render, act } from '@testing-library/react';
|
4
|
+
import * as api from 'foremanReact/redux/API';
|
5
|
+
import { JobWizard } from '../../../JobWizard';
|
6
|
+
import * as selectors from '../../../JobWizardSelectors';
|
7
|
+
import { testSetup, mockApi } from '../../../__tests__/fixtures';
|
8
|
+
import { WIZARD_TITLES } from '../../../JobWizardConstants';
|
9
|
+
|
10
|
+
const store = testSetup(selectors, api);
|
11
|
+
mockApi(api);
|
12
|
+
|
13
|
+
describe('TemplateInputs', () => {
|
14
|
+
it('should save data between steps for template input fields', async () => {
|
15
|
+
render(
|
16
|
+
<Provider store={store}>
|
17
|
+
<JobWizard />
|
18
|
+
</Provider>
|
19
|
+
);
|
20
|
+
await act(async () => {
|
21
|
+
fireEvent.click(screen.getByText(WIZARD_TITLES.hostsAndInputs));
|
22
|
+
});
|
23
|
+
const textValue = 'I am a plain text';
|
24
|
+
const textField = screen.getByLabelText('plain hidden', {
|
25
|
+
selector: 'textarea',
|
26
|
+
});
|
27
|
+
|
28
|
+
await act(async () => {
|
29
|
+
await fireEvent.change(textField, {
|
30
|
+
target: { value: textValue },
|
31
|
+
});
|
32
|
+
});
|
33
|
+
expect(
|
34
|
+
screen.getByLabelText('plain hidden', {
|
35
|
+
selector: 'textarea',
|
36
|
+
}).value
|
37
|
+
).toBe(textValue);
|
38
|
+
await act(async () => {
|
39
|
+
fireEvent.click(screen.getByText(WIZARD_TITLES.categoryAndTemplate));
|
40
|
+
});
|
41
|
+
expect(screen.getAllByText(WIZARD_TITLES.categoryAndTemplate)).toHaveLength(
|
42
|
+
3
|
43
|
+
);
|
44
|
+
|
45
|
+
await act(async () => {
|
46
|
+
fireEvent.click(screen.getByText(WIZARD_TITLES.hostsAndInputs));
|
47
|
+
});
|
48
|
+
expect(textField.value).toBe(textValue);
|
49
|
+
});
|
50
|
+
});
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { Button, Form, FormGroup } from '@patternfly/react-core';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
import { useSelector } from 'react-redux';
|
5
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
6
|
+
import { selectTemplateInputs } from '../../JobWizardSelectors';
|
7
|
+
import { SelectField } from '../form/SelectField';
|
8
|
+
import { SelectedChips } from './SelectedChips';
|
9
|
+
import { TemplateInputs } from './TemplateInputs';
|
10
|
+
import { WIZARD_TITLES } from '../../JobWizardConstants';
|
11
|
+
import { WizardTitle } from '../form/WizardTitle';
|
12
|
+
|
13
|
+
const HostsAndInputs = ({
|
14
|
+
templateValues,
|
15
|
+
setTemplateValues,
|
16
|
+
selectedHosts,
|
17
|
+
setSelectedHosts,
|
18
|
+
}) => {
|
19
|
+
const templateInputs = useSelector(selectTemplateInputs);
|
20
|
+
const hostMethods = [
|
21
|
+
__('Hosts'),
|
22
|
+
__('Host collection'),
|
23
|
+
__('Host group'),
|
24
|
+
__('Search query'),
|
25
|
+
];
|
26
|
+
const [hostMethod, setHostMethod] = useState(hostMethods[0]);
|
27
|
+
return (
|
28
|
+
<>
|
29
|
+
<WizardTitle title={WIZARD_TITLES.hostsAndInputs} />
|
30
|
+
<Form>
|
31
|
+
<FormGroup fieldId="host_selection">
|
32
|
+
<SelectField
|
33
|
+
fieldId="host_methods"
|
34
|
+
options={hostMethods}
|
35
|
+
setValue={setHostMethod}
|
36
|
+
value={hostMethod}
|
37
|
+
/>
|
38
|
+
<SelectedChips
|
39
|
+
selected={selectedHosts}
|
40
|
+
setSelected={setSelectedHosts}
|
41
|
+
/>
|
42
|
+
</FormGroup>
|
43
|
+
<span>
|
44
|
+
{__('Apply to')}{' '}
|
45
|
+
<Button variant="link" isInline>
|
46
|
+
{selectedHosts.length} {__('hosts')}
|
47
|
+
</Button>
|
48
|
+
</span>
|
49
|
+
<TemplateInputs
|
50
|
+
inputs={templateInputs}
|
51
|
+
value={templateValues}
|
52
|
+
setValue={setTemplateValues}
|
53
|
+
/>
|
54
|
+
</Form>
|
55
|
+
</>
|
56
|
+
);
|
57
|
+
};
|
58
|
+
|
59
|
+
HostsAndInputs.propTypes = {
|
60
|
+
templateValues: PropTypes.object.isRequired,
|
61
|
+
setTemplateValues: PropTypes.func.isRequired,
|
62
|
+
selectedHosts: PropTypes.array.isRequired,
|
63
|
+
setSelectedHosts: PropTypes.func.isRequired,
|
64
|
+
};
|
65
|
+
|
66
|
+
export default HostsAndInputs;
|
@@ -1,25 +1,28 @@
|
|
1
|
-
import React
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
2
3
|
import { FormGroup, Radio } from '@patternfly/react-core';
|
3
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
4
5
|
|
5
|
-
export const ScheduleType = () =>
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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>
|
23
|
+
);
|
24
|
+
|
25
|
+
ScheduleType.propTypes = {
|
26
|
+
isFuture: PropTypes.bool.isRequired,
|
27
|
+
setIsFuture: PropTypes.func.isRequired,
|
25
28
|
};
|
@@ -1,35 +1,48 @@
|
|
1
|
-
import React
|
1
|
+
import React from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
|
-
import { FormGroup,
|
3
|
+
import { FormGroup, Checkbox } from '@patternfly/react-core';
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import { DateTimePicker } from '../form/DateTimePicker';
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
export const StartEndDates = ({
|
8
|
+
starts,
|
9
|
+
setStarts,
|
10
|
+
ends,
|
11
|
+
setEnds,
|
12
|
+
isNeverEnds,
|
13
|
+
setIsNeverEnds,
|
14
|
+
}) => {
|
9
15
|
const toggleIsNeverEnds = (checked, event) => {
|
10
16
|
const value = event?.target?.checked;
|
11
17
|
setIsNeverEnds(value);
|
12
|
-
|
18
|
+
};
|
19
|
+
const validateEndDate = () => {
|
20
|
+
if (isNeverEnds) return 'success';
|
21
|
+
if (!starts || !ends) return 'success';
|
22
|
+
if (new Date(starts).getTime() <= new Date(ends).getTime())
|
23
|
+
return 'success';
|
24
|
+
return 'error';
|
13
25
|
};
|
14
26
|
return (
|
15
27
|
<>
|
16
|
-
<FormGroup
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
placeholder="mm/dd/yy, hh:mm UTC"
|
23
|
-
/>
|
28
|
+
<FormGroup
|
29
|
+
className="start-date"
|
30
|
+
label={__('Starts')}
|
31
|
+
fieldId="start-date"
|
32
|
+
>
|
33
|
+
<DateTimePicker dateTime={starts} setDateTime={setStarts} />
|
24
34
|
</FormGroup>
|
25
|
-
<FormGroup
|
26
|
-
|
35
|
+
<FormGroup
|
36
|
+
className="end-date"
|
37
|
+
label={__('Ends')}
|
38
|
+
fieldId="end-date"
|
39
|
+
helperTextInvalid={__('End time needs to be after start time')}
|
40
|
+
validated={validateEndDate()}
|
41
|
+
>
|
42
|
+
<DateTimePicker
|
43
|
+
dateTime={ends}
|
44
|
+
setDateTime={setEnds}
|
27
45
|
isDisabled={isNeverEnds}
|
28
|
-
id="end-date"
|
29
|
-
value={ends}
|
30
|
-
type="text"
|
31
|
-
onChange={newValue => setEnds(newValue)}
|
32
|
-
placeholder="mm/dd/yy, hh:mm UTC"
|
33
46
|
/>
|
34
47
|
<Checkbox
|
35
48
|
label={__('Never ends')}
|
@@ -48,4 +61,6 @@ StartEndDates.propTypes = {
|
|
48
61
|
setStarts: PropTypes.func.isRequired,
|
49
62
|
ends: PropTypes.string.isRequired,
|
50
63
|
setEnds: PropTypes.func.isRequired,
|
64
|
+
isNeverEnds: PropTypes.bool.isRequired,
|
65
|
+
setIsNeverEnds: PropTypes.func.isRequired,
|
51
66
|
};
|
@@ -0,0 +1,155 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Provider } from 'react-redux';
|
3
|
+
import configureMockStore from 'redux-mock-store';
|
4
|
+
import { fireEvent, screen, render, act } from '@testing-library/react';
|
5
|
+
import * as api from 'foremanReact/redux/API';
|
6
|
+
import { JobWizard } from '../../../JobWizard';
|
7
|
+
import * as selectors from '../../../JobWizardSelectors';
|
8
|
+
import { jobTemplate, jobTemplateResponse } from '../../../__tests__/fixtures';
|
9
|
+
|
10
|
+
const lodash = require('lodash');
|
11
|
+
|
12
|
+
lodash.debounce = fn => fn;
|
13
|
+
jest.spyOn(api, 'get');
|
14
|
+
jest.spyOn(selectors, 'selectJobTemplate');
|
15
|
+
jest.spyOn(selectors, 'selectJobTemplates');
|
16
|
+
jest.spyOn(selectors, 'selectJobCategories');
|
17
|
+
|
18
|
+
const jobCategories = ['Ansible Commands', 'Puppet', 'Services'];
|
19
|
+
|
20
|
+
selectors.selectJobCategories.mockImplementation(() => jobCategories);
|
21
|
+
|
22
|
+
selectors.selectJobTemplates.mockImplementation(() => [
|
23
|
+
jobTemplate,
|
24
|
+
{ ...jobTemplate, id: 2, name: 'template2' },
|
25
|
+
]);
|
26
|
+
selectors.selectJobTemplate.mockImplementation(() => jobTemplateResponse);
|
27
|
+
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
28
|
+
if (action.key === 'JOB_CATEGORIES') {
|
29
|
+
handleSuccess && handleSuccess({ data: { job_categories: jobCategories } });
|
30
|
+
} else if (action.key === 'JOB_TEMPLATE') {
|
31
|
+
handleSuccess &&
|
32
|
+
handleSuccess({
|
33
|
+
data: jobTemplateResponse,
|
34
|
+
});
|
35
|
+
} else if (action.key === 'JOB_TEMPLATES') {
|
36
|
+
handleSuccess &&
|
37
|
+
handleSuccess({
|
38
|
+
data: { results: [jobTemplateResponse.job_template] },
|
39
|
+
});
|
40
|
+
}
|
41
|
+
return { type: 'get', ...action };
|
42
|
+
});
|
43
|
+
|
44
|
+
const mockStore = configureMockStore([]);
|
45
|
+
const store = mockStore({});
|
46
|
+
jest.useFakeTimers();
|
47
|
+
|
48
|
+
describe('Schedule', () => {
|
49
|
+
it('should save date time between steps ', async () => {
|
50
|
+
render(
|
51
|
+
<Provider store={store}>
|
52
|
+
<JobWizard />
|
53
|
+
</Provider>
|
54
|
+
);
|
55
|
+
await act(async () => {
|
56
|
+
fireEvent.click(screen.getByText('Schedule'));
|
57
|
+
});
|
58
|
+
const newStartDate = '2020/03/12';
|
59
|
+
const newStartTime = '12:03';
|
60
|
+
const newEndsDate = '2030/03/12';
|
61
|
+
const newEndsTime = '17:34';
|
62
|
+
const [startsDateField, endsDateField] = screen.getAllByPlaceholderText(
|
63
|
+
'yyyy/mm/dd'
|
64
|
+
);
|
65
|
+
const [startsTimeField, endsTimeField] = screen.getAllByPlaceholderText(
|
66
|
+
'hh:mm'
|
67
|
+
);
|
68
|
+
await act(async () => {
|
69
|
+
await fireEvent.change(startsDateField, {
|
70
|
+
target: { value: newStartDate },
|
71
|
+
});
|
72
|
+
await fireEvent.change(startsTimeField, {
|
73
|
+
target: { value: newStartTime },
|
74
|
+
});
|
75
|
+
await fireEvent.change(endsDateField, { target: { value: newEndsDate } });
|
76
|
+
await fireEvent.change(endsTimeField, { target: { value: newEndsTime } });
|
77
|
+
jest.runAllTimers(); // to handle pf4 date picket popover useTimer
|
78
|
+
});
|
79
|
+
await act(async () => {
|
80
|
+
fireEvent.click(screen.getByText('Category and Template'));
|
81
|
+
});
|
82
|
+
expect(screen.getAllByText('Category and Template')).toHaveLength(3);
|
83
|
+
|
84
|
+
await act(async () => {
|
85
|
+
fireEvent.click(screen.getByText('Schedule'));
|
86
|
+
jest.runAllTimers();
|
87
|
+
});
|
88
|
+
expect(startsDateField.value).toBe(newStartDate);
|
89
|
+
expect(startsTimeField.value).toBe(newStartTime);
|
90
|
+
expect(endsDateField.value).toBe(newEndsDate);
|
91
|
+
expect(endsTimeField.value).toBe(newEndsTime);
|
92
|
+
});
|
93
|
+
it('should remove start date time on execute now', async () => {
|
94
|
+
render(
|
95
|
+
<Provider store={store}>
|
96
|
+
<JobWizard />
|
97
|
+
</Provider>
|
98
|
+
);
|
99
|
+
await act(async () => {
|
100
|
+
fireEvent.click(screen.getByText('Schedule'));
|
101
|
+
});
|
102
|
+
const executeNow = screen.getByLabelText('Execute now');
|
103
|
+
const executeFuture = screen.getByLabelText(
|
104
|
+
'Schedule for future execution'
|
105
|
+
);
|
106
|
+
expect(executeNow.checked).toBeTruthy();
|
107
|
+
const newStartDate = '2020/03/12';
|
108
|
+
const newStartTime = '12:03';
|
109
|
+
const [startsDateField] = screen.getAllByPlaceholderText('yyyy/mm/dd');
|
110
|
+
const [startsTimeField] = screen.getAllByPlaceholderText('hh:mm');
|
111
|
+
await act(async () => {
|
112
|
+
await fireEvent.change(startsDateField, {
|
113
|
+
target: { value: newStartDate },
|
114
|
+
});
|
115
|
+
await fireEvent.change(startsTimeField, {
|
116
|
+
target: { value: newStartTime },
|
117
|
+
});
|
118
|
+
await jest.runOnlyPendingTimers();
|
119
|
+
});
|
120
|
+
expect(startsDateField.value).toBe(newStartDate);
|
121
|
+
expect(startsTimeField.value).toBe(newStartTime);
|
122
|
+
expect(executeFuture.checked).toBeTruthy();
|
123
|
+
await act(async () => {
|
124
|
+
await fireEvent.click(executeNow);
|
125
|
+
});
|
126
|
+
expect(executeNow.checked).toBeTruthy();
|
127
|
+
expect(startsDateField.value).toBe('');
|
128
|
+
expect(startsTimeField.value).toBe('');
|
129
|
+
});
|
130
|
+
|
131
|
+
it('should disable end date on never ends', async () => {
|
132
|
+
render(
|
133
|
+
<Provider store={store}>
|
134
|
+
<JobWizard />
|
135
|
+
</Provider>
|
136
|
+
);
|
137
|
+
await act(async () => {
|
138
|
+
await fireEvent.click(screen.getByText('Schedule'));
|
139
|
+
jest.runAllTimers();
|
140
|
+
});
|
141
|
+
const neverEnds = screen.getByLabelText('Never ends');
|
142
|
+
expect(neverEnds.checked).toBeFalsy();
|
143
|
+
|
144
|
+
const [, endsDateField] = screen.getAllByPlaceholderText('yyyy/mm/dd');
|
145
|
+
const [, endsTimeField] = screen.getAllByPlaceholderText('hh:mm');
|
146
|
+
expect(endsDateField.disabled).toBeFalsy();
|
147
|
+
expect(endsTimeField.disabled).toBeFalsy();
|
148
|
+
await act(async () => {
|
149
|
+
fireEvent.click(neverEnds);
|
150
|
+
});
|
151
|
+
expect(neverEnds.checked).toBeTruthy();
|
152
|
+
expect(endsDateField.disabled).toBeTruthy();
|
153
|
+
expect(endsTimeField.disabled).toBeTruthy();
|
154
|
+
});
|
155
|
+
});
|
@@ -1,22 +1,23 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import { render, fireEvent, screen } from '@testing-library/react';
|
2
|
+
import { render, fireEvent, screen, act } from '@testing-library/react';
|
3
3
|
import { StartEndDates } from '../StartEndDates';
|
4
4
|
|
5
5
|
const setEnds = jest.fn();
|
6
|
+
const setIsNeverEnds = jest.fn();
|
6
7
|
const props = {
|
7
8
|
starts: '',
|
8
9
|
setStarts: jest.fn(),
|
9
10
|
ends: 'some-end-date',
|
10
11
|
setEnds,
|
12
|
+
setIsNeverEnds,
|
13
|
+
isNeverEnds: false,
|
11
14
|
};
|
12
15
|
|
13
16
|
describe('StartEndDates', () => {
|
14
|
-
it('never ends', () => {
|
15
|
-
render(<StartEndDates {...props} />);
|
16
|
-
const neverEnds = screen.
|
17
|
-
|
18
|
-
|
19
|
-
fireEvent.click(neverEnds);
|
20
|
-
expect(setEnds).toBeCalledWith('');
|
17
|
+
it('never ends', async () => {
|
18
|
+
await act(async () => render(<StartEndDates {...props} />));
|
19
|
+
const neverEnds = screen.getByRole('checkbox', { name: 'Never ends' });
|
20
|
+
await act(async () => fireEvent.click(neverEnds));
|
21
|
+
expect(setIsNeverEnds).toBeCalledWith(true);
|
21
22
|
});
|
22
23
|
});
|