From c35f1b751663335d4cabca13b66c6bda2b570d66 Mon Sep 17 00:00:00 2001 From: Andrei Lakatos Date: Tue, 30 Jun 2026 10:55:08 -0400 Subject: [PATCH 1/3] Fixes #39350 - implement expandable rows for task info --- .../TaskDetails/Components/TaskHelper.js | 54 +++ .../TaskDetails/Components/TaskInfo.js | 330 +++++++++++------- .../Components/__tests__/Task.test.js | 12 +- .../Components/__tests__/TaskHelper.test.js | 119 ++++++- .../Components/__tests__/TaskInfo.test.js | 149 ++++++-- .../TaskDetails/__tests__/TaskDetails.test.js | 6 +- 6 files changed, 520 insertions(+), 150 deletions(-) diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/TaskHelper.js b/webpack/ForemanTasks/Components/TaskDetails/Components/TaskHelper.js index 156d52f6e..f6f6b2166 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/TaskHelper.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/TaskHelper.js @@ -22,3 +22,57 @@ export const durationInWords = ( const numberWithDelimiter = x => x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + +export const isDelayed = ({ startAt, startedAt }) => { + if ( + startAt == null || + startedAt == null || + startAt === '' || + startedAt === '' + ) { + return false; + } + + const a = new Date(startAt); + const b = new Date(startedAt); + + if (Number.isNaN(a.getTime()) || Number.isNaN(b.getTime())) { + return false; + } + + a.setMilliseconds(0); + b.setMilliseconds(0); + + return a.getTime() !== b.getTime(); +}; + +export const parseUsernameLinkHref = usernamePath => { + if (!usernamePath || typeof usernamePath !== 'string') { + return null; + } + + const match = usernamePath.match(/href=(["'])(.*?)\1/i); + + return match ? match[2] : null; +}; + +export const formatTaskDuration = (startedAtStr, endedAtStr, state) => { + if (state !== 'stopped' || !startedAtStr || !endedAtStr) { + return __('N/A'); + } + + const startMs = new Date(startedAtStr).getTime(); + const endMs = new Date(endedAtStr).getTime(); + + if (Number.isNaN(startMs) || Number.isNaN(endMs) || endMs < startMs) { + return __('N/A'); + } + + const duration = durationInWords(startedAtStr, endedAtStr); + + if (typeof duration === 'string') { + return duration; + } + + return duration.text; +}; diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js b/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js index affab7a53..1e3f8ac8a 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js @@ -1,36 +1,39 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { + ClipboardCopy, + ExpandableSection, + ExpandableSectionToggle, + Flex, + FlexItem, Grid, GridItem, Progress, ProgressVariant, + Split, + SplitItem, + Text, + TextVariants, } from '@patternfly/react-core'; -import { translate as __ } from 'foremanReact/common/I18n'; +import { translate as __, sprintf } from 'foremanReact/common/I18n'; import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime'; import { taskResultIconEl } from '../../common/taskResultIcon'; +import { + formatTaskDuration, + isDelayed, + parseUsernameLinkHref, +} from './TaskHelper'; -const isDelayed = ({ startAt, startedAt }) => { - if ( - startAt == null || - startedAt == null || - startAt === '' || - startedAt === '' - ) { - return false; - } - const a = new Date(startAt); - const b = new Date(startedAt); - if (Number.isNaN(a.getTime()) || Number.isNaN(b.getTime())) { - return false; +const formatResultLabel = result => { + if (!result || typeof result !== 'string') { + return __('N/A'); } - a.setMilliseconds(0); - b.setMilliseconds(0); - return a.getTime() !== b.getTime(); + + return result.charAt(0).toUpperCase() + result.slice(1).toLowerCase(); }; -const progressVariantForResult = result => { +const progressVariant = result => { switch (result) { case 'error': return ProgressVariant.danger; @@ -43,127 +46,204 @@ const progressVariantForResult = result => { } }; +const MetadataField = ({ title, value, className, labelOuiaId }) => ( + + + + + {title} + + + {value} + + +); + +MetadataField.propTypes = { + title: PropTypes.node.isRequired, + value: PropTypes.node.isRequired, + className: PropTypes.string, + labelOuiaId: PropTypes.string.isRequired, +}; + +MetadataField.defaultProps = { + className: undefined, +}; + +const copyableText = text => + text ? ( + + {text} + + ) : ( + __('N/A') + ); + const TaskInfo = props => { const { action, endedAt, + externalId, + id, + label, result, startAt, - startBefore, startedAt, state, - help, - output, progress, username, usernamePath, } = props; - const details = [ - [ - { - title: 'Name', - value: action || __('N/A'), - className: 'details-name', - }, - { - title: 'Start at', - value: , - }, - ], - [ - { - title: 'Result', - value: ( - - {taskResultIconEl(state, result)} {result} - - ), - }, - { - title: 'Started at', - value: , - }, - ], - [ - { - title: 'Triggered by', - value: (usernamePath || '').includes('href') ? ( - {username} - ) : ( - username || '' - ), - }, - { - title: 'Ended at', - value: , - }, - ], - [ - { - title: 'Execution type', - value: __(isDelayed(props) ? 'Delayed' : 'Immediate'), - }, - { - title: 'Start before', - value: startBefore ? ( - - ) : ( - '-' - ), - }, - ], - ]; - - const progVariant = progressVariantForResult(result); + const [isExpanded, setIsExpanded] = useState(false); + const baseId = id || 'task-info'; + const contentId = `${baseId}-more-details-content`; + const toggleId = `${baseId}-more-details-toggle`; + + const usernameHref = parseUsernameLinkHref(usernamePath); + const triggeredBy = usernameHref ? ( + {username} + ) : ( + username || '' + ); + + const progVariant = progressVariant(result); return ( - - {details.map((items, key) => ( - - - - {__(items[0].title)}: - - - - {items[0].value} - - - - {__(items[1].title)}: - - - - {items[1].value} - - - ))} - + + + {taskResultIconEl(state, result)} + {formatResultLabel(result)} + + } + /> + + } + /> + + + + - {__('State')}: - {state} + + {isExpanded ? __('Show less details') : __('Show more details')} + + - + + + + + + } + /> + + } + /> + + + - - {help && ( - - {__('Troubleshooting')} -

