foreman-tasks 12.2.2 → 12.2.4

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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/config/routes.rb +0 -1
  3. data/lib/foreman_tasks/version.rb +1 -1
  4. data/webpack/ForemanTasks/Components/TaskActions/TaskAction.test.js +212 -42
  5. data/webpack/ForemanTasks/Components/TaskDetails/Components/Errors.js +224 -55
  6. data/webpack/ForemanTasks/Components/TaskDetails/Components/Errors.scss +41 -0
  7. data/webpack/ForemanTasks/Components/TaskDetails/Components/RunningSteps.js +152 -44
  8. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Errors.test.js +159 -11
  9. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/RunningSteps.test.js +39 -0
  10. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +3 -1
  11. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboard.test.js +94 -7
  12. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboardActions.test.js +97 -16
  13. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboardReducer.test.js +112 -46
  14. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboardSelectors.test.js +103 -15
  15. data/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js +1 -1
  16. data/webpack/Routes/routes.js +0 -5
  17. data/webpack/Routes/routes.test.js +0 -18
  18. metadata +2 -11
  19. data/webpack/ForemanTasks/Components/TaskActions/__snapshots__/TaskAction.test.js.snap +0 -233
  20. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboard.test.js.snap +0 -51
  21. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboardActions.test.js.snap +0 -151
  22. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboardReducer.test.js.snap +0 -275
  23. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboardSelectors.test.js.snap +0 -119
  24. data/webpack/ForemanTasks/Routes/ShowTask/ShowTask.js +0 -10
  25. data/webpack/ForemanTasks/Routes/ShowTask/__tests__/ShowTask.test.js +0 -14
  26. data/webpack/ForemanTasks/Routes/ShowTask/__tests__/__snapshots__/ShowTask.test.js.snap +0 -12
  27. data/webpack/ForemanTasks/Routes/ShowTask/index.js +0 -1
  28. data/webpack/ForemanTasks/Routes/ShowTask/showTask.scss +0 -0
@@ -1,68 +1,174 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Alert, AlertVariant, Button } from '@patternfly/react-core';
3
+ import {
4
+ Alert,
5
+ AlertVariant,
6
+ Button,
7
+ EmptyState,
8
+ EmptyStateBody,
9
+ EmptyStateHeader,
10
+ EmptyStateIcon,
11
+ EmptyStateVariant,
12
+ Flex,
13
+ FlexItem,
14
+ Grid,
15
+ GridItem,
16
+ Stack,
17
+ StackItem,
18
+ } from '@patternfly/react-core';
19
+ import { HourglassStartIcon } from '@patternfly/react-icons';
4
20
  import { translate as __, sprintf } from 'foremanReact/common/I18n';
5
21
 
