Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 75 additions & 25 deletions awx/ui/src/screens/Dashboard/Dashboard.test.js
Original file line number Diff line number Diff line change
@@ -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', () => () => <div data-testid="line-chart" />);

describe('<Dashboard />', () => {
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(<Dashboard />);
},
});
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(<Dashboard />);
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(<Dashboard />);
// 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(<Dashboard />);
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());
});
});
118 changes: 76 additions & 42 deletions awx/ui/src/screens/Dashboard/DashboardGraph.test.js
Original file line number Diff line number Diff line change
@@ -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', () => () => <div data-testid="line-chart" />);

// 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('<DashboardGraph/>', () => {
let pageWrapper;
let graphRequest;

beforeEach(async () => {
await act(async () => {
DashboardAPI.read.mockResolvedValue({});
graphRequest = DashboardAPI.readJobGraph;
graphRequest.mockResolvedValue({});
pageWrapper = mountWithContexts(<DashboardGraph />);
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(<DashboardGraph />);
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(<DashboardGraph />);

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);
});
});
17 changes: 8 additions & 9 deletions awx/ui/src/screens/Dashboard/shared/Count.test.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import React from 'react';

import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import { renderWithContexts } from '../../../../testUtils/rtlContexts';

import Count from './Count';

describe('<Count />', () => {
let pageWrapper;

test('initially renders without crashing', () => {
pageWrapper = mountWithContexts(<Count link="foo" />);
expect(pageWrapper.length).toBe(1);
const { container } = renderWithContexts(<Count link="foo" />);
expect(container).toBeInTheDocument();
expect(container.querySelector('h2')).toBeInTheDocument();
});

test('renders non-failed version of count without prop', () => {
pageWrapper = mountWithContexts(<Count link="foo" />);
expect(pageWrapper.find('h2').hasClass('failed')).toBe(false);
const { container } = renderWithContexts(<Count link="foo" />);
expect(container.querySelector('h2')).not.toHaveClass('failed');
});

test('renders failed version of count with appropriate prop', () => {
pageWrapper = mountWithContexts(<Count link="foo" failed />);
expect(pageWrapper.find('h2').hasClass('failed')).toBe(true);
const { container } = renderWithContexts(<Count link="foo" failed />);
expect(container.querySelector('h2')).toHaveClass('failed');
});
});