foreman_leapp 3.1.0 → 3.3.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_leapp/locale/de/foreman_leapp.js +5 -2
  3. data/app/assets/javascripts/foreman_leapp/locale/en/foreman_leapp.js +5 -2
  4. data/app/assets/javascripts/foreman_leapp/locale/fr/foreman_leapp.js +7 -4
  5. data/app/assets/javascripts/foreman_leapp/locale/ja/foreman_leapp.js +9 -6
  6. data/app/assets/javascripts/foreman_leapp/locale/ka/foreman_leapp.js +6 -3
  7. data/app/assets/javascripts/foreman_leapp/locale/ko/foreman_leapp.js +7 -4
  8. data/app/assets/javascripts/foreman_leapp/locale/zh_CN/foreman_leapp.js +7 -4
  9. data/app/views/api/v2/preupgrade_report_entries/base.json.rabl +1 -1
  10. data/lib/foreman_leapp/engine.rb +2 -0
  11. data/lib/foreman_leapp/version.rb +1 -1
  12. data/locale/de/LC_MESSAGES/foreman_leapp.mo +0 -0
  13. data/locale/de/foreman_leapp.po +5 -2
  14. data/locale/en/LC_MESSAGES/foreman_leapp.mo +0 -0
  15. data/locale/en/foreman_leapp.po +5 -2
  16. data/locale/foreman_leapp.pot +36 -9
  17. data/locale/fr/LC_MESSAGES/foreman_leapp.mo +0 -0
  18. data/locale/fr/foreman_leapp.po +8 -5
  19. data/locale/ja/LC_MESSAGES/foreman_leapp.mo +0 -0
  20. data/locale/ja/foreman_leapp.po +10 -7
  21. data/locale/ka/LC_MESSAGES/foreman_leapp.mo +0 -0
  22. data/locale/ka/foreman_leapp.po +6 -3
  23. data/locale/ko/LC_MESSAGES/foreman_leapp.mo +0 -0
  24. data/locale/ko/foreman_leapp.po +8 -5
  25. data/locale/zh_CN/LC_MESSAGES/foreman_leapp.mo +0 -0
  26. data/locale/zh_CN/foreman_leapp.po +8 -5
  27. data/webpack/components/PreupgradeReports/PreupgradeReports.js +1 -1
  28. data/webpack/components/PreupgradeReports/PreupgradeReportsHelpers.js +2 -2
  29. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReports.test.js.snap +1 -1
  30. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReportsHelpers.test.js.snap +25 -1
  31. data/webpack/components/PreupgradeReports/components/__snapshots__/NoReports.test.js.snap +6 -0
  32. data/webpack/components/PreupgradeReportsList/__tests__/PreupgradeReportsList.test.js +119 -35
  33. data/webpack/components/PreupgradeReportsList/index.js +2 -3
  34. data/webpack/components/PreupgradeReportsTable/PreupgradeReportsTable.scss +5 -0
  35. data/webpack/components/PreupgradeReportsTable/ReportDetails.js +134 -0
  36. data/webpack/components/PreupgradeReportsTable/__tests__/PreupgradeReportsTable.test.js +186 -0
  37. data/webpack/components/PreupgradeReportsTable/index.js +237 -0
  38. data/webpack/global_index.js +10 -0
  39. metadata +8 -8
  40. data/webpack/__mocks__/foremanReact/common/I18n.js +0 -2
  41. data/webpack/__mocks__/foremanReact/components/Pagination.js +0 -2
  42. data/webpack/__mocks__/foremanReact/components/common/EmptyState.js +0 -1
  43. data/webpack/__mocks__/foremanReact/components/common/MessageBox.js +0 -2
  44. data/webpack/components/PreupgradeReportsList/__tests__/__snapshots__/PreupgradeReportsList.test.js.snap +0 -135
@@ -7,14 +7,14 @@
7
7
  # 0868a4d1af5275b3f70b0a6dac4c99a4, 2022
8
8
  # Bryan Kearney <bryan.kearney@gmail.com>, 2022
