foreman-tasks 12.1.0 → 12.2.1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/app/lib/actions/helpers/lock.rb +0 -6
  4. data/app/lib/actions/middleware/keep_current_user.rb +6 -12
  5. data/app/views/foreman_tasks/api/tasks/show.json.rabl +1 -1
  6. data/foreman-tasks.gemspec +1 -3
  7. data/lib/foreman_tasks/version.rb +1 -1
  8. data/test/controllers/api/tasks_controller_test.rb +2 -2
  9. data/webpack/ForemanTasks/Components/TaskDetails/Components/Errors.js +25 -5
  10. data/webpack/ForemanTasks/Components/TaskDetails/Components/RunningSteps.js +12 -4
  11. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +217 -201
  12. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskSkeleton.js +30 -34
  13. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Dependencies.test.js +30 -30
  14. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Errors.test.js +50 -27
  15. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Raw.test.js +44 -22
  16. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/RunningSteps.test.js +56 -24
  17. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +42 -22
  18. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js +45 -54
  19. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +65 -25
  20. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js +37 -13
  21. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetailsActions.test.js +53 -8
  22. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/PausedTasksCard/PausedTasksCard.test.js +53 -7
  23. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/RunningTasksCard/RunningTasksCard.test.js +53 -7
  24. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/ScheduledTasksCard.js +5 -4
  25. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/ScheduledTasksCard.test.js +44 -14
  26. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.js +6 -5
  27. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/TasksDonutCard.js +5 -4
  28. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/TasksDonutCard.scss +3 -8
  29. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/TasksDonutCard.test.js +75 -34
  30. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/TasksDonutChart.test.js +57 -34
  31. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/TasksDonutChartHelper.test.js +23 -9
  32. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.fixtures.js +14 -11
  33. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.js +19 -21
  34. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.test.js +63 -14
  35. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/Components/TimeDropDown/TimeDropDown.js +9 -17
  36. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/Components/TimeDropDown/TimeDropDown.test.js +49 -13
  37. metadata +2 -45
  38. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Errors.test.js.snap +0 -77
  39. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Raw.test.js.snap +0 -174
  40. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/RunningSteps.test.js.snap +0 -62
  41. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap +0 -127
  42. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +0 -580
  43. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap +0 -172
  44. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetailsActions.test.js.snap +0 -52
  45. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/PausedTasksCard/__snapshots__/PausedTasksCard.test.js.snap +0 -38
  46. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/RunningTasksCard/__snapshots__/RunningTasksCard.test.js.snap +0 -38
  47. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/__snapshots__/ScheduledTasksCard.test.js.snap +0 -97
  48. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/__snapshots__/TasksDonutCard.test.js.snap +0 -183
  49. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/__snapshots__/TasksDonutChart.test.js.snap +0 -302
  50. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/__snapshots__/TasksDonutChartHelper.test.js.snap +0 -21
  51. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/__snapshots__/TasksCardsGrid.test.js.snap +0 -210
  52. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/Components/TimeDropDown/__snapshots__/TimeDropDown.test.js.snap +0 -85
@@ -1,6 +1,6 @@
1
- import React, { useEffect } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Tab, Tabs } from 'patternfly-react';
3
+ import { Tabs, Tab, TabTitleText } from '@patternfly/react-core';
4
4
  import { translate as __, sprintf } from 'foremanReact/common/I18n';
5
5
  import { STATUS } from 'foremanReact/constants';
6
6
  import MessageBox from 'foremanReact/components/common/MessageBox';