22
+ const RunningStepDetailBlock = ({ label, children }) => (
23
+ <GridItem span={12}>
24
+ <Flex
25
+ direction={{ default: 'column' }}
26
+ spaceItems={{ default: 'spaceItemsXs' }}
27
+ >
28
+ <FlexItem>
29
+ <strong>{label}</strong>
30
+ </FlexItem>
31
+ <FlexItem>{children}</FlexItem>
32
+ </Flex>
33
+ </GridItem>
34
+ );
35
+
36
+ RunningStepDetailBlock.propTypes = {
37
+ label: PropTypes.node.isRequired,
38
+ children: PropTypes.node.isRequired,
39
+ };
40
+
6
41
  const RunningSteps = ({
42
+ executionPlan,
43
+ result,
7
44
  runningSteps,
8
45
  id,
9
46
  cancelStep,
10
47
  taskReload,
11
48
  taskReloadStart,
12
49
  }) => {
13
- if (!runningSteps.length) return <span>{__('No running steps')}</span>;
14
- return (
15
- <div>
16
- {runningSteps.map((step, i) => (
50
+ const planState = executionPlan?.state;
51
+ const resultIsPending = String(result) === 'pending';
52
+
53
+ if (!runningSteps.length) {
54
+ if (planState === 'running' && resultIsPending) {
55
+ return (
17
56
  <Alert
18
57
  variant={AlertVariant.warning}
19
58
  isInline
20
- key={step.id || i}
21
- ouiaId={`running-step-${i}`}
22
- title={sprintf(__('Running step %s'), i + 1)}
59
+ ouiaId="running-steps-suspended-pending"
60
+ title={__('Temporarily suspended step(s)')}
23
61
  >
24
- {step.cancellable && (
25
- <p>
26
- <Button
27
- variant="danger"
28
- size="sm"
29
- ouiaId={`running-step-cancel-button-${i}`}
30
- onClick={() => {
31
- if (!taskReload) {
32
- taskReloadStart(id);
33
- }
34
- cancelStep(id, step.id);
35
- }}
36
- >
37
- {__('Cancel')}
38
- </Button>
39
- </p>
40
- )}
41
-
42
- <p>
43
- <span>{__('Action')}:</span>
44
- <span />
45
- </p>
46
- <pre>{step.action_class}</pre>
47
- <p>
48
- <span>{__('State')}:</span>
49
- <span>{step.state}</span>
50
- </p>
51
- <span>{__('Input')}:</span>
52
- <span>
53
- <pre>{step.input}</pre>
54
- </span>
55
- <span>{__('Output')}:</span>
56
- <span>
57
- <pre>{step.output}</pre>
58
- </span>
62
+ {__('The task is still being processed. Please wait.')}
59
63
  </Alert>
64
+ );
65
+ }
66
+
67
+ if (planState === 'planned' && resultIsPending) {
68
+ return (
69
+ <Grid>
70
+ <GridItem span={12}>
71
+ <Flex
72
+ direction={{ default: 'column' }}
73
+ alignItems={{ default: 'alignItemsCenter' }}
74
+ justifyContent={{ default: 'justifyContentCenter' }}
75
+ fullWidth={{ default: 'fullWidth' }}
76
+ >
77
+ <FlexItem>
78
+ <EmptyState variant={EmptyStateVariant.full}>
79
+ <EmptyStateHeader
80
+ titleText={__('Planned task')}
81
+ headingLevel="h2"
82
+ icon={<EmptyStateIcon icon={HourglassStartIcon} />}
83
+ />
84
+ <EmptyStateBody>
85
+ {__('The task has not started yet.')}
86
+ </EmptyStateBody>
87
+ </EmptyState>
88
+ </FlexItem>
89
+ </Flex>
90
+ </GridItem>
91
+ </Grid>
92
+ );
93
+ }
94
+
95
+ return <span>{__('No running steps')}</span>;
96
+ }
97
+
98
+ return (
99
+ <Stack hasGutter>
100
+ {runningSteps.map((step, i) => (
101
+ <StackItem key={step.id || i}>
102
+ <Alert
103
+ variant={AlertVariant.warning}
104
+ isInline
105
+ title={sprintf(__('Running step %s'), i + 1)}
106
+ ouiaId={`running-step-${i}`}
107
+ >
108
+ <Grid hasGutter>
109
+ {step.cancellable && (
110
+ <GridItem span={12}>
111
+ <Flex>
112
+ <FlexItem>
113
+ <Button
114
+ ouiaId={`running-step-${i}-cancel`}
115
+ variant="danger"
116
+ size="sm"
117
+ onClick={() => {
118
+ if (!taskReload) {
119
+ taskReloadStart(id);
120
+ }
121
+
122
+ cancelStep(id, step.id);
123
+ }}
124
+ >
125
+ {__('Cancel')}
126
+ </Button>
127
+ </FlexItem>
128
+ </Flex>
129
+ </GridItem>
130
+ )}
131
+ <GridItem span={12}>
132
+ <Flex
133
+ direction={{ default: 'row' }}
134
+ spaceItems={{ default: 'spaceItemsSm' }}
135
+ alignItems={{ default: 'alignItemsBaseline' }}
136
+ >
137
+ <FlexItem>
138
+ <strong>{`${__('Action')}:`}</strong>
139
+ </FlexItem>
140
+ <FlexItem>{step.action_class}</FlexItem>
141
+ </Flex>
142
+ </GridItem>
143
+ <GridItem span={12}>
144
+ <Flex
145
+ direction={{ default: 'row' }}
146
+ spaceItems={{ default: 'spaceItemsSm' }}
147
+ alignItems={{ default: 'alignItemsBaseline' }}
148
+ >
149
+ <FlexItem>
150
+ <strong>{`${__('State')}:`}</strong>
151
+ </FlexItem>
152
+ <FlexItem>{step.state}</FlexItem>
153
+ </Flex>
154
+ </GridItem>
155
+ <RunningStepDetailBlock label={__('Input')}>
156
+ <pre>{step.input}</pre>
157
+ </RunningStepDetailBlock>
158
+ <RunningStepDetailBlock label={__('Output')}>
159
+ <pre>{step.output}</pre>
160
+ </RunningStepDetailBlock>
161
+ </Grid>
162
+ </Alert>
163
+ </StackItem>
60
164
  ))}
61
- </div>
165
+ </Stack>
62
166
  );
63
167
  };
64
168
 
65
169
  RunningSteps.propTypes = {
170
+ executionPlan: PropTypes.shape({ state: PropTypes.string }),
171
+ result: PropTypes.string,
66
172
  runningSteps: PropTypes.array,
67
173
  id: PropTypes.string.isRequired,
68
174
  cancelStep: PropTypes.func.isRequired,
@@ -72,6 +178,8 @@ RunningSteps.propTypes = {
72
178
 
73
179
  RunningSteps.defaultProps = {
74
180
  runningSteps: [],
181
+ executionPlan: {},
182
+ result: undefined,
75
183
  };
76
184
 
77
185
  export default RunningSteps;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
3
  import '@testing-library/jest-dom';
4
4
 
5
5
  import Errors from '../Errors';
@@ -20,6 +20,8 @@ const failedStepFixture = {
20
20
  output: '{}\n',
21
21
  };
22
22
 
23
+ const executionPlan = { state: 'paused', cancellable: false };
24
+
23
25
  describe('Errors', () => {
24
26
  it('renders warning when execution plan is missing', () => {
25
27
  render(<Errors failedSteps={[]} executionPlan={null} />);
@@ -30,30 +32,176 @@ describe('Errors', () => {
30
32
 
31
33
  it('renders success state when there are no failed steps', () => {
32
34
  render(<Errors failedSteps={[]} executionPlan={{ state: 'paused' }} />);
33
- const noErrors = screen.getAllByText(/^no errors$/i);
34
- expect(noErrors.length).toBeGreaterThanOrEqual(1);
35
+ expect(
36
+ screen.getByRole('heading', { level: 2, name: /no errors found/i })
37
+ ).toBeInTheDocument();
38
+ expect(
39
+ screen.getByText(/the task finished with no errors or warnings/i)
40
+ ).toBeInTheDocument();
35
41
  });
36
42
 
37
43
  it('renders failed step details when failedSteps is non-empty', () => {
38
44
  const { container } = render(
39
45
  <Errors
40
- executionPlan={{ state: 'paused', cancellable: false }}
46
+ executionPlan={executionPlan}
41
47
  failedSteps={[failedStepFixture]}
42
48
  />
43
49
  );
44
- const stepAlert = container.querySelector(
50
+ const errorTab = container.querySelector(
45
51
  '[data-ouia-component-id="task-error-0"]'
46
52
  );
47
- expect(stepAlert).toBeInTheDocument();
48
- expect(stepAlert).toHaveClass('pf-m-inline');
53
+ expect(errorTab).toBeInTheDocument();
54
+ expect(
55
+ screen.getByRole('tab', {
56
+ name: /action actions::katello::eventqueue::monitor is already active/i,
57
+ })
58
+ ).toBeInTheDocument();
49
59
  expect(
50
- screen.getByText('Actions::Katello::EventQueue::Monitor')
60
+ screen.getByLabelText(/failed task errors/i)
51
61
  ).toBeInTheDocument();
62
+ expect(screen.getByText('Input')).toBeInTheDocument();
63
+ expect(screen.getByText('Output')).toBeInTheDocument();
64
+ expect(screen.getByText('Exception:')).toBeInTheDocument();
65
+ expect(screen.getByText('Backtrace')).toBeInTheDocument();
52
66
  expect(screen.getByText(/runtimeerror/i)).toBeInTheDocument();
67
+ expect(screen.getByText(/singleton_lock/i)).toBeInTheDocument();
68
+ });
69
+
70
+ it('switches detail pane when a different error tab is clicked', () => {
71
+ const firstStep = {
72
+ ...failedStepFixture,
73
+ action_class: 'Action::First',
74
+ input: 'INPUT_FROM_FIRST_STEP',
75
+ output: '{}',
76
+ };
77
+ const secondStep = {
78
+ ...failedStepFixture,
79
+ action_class: 'Action::Second',
80
+ error: {
81
+ exception_class: 'StandardError',
82
+ message: 'second step failure',
83
+ backtrace: [],
84
+ },
85
+ input: 'INPUT_FROM_SECOND_STEP',
86
+ output: 'OUTPUT_SECOND',
87
+ state: 'error',
88
+ };
89
+
90
+ render(
91
+ <Errors
92
+ executionPlan={executionPlan}
93
+ failedSteps={[firstStep, secondStep]}
94
+ />
95
+ );
96
+
97
+ expect(screen.getByText('INPUT_FROM_FIRST_STEP')).toBeInTheDocument();
98
+ expect(screen.queryByText('INPUT_FROM_SECOND_STEP')).not.toBeInTheDocument();
99
+
100
+ const tabs = screen.getAllByRole('tab');
101
+ expect(tabs[0]).toHaveAttribute('aria-selected', 'true');
102
+ expect(tabs[1]).toHaveAttribute('aria-selected', 'false');
103
+
104
+ fireEvent.click(tabs[1]);
105
+
106
+ expect(screen.getByText('INPUT_FROM_SECOND_STEP')).toBeInTheDocument();
107
+ expect(screen.queryByText('INPUT_FROM_FIRST_STEP')).not.toBeInTheDocument();
108
+ expect(tabs[0]).toHaveAttribute('aria-selected', 'false');
109
+ expect(tabs[1]).toHaveAttribute('aria-selected', 'true');
110
+ expect(screen.getByText('OUTPUT_SECOND')).toBeInTheDocument();
111
+ });
112
+
113
+ it('clamps selection when failedSteps shrinks', () => {
114
+ const firstStep = {
115
+ ...failedStepFixture,
116
+ action_class: 'Action::First',
117
+ input: 'AFTER_CLAMP',
118
+ output: '{}',
119
+ };
120
+ const secondStep = {
121
+ ...failedStepFixture,
122
+ action_class: 'Action::Second',
123
+ input: 'REMOVED',
124
+ output: '{}',
125
+ state: 'error',
126
+ };
127
+
128
+ const { rerender } = render(
129
+ <Errors
130
+ executionPlan={executionPlan}
131
+ failedSteps={[firstStep, secondStep]}
132
+ />
133
+ );
134
+
135
+ fireEvent.click(screen.getAllByRole('tab')[1]);
136
+ expect(screen.getByText('REMOVED')).toBeInTheDocument();
137
+
138
+ rerender(
139
+ <Errors executionPlan={executionPlan} failedSteps={[firstStep]} />
140
+ );
141
+
142
+ expect(screen.getAllByRole('tab')).toHaveLength(1);
143
+ expect(screen.getByText('AFTER_CLAMP')).toBeInTheDocument();
144
+ });
145
+
146
+ it('truncates long tab titles to 120 characters with ellipsis', () => {
147
+ const longMessage = 'x'.repeat(150);
148
+ const longStep = {
149
+ ...failedStepFixture,
150
+ error: {
151
+ ...failedStepFixture.error,
152
+ message: longMessage,
153
+ },
154
+ };
155
+
156
+ const { container } = render(
157
+ <Errors executionPlan={executionPlan} failedSteps={[longStep]} />
158
+ );
159
+
160
+ const tabTitle = container.querySelector('.task-errors-tab-title');
161
+ expect(tabTitle.textContent).toHaveLength(120);
162
+ expect(tabTitle.textContent.endsWith('...')).toBe(true);
163
+ expect(
164
+ screen.getByRole('tab', { name: longMessage })
165
+ ).toBeInTheDocument();
166
+ });
167
+
168
+ it('uses warning styling for skipped steps', () => {
169
+ const skippedStep = {
170
+ action_class: 'Actions::Example',
171
+ state: 'skipped',
172
+ input: '{}',
173
+ output: '{}',
174
+ };
175
+
176
+ const { container } = render(
177
+ <Errors executionPlan={executionPlan} failedSteps={[skippedStep]} />
178
+ );
179
+
53
180
  expect(
54
- screen.getByText(
55
- /action actions::katello::eventqueue::monitor is already active/i
56
- )
181
+ screen.getByRole('tab', { name: /actions::example/i })
57
182
  ).toBeInTheDocument();
183
+
184
+ const errorTab = container.querySelector(
185
+ '[data-ouia-component-id="task-error-0"]'
186
+ );
187
+ expect(errorTab.querySelector('.pf-m-warning')).toBeInTheDocument();
188
+ });
189
+
190
+ it('omits exception and backtrace when step has no error object', () => {
191
+ const stepWithoutError = {
192
+ action_class: 'Actions::NoError',
193
+ state: 'error',
194
+ input: 'plain-input',
195
+ output: 'plain-output',
196
+ };
197
+
198
+ render(
199
+ <Errors executionPlan={executionPlan} failedSteps={[stepWithoutError]} />
200
+ );
201
+
202
+ expect(screen.getByText('plain-input')).toBeInTheDocument();
203
+ expect(screen.getByText('plain-output')).toBeInTheDocument();
204
+ expect(screen.queryByText('Exception:')).not.toBeInTheDocument();
205
+ expect(screen.queryByText('Backtrace')).not.toBeInTheDocument();
58
206
  });
59
207
  });
@@ -27,6 +27,45 @@ describe('RunningSteps', () => {
27
27
  expect(screen.getByText(/no running steps/i)).toBeInTheDocument();
28
28
  });
29
29
 
30
+ it('shows suspended warning when plan is running, result pending, no steps', () => {
31
+ render(
32
+ <RunningSteps
33
+ {...baseProps}
34
+ runningSteps={[]}
35
+ executionPlan={{ state: 'running', cancellable: false }}
36
+ result="pending"
37
+ />
38
+ );
39
+
40
+ expect(
41
+ screen.getByRole('heading', {
42
+ level: 4,
43
+ name: /temporarily suspended step/i,
44
+ })
45
+ ).toBeInTheDocument();
46
+ expect(
47
+ screen.getByText(/the task is still being processed/i)
48
+ ).toBeInTheDocument();
49
+ });
50
+
51
+ it('shows planned empty state when plan is planned, result pending, no steps', () => {
52
+ render(
53
+ <RunningSteps
54
+ {...baseProps}
55
+ runningSteps={[]}
56
+ executionPlan={{ state: 'planned', cancellable: false }}
57
+ result="pending"
58
+ />
59
+ );
60
+
61
+ expect(
62
+ screen.getByRole('heading', { level: 2, name: /planned task/i })
63
+ ).toBeInTheDocument();
64
+ expect(
65
+ screen.getByText(/the task has not started yet/i)
66
+ ).toBeInTheDocument();
67
+ });
68
+
30
69
  it('renders running step fields and Cancel when step is cancellable', () => {
31
70
  const cancelStep = jest.fn();
32
71
  render(
@@ -30,7 +30,7 @@ const TaskDetails = ({
30
30
  ...props
31
31
  }) => {
32
32
  const id = getTaskID();
33
- const { taskReload, status, isLoading } = props;
33
+ const { taskReload, status, isLoading, result } = props;
34
34
  const [activeTabKey, setActiveTabKey] = useState(1);
35
35
 
36
36
  useEffect(() => {
@@ -96,6 +96,8 @@ const TaskDetails = ({
96
96
  ouiaId="task-details-tab-running-steps"
97
97
  >
98
98
  <RunningSteps
99
+ executionPlan={executionPlan}
100
+ result={result}
99
101
  runningSteps={runningSteps}
100
102
  id={id}
101
103
  cancelStep={cancelStep}
@@ -1,13 +1,100 @@
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
 
5
+ import { TASKS_DASHBOARD_AVAILABLE_TIMES } from '../TasksDashboardConstants';
6
+ import { getQueryFromUrl } from '../TasksDashboardHelper';
3
7
  import TasksDashboard from '../TasksDashboard';
4
8
 
5
- const fixtures = {
6
- 'render without Props': { history: {} },
7
- /** fixtures, props for the component */
8
- };
9
+ jest.mock('../TasksDashboardHelper', () => ({
10
+ ...jest.requireActual('../TasksDashboardHelper'),
11
+ getQueryFromUrl: jest.fn(() => ({})),
12
+ }));
13
+
14
+ jest.mock(
15
+ '../Components/TasksCardsGrid/Components/TasksDonutChart/TasksDonutChart',
16
+ () => {
17
+ const React = require('react');
18
+ const Stub = () => <div data-testid="tasks-donut-chart-stub" />;
19
+ Stub.displayName = 'TasksDonutChart';
20
+
21
+ return Stub;
22
+ }
23
+ );
9
24
 
10
25
  describe('TasksDashboard', () => {
11
- describe('rendering', () =>
12
- testComponentSnapshotsWithFixtures(TasksDashboard, fixtures));
26
+ const cardIds = [
27
+ 'running-tasks-card',
28
+ 'paused-tasks-card',
29
+ 'stopped-tasks-card',
30
+ 'scheduled-tasks-card',
31
+ ];
32
+
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ getQueryFromUrl.mockReturnValue({});
36
+ });
37
+
38
+ it('renders the dashboard grid with time picker and task cards', () => {
39
+ const { container } = render(<TasksDashboard history={{}} />);
40
+
41
+ expect(container.querySelector('.tasks-dashboard-grid')).toBeInTheDocument();
42
+ expect(screen.getByText('With focus on last')).toBeInTheDocument();
43
+ expect(
44
+ screen.getByRole('button', { name: /24h/i })
45
+ ).toBeInTheDocument();
46
+
47
+ cardIds.forEach(id => {
48
+ expect(container.querySelector(`#${id}`)).toBeInTheDocument();
49
+ });
50
+ });
51
+
52
+ it('initializes the dashboard and fetches the summary on mount', () => {
53
+ const initializeDashboard = jest.fn();
54
+ const fetchTasksSummary = jest.fn();
55
+
56
+ render(
57
+ <TasksDashboard
58
+ history={{}}
59
+ initializeDashboard={initializeDashboard}
60
+ fetchTasksSummary={fetchTasksSummary}
61
+ />
62
+ );
63
+
64
+ expect(initializeDashboard).toHaveBeenCalledWith({
65
+ time: undefined,
66
+ query: {},
67
+ });
68
+ expect(fetchTasksSummary).toHaveBeenCalledWith(
69
+ TASKS_DASHBOARD_AVAILABLE_TIMES.H24,
70
+ ''
71
+ );
72
+ });
73
+
74
+ it('fetches the summary again when time changes', () => {
75
+ const fetchTasksSummary = jest.fn();
76
+
77
+ const { rerender } = render(
78
+ <TasksDashboard
79
+ history={{}}
80
+ fetchTasksSummary={fetchTasksSummary}
81
+ time={TASKS_DASHBOARD_AVAILABLE_TIMES.H24}
82
+ />
83
+ );
84
+
85
+ fetchTasksSummary.mockClear();
86
+
87
+ rerender(
88
+ <TasksDashboard
89
+ history={{}}
90
+ fetchTasksSummary={fetchTasksSummary}
91
+ time={TASKS_DASHBOARD_AVAILABLE_TIMES.H12}
92
+ />
93
+ );
94
+
95
+ expect(fetchTasksSummary).toHaveBeenCalledWith(
96
+ TASKS_DASHBOARD_AVAILABLE_TIMES.H12,
97
+ ''
98
+ );
99
+ });
13
100
  });