foreman_remote_execution 16.0.4 → 16.0.5
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/app/controllers/job_invocations_controller.rb +28 -1
- data/app/controllers/template_invocations_controller.rb +1 -1
- data/config/routes.rb +1 -0
- data/lib/foreman_remote_execution/engine.rb +9 -256
- data/lib/foreman_remote_execution/plugin.rb +246 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +1 -1
- data/test/unit/job_invocation_report_template_test.rb +6 -6
- data/webpack/JobInvocationDetail/CheckboxesActions.js +196 -0
- data/webpack/JobInvocationDetail/{JobInvocationHostTableToolbar.js → DropdownFilter.js} +3 -6
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +6 -6
- data/webpack/JobInvocationDetail/JobInvocationDetail.scss +18 -0
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +205 -89
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +30 -3
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +20 -27
- data/webpack/JobInvocationDetail/OpenAllInvocationsModal.js +118 -0
- data/webpack/JobInvocationDetail/TemplateInvocation.js +54 -24
- data/webpack/JobInvocationDetail/TemplateInvocationComponents/TemplateActionButtons.js +8 -10
- data/webpack/JobInvocationDetail/TemplateInvocationPage.js +1 -1
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +1 -1
- data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +202 -0
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +34 -28
- data/webpack/JobInvocationDetail/index.js +61 -30
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +26 -7
- data/webpack/react_app/components/TargetingHosts/TargetingHostsLabelsRow.scss +1 -1
- metadata +8 -6
- data/webpack/JobInvocationDetail/OpenAlInvocations.js +0 -111
- data/webpack/JobInvocationDetail/__tests__/OpenAlInvocations.test.js +0 -110
@@ -3,11 +3,13 @@ import {
|
|
3
3
|
Flex,
|
4
4
|
PageSection,
|
5
5
|
PageSectionVariants,
|
6
|
+
Skeleton,
|
6
7
|
} from '@patternfly/react-core';
|
7
8
|
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
|
8
9
|
import { translate as __, documentLocale } from 'foremanReact/common/I18n';
|
9
10
|
import { stopInterval } from 'foremanReact/redux/middlewares/IntervalMiddleware';
|
10
11
|
import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
|
12
|
+
import SkeletonLoader from 'foremanReact/components/common/SkeletonLoader';
|
11
13
|
import PropTypes from 'prop-types';
|
12
14
|
import React, { useEffect, useState } from 'react';
|
13
15
|
import { useDispatch, useSelector } from 'react-redux';
|
@@ -20,6 +22,7 @@ import {
|
|
20
22
|
DATE_OPTIONS,
|
21
23
|
JOB_INVOCATION_KEY,
|
22
24
|
STATUS,
|
25
|
+
STATUS_UPPERCASE,
|
23
26
|
} from './JobInvocationConstants';
|
24
27
|
import JobInvocationHostTable from './JobInvocationHostTable';
|
25
28
|
import JobInvocationOverview from './JobInvocationOverview';
|
@@ -38,21 +41,20 @@ const JobInvocationDetailPage = ({
|
|
38
41
|
const items = useSelector(selectItems);
|
39
42
|
const {
|
40
43
|
description,
|
44
|
+
failed = 0,
|
41
45
|
status_label: statusLabel,
|
42
46
|
task,
|
43
47
|
start_at: startAt,
|
44
|
-
targeting,
|
48
|
+
targeting = {},
|
45
49
|
} = items;
|
46
50
|
const finished =
|
47
51
|
statusLabel === STATUS.FAILED ||
|
48
52
|
statusLabel === STATUS.SUCCEEDED ||
|
49
53
|
statusLabel === STATUS.CANCELLED;
|
50
54
|
const autoRefresh = task?.state === STATUS.PENDING || false;
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
CURRENT_PERMISSIONS
|
55
|
-
);
|
55
|
+
useAPI('get', currentPermissionsUrl, {
|
56
|
+
key: CURRENT_PERMISSIONS,
|
57
|
+
});
|
56
58
|
const [selectedFilter, setSelectedFilter] = useState('');
|
57
59
|
|
58
60
|
const handleFilterChange = newFilter => {
|
@@ -88,10 +90,22 @@ const JobInvocationDetailPage = ({
|
|
88
90
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
89
91
|
}, [dispatch, task?.id]);
|
90
92
|
|
93
|
+
const pageStatus =
|
94
|
+
items.id === undefined
|
95
|
+
? STATUS_UPPERCASE.PENDING
|
96
|
+
: STATUS_UPPERCASE.RESOLVED;
|
97
|
+
|
91
98
|
const breadcrumbOptions = {
|
92
99
|
breadcrumbItems: [
|
93
100
|
{ caption: __('Jobs'), url: `/job_invocations` },
|
94
|
-
{
|
101
|
+
{
|
102
|
+
caption:
|
103
|
+
pageStatus === STATUS_UPPERCASE.PENDING ? (
|
104
|
+
<Skeleton width="350px" />
|
105
|
+
) : (
|
106
|
+
description
|
107
|
+
),
|
108
|
+
},
|
95
109
|
],
|
96
110
|
isPf4: true,
|
97
111
|
};
|
@@ -101,26 +115,27 @@ const JobInvocationDetailPage = ({
|
|
101
115
|
<PageLayout
|
102
116
|
header={description}
|
103
117
|
breadcrumbOptions={breadcrumbOptions}
|
104
|
-
toolbarButtons={
|
105
|
-
<JobInvocationToolbarButtons
|
106
|
-
jobId={id}
|
107
|
-
data={items}
|
108
|
-
currentPermissions={response.results}
|
109
|
-
permissionsStatus={status}
|
110
|
-
/>
|
111
|
-
}
|
118
|
+
toolbarButtons={<JobInvocationToolbarButtons jobId={id} data={items} />}
|
112
119
|
searchable={false}
|
113
120
|
>
|
114
121
|
<Flex
|
115
122
|
className="job-invocation-detail-flex"
|
116
123
|
alignItems={{ default: 'alignItemsFlexStart' }}
|
117
124
|
>
|
118
|
-
<
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
125
|
+
<SkeletonLoader
|
126
|
+
status={pageStatus}
|
127
|
+
skeletonProps={{
|
128
|
+
height: 105,
|
129
|
+
width: 375,
|
130
|
+
}}
|
131
|
+
>
|
132
|
+
<JobInvocationSystemStatusChart
|
133
|
+
data={items}
|
134
|
+
isAlreadyStarted={isAlreadyStarted}
|
135
|
+
formattedStartDate={formattedStartDate}
|
136
|
+
onFilterChange={handleFilterChange}
|
137
|
+
/>
|
138
|
+
</SkeletonLoader>
|
124
139
|
<Divider
|
125
140
|
orientation={{
|
126
141
|
default: 'vertical',
|
@@ -130,35 +145,51 @@ const JobInvocationDetailPage = ({
|
|
130
145
|
className="job-overview"
|
131
146
|
alignItems={{ default: 'alignItemsCenter' }}
|
132
147
|
>
|
133
|
-
<
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
148
|
+
<SkeletonLoader
|
149
|
+
status={pageStatus}
|
150
|
+
skeletonProps={{
|
151
|
+
height: 105,
|
152
|
+
width: 270,
|
153
|
+
}}
|
154
|
+
>
|
155
|
+
<JobInvocationOverview
|
156
|
+
data={items}
|
157
|
+
isAlreadyStarted={isAlreadyStarted}
|
158
|
+
formattedStartDate={formattedStartDate}
|
159
|
+
/>
|
160
|
+
</SkeletonLoader>
|
139
161
|
</Flex>
|
140
162
|
</Flex>
|
141
163
|
<PageSection
|
142
164
|
variant={PageSectionVariants.light}
|
143
165
|
className="job-additional-info"
|
144
166
|
>
|
145
|
-
|
167
|
+
<SkeletonLoader
|
168
|
+
status={pageStatus}
|
169
|
+
skeletonProps={{ height: 150, width: '100%' }}
|
170
|
+
>
|
171
|
+
<JobAdditionInfo data={items} />
|
172
|
+
</SkeletonLoader>
|
146
173
|
</PageSection>
|
147
174
|
</PageLayout>
|
148
175
|
<PageSection
|
149
176
|
variant={PageSectionVariants.light}
|
150
177
|
className="job-details-table-section table-section"
|
151
178
|
>
|
152
|
-
|
179
|
+
<SkeletonLoader
|
180
|
+
status={pageStatus}
|
181
|
+
skeletonProps={{ height: 400, width: '100%' }}
|
182
|
+
>
|
153
183
|
<JobInvocationHostTable
|
154
184
|
id={id}
|
155
185
|
targeting={targeting}
|
186
|
+
failedCount={failed}
|
156
187
|
finished={finished}
|
157
188
|
autoRefresh={autoRefresh}
|
158
189
|
initialFilter={selectedFilter}
|
159
190
|
onFilterUpdate={handleFilterChange}
|
160
191
|
/>
|
161
|
-
|
192
|
+
</SkeletonLoader>
|
162
193
|
</PageSection>
|
163
194
|
</>
|
164
195
|
);
|
@@ -1,12 +1,31 @@
|
|
1
1
|
export const buildHostQuery = (selected, search) => {
|
2
|
+
const nameEscape = name => `"${name.replaceAll('"', '\\"')}"`;
|
2
3
|
const { hosts, hostCollections, hostGroups } = selected;
|
3
|
-
const
|
4
|
-
|
5
|
-
|
6
|
-
.join(',')})`;
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
const MAX_NAME_ITEMS = 50;
|
5
|
+
let hostsSearch;
|
6
|
+
if (hosts.length < MAX_NAME_ITEMS)
|
7
|
+
hostsSearch = `name ^ (${hosts.map(({ name }) => name).join(',')})`;
|
8
|
+
else hostsSearch = `id ^ (${hosts.map(({ id }) => id).join(',')})`;
|
9
|
+
|
10
|
+
let hostCollectionsSearch;
|
11
|
+
if (hostCollections.length < MAX_NAME_ITEMS)
|
12
|
+
hostCollectionsSearch = `host_collection ^ (${hostCollections
|
13
|
+
.map(({ name }) => nameEscape(name))
|
14
|
+
.join(',')})`;
|
15
|
+
else
|
16
|
+
hostCollectionsSearch = `host_collection_id ^ (${hostCollections
|
17
|
+
.map(({ id }) => id)
|
18
|
+
.join(',')})`;
|
19
|
+
|
20
|
+
let hostGroupsSearch;
|
21
|
+
if (hostCollections.length < MAX_NAME_ITEMS)
|
22
|
+
hostGroupsSearch = `hostgroup_fullname ^ (${hostGroups
|
23
|
+
.map(({ name }) => nameEscape(name))
|
24
|
+
.join(',')})`;
|
25
|
+
else
|
26
|
+
hostGroupsSearch = `hostgroup_id ^ (${hostGroups
|
27
|
+
.map(({ id }) => id)
|
28
|
+
.join(',')})`;
|
10
29
|
const queryParts = [
|
11
30
|
hosts.length ? hostsSearch : false,
|
12
31
|
hostCollections.length ? hostCollectionsSearch : false,
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 16.0.
|
4
|
+
version: 16.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foreman Remote Execution team
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-07-28 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: deface
|
@@ -353,6 +353,7 @@ files:
|
|
353
353
|
- extra/cockpit/settings.yml.example
|
354
354
|
- lib/foreman_remote_execution.rb
|
355
355
|
- lib/foreman_remote_execution/engine.rb
|
356
|
+
- lib/foreman_remote_execution/plugin.rb
|
356
357
|
- lib/foreman_remote_execution/tasks/explain_proxy_selection.rake
|
357
358
|
- lib/foreman_remote_execution/version.rb
|
358
359
|
- lib/tasks/foreman_remote_execution_tasks.rake
|
@@ -419,17 +420,18 @@ files:
|
|
419
420
|
- test/unit/renderer_scope_input_test.rb
|
420
421
|
- test/unit/targeting_test.rb
|
421
422
|
- test/unit/template_invocation_input_value_test.rb
|
423
|
+
- webpack/JobInvocationDetail/CheckboxesActions.js
|
424
|
+
- webpack/JobInvocationDetail/DropdownFilter.js
|
422
425
|
- webpack/JobInvocationDetail/JobAdditionInfo.js
|
423
426
|
- webpack/JobInvocationDetail/JobInvocationActions.js
|
424
427
|
- webpack/JobInvocationDetail/JobInvocationConstants.js
|
425
428
|
- webpack/JobInvocationDetail/JobInvocationDetail.scss
|
426
429
|
- webpack/JobInvocationDetail/JobInvocationHostTable.js
|
427
|
-
- webpack/JobInvocationDetail/JobInvocationHostTableToolbar.js
|
428
430
|
- webpack/JobInvocationDetail/JobInvocationOverview.js
|
429
431
|
- webpack/JobInvocationDetail/JobInvocationSelectors.js
|
430
432
|
- webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js
|
431
433
|
- webpack/JobInvocationDetail/JobInvocationToolbarButtons.js
|
432
|
-
- webpack/JobInvocationDetail/
|
434
|
+
- webpack/JobInvocationDetail/OpenAllInvocationsModal.js
|
433
435
|
- webpack/JobInvocationDetail/TemplateInvocation.js
|
434
436
|
- webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js
|
435
437
|
- webpack/JobInvocationDetail/TemplateInvocationComponents/OutputToggleGroup.js
|
@@ -438,8 +440,8 @@ files:
|
|
438
440
|
- webpack/JobInvocationDetail/TemplateInvocationComponents/index.scss
|
439
441
|
- webpack/JobInvocationDetail/TemplateInvocationPage.js
|
440
442
|
- webpack/JobInvocationDetail/__tests__/MainInformation.test.js
|
441
|
-
- webpack/JobInvocationDetail/__tests__/OpenAlInvocations.test.js
|
442
443
|
- webpack/JobInvocationDetail/__tests__/OutputCodeBlock.test.js
|
444
|
+
- webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js
|
443
445
|
- webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js
|
444
446
|
- webpack/JobInvocationDetail/__tests__/fixtures.js
|
445
447
|
- webpack/JobInvocationDetail/index.js
|
@@ -605,7 +607,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
605
607
|
- !ruby/object:Gem::Version
|
606
608
|
version: '0'
|
607
609
|
requirements: []
|
608
|
-
rubygems_version: 3.6.
|
610
|
+
rubygems_version: 3.6.9
|
609
611
|
specification_version: 4
|
610
612
|
summary: A plugin bringing remote execution to the Foreman, completing the config
|
611
613
|
management functionality with remote management functionality.
|
@@ -1,111 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import PropTypes from 'prop-types';
|
3
|
-
import {
|
4
|
-
Alert,
|
5
|
-
AlertActionCloseButton,
|
6
|
-
Button,
|
7
|
-
Modal,
|
8
|
-
ModalVariant,
|
9
|
-
} from '@patternfly/react-core';
|
10
|
-
import { OutlinedWindowRestoreIcon } from '@patternfly/react-icons';
|
11
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
12
|
-
import { templateInvocationPageUrl } from './JobInvocationConstants';
|
13
|
-
|
14
|
-
export const PopupAlert = ({ setShowAlert }) => (
|
15
|
-
<Alert
|
16
|
-
ouiaId="template-invocation-new-tab-popup-alert"
|
17
|
-
variant="warning"
|
18
|
-
actionClose={<AlertActionCloseButton onClose={() => setShowAlert(false)} />}
|
19
|
-
title={__(
|
20
|
-
'Popups are blocked by your browser. Please allow popups for this site to open all invocations in new tabs.'
|
21
|
-
)}
|
22
|
-
/>
|
23
|
-
);
|
24
|
-
export const OpenAlInvocations = ({ results, id, setShowAlert }) => {
|
25
|
-
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
26
|
-
const handleModalToggle = () => {
|
27
|
-
setIsModalOpen(!isModalOpen);
|
28
|
-
};
|
29
|
-
|
30
|
-
const openLink = url => {
|
31
|
-
const newWin = window.open(url);
|
32
|
-
|
33
|
-
if (!newWin || newWin.closed || typeof newWin.closed === 'undefined') {
|
34
|
-
setShowAlert(true);
|
35
|
-
}
|
36
|
-
};
|
37
|
-
const OpenAllButton = () => (
|
38
|
-
<Button
|
39
|
-
variant="link"
|
40
|
-
isInline
|
41
|
-
aria-label="open all template invocations in a new tab"
|
42
|
-
ouiaId="template-invocation-new-tab-button"
|
43
|
-
onClick={() => {
|
44
|
-
if (results.length <= 3) {
|
45
|
-
results.forEach(result => {
|
46
|
-
openLink(templateInvocationPageUrl(result.id, id), '_blank');
|
47
|
-
});
|
48
|
-
} else {
|
49
|
-
handleModalToggle();
|
50
|
-
}
|
51
|
-
}}
|
52
|
-
>
|
53
|
-
<OutlinedWindowRestoreIcon />
|
54
|
-
</Button>
|
55
|
-
);
|
56
|
-
const OpenAllModal = () => (
|
57
|
-
<Modal
|
58
|
-
ouiaId="template-invocation-new-tab-modal"
|
59
|
-
title={__('Open all invocations in new tabs')}
|
60
|
-
isOpen={isModalOpen}
|
61
|
-
onClose={handleModalToggle}
|
62
|
-
variant={ModalVariant.small}
|
63
|
-
titleIconVariant="warning"
|
64
|
-
actions={[
|
65
|
-
<Button
|
66
|
-
ouiaId="template-invocation-new-tab-modal-confirm"
|
67
|
-
key="confirm"
|
68
|
-
variant="primary"
|
69
|
-
onClick={() => {
|
70
|
-
results.forEach(result => {
|
71
|
-
openLink(templateInvocationPageUrl(result.id, id), '_blank');
|
72
|
-
});
|
73
|
-
handleModalToggle();
|
74
|
-
}}
|
75
|
-
>
|
76
|
-
{__('Open all in new tabs')}
|
77
|
-
</Button>,
|
78
|
-
<Button
|
79
|
-
ouiaId="template-invocation-new-tab-modal-cancel"
|
80
|
-
key="cancel"
|
81
|
-
variant="link"
|
82
|
-
onClick={handleModalToggle}
|
83
|
-
>
|
84
|
-
{__('Cancel')}
|
85
|
-
</Button>,
|
86
|
-
]}
|
87
|
-
>
|
88
|
-
{__('Are you sure you want to open all invocations in new tabs?')}
|
89
|
-
<br />
|
90
|
-
{__('This will open a new tab for each invocation.')}
|
91
|
-
<br />
|
92
|
-
{__('The number of invocations is:')} <b>{results.length}</b>
|
93
|
-
</Modal>
|
94
|
-
);
|
95
|
-
return (
|
96
|
-
<>
|
97
|
-
<OpenAllButton />
|
98
|
-
<OpenAllModal />
|
99
|
-
</>
|
100
|
-
);
|
101
|
-
};
|
102
|
-
|
103
|
-
OpenAlInvocations.propTypes = {
|
104
|
-
results: PropTypes.array.isRequired,
|
105
|
-
id: PropTypes.string.isRequired,
|
106
|
-
setShowAlert: PropTypes.func.isRequired,
|
107
|
-
};
|
108
|
-
|
109
|
-
PopupAlert.propTypes = {
|
110
|
-
setShowAlert: PropTypes.func.isRequired,
|
111
|
-
};
|
@@ -1,110 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
3
|
-
import '@testing-library/jest-dom/extend-expect';
|
4
|
-
import { OpenAlInvocations, PopupAlert } from '../OpenAlInvocations';
|
5
|
-
import { templateInvocationPageUrl } from '../JobInvocationConstants';
|
6
|
-
|
7
|
-
// Mock the templateInvocationPageUrl function
|
8
|
-
jest.mock('../JobInvocationConstants', () => ({
|
9
|
-
...jest.requireActual('../JobInvocationConstants'),
|
10
|
-
templateInvocationPageUrl: jest.fn((resultId, id) => `url/${resultId}/${id}`),
|
11
|
-
}));
|
12
|
-
|
13
|
-
describe('OpenAlInvocations', () => {
|
14
|
-
const mockResults = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
15
|
-
const mockSetShowAlert = jest.fn();
|
16
|
-
let windowSpy;
|
17
|
-
const windowOpen = window.open;
|
18
|
-
|
19
|
-
beforeAll(() => {
|
20
|
-
window.open = () => {};
|
21
|
-
});
|
22
|
-
afterAll(() => {
|
23
|
-
windowSpy.mockRestore();
|
24
|
-
jest.clearAllMocks();
|
25
|
-
window.open = windowOpen;
|
26
|
-
});
|
27
|
-
|
28
|
-
test('renders without crashing', () => {
|
29
|
-
render(
|
30
|
-
<OpenAlInvocations
|
31
|
-
results={mockResults}
|
32
|
-
id="test-id"
|
33
|
-
setShowAlert={mockSetShowAlert}
|
34
|
-
/>
|
35
|
-
);
|
36
|
-
});
|
37
|
-
|
38
|
-
test('opens links when results length is less than or equal to 3', () => {
|
39
|
-
render(
|
40
|
-
<OpenAlInvocations
|
41
|
-
results={mockResults}
|
42
|
-
id="test-id"
|
43
|
-
setShowAlert={mockSetShowAlert}
|
44
|
-
/>
|
45
|
-
);
|
46
|
-
|
47
|
-
const button = screen.getByRole('button', { name: /open all/i });
|
48
|
-
fireEvent.click(button);
|
49
|
-
|
50
|
-
expect(templateInvocationPageUrl).toHaveBeenCalledTimes(mockResults.length);
|
51
|
-
mockResults.forEach(result => {
|
52
|
-
expect(templateInvocationPageUrl).toHaveBeenCalledWith(
|
53
|
-
result.id,
|
54
|
-
'test-id'
|
55
|
-
);
|
56
|
-
});
|
57
|
-
});
|
58
|
-
|
59
|
-
test('shows modal when results length is greater than 3', () => {
|
60
|
-
const largeResults = [...mockResults, { id: 4 }];
|
61
|
-
render(
|
62
|
-
<OpenAlInvocations
|
63
|
-
results={largeResults}
|
64
|
-
id="test-id"
|
65
|
-
setShowAlert={mockSetShowAlert}
|
66
|
-
/>
|
67
|
-
);
|
68
|
-
|
69
|
-
const button = screen.getByRole('button', { name: /open all/i });
|
70
|
-
fireEvent.click(button);
|
71
|
-
|
72
|
-
expect(
|
73
|
-
screen.getAllByText(/open all invocations in new tabs/i)
|
74
|
-
).toHaveLength(2);
|
75
|
-
});
|
76
|
-
|
77
|
-
test('shows alert when popups are blocked', () => {
|
78
|
-
window.open = jest.fn().mockReturnValueOnce(null);
|
79
|
-
|
80
|
-
render(
|
81
|
-
<OpenAlInvocations
|
82
|
-
results={mockResults}
|
83
|
-
id="test-id"
|
84
|
-
setShowAlert={mockSetShowAlert}
|
85
|
-
/>
|
86
|
-
);
|
87
|
-
|
88
|
-
const button = screen.getByRole('button', { name: /open all/i });
|
89
|
-
fireEvent.click(button);
|
90
|
-
|
91
|
-
expect(mockSetShowAlert).toHaveBeenCalledWith(true);
|
92
|
-
});
|
93
|
-
});
|
94
|
-
|
95
|
-
describe('PopupAlert', () => {
|
96
|
-
const mockSetShowAlert = jest.fn();
|
97
|
-
|
98
|
-
test('renders without crashing', () => {
|
99
|
-
render(<PopupAlert setShowAlert={mockSetShowAlert} />);
|
100
|
-
});
|
101
|
-
|
102
|
-
test('closes alert when close button is clicked', () => {
|
103
|
-
render(<PopupAlert setShowAlert={mockSetShowAlert} />);
|
104
|
-
|
105
|
-
const closeButton = screen.getByRole('button', { name: /close/i });
|
106
|
-
fireEvent.click(closeButton);
|
107
|
-
|
108
|
-
expect(mockSetShowAlert).toHaveBeenCalledWith(false);
|
109
|
-
});
|
110
|
-
});
|