- - )} - {output && output.length > 0 && ( + + {state !== 'stopped' && ( - {__('Output:')} -

{output}
+ + + + + + {`${__('State')}: `} + {state} + + + + + {sprintf(__('%s%% %s'), progress, __('Complete'))} + + + + + + + +
)}
@@ -173,31 +253,33 @@ const TaskInfo = props => { TaskInfo.propTypes = { action: PropTypes.string, endedAt: PropTypes.string, + externalId: PropTypes.string, + id: PropTypes.string, + label: PropTypes.string, result: PropTypes.string, startAt: PropTypes.string, startBefore: PropTypes.string, startedAt: PropTypes.string, state: PropTypes.string, - help: PropTypes.string, progress: PropTypes.number, username: PropTypes.string, usernamePath: PropTypes.string, - output: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), }; TaskInfo.defaultProps = { action: '', endedAt: '', + externalId: '', + id: '', + label: '', result: 'error', startAt: '', startBefore: '', startedAt: '', state: '', - help: '', progress: 0, username: '', usernamePath: '', - output: '', }; export default TaskInfo; diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js index d21ff7b67..2a0afce05 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js @@ -1,5 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; import Task from '../Task'; @@ -13,10 +14,17 @@ jest.mock('foremanReact/components/common/dates/RelativeDateTime', () => { }); describe('Task', () => { - it('renders task overview metadata via TaskInfo', () => { + it('renders task overview metadata via TaskInfo', async () => { render(); - expect(screen.getByText(/name:/i)).toBeInTheDocument(); + expect(screen.getByText('Result')).toBeInTheDocument(); + expect(screen.getByText('State')).toBeInTheDocument(); + expect(screen.getByText('test')).toBeInTheDocument(); + + await userEvent.click( + screen.getByRole('button', { name: /show more details/i }) + ); + expect(screen.getByText('Refresh hosts')).toBeInTheDocument(); expect(screen.getByText(/result:/i)).toBeInTheDocument(); expect( diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js index db3e12ca2..da0afe47b 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js @@ -1,4 +1,9 @@ -import { durationInWords } from '../TaskHelper'; +import { + durationInWords, + formatTaskDuration, + isDelayed, + parseUsernameLinkHref, +} from '../TaskHelper'; describe('durationInWords', () => { it('should work for seconds', () => { @@ -20,3 +25,115 @@ describe('durationInWords', () => { }); }); }); + +describe('isDelayed', () => { + it('returns false when startAt is missing', () => { + expect(isDelayed({ startAt: null, startedAt: '2020-01-02' })).toBe(false); + expect(isDelayed({ startAt: '', startedAt: '2020-01-02' })).toBe(false); + expect(isDelayed({ startedAt: '2020-01-02' })).toBe(false); + }); + + it('returns false when startedAt is missing', () => { + expect(isDelayed({ startAt: '2020-01-01', startedAt: null })).toBe(false); + expect(isDelayed({ startAt: '2020-01-01', startedAt: '' })).toBe(false); + expect(isDelayed({ startAt: '2020-01-01' })).toBe(false); + }); + + it('returns false when either date is invalid', () => { + expect( + isDelayed({ startAt: 'not-a-date', startedAt: '2020-01-02' }) + ).toBe(false); + expect( + isDelayed({ startAt: '2020-01-01', startedAt: 'not-a-date' }) + ).toBe(false); + }); + + it('returns false when startAt and startedAt are the same instant', () => { + expect( + isDelayed({ startAt: '2020-01-01T10:00:00', startedAt: '2020-01-01T10:00:00' }) + ).toBe(false); + }); + + it('ignores millisecond differences when comparing startAt and startedAt', () => { + expect( + isDelayed({ + startAt: '2020-01-01T10:00:00.123', + startedAt: '2020-01-01T10:00:00.456', + }) + ).toBe(false); + }); + + it('returns true when startAt and startedAt differ', () => { + expect( + isDelayed({ startAt: '2020-01-01', startedAt: '2020-01-02' }) + ).toBe(true); + }); +}); + +describe('parseUsernameLinkHref', () => { + it('returns null for empty or non-string input', () => { + expect(parseUsernameLinkHref(null)).toBeNull(); + expect(parseUsernameLinkHref(undefined)).toBeNull(); + expect(parseUsernameLinkHref('')).toBeNull(); + expect(parseUsernameLinkHref(123)).toBeNull(); + }); + + it('extracts href from double-quoted attribute values', () => { + expect( + parseUsernameLinkHref('Bob') + ).toBe('/users/bob'); + }); + + it('extracts href from single-quoted attribute values', () => { + expect( + parseUsernameLinkHref("Bob") + ).toBe('/users/bob'); + }); + + it('returns null when no href attribute is present', () => { + expect(parseUsernameLinkHref('Bob')).toBeNull(); + }); +}); + +describe('formatTaskDuration', () => { + it('returns N/A when the task is not stopped', () => { + expect( + formatTaskDuration('2020-01-01T10:00:00', '2020-01-01T10:00:01', 'running') + ).toBe('N/A'); + }); + + it('returns N/A when start or end time is missing', () => { + expect(formatTaskDuration('', '2020-01-01T10:00:01', 'stopped')).toBe( + 'N/A' + ); + expect(formatTaskDuration('2020-01-01T10:00:00', '', 'stopped')).toBe( + 'N/A' + ); + }); + + it('returns N/A when dates are invalid or end precedes start', () => { + expect( + formatTaskDuration('not-a-date', '2020-01-01T10:00:01', 'stopped') + ).toBe('N/A'); + expect( + formatTaskDuration('2020-01-01T10:00:00', 'not-a-date', 'stopped') + ).toBe('N/A'); + expect( + formatTaskDuration( + '2020-01-01T10:00:01', + '2020-01-01T10:00:00', + 'stopped' + ) + ).toBe('N/A'); + }); + + it('returns a humanized duration for stopped tasks with valid timestamps', () => { + expect( + formatTaskDuration( + '1/1/1 10:00:00', + '1/1/1 10:00:01', + 'stopped' + ) + ).toBe('1 second'); + }); +}); diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js index 821d71621..4a6af024d 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js @@ -1,5 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; jest.mock('foremanReact/components/common/dates/RelativeDateTime', () => { @@ -10,41 +11,149 @@ jest.mock('foremanReact/components/common/dates/RelativeDateTime', () => { }); import TaskInfo from '../TaskInfo'; +import { durationInWords } from '../TaskHelper'; describe('TaskInfo', () => { - it('renders default metadata labels when given minimal props', () => { - render(); - expect(screen.getByText(/name:/i)).toBeInTheDocument(); - expect(screen.getByText(/start at:/i)).toBeInTheDocument(); - expect(screen.getByText(/result:/i)).toBeInTheDocument(); + afterEach(() => { + jest.clearAllMocks(); }); - it('renders task fields and troubleshooting when full props are provided', () => { + it('renders overview metadata headings for minimal props', () => { + render(); + + expect(screen.getByText('Result')).toBeInTheDocument(); + expect(screen.getByText('State')).toBeInTheDocument(); + expect(screen.getByText('Started at')).toBeInTheDocument(); + expect(screen.getByText('Duration')).toBeInTheDocument(); + expect(screen.getByText('Triggered by')).toBeInTheDocument(); + expect(screen.getByText('Id')).toBeInTheDocument(); + + expect( + screen.getByRole('button', { name: /show more details/i }) + ).toBeInTheDocument(); + expect(screen.getByText('tid')).toBeInTheDocument(); + expect(screen.getByText(/^Error$/)).toBeInTheDocument(); + }); + + it('shows progress only while task state is not stopped', () => { + const props = { + id: 'r1', + state: 'running', + result: 'pending', + progress: 42, + startedAt: '', + usernamePath: '', + }; + + const { rerender } = render(); + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + expect(screen.getByRole('progressbar')).toHaveAttribute( + 'aria-valuenow', + '42' + ); + + rerender(); + expect(screen.queryByRole('progressbar')).not.toBeInTheDocument(); + }); + + it('expands to show label, execution type, start at, ended at, and external id', async () => { render( ); - expect(screen.getByText('Monitor Event Queue')).toBeInTheDocument(); - expect(screen.getByText(/troubleshooting/i)).toBeInTheDocument(); + + await userEvent.click( + screen.getByRole('button', { name: /show more details/i }) + ); + + expect(screen.getByText('Execution type')).toBeInTheDocument(); + expect(screen.getByText('Label')).toBeInTheDocument(); + expect(screen.getByText('Start at')).toBeInTheDocument(); + expect(screen.getByText('Ended at')).toBeInTheDocument(); + expect(screen.getByText('External Id')).toBeInTheDocument(); + expect( + screen.getByText('Actions::Katello::EventQueue::Monitor') + ).toBeInTheDocument(); + expect(screen.getByText('ext-42')).toBeInTheDocument(); + expect(screen.getByText(/^Error$/)).toBeInTheDocument(); + expect(screen.getAllByText(/^paused$/).length).toBeGreaterThanOrEqual(1); + }); + + it('reports delayed execution type when startAt and startedAt differ', async () => { + render( + + ); + + await userEvent.click( + screen.getByRole('button', { name: /show more details/i }) + ); + + expect(screen.getByText('Delayed')).toBeInTheDocument(); + }); + + it('reports immediate execution type when startAt is missing', async () => { + render(); + + await userEvent.click( + screen.getByRole('button', { name: /show more details/i }) + ); + + expect(screen.getByText('Immediate')).toBeInTheDocument(); + }); + + it('renders triggered-by link when usernamePath embeds an href', () => { + render( + + ); + + const link = screen.getByRole('link', { name: 'bob' }); + expect(link).toHaveAttribute('href', '/users/bob'); + }); + + it('renders triggered-by link when usernamePath uses single-quoted href', () => { + render( + + ); + + const link = screen.getByRole('link', { name: 'bob' }); + expect(link).toHaveAttribute('href', '/users/bob'); + }); + + it('formats stop duration when started and ended are valid', () => { + const startedAt = '2020-01-01T10:00:00.000Z'; + const endedAt = '2020-01-01T10:00:45.000Z'; + + render(); + expect( - screen.getByText(/a paused task represents a process/i) + screen.getByText(durationInWords(startedAt, endedAt).text) ).toBeInTheDocument(); - expect(screen.getByText(/^paused$/)).toBeInTheDocument(); - expect(screen.getByText(/^error$/)).toBeInTheDocument(); }); }); diff --git a/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js b/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js index b046ad0d1..d94e1c809 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js +++ b/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js @@ -221,11 +221,11 @@ describe('TaskDetails', () => { expect(screen.getByRole('tab', { name: /raw/i })).toBeDisabled(); }); - it('renders troubleshooting help in the overview section', () => { + it('does not render troubleshooting help in the overview section', () => { renderTaskDetails({ ...fixtureWithOverviewMessages }); - expect(screen.getByText(/troubleshooting/i)).toBeInTheDocument(); - expect(screen.getByText('See logs')).toBeInTheDocument(); + expect(screen.queryByText(/troubleshooting/i)).not.toBeInTheDocument(); + expect(screen.queryByText('See logs')).not.toBeInTheDocument(); }); it('renders raw output when the Raw tab is selected', async () => { From c445aee78c318a7449ef95bb1bdf5cc602af4c34 Mon Sep 17 00:00:00 2001 From: Andrei Lakatos Date: Wed, 1 Jul 2026 10:27:44 -0400 Subject: [PATCH 2/3] Refs #39350 - fix tests --- .../Components/TaskDetails/Components/__tests__/Task.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js index 2a0afce05..bb996eaf2 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js @@ -26,7 +26,8 @@ describe('Task', () => { ); expect(screen.getByText('Refresh hosts')).toBeInTheDocument(); - expect(screen.getByText(/result:/i)).toBeInTheDocument(); + expect(screen.getByText('Label')).toBeInTheDocument(); + expect(screen.getByText('Execution type')).toBeInTheDocument(); expect( screen.queryByRole('button', { name: /cancel/i }) ).not.toBeInTheDocument(); From fbb80ab8a96745e2d95f10c62f25dbeec8a557ef Mon Sep 17 00:00:00 2001 From: Andrei Lakatos Date: Tue, 7 Jul 2026 07:11:17 -0400 Subject: [PATCH 3/3] Refs #39350 - add startBefore in the startAt and troubleshooting text back --- .../TaskDetails/Components/TaskInfo.js | 37 +++++++++++- .../Components/__tests__/TaskInfo.test.js | 60 +++++++++++++++++++ .../TaskDetails/__tests__/TaskDetails.test.js | 10 +++- 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js b/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js index 1e3f8ac8a..9bf000779 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js @@ -46,8 +46,11 @@ const progressVariant = result => { } }; -const MetadataField = ({ title, value, className, labelOuiaId }) => ( - +const MetadataField = ({ title, value, className, labelOuiaId, span }) => ( + @@ -96,7 +101,9 @@ const TaskInfo = props => { result, startAt, startedAt, + startBefore, state, + help, progress, username, usernamePath, @@ -188,7 +195,21 @@ const TaskInfo = props => { labelOuiaId="task-info-metadata-start-at-label" title={__('Start at')} value={ - + + + + + {startBefore && ( + + ({__('before')} + + ) + + )} + } /> { title={__('External Id')} value={copyableText(externalId)} /> + {help && ( + } + /> + )}
@@ -261,6 +290,7 @@ TaskInfo.propTypes = { startBefore: PropTypes.string, startedAt: PropTypes.string, state: PropTypes.string, + help: PropTypes.string, progress: PropTypes.number, username: PropTypes.string, usernamePath: PropTypes.string, @@ -277,6 +307,7 @@ TaskInfo.defaultProps = { startBefore: '', startedAt: '', state: '', + help: '', progress: 0, username: '', usernamePath: '', diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js index 4a6af024d..b146f6cbb 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js @@ -156,4 +156,64 @@ describe('TaskInfo', () => { screen.getByText(durationInWords(startedAt, endedAt).text) ).toBeInTheDocument(); }); + + it('shows start before deadline when startBefore is set', async () => { + render( + + ); + + await userEvent.click( + screen.getByRole('button', { name: /show more details/i }) + ); + + expect(screen.getByText('2020-01-01')).toBeInTheDocument(); + expect(screen.getByText(/before/i)).toBeInTheDocument(); + expect(screen.getByText('2020-01-05')).toBeInTheDocument(); + }); + + it('does not show start before text when startBefore is missing', async () => { + render(); + + await userEvent.click( + screen.getByRole('button', { name: /show more details/i }) + ); + + expect(screen.getByText('2020-01-01')).toBeInTheDocument(); + expect(screen.queryByText(/before/i)).not.toBeInTheDocument(); + }); + + it('shows troubleshooting help when expanded and help is provided', async () => { + render( + + ); + + await userEvent.click( + screen.getByRole('button', { name: /show more details/i }) + ); + + expect(screen.getByText('Troubleshooting')).toBeInTheDocument(); + expect(screen.getByText(/Investigate the error/i)).toBeInTheDocument(); + expect( + screen.getByRole('link', { name: 'troubleshooting documentation' }) + ).toHaveAttribute('href', '/docs'); + }); + + it('does not show troubleshooting help when help is empty', async () => { + render(); + + await userEvent.click( + screen.getByRole('button', { name: /show more details/i }) + ); + + expect(screen.queryByText('Troubleshooting')).not.toBeInTheDocument(); + }); }); diff --git a/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js b/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js index d94e1c809..4b8df535e 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js +++ b/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js @@ -221,11 +221,15 @@ describe('TaskDetails', () => { expect(screen.getByRole('tab', { name: /raw/i })).toBeDisabled(); }); - it('does not render troubleshooting help in the overview section', () => { + it('renders troubleshooting help in overview after expanding details', async () => { renderTaskDetails({ ...fixtureWithOverviewMessages }); - expect(screen.queryByText(/troubleshooting/i)).not.toBeInTheDocument(); - expect(screen.queryByText('See logs')).not.toBeInTheDocument(); + await userEvent.click( + screen.getByRole('button', { name: /show more details/i }) + ); + + expect(screen.getByText('Troubleshooting')).toBeInTheDocument(); + expect(screen.getByText('See logs')).toBeInTheDocument(); }); it('renders raw output when the Raw tab is selected', async () => {