9
9
  # Ewoud Kohl van Wijngaarden <ewoud+transifex@kohlvanwijngaarden.nl>, 2025
10
+ # Ondřej Gajdušek, 2026
10
11
  #
11
12
  msgid ""
12
13
  msgstr ""
13
- "Project-Id-Version: foreman_leapp 3.0.0\n"
14
+ "Project-Id-Version: foreman_leapp 3.2.0\n"
14
15
  "Report-Msgid-Bugs-To: \n"
15
16
  "PO-Revision-Date: 2022-10-12 12:00+0000\n"
16
- "Last-Translator: Ewoud Kohl van Wijngaarden <ewoud+transifex@kohlvanwijngaarde"
17
- "n.nl>, 2025\n"
17
+ "Last-Translator: Ondřej Gajdušek, 2026\n"
18
18
  "Language-Team: Korean (https://app.transifex.com/foreman/teams/114/ko/)\n"
19
19
  "MIME-Version: 1.0\n"
20
20
  "Content-Type: text/plain; charset=UTF-8\n"
@@ -31,8 +31,8 @@ msgstr "모두"
31
31
  msgid "Command"
32
32
  msgstr "명령어"
33
33
 
34
- msgid "Could not retrieve data: %(status) - %(msg)"
35
- msgstr "%(status) - %(msg) 데이터를 검색할 수 없습니다:"
34
+ msgid "Could not retrieve data: %(status)s - %(msg)s"
35
+ msgstr "데이터를 가져올 수 없습니다:%(status)s - %(msg)s"
36
36
 
37
37
  msgid "Failed to fetch preupgrade reports from server."
38
38
  msgstr "서버에서 사전 업그레이드 보고서를 가져오지 못했습니다."
@@ -136,6 +136,9 @@ msgstr "태그"
136
136
  msgid "The preupgrade report could not be generated, check the job details for the reason"
137
137
  msgstr "사전 업그레이드 보고서를 생성할 수 없습니다. 해당 사유에 대한 작업 세부 정보를 확인하세요."
138
138
 
139
+ msgid "The preupgrade report shows no issues."
140
+ msgstr "사전 업그레이드 보고서에는 문제가 없는 것으로 표시됩니다."
141
+
139
142
  msgid "The preupgrade report will be available after the job finishes"
140
143
  msgstr "작업이 완료되면 사전 업그레이드 보고서를 사용할 수 있습니다."
141
144
 
@@ -7,14 +7,14 @@
7
7
  # Bryan Kearney <bryan.kearney@gmail.com>, 2022
8
8
  # Amit Upadhye <aupadhye@redhat.com>, 2023
9
9
  # Ewoud Kohl van Wijngaarden <ewoud+transifex@kohlvanwijngaarden.nl>, 2023
10
+ # Ondřej Gajdušek, 2026
10
11
  #
11
12
  msgid ""
12
13
  msgstr ""
13
- "Project-Id-Version: foreman_leapp 2.0.3\n"
14
+ "Project-Id-Version: foreman_leapp 3.2.0\n"
14
15
  "Report-Msgid-Bugs-To: \n"
15
16
  "PO-Revision-Date: 2022-10-12 12:00+0000\n"
16
- "Last-Translator: Ewoud Kohl van Wijngaarden <ewoud+transifex@kohlvanwijngaarde"
17
- "n.nl>, 2023\n"
17
+ "Last-Translator: Ondřej Gajdušek, 2026\n"
18
18
  "Language-Team: Chinese (China) (https://app.transifex.com/foreman/teams/114/zh"
19
19
  "_CN/)\n"
20
20
  "MIME-Version: 1.0\n"
@@ -32,8 +32,8 @@ msgstr "全部"
32
32
  msgid "Command"
33
33
  msgstr "命令"
34
34
 
35
- msgid "Could not retrieve data: %(status) - %(msg)"
36
- msgstr "无法检索数据:%(status) - %(msg)"
35
+ msgid "Could not retrieve data: %(status)s - %(msg)s"
36
+ msgstr "无法检索数据:%(status)s - %(msg)s"
37
37
 
