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'); }); });