@@ -31,6 +31,7 @@ const TaskDetails = ({
31
31
  }) => {
32
32
  const id = getTaskID();
33
33
  const { taskReload, status, isLoading } = props;
34
+ const [activeTabKey, setActiveTabKey] = useState(1);
34
35
 
35
36
  useEffect(() => {
36
37
  taskReloadStart(id);
@@ -58,27 +59,42 @@ const TaskDetails = ({
58
59
  }
59
60
  const resumable = executionPlan ? executionPlan.state === 'paused' : false;
60
61
  const cancellable = executionPlan ? executionPlan.cancellable : false;
62
+ const lockRecords = locks.concat(links);
63
+
64
+ const taskComponentProps = {
65
+ ...props,
66
+ cancellable,
67
+ resumable,
68
+ id,
69
+ status,
70
+ taskProgressToggle,
71
+ taskReloadStart,
72
+ };
73
+
61
74
  return (
62
75
  <div className="task-details-react well">
63
- <Tabs defaultActiveKey={1} animation={false} id="task-details-tabs">
64
- <Tab eventKey={1} title={__('Task')}>
65
- {isLoading ? (
66
- <TaskSkeleton />
67
- ) : (
68
- <Task
69
- {...{
70
- ...props,
71
- cancellable,
72
- resumable,
73
- id,
74
- status,
75
- taskProgressToggle,
76
- taskReloadStart,
77
- }}
78
- />
79
- )}
76
+ <Tabs
77
+ id="task-details-tabs"
78
+ ouiaId="task-details-tabs"
79
+ activeKey={activeTabKey}
80
+ onSelect={(_event, tabKey) => setActiveTabKey(tabKey)}
81
+ mountOnEnter
82
+ >
83
+ <Tab
84
+ eventKey={1}
85
+ title={<TabTitleText>{__('Task')}</TabTitleText>}
86
+ aria-label={__('Task')}
87
+ ouiaId="task-details-tab-task"
88
+ >
89
+ {isLoading ? <TaskSkeleton /> : <Task {...taskComponentProps} />}
80
90
  </Tab>
81
- <Tab eventKey={2} disabled={isLoading} title={__('Running Steps')}>
91
+ <Tab
92
+ eventKey={2}
93
+ title={<TabTitleText>{__('Running Steps')}</TabTitleText>}
94
+ isDisabled={isLoading}
95
+ aria-label={__('Running Steps')}
96
+ ouiaId="task-details-tab-running-steps"
97
+ >
82
98
  <RunningSteps
83
99
  runningSteps={runningSteps}
84
100
  id={id}
@@ -87,16 +103,40 @@ const TaskDetails = ({
87
103
  taskReloadStart={taskReloadStart}
88
104
  />
89
105
  </Tab>
90
- <Tab eventKey={3} disabled={isLoading} title={__('Errors')}>
106
+ <Tab
107
+ eventKey={3}
108
+ title={<TabTitleText>{__('Errors')}</TabTitleText>}
109
+ isDisabled={isLoading}
110
+ aria-label={__('Errors')}
111
+ ouiaId="task-details-tab-errors"
112
+ >
91
113
  <Errors executionPlan={executionPlan} failedSteps={failedSteps} />
92
114
  </Tab>
93
- <Tab eventKey={4} disabled={isLoading} title={__('Locks')}>
94
- <Locks locks={locks.concat(links)} />
115
+ <Tab
116
+ eventKey={4}
117
+ title={<TabTitleText>{__('Locks')}</TabTitleText>}
118
+ isDisabled={isLoading}
119
+ aria-label={__('Locks')}
120
+ ouiaId="task-details-tab-locks"
121
+ >
122
+ <Locks locks={lockRecords} />
95
123
  </Tab>
96
- <Tab eventKey={5} disabled={isLoading} title={__('Dependencies')}>
124
+ <Tab
125
+ eventKey={5}
126
+ title={<TabTitleText>{__('Dependencies')}</TabTitleText>}
127
+ isDisabled={isLoading}
128
+ aria-label={__('Dependencies')}
129
+ ouiaId="task-details-tab-dependencies"
130
+ >
97
131
  <Dependencies dependsOn={dependsOn} blocks={blocks} />
98
132
  </Tab>
99
- <Tab eventKey={6} disabled={isLoading} title={__('Raw')}>
133
+ <Tab
134
+ eventKey={6}
135
+ title={<TabTitleText>{__('Raw')}</TabTitleText>}
136
+ isDisabled={isLoading}
137
+ aria-label={__('Raw')}
138
+ ouiaId="task-details-tab-raw"
139
+ >
100
140
  <Raw
101
141
  id={id}
102
142
  label={props.label}
@@ -1,24 +1,48 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
2
4
 
3
5
  import TaskDetails from '../TaskDetails';
4
6
  import { minProps } from './TaskDetails.fixtures';
5
7
 
6
- const fixtures = {
7
- 'render with loading Props': { ...minProps, isLoading: true },
8
- 'render with error Props': {
9
- ...minProps,
10
- status: 'ERROR',
11
- APIerror: { message: 'some-error' },
12
- },
13
- 'render with min Props': minProps,
14
- };
15
-
16
8
  delete window.location;
17
9
  window.location = new URL(
18
10
  'https://foreman.com/foreman_tasks/tasks/a15dd820-32f1-4ced-9ab7-c0fab8234c47/'
19
11
  );
20
12
 
21
13
  describe('TaskDetails', () => {
22
- describe('rendering', () =>
23
- testComponentSnapshotsWithFixtures(TaskDetails, fixtures));
14
+ it('shows error message when status is ERROR', () => {
15
+ render(
16
+ <TaskDetails
17
+ {...minProps}
18
+ status="ERROR"
19
+ APIerror={{ message: 'some-error' }}
20
+ />
21
+ );
22
+ expect(
23
+ screen.getByText(/could not receive data: some-error/i)
24
+ ).toBeInTheDocument();
25
+ });
26
+
27
+ it('shows skeleton while loading on the Task tab', () => {
28
+ const { container } = render(<TaskDetails {...minProps} isLoading />);
29
+ expect(
30
+ container.querySelector('.react-loading-skeleton')
31
+ ).toBeInTheDocument();
32
+ });
33
+
34
+ it('renders six tabs with expected labels', () => {
35
+ render(<TaskDetails {...minProps} />);
36
+ expect(document.getElementById('task-details-tabs')).toBeInTheDocument();
37
+ expect(screen.getByRole('tab', { name: /^task$/i })).toBeInTheDocument();
38
+ expect(
39
+ screen.getByRole('tab', { name: /running steps/i })
40
+ ).toBeInTheDocument();
41
+ expect(screen.getByRole('tab', { name: /errors/i })).toBeInTheDocument();
42
+ expect(screen.getByRole('tab', { name: /locks/i })).toBeInTheDocument();
43
+ expect(
44
+ screen.getByRole('tab', { name: /dependencies/i })
45
+ ).toBeInTheDocument();
46
+ expect(screen.getByRole('tab', { name: /raw/i })).toBeInTheDocument();
47
+ });
24
48
  });
@@ -1,9 +1,12 @@
1
- import { testActionSnapshotWithFixtures } from '@theforeman/test';
2
1
  import {
3
2
  taskReloadStop,
4
3
  taskReloadStart,
5
4
  cancelStep,
6
5
  } from '../TaskDetailsActions';
6
+ import {
7
+ FOREMAN_TASK_DETAILS,
8
+ TASK_STEP_CANCEL,
9
+ } from '../TaskDetailsConstants';
7
10
 
8
11
  jest.mock('foremanReact/components/ToastsList', () => ({
9
12
  addToast: toast => ({
@@ -14,11 +17,53 @@ jest.mock('foremanReact/components/ToastsList', () => ({
14
17
  }),
15
18
  }));
16
19
 
17
- const fixtures = {
18
- 'should start reload': () => taskReloadStart(1),
19
- 'should stop reload': () => taskReloadStop(),
20
- 'should cancelStep': () => cancelStep('task-id', 'step-id'),
21
- };
20
+ describe('TaskDetails - Actions', () => {
21
+ it('should stop reload', () => {
22
+ expect(taskReloadStop()).toEqual({
23
+ type: 'STOP_INTERVAL',
24
+ key: FOREMAN_TASK_DETAILS,
25
+ });
26
+ });
27
+
28
+ it('should start reload', () => {
29
+ const dispatch = jest.fn();
30
+ taskReloadStart(1)(dispatch);
31
+
32
+ expect(dispatch).toHaveBeenCalledTimes(1);
33
+ const [action] = dispatch.mock.calls[0];
34
+ expect(action.type).toBe('API_GET');
35
+ expect(action.interval).toBe(5000);
36
+ expect(action.payload.key).toBe(FOREMAN_TASK_DETAILS);
37
+ expect(action.payload.url).toBe(
38
+ '/foreman_tasks/api/tasks/1/details?include_permissions'
39
+ );
40
+ expect(action.payload.handleSuccess).toEqual(expect.any(Function));
41
+ expect(action.payload.handleError).toEqual(expect.any(Function));
42
+ });
43
+
44
+ it('should cancelStep', async () => {
45
+ const dispatch = jest.fn();
46
+ await cancelStep('task-id', 'step-id')(dispatch);
47
+
48
+ expect(dispatch).toHaveBeenCalledTimes(2);
49
+
50
+ expect(dispatch.mock.calls[0][0]).toEqual({
51
+ type: 'TOASTS_ADD',
52
+ payload: {
53
+ message: {
54
+ message: 'Trying to cancel step step-id',
55
+ type: 'info',
56
+ },
57
+ },
58
+ });
22
59
 
23
- describe('TaskDetails - Actions', () =>
24
- testActionSnapshotWithFixtures(fixtures));
60
+ const postAction = dispatch.mock.calls[1][0];
61
+ expect(postAction.type).toBe('API_POST');
62
+ expect(postAction.payload.key).toBe(TASK_STEP_CANCEL);
63
+ expect(postAction.payload.url).toBe(
64
+ '/foreman_tasks/tasks/task-id/cancel_step?step_id=step-id'
65
+ );
66
+ expect(postAction.payload.handleSuccess).toEqual(expect.any(Function));
67
+ expect(postAction.payload.handleError).toEqual(expect.any(Function));
68
+ });
69
+ });
@@ -1,11 +1,57 @@
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';
4
+
5
+ import { TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS } from '../TasksDonutChart/TasksDonutChartConstants';
2
6
 
3
7
  import PausedTasksCard from './PausedTasksCard';
4
8
 
5
- const fixtures = {
6
- 'render with minimal props': {},
7
- 'render with some props': { some: 'prop' },
8
- };
9
+ jest.mock('../TasksDonutChart/TasksDonutChart', () => {
10
+ const React = require('react');
11
+ const Stub = props => (
12
+ <div
13
+ data-testid="tasks-donut-chart-stub"
14
+ data-last={props.last}
15
+ data-older={props.older}
16
+ data-focused-on={props.focusedOn}
17
+ />
18
+ );
19
+ Stub.displayName = 'TasksDonutChart';
20
+ return Stub;
21
+ });
22
+
23
+ describe('PausedTasksCard', () => {
24
+ it('renders Paused title, card id, and chart area', () => {
25
+ render(<PausedTasksCard />);
26
+ expect(screen.getByText('Paused')).toBeInTheDocument();
27
+ expect(document.getElementById('paused-tasks-card')).toBeInTheDocument();
28
+ const chart = screen.getByTestId('tasks-donut-chart-stub');
29
+ expect(chart).toHaveAttribute('data-last', '0');
30
+ expect(chart).toHaveAttribute('data-older', '0');
31
+ expect(chart).toHaveAttribute(
32
+ 'data-focused-on',
33
+ TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS.NORMAL
34
+ );
35
+ });
36
+
37
+ it('forwards extra props to the underlying donut card', () => {
38
+ const updateQuery = jest.fn();
39
+ render(
40
+ <PausedTasksCard
41
+ updateQuery={updateQuery}
42
+ data={{ last: 1, older: 2 }}
43
+ query={{ state: 'paused' }}
44
+ />
45
+ );
46
+ const chart = screen.getByTestId('tasks-donut-chart-stub');
47
+ expect(chart).toHaveAttribute('data-last', '1');
48
+ expect(chart).toHaveAttribute('data-older', '2');
49
+ expect(chart).toHaveAttribute(
50
+ 'data-focused-on',
51
+ TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS.TOTAL
52
+ );
9
53
 
10
- describe('PausedTasksCard', () =>
11
- testComponentSnapshotsWithFixtures(PausedTasksCard, fixtures));
54
+ fireEvent.click(screen.getByText('Paused'));
55
+ expect(updateQuery).toHaveBeenCalledWith({ state: 'paused' });
56
+ });
57
+ });
@@ -1,11 +1,57 @@
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';
4
+
5
+ import { TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS } from '../TasksDonutChart/TasksDonutChartConstants';
2
6
 
3
7
  import RunningTasksCard from './RunningTasksCard';
4
8
 
5
- const fixtures = {
6
- 'render with minimal props': {},
7
- 'render with some props': { some: 'prop' },
8
- };
9
+ jest.mock('../TasksDonutChart/TasksDonutChart', () => {
10
+ const React = require('react');
11
+ const Stub = props => (
12
+ <div
13
+ data-testid="tasks-donut-chart-stub"
14
+ data-last={props.last}
15
+ data-older={props.older}
16
+ data-focused-on={props.focusedOn}
17
+ />
18
+ );
19
+ Stub.displayName = 'TasksDonutChart';
20
+ return Stub;
21
+ });
22
+
23
+ describe('RunningTasksCard', () => {
24
+ it('renders Running title, card id, and chart area', () => {
25
+ render(<RunningTasksCard />);
26
+ expect(screen.getByText('Running')).toBeInTheDocument();
27
+ expect(document.getElementById('running-tasks-card')).toBeInTheDocument();
28
+ const chart = screen.getByTestId('tasks-donut-chart-stub');
29
+ expect(chart).toHaveAttribute('data-last', '0');
30
+ expect(chart).toHaveAttribute('data-older', '0');
31
+ expect(chart).toHaveAttribute(
32
+ 'data-focused-on',
33
+ TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS.NORMAL
34
+ );
35
+ });
36
+
37
+ it('forwards extra props to the underlying donut card', () => {
38
+ const updateQuery = jest.fn();
39
+ render(
40
+ <RunningTasksCard
41
+ updateQuery={updateQuery}
42
+ data={{ last: 2, older: 4 }}
43
+ query={{ state: 'running' }}
44
+ />
45
+ );
46
+ const chart = screen.getByTestId('tasks-donut-chart-stub');
47
+ expect(chart).toHaveAttribute('data-last', '2');
48
+ expect(chart).toHaveAttribute('data-older', '4');
49
+ expect(chart).toHaveAttribute(
50
+ 'data-focused-on',
51
+ TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS.TOTAL
52
+ );
9
53
 
10
- describe('RunningTasksCard', () =>
11
- testComponentSnapshotsWithFixtures(RunningTasksCard, fixtures));
54
+ fireEvent.click(screen.getByText('Running'));
55
+ expect(updateQuery).toHaveBeenCalledWith({ state: 'running' });
56
+ });
57
+ });
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Card } from 'patternfly-react';
3
+ import { Card, CardTitle, CardBody } from '@patternfly/react-core';
4
4
  import classNames from 'classnames';
5
5
  import { noop } from 'foremanReact/common/helpers';
6
6
  import { translate as __ } from 'foremanReact/common/I18n';
@@ -32,14 +32,15 @@ const ScheduledTasksCard = ({
32
32
  )}
33
33
  {...props}
34
34
  id="scheduled-tasks-card"
35
+ ouiaId="scheduled-tasks-card"
35
36
  >
36
- <Card.Title onClick={onClick}>{__('Scheduled')}</Card.Title>
37
- <Card.Body>
37
+ <CardTitle onClick={onClick}>{__('Scheduled')}</CardTitle>
38
+ <CardBody>
38
39
  <div className="scheduled-data" onClick={onClick}>
39
40
  {data}
40
41
  <p>{__('Total')}</p>
41
42
  </div>
42
- </Card.Body>
43
+ </CardBody>
43
44
  </Card>
44
45
  );
45
46
  };
@@ -1,18 +1,48 @@
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 { TASKS_DASHBOARD_AVAILABLE_QUERY_STATES } from '../../../../TasksDashboardConstants';
4
6
  import ScheduledTasksCard from './ScheduledTasksCard';
5
7
 
6
- const fixtures = {
7
- 'render with minimal props': {},
8
- 'render with props': {
9
- data: 3,
10
- className: 'some-class',
11
- },
12
- 'render selected': {
13
- query: { state: TASKS_DASHBOARD_AVAILABLE_QUERY_STATES.SCHEDULED },
14
- },
15
- };
16
-
17
- describe('ScheduledTasksCard', () =>
18
- testComponentSnapshotsWithFixtures(ScheduledTasksCard, fixtures));
8
+ const { SCHEDULED } = TASKS_DASHBOARD_AVAILABLE_QUERY_STATES;
9
+
10
+ describe('ScheduledTasksCard', () => {
11
+ it('renders scheduled count, labels, and card structure', () => {
12
+ render(<ScheduledTasksCard data={3} />);
13
+ expect(screen.getByText('Scheduled')).toBeInTheDocument();
14
+ expect(screen.getByText('Total')).toBeInTheDocument();
15
+ expect(document.getElementById('scheduled-tasks-card')).toBeInTheDocument();
16
+ expect(document.querySelector('.scheduled-data')?.textContent).toContain(
17
+ '3'
18
+ );
19
+ });
20
+
21
+ it('merges optional className onto the card', () => {
22
+ const { container } = render(
23
+ <ScheduledTasksCard className="extra-scheduled-class" />
24
+ );
25
+ expect(container.querySelector('.extra-scheduled-class')).toBeInTheDocument();
26
+ });
27
+
28
+ it('marks the card selected when query state is scheduled', () => {
29
+ const { container } = render(
30
+ <ScheduledTasksCard query={{ state: SCHEDULED }} />
31
+ );
32
+ expect(container.querySelector('#scheduled-tasks-card')).toHaveClass(
33
+ 'selected-tasks-card'
34
+ );
35
+ });
36
+
37
+ it('calls updateQuery with scheduled state when title or data area is clicked', () => {
38
+ const updateQuery = jest.fn();
39
+ render(<ScheduledTasksCard updateQuery={updateQuery} />);
40
+
41
+ fireEvent.click(screen.getByText('Scheduled'));
42
+ expect(updateQuery).toHaveBeenCalledWith({ state: SCHEDULED });
43
+
44
+ updateQuery.mockClear();
45
+ fireEvent.click(document.querySelector('.scheduled-data'));
46
+ expect(updateQuery).toHaveBeenCalledWith({ state: SCHEDULED });
47
+ });
48
+ });
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Card } from 'patternfly-react';
3
+ import { Card, CardTitle, CardBody } from '@patternfly/react-core';
4
4
  import classNames from 'classnames';
5
5
  import { noop } from 'foremanReact/common/helpers';
6
6
  import { translate as __ } from 'foremanReact/common/I18n';
@@ -38,11 +38,12 @@ const StoppedTasksCard = ({
38
38
  )}
39
39
  {...props}
40
40
  id="stopped-tasks-card"
41
+ ouiaId="stopped-tasks-card"
41
42
  >
42
- <Card.Title onClick={() => updateQuery({ state: STOPPED })}>
43
+ <CardTitle onClick={() => updateQuery({ state: STOPPED })}>
43
44
  {__('Stopped')}
44
- </Card.Title>
45
- <Card.Body>
45
+ </CardTitle>
46
+ <CardBody>
46
47
  <React.Fragment>
47
48
  <StoppedTable
48
49
  data={data.results}
@@ -56,7 +57,7 @@ const StoppedTasksCard = ({
56
57
  query={query}
57
58
  />
58
59
  </React.Fragment>
59
- </Card.Body>
60
+ </CardBody>
60
61
  </Card>
61
62
  );
62
63
  };
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import classNames from 'classnames';
4
- import { Card } from 'patternfly-react';
4
+ import { Card, CardTitle, CardBody } from '@patternfly/react-core';
5
5
  import { noop } from 'foremanReact/common/helpers';
6
6
 
7
7
  import {
@@ -42,9 +42,10 @@ const TasksDonutCard = ({
42
42
  'not-focused': focusedOn === TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS.NONE,
43
43
  })}
44
44
  {...props}
45
+ ouiaId="tasks-donut-card"
45
46
  >
46
- <Card.Title onClick={onTotalClick}>{title}</Card.Title>
47
- <Card.Body>
47
+ <CardTitle onClick={onTotalClick}>{title}</CardTitle>
48
+ <CardBody className="card-pf-body">
48
49
  <TasksDonutChart
49
50
  last={data.last}
50
51
  older={data.older}
@@ -66,7 +67,7 @@ const TasksDonutCard = ({
66
67
  })
67
68
  }
68
69
  />
69
- </Card.Body>
70
+ </CardBody>
70
71
  </Card>
71
72
  );
72
73
  };
@@ -1,22 +1,17 @@
1
1
  .tasks-donut-card {
2
2
  &.not-focused {
3
- .card-pf-title,
3
+ .pf-v5-c-card__title-text,
4
4
  .card-pf-body {
5
5
  opacity: 0.6;
6
6
  }
7
7
  }
8
- .card-pf-title {
8
+ .pf-v5-c-card__title-text {
9
9
  text-align: center;
10
- font-size: 180%;
11
10
  cursor: pointer;
12
11
  }
13
12
 
14
- .card-pf-body {
15
- padding-left: 15px;
16
- }
17
-
18
13
  &.selected-tasks-card {
19
- .card-pf-title {
14
+ .pf-v5-c-card__title-text {
20
15
  font-weight: bold;
21
16
  }
22
17
  }