foreman-tasks 12.1.1 → 12.2.2

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/app/views/foreman_tasks/api/tasks/show.json.rabl +1 -1
  4. data/foreman-tasks.gemspec +1 -3
  5. data/lib/foreman_tasks/version.rb +1 -1
  6. data/test/controllers/api/tasks_controller_test.rb +2 -2
  7. data/webpack/ForemanTasks/Components/TaskDetails/Components/Dependencies.js +69 -58
  8. data/webpack/ForemanTasks/Components/TaskDetails/Components/Errors.js +25 -5
  9. data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +170 -43
  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 +90 -29
  14. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Errors.test.js +50 -27
  15. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Locks.test.js +256 -23
  16. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Raw.test.js +44 -22
  17. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/RunningSteps.test.js +56 -24
  18. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +42 -22
  19. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js +45 -54
  20. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +65 -25
  21. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js +37 -13
  22. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetailsActions.test.js +53 -8
  23. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/PausedTasksCard/PausedTasksCard.test.js +53 -7
  24. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/RunningTasksCard/RunningTasksCard.test.js +53 -7
  25. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/ScheduledTasksCard.js +5 -4
  26. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/ScheduledTasksCard.test.js +44 -14
  27. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.js +6 -5
  28. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/TasksDonutCard.js +5 -4
  29. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/TasksDonutCard.scss +3 -8
  30. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/TasksDonutCard.test.js +75 -34
  31. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/TasksDonutChart.test.js +57 -34
  32. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/TasksDonutChartHelper.test.js +23 -9
  33. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.fixtures.js +14 -11
  34. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.js +19 -21
  35. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.test.js +63 -14
  36. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/Components/TimeDropDown/TimeDropDown.js +9 -17
  37. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/Components/TimeDropDown/TimeDropDown.test.js +49 -13
  38. metadata +2 -46
  39. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Errors.test.js.snap +0 -77
  40. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +0 -116
  41. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Raw.test.js.snap +0 -174
  42. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/RunningSteps.test.js.snap +0 -62
  43. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap +0 -127
  44. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +0 -580
  45. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap +0 -172
  46. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetailsActions.test.js.snap +0 -52
  47. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/PausedTasksCard/__snapshots__/PausedTasksCard.test.js.snap +0 -38
  48. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/RunningTasksCard/__snapshots__/RunningTasksCard.test.js.snap +0 -38
  49. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/__snapshots__/ScheduledTasksCard.test.js.snap +0 -97
  50. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/__snapshots__/TasksDonutCard.test.js.snap +0 -183
  51. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/__snapshots__/TasksDonutChart.test.js.snap +0 -302
  52. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/__snapshots__/TasksDonutChartHelper.test.js.snap +0 -21
  53. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/__snapshots__/TasksCardsGrid.test.js.snap +0 -210
  54. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/Components/TimeDropDown/__snapshots__/TimeDropDown.test.js.snap +0 -85
@@ -1,59 +1,50 @@
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
- import TaskInfo from '../TaskInfo';
5
+ jest.mock('foremanReact/components/common/dates/RelativeDateTime', () => {
6
+ const RelativeDateTime = ({ date, defaultValue }) => (
7
+ <span>{date || defaultValue}</span>
8
+ );
9
+ return RelativeDateTime;
10
+ });
4
11
 
5
- const fixtures = {
6
- 'render without Props': { id: 'test' },
7
- 'render with Props': {
8
- id: 'test',
9
- startAt: '2019-06-17 16:04:09 +0300',
10
- label: 'Actions::Katello::EventQueue::Monitor',
11
- pending: true,
12
- action: 'Monitor Event Queue',
13
- username: 'admin',
14
- startedAt: '2019-06-17 16:04:09 +0300',
15
- endedAt: null,
16
- state: 'paused',
17
- result: 'error',
18
- progress: 0.5,
19
- input: {
20
- locale: 'en',
21
- current_request_id: null,
22
- current_timezone: 'Asia/Jerusalem',
23
- current_user_id: 4,
24
- current_organization_id: null,
25
- current_location_id: null,
26
- },
27
- output: {},
28
- humanized: {
29
- action: 'Monitor Event Queue',
30
- input: '',
31
- output: '',
32
- errors: [
33
- 'Action Actions::Katello::EventQueue::Monitor is already active',
34
- ],
35
- },
36
- cli_example: null,
37
- executionPlan: {
38
- state: 'paused',
39
- cancellable: false,
40
- },
41
- help:
42
- "A paused task represents a process that has not finished properly. Any task in paused state can lead to potential inconsistency and needs to be resolved.\nThe recommended approach is to investigate the error messages below and in 'errors' tab, address the primary cause of the issue and resume the task.",
43
- hasSubTasks: false,
44
- locks: [
45
- {
46
- name: 'task_owner',
47
- exclusive: false,
48
- resource_type: 'User',
49
- resource_id: 4,
50
- },
51
- ],
52
- username_path: '<a href="/users/4-admin/edit">admin</a>',
53
- },
54
- };
12
+ import TaskInfo from '../TaskInfo';
55
13
 
56
14
  describe('TaskInfo', () => {
57
- describe('rendering', () =>
58
- testComponentSnapshotsWithFixtures(TaskInfo, fixtures));
15
+ it('renders default metadata labels when given minimal props', () => {
16
+ render(<TaskInfo id="test" />);
17
+ expect(screen.getByText(/name:/i)).toBeInTheDocument();
18
+ expect(screen.getByText(/start at:/i)).toBeInTheDocument();
19
+ expect(screen.getByText(/result:/i)).toBeInTheDocument();
20
+ });
21
+
22
+ it('renders task fields and troubleshooting when full props are provided', () => {
23
+ render(
24
+ <TaskInfo
25
+ id="test"
26
+ startAt="2019-06-17 16:04:09 +0300"
27
+ label="Actions::Katello::EventQueue::Monitor"
28
+ action="Monitor Event Queue"
29
+ username="admin"
30
+ startedAt="2019-06-17 16:04:09 +0300"
31
+ endedAt={null}
32
+ state="paused"
33
+ result="error"
34
+ progress={0.5}
35
+ help={
36
+ "A paused task represents a process that has not finished properly. Any task in paused state can lead to potential inconsistency and needs to be resolved.\nThe recommended approach is to investigate the error messages below and in 'errors' tab, address the primary cause of the issue and resume the task."
37
+ }
38
+ output={{}}
39
+ usernamePath=""
40
+ />
41
+ );
42
+ expect(screen.getByText('Monitor Event Queue')).toBeInTheDocument();
43
+ expect(screen.getByText(/troubleshooting/i)).toBeInTheDocument();
44
+ expect(
45
+ screen.getByText(/a paused task represents a process/i)
46
+ ).toBeInTheDocument();
47
+ expect(screen.getByText(/^paused$/)).toBeInTheDocument();
48
+ expect(screen.getByText(/^error$/)).toBeInTheDocument();
49
+ });
59
50
  });
@@ -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
  };