38
38
  msgid "Failed to fetch preupgrade reports from server."
39
39
  msgstr "从服务器获取预升级报告失败。"
@@ -137,6 +137,9 @@ msgstr "标签"
137
137
  msgid "The preupgrade report could not be generated, check the job details for the reason"
138
138
  msgstr "无法生成预升级报告,查看作业详情以了解更多信息"
139
139
 
140
+ msgid "The preupgrade report shows no issues."
141
+ msgstr "预升级报告没有显示问题。"
142
+
140
143
  msgid "The preupgrade report will be available after the job finishes"
141
144
  msgstr "预升级报告将在作业完成后可用"
142
145
 
@@ -119,7 +119,7 @@ const withLoadingState = Component => componentProps => {
119
119
  <MessageBox
120
120
  key="preupgrade-reports-error"
121
121
  icontype="error-circle-o"
122
- msg={sprintf(__('Could not retrieve data: %(status) - %(msg)'), {
122
+ msg={sprintf(__('Could not retrieve data: %(status)s - %(msg)s'), {
123
123
  status: error.statusText,
124
124
  msg: error.errorMsg,
125
125
  })}
@@ -57,9 +57,9 @@ export const idsForInvocationFromReports = reports =>
57
57
  idsForInvocationFromEntries(flattenEntries(reports));
58
58
 
59
59
  export const entriesPage = (entries, pagination) => {
60
- const offset = (pagination.page - 1) * pagination.per_page;
60
+ const offset = (pagination.page - 1) * pagination.perPage;
61
61
 
62
- return entries.slice(offset, offset + pagination.per_page);
62
+ return entries.slice(offset, offset + pagination.perPage);
63
63
  };
64
64
 
65
65
  export const filterEntries = (attribute, value, entries) => {
@@ -4,7 +4,7 @@ exports[`PreupgradeReports should render error 1`] = `
4
4
  <MessageBox
5
5
  icontype="error-circle-o"
6
6
  key="preupgrade-reports-error"
7
- msg="Could not retrieve data: %(status) - %(msg)"
7
+ msg="Could not retrieve data: Internal server error - Well, this is embarassing"
8
8
  />
9
9
  `;
10
10
 
@@ -131,7 +131,31 @@ Array [
131
131
  ]
132
132
  `;
133
133
 
134
- exports[`PreupgradeReportsHelpers should return entries page 1`] = `Array []`;
134
+ exports[`PreupgradeReportsHelpers should return entries page 1`] = `
135
+ Array [
136
+ Object {
137
+ "flags": Array [],
138
+ "hostname": "foo.example.com",
139
+ "id": 45,
140
+ "severity": "low",
141
+ "title": "Not enough credits",
142
+ },
143
+ Object {
144
+ "flags": Array [],
145
+ "hostname": "foo.example.com",
146
+ "id": 46,
147
+ "severity": "medium",
148
+ "title": "SELinux is turned off",
149
+ },
150
+ Object {
151
+ "flags": Array [],
152
+ "hostname": "foo.example.com",
153
+ "id": 47,
154
+ "severity": "medium",
155
+ "title": "Root password is too short",
156
+ },
157
+ ]
158
+ `;
135
159
 
136
160
  exports[`PreupgradeReportsHelpers should return fixable entries 1`] = `
137
161
  Array [
@@ -2,18 +2,24 @@
2
2
 
3
3
  exports[`NoReports should render when reports expected 1`] = `
4
4
  <EmptyStatePattern
5
+ action={null}
5
6
  description="The preupgrade report could not be generated, check the job details for the reason"
7
+ documentation={null}
6
8
  header="No Preupgrade Report Available"
7
9
  icon="warning-triangle-o"
8
10
  iconType="pf"
11
+ secondaryActions={Array []}
9
12
  />
10
13
  `;
11
14
 
12
15
  exports[`NoReports should render when reports not expected 1`] = `
13
16
  <EmptyStatePattern
17
+ action={null}
14
18
  description="The preupgrade report will be available after the job finishes"
19
+ documentation={null}
15
20
  header="No Preupgrade Report Available"
16
21
  icon="in-progress"
17
22
  iconType="pf"
23
+ secondaryActions={Array []}
18
24
  />
19
25
  `;
@@ -1,40 +1,124 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
2
4
 
3
5
  import PreupgradeReportsList from '../index';
4
6
 
5
- const allEntries = [
6
- { title: 'Fix me!', severity: 'Too severe to talk about' },
7
- { title: 'I am broken too', severity: 'medium' },
8
- { title: 'Octocat is not happy', severity: 'high' },
9
- { title: 'Not enough credits', severity: 'low' },
10
- ];
11
-
12
- const isSelected = () => false;
13
- const toggleSelected = () => {};
14
- const sort = { attribute: '', order: 'asc' };
15
- const changeSort = () => {};
16
- const toggleSelectAll = () => {};
17
-
18
- const fixtures = {
19
- 'should render': {
20
- allEntries,
21
- fixAllWorking: false,
22
- isSelected,
23
- toggleSelected,
24
- sort,
25
- changeSort,
26
- toggleSelectAll,
27
- },
28
- 'should render when working': {
29
- allEntries,
30
- fixAllWorking: true,
31
- isSelected,
32
- toggleSelected,
33
- sort,
34
- changeSort,
35
- toggleSelectAll,
36
- },
7
+ jest.mock('foremanReact/components/Pagination', () => {
8
+ const MockPagination = () => <div data-testid="pagination">Pagination</div>;
9
+ return MockPagination;
10
+ });
11
+
12
+ jest.mock('../components/images/i_severity-high.svg', () => 'severity-high.svg');
13
+ jest.mock('../components/images/i_severity-med.svg', () => 'severity-med.svg');
14
+ jest.mock('../components/images/i_severity-low.svg', () => 'severity-low.svg');
15
+
16
+ const createMockEntries = count =>
17
+ Array.from({ length: count }, (_, i) => ({
18
+ id: i + 1,
19
+ preupgradeReportId: 100,
20
+ title: `Entry ${i + 1}`,
21
+ hostname: `host${i + 1}.example.com`,
22
+ severity: i % 3 === 0 ? 'high' : i % 3 === 1 ? 'medium' : 'low',
23
+ flags: i === 0 ? ['inhibitor'] : [],
24
+ detail: {
25
+ remediations:
26
+ i < 2
27
+ ? [{ type: 'command', context: ['echo', 'fix', 'command'] }]
28
+ : [],
29
+ },
30
+ }));
31
+
32
+ const defaultProps = {
33
+ allEntries: createMockEntries(4),
34
+ isSelected: () => false,
35
+ toggleSelected: jest.fn(),
36
+ sort: { attribute: '', order: 'asc' },
37
+ changeSort: jest.fn(),
38
+ toggleSelectAll: jest.fn(),
37
39
  };
38
40
 
39
- describe('PreupgradeReportsList', () =>
40
- testComponentSnapshotsWithFixtures(PreupgradeReportsList, fixtures));
41
+ const renderComponent = (props = {}) =>
42
+ render(<PreupgradeReportsList {...defaultProps} {...props} />);
43
+
44
+ describe('PreupgradeReportsList', () => {
45
+ beforeEach(() => {
46
+ jest.clearAllMocks();
47
+ });
48
+
49
+ it('renders the list header', () => {
50
+ renderComponent();
51
+
52
+ expect(screen.getByText('Title')).toBeInTheDocument();
53
+ expect(screen.getByText('Host')).toBeInTheDocument();
54
+ expect(screen.getByText('Risk Factor')).toBeInTheDocument();
55
+ expect(screen.getByText('Has Remediation?')).toBeInTheDocument();
56
+ expect(screen.getByText('Inhibitor?')).toBeInTheDocument();
57
+ });
58
+
59
+ it('renders entry titles', () => {
60
+ renderComponent();
61
+
62
+ expect(screen.getByText('Entry 1')).toBeInTheDocument();
63
+ expect(screen.getByText('Entry 2')).toBeInTheDocument();
64
+ expect(screen.getByText('Entry 3')).toBeInTheDocument();
65
+ expect(screen.getByText('Entry 4')).toBeInTheDocument();
66
+ });
67
+
68
+ it('renders entry hostnames', () => {
69
+ renderComponent();
70
+
71
+ expect(screen.getByText('host1.example.com')).toBeInTheDocument();
72
+ expect(screen.getByText('host2.example.com')).toBeInTheDocument();
73
+ });
74
+
75
+ it('renders checkboxes for entries', () => {
76
+ renderComponent();
77
+
78
+ const checkboxes = screen.getAllByRole('checkbox');
79
+ expect(checkboxes.length).toBeGreaterThan(0);
80
+ });
81
+
82
+ it('calls toggleSelectAll when header checkbox is clicked', () => {
83
+ const toggleSelectAll = jest.fn();
84
+ renderComponent({ toggleSelectAll });
85
+
86
+ const checkboxes = screen.getAllByRole('checkbox');
87
+ fireEvent.click(checkboxes[0]);
88
+
89
+ expect(toggleSelectAll).toHaveBeenCalled();
90
+ });
91
+
92
+ it('renders with empty entries array', () => {
93
+ renderComponent({ allEntries: [] });
94
+
95
+ expect(screen.getByText('Title')).toBeInTheDocument();
96
+ expect(screen.queryByText('Entry 1')).not.toBeInTheDocument();
97
+ });
98
+
99
+ it('renders entries with selected state', () => {
100
+ const isSelected = entry => entry.id === 1;
101
+ renderComponent({ isSelected });
102
+
103
+ const checkboxes = screen.getAllByRole('checkbox');
104
+ const entryCheckboxes = checkboxes.slice(1);
105
+ expect(entryCheckboxes[0]).toBeChecked();
106
+ expect(entryCheckboxes[1]).not.toBeChecked();
107
+ });
108
+
109
+ it('calls toggleSelected when entry checkbox is clicked', () => {
110
+ const toggleSelected = jest.fn();
111
+ renderComponent({ toggleSelected });
112
+
113
+ const checkboxes = screen.getAllByRole('checkbox');
114
+ fireEvent.click(checkboxes[1]);
115
+
116
+ expect(toggleSelected).toHaveBeenCalled();
117
+ });
118
+
119
+ it('renders pagination component', () => {
120
+ renderComponent();
121
+
122
+ expect(screen.getByTestId('pagination')).toBeInTheDocument();
123
+ });
124
+ });
@@ -19,11 +19,10 @@ const PreupgradeReportsList = ({
19
19
  changeSort,
20
20
  toggleSelectAll,
21
21
  }) => {
22
- const { perPage, perPageOptions } = useForemanSettings();
22
+ const { perPage } = useForemanSettings();
23
23
  const [pagination, setPagination] = useState({
24
24
  page: 1,
25
- per_page: perPage,
26
- perPageOptions,
25
+ perPage,
27
26
  });
28
27
 
29
28
  return (
@@ -0,0 +1,5 @@
1
+ .leapp-report-details {
2
+ dd {
3
+ white-space: pre-wrap;
4
+ }
5
+ }
@@ -0,0 +1,134 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ DescriptionList,
5
+ DescriptionListGroup,
6
+ DescriptionListTerm,
7
+ DescriptionListDescription,
8
+ Label,
9
+ LabelGroup,
10
+ } from '@patternfly/react-core';
11
+ import { translate as __ } from 'foremanReact/common/I18n';
12
+ import './PreupgradeReportsTable.scss';
13
+
14
+ export const renderSeverityLabel = severity => {
15
+ switch (severity) {
16
+ case 'high':
17
+ return <Label color="red">{__('High')}</Label>;
18
+ case 'medium':
19
+ return <Label color="orange">{__('Medium')}</Label>;
20
+ case 'low':
21
+ return <Label color="blue">{__('Low')}</Label>;
22
+ case 'info':
23
+ return <Label color="grey">{__('Info')}</Label>;
24
+ default:
25
+ return <Label color="grey">{severity || __('Info')}</Label>;
26
+ }
27
+ };
28
+
29
+ const ReportDetails = ({ entry }) => (
30
+ <DescriptionList isHorizontal isCompact className="leapp-report-details">
31
+ {entry.title && (
32
+ <DescriptionListGroup>
33
+ <DescriptionListTerm>{__('Title')}</DescriptionListTerm>
34
+ <DescriptionListDescription>{entry.title}</DescriptionListDescription>
35
+ </DescriptionListGroup>
36
+ )}
37
+
38
+ {entry.severity && (
39
+ <DescriptionListGroup>
40
+ <DescriptionListTerm>{__('Risk Factor')}</DescriptionListTerm>
41
+ <DescriptionListDescription>
42
+ {renderSeverityLabel(entry.severity)}
43
+ </DescriptionListDescription>
44
+ </DescriptionListGroup>
45
+ )}
46
+
47
+ {entry.summary && (
48
+ <DescriptionListGroup>
49
+ <DescriptionListTerm>{__('Summary')}</DescriptionListTerm>
50
+ <DescriptionListDescription>{entry.summary}</DescriptionListDescription>
51
+ </DescriptionListGroup>
52
+ )}
53
+
54
+ {entry.tags && entry.tags.length > 0 && (
55
+ <DescriptionListGroup>
56
+ <DescriptionListTerm>{__('Tags')}</DescriptionListTerm>
57
+ <DescriptionListDescription>
58
+ <LabelGroup>
59
+ {entry.tags.map(tag => (
60
+ <Label key={tag} color="blue">
61
+ {tag}
62
+ </Label>
63
+ ))}
64
+ </LabelGroup>
65
+ </DescriptionListDescription>
66
+ </DescriptionListGroup>
67
+ )}
68
+
69
+ {entry.detail?.external?.filter(link => link.url).length > 0 && (
70
+ <DescriptionListGroup>
71
+ <DescriptionListTerm>{__('Links')}</DescriptionListTerm>
72
+ <DescriptionListDescription>
73
+ {entry.detail.external
74
+ .filter(link => link.url)
75
+ .map((item, i) => (
76
+ <div key={item.url || i}>
77
+ <a href={item.url} target="_blank" rel="noopener noreferrer">
78
+ {item.title || item.url}
79
+ </a>
80
+ </div>
81
+ ))}
82
+ </DescriptionListDescription>
83
+ </DescriptionListGroup>
84
+ )}
85
+
86
+ {entry.detail?.remediations?.length > 0 &&
87
+ entry.detail.remediations.map((item, i) => (
88
+ <DescriptionListGroup key={`remediations-${i}`}>
89
+ <DescriptionListTerm>
90
+ {item.type === 'command' ? __('Command') : __('Hint')}
91
+ </DescriptionListTerm>
92
+ <DescriptionListDescription>
93
+ {item.type === 'command' ? (
94
+ <code>
95
+ {Array.isArray(item.context)
96
+ ? item.context.join(' ')
97
+ : item.context}
98
+ </code>
99
+ ) : (
100
+ item.context
101
+ )}
102
+ </DescriptionListDescription>
103
+ </DescriptionListGroup>
104
+ ))}
105
+ </DescriptionList>
106
+ );
107
+
108
+ ReportDetails.propTypes = {
109
+ entry: PropTypes.shape({
110
+ title: PropTypes.string,
111
+ severity: PropTypes.string,
112
+ summary: PropTypes.string,
113
+ tags: PropTypes.arrayOf(PropTypes.string),
114
+ detail: PropTypes.shape({
115
+ external: PropTypes.arrayOf(
116
+ PropTypes.shape({
117
+ url: PropTypes.string,
118
+ title: PropTypes.string,
119
+ })
120
+ ),
121
+ remediations: PropTypes.arrayOf(
122
+ PropTypes.shape({
123
+ type: PropTypes.string,
124
+ context: PropTypes.oneOfType([
125
+ PropTypes.string,
126
+ PropTypes.arrayOf(PropTypes.string),
127
+ ]),
128
+ })
129
+ ),
130
+ }),
131
+ }).isRequired,
132
+ };
133
+
134
+ export default ReportDetails;
@@ -0,0 +1,186 @@
1
+ import React from 'react';
2
+ import {
3
+ render,
4
+ screen,
5
+ waitFor,
6
+ fireEvent,
7
+ within,
8
+ } from '@testing-library/react';
9
+ import '@testing-library/jest-dom/extend-expect';
10
+ import { Provider } from 'react-redux';
11
+ import configureMockStore from 'redux-mock-store';
12
+ import thunk from 'redux-thunk';
13
+ import { APIActions } from 'foremanReact/redux/API';
14
+ import PreupgradeReportsTable from '../index';
15
+
16
+ jest.mock('foremanReact/redux/API');
17
+
18
+ const mockStore = configureMockStore([thunk]);
19
+
20
+ const mockJobId = 42;
21
+ const mockReportId = 999;
22
+ const mockJobData = {
23
+ id: mockJobId,
24
+ template_name: 'Run preupgrade via Leapp',
25
+ };
26
+
27
+ const mockEntries = Array.from({ length: 12 }, (_, i) => ({
28
+ id: i + 1,
29
+ title: `Report Entry ${i + 1}`,
30
+ hostname: 'example.com',
31
+ severity: i === 0 ? 'high' : 'low',
32
+ summary: `Summary for report entry ${i + 1}`,
33
+ tags: i === 0 ? ['security', 'network'] : [],
34
+ flags: i === 0 ? ['inhibitor'] : [],
35
+ detail: {
36
+ remediations:
37
+ i === 0 ? [{ type: 'command', context: ['echo', 'fix_command'] }] : [],
38
+ external:
39
+ i === 0 ? [{ url: 'http://example.com', title: 'External Link' }] : [],
40
+ },
41
+ }));
42
+
43
+ describe('PreupgradeReportsTable', () => {
44
+ let store;
45
+
46
+ beforeEach(() => {
47
+ store = mockStore({ API: {} });
48
+ jest.clearAllMocks();
49
+
50
+ APIActions.get.mockImplementation(({ key, handleSuccess }) => {
51
+ return dispatch => {
52
+ if (key.includes('GET_LEAPP_REPORT_LIST'))
53
+ handleSuccess({ results: [{ id: mockReportId }] });
54
+ if (key.includes('GET_LEAPP_REPORT_DETAIL'))
55
+ handleSuccess({
56
+ id: mockReportId,
57
+ preupgrade_report_entries: mockEntries,
58
+ });
59
+ return { type: 'MOCK_API_SUCCESS' };
60
+ };
61
+ });
62
+ });
63
+
64
+ const renderComponent = (data = mockJobData) =>
65
+ render(
66
+ <Provider store={store}>
67
+ <PreupgradeReportsTable data={data} />
68
+ </Provider>
69
+ );
70
+
71
+ const expandSection = () => {
72
+ fireEvent.click(screen.getByText('Leapp preupgrade report'));
73
+ };
74
+
75
+ it('renders data', async () => {
76
+ renderComponent();
77
+ expandSection();
78
+ await waitFor(() => screen.getByText('Report Entry 1', { selector: 'td' }));
79
+ expect(
80
+ screen.getByText('Report Entry 1', { selector: 'td' })
81
+ ).toBeInTheDocument();
82
+ });
83
+
84
+ it('expands a row and shows details', async () => {
85
+ renderComponent();
86
+ expandSection();
87
+ await waitFor(() => screen.getByText('Report Entry 1', { selector: 'td' }));
88
+
89
+ const rowExpandButtons = screen.getAllByLabelText('Details');
90
+ fireEvent.click(rowExpandButtons[0]);
91
+
92
+ expect(await screen.findByText('Summary')).toBeInTheDocument();
93
+ expect(
94
+ await screen.findByText('Summary for report entry 1')
95
+ ).toBeInTheDocument();
96
+ });
97
+
98
+ it('expands all rows', async () => {
99
+ renderComponent();
100
+ expandSection();
101
+ await waitFor(() => screen.getByText('Report Entry 1', { selector: 'td' }));
102
+
103
+ const expandAllButton = screen.getByLabelText('Expand all rows');
104
+ fireEvent.click(expandAllButton);
105
+
106
+ expect(
107
+ await screen.findByText('Summary for report entry 1')
108
+ ).toBeInTheDocument();
109
+ expect(
110
+ await screen.findByText('Summary for report entry 5')
111
+ ).toBeInTheDocument();
112
+ });
113
+
114
+ it('paginates to the next page', async () => {
115
+ renderComponent();
116
+ expandSection();
117
+ await waitFor(() => screen.getByText('Report Entry 1', { selector: 'td' }));
118
+
119
+ fireEvent.click(screen.getAllByLabelText('Go to next page')[0]);
120
+ await waitFor(() => screen.getByText('Report Entry 6', { selector: 'td' }));
121
+
122
+ expect(
123
+ screen.getByText('Report Entry 10', { selector: 'td' })
124
+ ).toBeInTheDocument();
125
+ });
126
+
127
+ it('changes perPage limit to 10', async () => {
128
+ renderComponent();
129
+ expandSection();
130
+ await waitFor(() => screen.getByText('Report Entry 1', { selector: 'td' }));
131
+
132
+ fireEvent.click(screen.getAllByLabelText('Items per page')[0]);
133
+ fireEvent.click(screen.getAllByText('10 per page')[0]);
134
+
135
+ await waitFor(() => {
136
+ expect(
137
+ screen.getByText('Report Entry 10', { selector: 'td' })
138
+ ).toBeInTheDocument();
139
+ });
140
+ });
141
+
142
+ it('renders empty state message when no issues found', async () => {
143
+ APIActions.get.mockImplementation(({ key, handleSuccess }) => {
144
+ return () => {
145
+ if (key.includes('GET_LEAPP_REPORT_LIST'))
146
+ handleSuccess({ results: [{ id: mockReportId }] });
147
+ if (key.includes('GET_LEAPP_REPORT_DETAIL'))
148
+ handleSuccess({ id: mockReportId, preupgrade_report_entries: [] });
149
+ return { type: 'EMPTY' };
150
+ };
151
+ });
152
+ renderComponent();
153
+ expandSection();
154
+ await waitFor(() => {
155
+ expect(
156
+ screen.getByText('The preupgrade report shows no issues.')
157
+ ).toBeInTheDocument();
158
+ });
159
+ });
160
+
161
+ it('does not render anything for non-Leapp jobs', () => {
162
+ const nonLeappData = { id: 55, template_name: 'Standard RHEL Update' };
163
+ renderComponent(nonLeappData);
164
+ expect(
165
+ screen.queryByText('Leapp preupgrade report')
166
+ ).not.toBeInTheDocument();
167
+ });
168
+
169
+ it('displays correct inhibitor status based on flags', async () => {
170
+ renderComponent();
171
+ expandSection();
172
+ await waitFor(() => screen.getByText('Report Entry 1', { selector: 'td' }));
173
+
174
+ const row1 = screen
175
+ .getByText('Report Entry 1', { selector: 'td' })
176
+ .closest('tr');
177
+ const inhibitorCell1 = row1.querySelector('td[data-label="Inhibitor?"]');
178
+ expect(within(inhibitorCell1).getByText('Yes')).toBeInTheDocument();
179
+
180
+ const row2 = screen
181
+ .getByText('Report Entry 2', { selector: 'td' })
182
+ .closest('tr');
183
+ const inhibitorCell2 = row2.querySelector('td[data-label="Inhibitor?"]');
184
+ expect(within(inhibitorCell2).getByText('No')).toBeInTheDocument();
185
+ });
186
+ });