diff --git a/awx/ui/src/screens/Dashboard/Dashboard.test.js b/awx/ui/src/screens/Dashboard/Dashboard.test.js
index dfc06ff60..605063b05 100644
--- a/awx/ui/src/screens/Dashboard/Dashboard.test.js
+++ b/awx/ui/src/screens/Dashboard/Dashboard.test.js
@@ -1,45 +1,95 @@
import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { DashboardAPI, RootAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
+import { screen, waitFor } from '@testing-library/react';
+import {
+ DashboardAPI,
+ RootAPI,
+ UnifiedJobTemplatesAPI,
+ JobTemplatesAPI,
+ WorkflowJobTemplatesAPI,
+} from 'api';
+import { renderWithContexts } from '../../../testUtils/rtlContexts';
import Dashboard from './Dashboard';
jest.mock('../../api');
+// DashboardGraph's LineChart draws with d3, which needs
+// SVGPathElement.getTotalLength (absent in jsdom). The chart isn't what these
+// tests cover, so stub it and assert on the dashboard's tabs/counts + requests.
+jest.mock('./shared/LineChart', () => () =>
);
+
describe('', () => {
- let pageWrapper;
let graphRequest;
- beforeEach(async () => {
- await act(async () => {
- DashboardAPI.read.mockResolvedValue({});
- RootAPI.readAssetVariables.mockResolvedValue({
- data: {
- BRAND_NAME: 'AWX',
+ beforeEach(() => {
+ DashboardAPI.read.mockResolvedValue({});
+ RootAPI.readAssetVariables.mockResolvedValue({
+ data: {
+ BRAND_NAME: 'AWX',
+ },
+ });
+ graphRequest = DashboardAPI.readJobGraph;
+ graphRequest.mockResolvedValue({
+ data: {
+ jobs: {
+ successful: [
+ [1609459200, 2],
+ [1609545600, 4],
+ ],
+ failed: [
+ [1609459200, 1],
+ [1609545600, 0],
+ ],
},
- });
- graphRequest = DashboardAPI.readJobGraph;
- graphRequest.mockResolvedValue({});
- pageWrapper = mountWithContexts();
+ },
+ });
+ UnifiedJobTemplatesAPI.read.mockResolvedValue({
+ data: { count: 0, results: [] },
+ });
+ UnifiedJobTemplatesAPI.readOptions.mockResolvedValue({
+ data: { actions: {}, related_search_fields: [] },
});
+ JobTemplatesAPI.readOptions.mockResolvedValue({
+ data: { actions: {} },
+ });
+ WorkflowJobTemplatesAPI.readOptions.mockResolvedValue({
+ data: { actions: {} },
+ });
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
});
- test('initially renders without crashing', () => {
- expect(pageWrapper.length).toBe(1);
+ test('initially renders without crashing', async () => {
+ renderWithContexts();
+ expect(
+ await screen.findByRole('tab', { name: 'Job status graph tab' })
+ ).toBeInTheDocument();
});
- test('renders dashboard graph by default', () => {
- expect(pageWrapper.find('LineChart').length).toBe(1);
+ test('renders dashboard graph by default', async () => {
+ renderWithContexts();
+ // The Job status tab is active by default, so DashboardGraph mounts and
+ // requests the default (all/month) job graph data.
+ await screen.findByRole('tab', { name: 'Job status graph tab' });
+ expect(await screen.findByTestId('line-chart')).toBeInTheDocument();
+ await waitFor(() =>
+ expect(graphRequest).toHaveBeenCalledWith({
+ job_type: 'all',
+ period: 'month',
+ })
+ );
});
test('renders template list when the active tab is changed', async () => {
- expect(pageWrapper.find('DashboardTemplateList').length).toBe(0);
- await act(async () => {
- pageWrapper
- .find('button[aria-label="Recent Templates list tab"]')
- .simulate('click');
+ const { user } = renderWithContexts();
+ const templatesTab = await screen.findByRole('tab', {
+ name: 'Recent Templates list tab',
});
- pageWrapper.update();
- expect(pageWrapper.find('TemplateList').length).toBe(1);
+ await user.click(templatesTab);
+ // TemplateList mounts and fetches; with an empty result it renders its
+ // empty-state, confirming the list (not the graph) is now shown.
+ expect(await screen.findByText('No Templates Found')).toBeInTheDocument();
+ await waitFor(() => expect(UnifiedJobTemplatesAPI.read).toHaveBeenCalled());
});
});
diff --git a/awx/ui/src/screens/Dashboard/DashboardGraph.test.js b/awx/ui/src/screens/Dashboard/DashboardGraph.test.js
index 606b068da..299e6243c 100644
--- a/awx/ui/src/screens/Dashboard/DashboardGraph.test.js
+++ b/awx/ui/src/screens/Dashboard/DashboardGraph.test.js
@@ -1,61 +1,95 @@
import React from 'react';
-import { act } from 'react-dom/test-utils';
+import { screen, waitFor, within } from '@testing-library/react';
import { DashboardAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
+import { renderWithContexts } from '../../../testUtils/rtlContexts';
import DashboardGraph from './DashboardGraph';
jest.mock('../../api');
+// LineChart renders via d3, which relies on SVGPathElement.getTotalLength —
+// not implemented by jsdom — so the real chart throws while drawing. The chart
+// itself isn't under test here (the filter controls and the data request are),
+// so stub it out and keep the assertions on the surrounding UI + API calls.
+jest.mock('./shared/LineChart', () => () => );
+
+// The three PF Select toggles all expose the accessible name "Options menu",
+// so they can't be told apart by role+name. They carry distinct classNames
+// (periodSelect / jobTypeSelect / jobStatusSelect) wired up in the source, so
+// scope to each select wrapper and grab its toggle button.
+function getToggle(container, className) {
+ return container.querySelector(`.${className} button.pf-c-select__toggle`);
+}
+
describe('', () => {
- let pageWrapper;
let graphRequest;
- beforeEach(async () => {
- await act(async () => {
- DashboardAPI.read.mockResolvedValue({});
- graphRequest = DashboardAPI.readJobGraph;
- graphRequest.mockResolvedValue({});
- pageWrapper = mountWithContexts();
+ beforeEach(() => {
+ DashboardAPI.read.mockResolvedValue({});
+ graphRequest = DashboardAPI.readJobGraph;
+ graphRequest.mockResolvedValue({
+ data: {
+ jobs: {
+ successful: [
+ [1609459200, 2],
+ [1609545600, 4],
+ ],
+ failed: [
+ [1609459200, 1],
+ [1609545600, 0],
+ ],
+ },
+ },
});
});
- test('renders month-based/all job type chart by default', () => {
- expect(graphRequest).toHaveBeenCalledWith({
- job_type: 'all',
- period: 'month',
- });
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('renders month-based/all job type chart by default', async () => {
+ renderWithContexts();
+ await waitFor(() =>
+ expect(graphRequest).toHaveBeenCalledWith({
+ job_type: 'all',
+ period: 'month',
+ })
+ );
});
test('should render all three line chart filters with correct number of options', async () => {
- expect(pageWrapper.find('Select[variant="single"]')).toHaveLength(3);
- await act(async () => {
- pageWrapper
- .find('Select[placeholderText="Select job type"]')
- .prop('onToggle')(true);
- });
- pageWrapper.update();
- expect(pageWrapper.find('SelectOption')).toHaveLength(4);
- await act(async () => {
- pageWrapper
- .find('Select[placeholderText="Select job type"]')
- .prop('onToggle')(false);
- pageWrapper
- .find('Select[placeholderText="Select period"]')
- .prop('onToggle')(true);
- });
- pageWrapper.update();
- expect(pageWrapper.find('SelectOption')).toHaveLength(4);
- await act(async () => {
- pageWrapper
- .find('Select[placeholderText="Select period"]')
- .prop('onToggle')(false);
- pageWrapper
- .find('Select[placeholderText="Select status"]')
- .prop('onToggle')(true);
- });
- pageWrapper.update();
- expect(pageWrapper.find('SelectOption')).toHaveLength(3);
+ const { user, container } = renderWithContexts();
+
+ await waitFor(() => expect(graphRequest).toHaveBeenCalled());
+
+ const periodToggle = getToggle(container, 'periodSelect');
+ const jobTypeToggle = getToggle(container, 'jobTypeSelect');
+ const statusToggle = getToggle(container, 'jobStatusSelect');
+ expect(periodToggle).toBeInTheDocument();
+ expect(jobTypeToggle).toBeInTheDocument();
+ expect(statusToggle).toBeInTheDocument();
+
+ await user.click(jobTypeToggle);
+ let listbox = await screen.findByRole('listbox');
+ expect(within(listbox).getAllByRole('option')).toHaveLength(4);
+
+ await user.click(jobTypeToggle);
+ await waitFor(() =>
+ expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
+ );
+
+ await user.click(periodToggle);
+ listbox = await screen.findByRole('listbox');
+ expect(within(listbox).getAllByRole('option')).toHaveLength(4);
+
+ await user.click(periodToggle);
+ await waitFor(() =>
+ expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
+ );
+
+ await user.click(statusToggle);
+ listbox = await screen.findByRole('listbox');
+ expect(within(listbox).getAllByRole('option')).toHaveLength(3);
});
});
diff --git a/awx/ui/src/screens/Dashboard/shared/Count.test.js b/awx/ui/src/screens/Dashboard/shared/Count.test.js
index ded7ad951..95f8252cd 100644
--- a/awx/ui/src/screens/Dashboard/shared/Count.test.js
+++ b/awx/ui/src/screens/Dashboard/shared/Count.test.js
@@ -1,24 +1,23 @@
import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
+import { renderWithContexts } from '../../../../testUtils/rtlContexts';
import Count from './Count';
describe('', () => {
- let pageWrapper;
-
test('initially renders without crashing', () => {
- pageWrapper = mountWithContexts();
- expect(pageWrapper.length).toBe(1);
+ const { container } = renderWithContexts();
+ expect(container).toBeInTheDocument();
+ expect(container.querySelector('h2')).toBeInTheDocument();
});
test('renders non-failed version of count without prop', () => {
- pageWrapper = mountWithContexts();
- expect(pageWrapper.find('h2').hasClass('failed')).toBe(false);
+ const { container } = renderWithContexts();
+ expect(container.querySelector('h2')).not.toHaveClass('failed');
});
test('renders failed version of count with appropriate prop', () => {
- pageWrapper = mountWithContexts();
- expect(pageWrapper.find('h2').hasClass('failed')).toBe(true);
+ const { container } = renderWithContexts();
+ expect(container.querySelector('h2')).toHaveClass('failed');
});
});