foreman-tasks 12.1.1 → 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 (50) 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/Errors.js +25 -5
  8. data/webpack/ForemanTasks/Components/TaskDetails/Components/RunningSteps.js +12 -4
  9. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +217 -201
  10. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskSkeleton.js +30 -34
  11. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Dependencies.test.js +30 -30
  12. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Errors.test.js +50 -27
  13. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Raw.test.js +44 -22
  14. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/RunningSteps.test.js +56 -24
  15. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +42 -22
  16. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js +45 -54
  17. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +65 -25
  18. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js +37 -13
  19. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetailsActions.test.js +53 -8
  20. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/PausedTasksCard/PausedTasksCard.test.js +53 -7
  21. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/RunningTasksCard/RunningTasksCard.test.js +53 -7
  22. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/ScheduledTasksCard.js +5 -4
  23. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/ScheduledTasksCard.test.js +44 -14
  24. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.js +6 -5
  25. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/TasksDonutCard.js +5 -4
  26. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/TasksDonutCard.scss +3 -8
  27. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/TasksDonutCard.test.js +75 -34
  28. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/TasksDonutChart.test.js +57 -34
  29. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/TasksDonutChartHelper.test.js +23 -9
  30. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.fixtures.js +14 -11
  31. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.js +19 -21
  32. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/TasksCardsGrid.test.js +63 -14
  33. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/Components/TimeDropDown/TimeDropDown.js +9 -17
  34. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/Components/TimeDropDown/TimeDropDown.test.js +49 -13
  35. metadata +2 -45
  36. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Errors.test.js.snap +0 -77
  37. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Raw.test.js.snap +0 -174
  38. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/RunningSteps.test.js.snap +0 -62
  39. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap +0 -127
  40. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +0 -580
  41. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap +0 -172
  42. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetailsActions.test.js.snap +0 -52
  43. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/PausedTasksCard/__snapshots__/PausedTasksCard.test.js.snap +0 -38
  44. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/RunningTasksCard/__snapshots__/RunningTasksCard.test.js.snap +0 -38
  45. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/__snapshots__/ScheduledTasksCard.test.js.snap +0 -97
  46. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutCard/__snapshots__/TasksDonutCard.test.js.snap +0 -183
  47. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/__snapshots__/TasksDonutChart.test.js.snap +0 -302
  48. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/__snapshots__/TasksDonutChartHelper.test.js.snap +0 -21
  49. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/__snapshots__/TasksCardsGrid.test.js.snap +0 -210
  50. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/Components/TimeDropDown/__snapshots__/TimeDropDown.test.js.snap +0 -85
@@ -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
  }
@@ -1,4 +1,6 @@
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 {
4
6
  TASKS_DASHBOARD_AVAILABLE_TIMES,
@@ -6,38 +8,77 @@ import {
6
8
  } from '../../../../TasksDashboardConstants';
7
9
  import TasksDonutCard from './TasksDonutCard';
8
10
 
9
- const fixtures = {
10
- 'render with minimal props': {},
11
- 'render with props': {
12
- title: 'some title',
13
- time: TASKS_DASHBOARD_AVAILABLE_TIMES.WEEK,
14
- wantedQueryState: 'some-state',
15
- className: 'some-classname',
11
+ jest.mock('../TasksDonutChart/TasksDonutChart', () => {
12
+ const React = require('react');
13
+ const Stub = props => (
14
+ <div
15
+ data-testid="tasks-donut-chart-stub"
16
+ data-focused-on={props.focusedOn}
17
+ data-time={props.time}
18
+ />
19
+ );
20
+ Stub.displayName = 'TasksDonutChart';
21
+ return Stub;
22
+ });
23
+
24
+ describe('TasksDonutCard', () => {
25
+ const baseProps = {
26
+ title: 'Card title',
27
+ wantedQueryState: 'running',
16
28
  data: { last: 3, older: 5 },
17
- },
18
- 'render with total selected': {
19
- wantedQueryState: 'some-state',
20
- query: { state: 'some-state' },
21
- },
22
- 'render with last selected': {
23
- wantedQueryState: 'some-state',
24
- time: TASKS_DASHBOARD_AVAILABLE_TIMES.WEEK,
25
- query: {
26
- state: 'some-state',
27
- mode: TASKS_DASHBOARD_AVAILABLE_QUERY_MODES.LAST,
28
- time: TASKS_DASHBOARD_AVAILABLE_TIMES.WEEK,
29
- },
30
- },
31
- 'render with older selected': {
32
- wantedQueryState: 'some-state',
33
29
  time: TASKS_DASHBOARD_AVAILABLE_TIMES.WEEK,
34
- query: {
35
- state: 'some-state',
36
- mode: TASKS_DASHBOARD_AVAILABLE_QUERY_MODES.OLDER,
37
- time: TASKS_DASHBOARD_AVAILABLE_TIMES.WEEK,
38
- },
39
- },
40
- };
41
-
42
- describe('TasksDonutCard', () =>
43
- testComponentSnapshotsWithFixtures(TasksDonutCard, fixtures));
30
+ };
31
+
32
+ it('renders title and wires chart stub with time and focus from query', () => {
33
+ render(
34
+ <TasksDonutCard
35
+ {...baseProps}
36
+ query={{ state: 'running', mode: TASKS_DASHBOARD_AVAILABLE_QUERY_MODES.LAST, time: TASKS_DASHBOARD_AVAILABLE_TIMES.WEEK }}
37
+ />
38
+ );
39
+ expect(screen.getByText('Card title')).toBeInTheDocument();
40
+ const chart = screen.getByTestId('tasks-donut-chart-stub');
41
+ expect(chart).toHaveAttribute('data-focused-on', 'last');
42
+ });
43
+
44
+ it('clicking the title requests the wanted query state', () => {
45
+ const updateQuery = jest.fn();
46
+ render(<TasksDonutCard {...baseProps} updateQuery={updateQuery} />);
47
+ fireEvent.click(screen.getByText('Card title'));
48
+ expect(updateQuery).toHaveBeenCalledWith({ state: 'running' });
49
+ });
50
+
51
+ it('applies selected styling when this card state matches the query (total focus)', () => {
52
+ const { container } = render(
53
+ <TasksDonutCard {...baseProps} query={{ state: 'running' }} />
54
+ );
55
+ expect(container.querySelector('.tasks-donut-card')).toHaveClass(
56
+ 'selected-tasks-card'
57
+ );
58
+ });
59
+
60
+ it('applies not-focused when another dashboard state is active', () => {
61
+ const { container } = render(
62
+ <TasksDonutCard {...baseProps} query={{ state: 'paused' }} />
63
+ );
64
+ expect(container.querySelector('.tasks-donut-card')).toHaveClass(
65
+ 'not-focused'
66
+ );
67
+ });
68
+
69
+ it('merges className onto the card', () => {
70
+ const { container } = render(
71
+ <TasksDonutCard {...baseProps} className="extra-donut" />
72
+ );
73
+ expect(container.querySelector('.tasks-donut-card')).toHaveClass(
74
+ 'extra-donut'
75
+ );
76
+ });
77
+
78
+ it('does not mark the card selected or dimmed when the query is empty', () => {
79
+ const { container } = render(<TasksDonutCard {...baseProps} query={{}} />);
80
+ const card = container.querySelector('.tasks-donut-card');
81
+ expect(card).not.toHaveClass('selected-tasks-card');
82
+ expect(card).not.toHaveClass('not-focused');
83
+ });
84
+ });
@@ -1,40 +1,63 @@
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 { TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS_ARRAY } from './TasksDonutChartConstants';
5
+ import { TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS } from './TasksDonutChartConstants';
4
6
  import TasksDonutChart from './TasksDonutChart';
5
7
 
6
- jest.mock('./TasksDonutChartHelper', () => ({
7
- shouleBeSelected: focusedOn => focusedOn !== 'normal' && focusedOn !== 'none',
8
- getBaseChartConfig: jest.fn(() => ({ base: 'some-base-config' })),
9
- createChartData: jest.fn(() => ({
10
- columns: 'some-columns',
11
- names: 'some-names',
12
- onItemClick: jest.fn(),
13
- })),
14
- updateChartTitle: jest.fn(),
15
- }));
16
-
17
- const createRequiredProps = () => ({ last: 3, older: 5 });
18
-
19
- const fixtures = {
20
- 'render with minimal props': { ...createRequiredProps() },
21
- 'render with props': {
22
- ...createRequiredProps(),
23
- className: 'some-class',
24
- time: 'time-period',
25
- colorsPattern: ['color1', 'color2'],
26
- onTotalClick: jest.fn(),
27
- onLastClick: jest.fn(),
28
- onOlderClick: jest.fn(),
29
- },
30
- };
31
-
32
- TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS_ARRAY.forEach(mode => {
33
- fixtures[`render with focused-on ${mode}`] = {
34
- ...createRequiredProps(),
35
- focusedOn: mode,
8
+ jest.mock('./TasksDonutChartHelper', () => {
9
+ const actual = jest.requireActual('./TasksDonutChartHelper');
10
+ return {
11
+ ...actual,
12
+ getBaseChartConfig: jest.fn(() => ({ mockBase: true })),
13
+ updateChartTitle: jest.fn(),
14
+ assignExtraChartEvents: jest.fn(),
15
+ clearExtraChartEvents: jest.fn(),
36
16
  };
37
17
  });
38
18
 
39
- describe('TasksDonutChart', () =>
40
- testComponentSnapshotsWithFixtures(TasksDonutChart, fixtures));
19
+ jest.mock('../../../../../../Components/Chart/Chart', () => {
20
+ const React = require('react');
21
+ return class MockChart extends React.Component {
22
+ componentDidMount() {
23
+ this.props.onChartCreate?.({
24
+ element: {},
25
+ focus: jest.fn(),
26
+ revert: jest.fn(),
27
+ });
28
+ }
29
+
30
+ render() {
31
+ return <div data-testid="chart-root" className={this.props.className} />;
32
+ }
33
+ };
34
+ });
35
+
36
+ const { NORMAL, TOTAL } = TASKS_DONUT_CHART_FOCUSED_ON_OPTIONS;
37
+
38
+ describe('TasksDonutChart', () => {
39
+ const required = { last: 3, older: 5 };
40
+
41
+ it('renders a chart with donut classes', () => {
42
+ render(<TasksDonutChart {...required} />);
43
+ const root = screen.getByTestId('chart-root');
44
+ expect(root).toHaveClass('donut-chart-pf', 'tasks-donut-chart');
45
+ });
46
+
47
+ it('adds selection class when focusedOn is not normal or none', () => {
48
+ render(<TasksDonutChart {...required} focusedOn={TOTAL} />);
49
+ expect(screen.getByTestId('chart-root')).toHaveClass('tasks-donut-selected');
50
+ });
51
+
52
+ it('does not add selection class in normal focus', () => {
53
+ render(<TasksDonutChart {...required} focusedOn={NORMAL} />);
54
+ expect(screen.getByTestId('chart-root')).not.toHaveClass(
55
+ 'tasks-donut-selected'
56
+ );
57
+ });
58
+
59
+ it('merges custom className', () => {
60
+ render(<TasksDonutChart {...required} className="custom-chart" />);
61
+ expect(screen.getByTestId('chart-root')).toHaveClass('custom-chart');
62
+ });
63
+ });