mirror of
https://github.com/ansible/awx.git
synced 2026-03-13 15:09:32 -02:30
Remove all inventory route logic from Host screens
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
||||
useLocation,
|
||||
} from 'react-router-dom';
|
||||
import { Card, CardActions } from '@patternfly/react-core';
|
||||
import { CaretLeftIcon } from '@patternfly/react-icons';
|
||||
|
||||
import { TabbedCardHeader } from '@components/Card';
|
||||
import CardCloseButton from '@components/CardCloseButton';
|
||||
@@ -24,20 +23,13 @@ import HostEdit from './HostEdit';
|
||||
import HostGroups from './HostGroups';
|
||||
import { HostsAPI } from '@api';
|
||||
|
||||
function Host({ inventory, i18n, setBreadcrumb }) {
|
||||
function Host({ i18n, setBreadcrumb }) {
|
||||
const [host, setHost] = useState(null);
|
||||
const [contentError, setContentError] = useState(null);
|
||||
const [hasContentLoading, setHasContentLoading] = useState(true);
|
||||
|
||||
const location = useLocation();
|
||||
const hostsMatch = useRouteMatch('/hosts/:id');
|
||||
const inventoriesMatch = useRouteMatch(
|
||||
'/inventories/inventory/:id/hosts/:hostId'
|
||||
);
|
||||
const baseUrl = hostsMatch ? hostsMatch.url : inventoriesMatch.url;
|
||||
const hostListUrl = hostsMatch
|
||||
? '/hosts'
|
||||
: `/inventories/inventory/${inventoriesMatch.params.id}/hosts`;
|
||||
const match = useRouteMatch('/hosts/:id');
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -45,17 +37,10 @@ function Host({ inventory, i18n, setBreadcrumb }) {
|
||||
setHasContentLoading(true);
|
||||
|
||||
try {
|
||||
const hostId = hostsMatch
|
||||
? hostsMatch.params.id
|
||||
: inventoriesMatch.params.hostId;
|
||||
const { data } = await HostsAPI.readDetail(hostId);
|
||||
setHost(data);
|
||||
const { data } = await HostsAPI.readDetail(match.params.id);
|
||||
|
||||
if (hostsMatch) {
|
||||
setBreadcrumb(data);
|
||||
} else if (inventoriesMatch) {
|
||||
setBreadcrumb(inventory, data);
|
||||
}
|
||||
setHost(data);
|
||||
setBreadcrumb(data);
|
||||
} catch (error) {
|
||||
setContentError(error);
|
||||
} finally {
|
||||
@@ -67,44 +52,31 @@ function Host({ inventory, i18n, setBreadcrumb }) {
|
||||
const tabsArray = [
|
||||
{
|
||||
name: i18n._(t`Details`),
|
||||
link: `${baseUrl}/details`,
|
||||
link: `${match.url}/details`,
|
||||
id: 0,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Facts`),
|
||||
link: `${baseUrl}/facts`,
|
||||
link: `${match.url}/facts`,
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Groups`),
|
||||
link: `${baseUrl}/groups`,
|
||||
link: `${match.url}/groups`,
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Completed Jobs`),
|
||||
link: `${baseUrl}/completed_jobs`,
|
||||
link: `${match.url}/completed_jobs`,
|
||||
id: 3,
|
||||
},
|
||||
];
|
||||
|
||||
if (inventoriesMatch) {
|
||||
tabsArray.unshift({
|
||||
name: (
|
||||
<>
|
||||
<CaretLeftIcon />
|
||||
{i18n._(t`Back to Hosts`)}
|
||||
</>
|
||||
),
|
||||
link: hostListUrl,
|
||||
id: 99,
|
||||
});
|
||||
}
|
||||
|
||||
let cardHeader = (
|
||||
<TabbedCardHeader>
|
||||
<RoutedTabs tabsArray={tabsArray} />
|
||||
<CardActions>
|
||||
<CardCloseButton linkTo={hostListUrl} />
|
||||
<CardCloseButton linkTo="/hosts" />
|
||||
</CardActions>
|
||||
</TabbedCardHeader>
|
||||
);
|
||||
@@ -124,7 +96,7 @@ function Host({ inventory, i18n, setBreadcrumb }) {
|
||||
{contentError.response && contentError.response.status === 404 && (
|
||||
<span>
|
||||
{i18n._(`Host not found.`)}{' '}
|
||||
<Link to={hostListUrl}>{i18n._(`View all Hosts.`)}</Link>
|
||||
<Link to="/hosts">{i18n._(`View all Hosts.`)}</Link>
|
||||
</span>
|
||||
)}
|
||||
</ContentError>
|
||||
@@ -132,72 +104,35 @@ function Host({ inventory, i18n, setBreadcrumb }) {
|
||||
);
|
||||
}
|
||||
|
||||
const redirect = hostsMatch ? (
|
||||
<Redirect from="/hosts/:id" to="/hosts/:id/details" exact />
|
||||
) : (
|
||||
<Redirect
|
||||
from="/inventories/inventory/:id/hosts/:hostId"
|
||||
to="/inventories/inventory/:id/hosts/:hostId/details"
|
||||
exact
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{cardHeader}
|
||||
<Switch>
|
||||
{redirect}
|
||||
{host && (
|
||||
<Route
|
||||
path={[
|
||||
'/hosts/:id/details',
|
||||
'/inventories/inventory/:id/hosts/:hostId/details',
|
||||
]}
|
||||
>
|
||||
<HostDetail
|
||||
host={host}
|
||||
onUpdateHost={newHost => setHost(newHost)}
|
||||
/>
|
||||
</Route>
|
||||
)}
|
||||
{host && (
|
||||
<Route
|
||||
path={[
|
||||
'/hosts/:id/edit',
|
||||
'/inventories/inventory/:id/hosts/:hostId/edit',
|
||||
]}
|
||||
render={() => <HostEdit host={host} />}
|
||||
/>
|
||||
)}
|
||||
{host && (
|
||||
<Route
|
||||
path="/hosts/:id/facts"
|
||||
render={() => <HostFacts host={host} />}
|
||||
/>
|
||||
)}
|
||||
{host && (
|
||||
<Route
|
||||
path="/hosts/:id/groups"
|
||||
render={() => <HostGroups host={host} />}
|
||||
/>
|
||||
)}
|
||||
{host?.id && (
|
||||
<Route
|
||||
path={[
|
||||
'/hosts/:id/completed_jobs',
|
||||
'/inventories/inventory/:id/hosts/:hostId/completed_jobs',
|
||||
]}
|
||||
>
|
||||
<Redirect from="/hosts/:id" to="/hosts/:id/details" exact />
|
||||
{host && [
|
||||
<Route path="/hosts/:id/details" key="details">
|
||||
<HostDetail host={host} />
|
||||
</Route>,
|
||||
<Route path="/hosts/:id/edit" key="edit">
|
||||
<HostEdit host={host} />
|
||||
</Route>,
|
||||
<Route path="/hosts/:id/facts" key="facts">
|
||||
<HostFacts host={host} />
|
||||
</Route>,
|
||||
<Route path="/hosts/:id/groups" key="groups">
|
||||
<HostGroups host={host} />
|
||||
</Route>,
|
||||
<Route path="/hosts/:id/completed_jobs" key="completed-jobs">
|
||||
<JobList defaultParams={{ job__hosts: host.id }} />
|
||||
</Route>
|
||||
)}
|
||||
</Route>,
|
||||
]}
|
||||
<Route
|
||||
key="not-found"
|
||||
path="*"
|
||||
render={() =>
|
||||
!hasContentLoading && (
|
||||
<ContentError isNotFound>
|
||||
<Link to={`${baseUrl}/details`}>
|
||||
<Link to={`${match.url}/details`}>
|
||||
{i18n._(`View Host Details`)}
|
||||
</Link>
|
||||
</ContentError>
|
||||
|
||||
@@ -3,53 +3,41 @@ import { act } from 'react-dom/test-utils';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { HostsAPI } from '@api';
|
||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||
import mockDetails from './data.host.json';
|
||||
import mockHost from './data.host.json';
|
||||
import Host from './Host';
|
||||
|
||||
jest.mock('@api');
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useRouteMatch: () => ({
|
||||
url: '/hosts/1',
|
||||
params: { id: 1 },
|
||||
}),
|
||||
}));
|
||||
|
||||
HostsAPI.readDetail.mockResolvedValue({
|
||||
data: { ...mockHost },
|
||||
});
|
||||
|
||||
describe('<Host />', () => {
|
||||
let wrapper;
|
||||
let history;
|
||||
|
||||
HostsAPI.readDetail.mockResolvedValue({
|
||||
data: { ...mockDetails },
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<Host setBreadcrumb={() => {}} />);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('initially renders succesfully', async () => {
|
||||
history = createMemoryHistory({
|
||||
initialEntries: ['/hosts/1/edit'],
|
||||
test('should render expected tabs', async () => {
|
||||
const expectedTabs = ['Details', 'Facts', 'Groups', 'Completed Jobs'];
|
||||
wrapper.find('RoutedTabs li').forEach((tab, index) => {
|
||||
expect(tab.text()).toEqual(expectedTabs[index]);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<Host setBreadcrumb={() => {}} />, {
|
||||
context: { router: { history } },
|
||||
});
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
expect(wrapper.find('Host').length).toBe(1);
|
||||
});
|
||||
|
||||
test('should render "Back to Hosts" tab when navigating from inventories', async () => {
|
||||
history = createMemoryHistory({
|
||||
initialEntries: ['/inventories/inventory/1/hosts/1'],
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<Host setBreadcrumb={() => {}} />, {
|
||||
context: { router: { history } },
|
||||
});
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
expect(
|
||||
wrapper
|
||||
.find('RoutedTabs li')
|
||||
.first()
|
||||
.text()
|
||||
).toBe('Back to Hosts');
|
||||
});
|
||||
|
||||
test('should show content error when api throws error on initial render', async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useHistory, useParams, useLocation } from 'react-router-dom';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Host } from '@types';
|
||||
@@ -14,42 +14,36 @@ import DeleteButton from '@components/DeleteButton';
|
||||
import { HostsAPI } from '@api';
|
||||
import HostToggle from '@components/HostToggle';
|
||||
|
||||
function HostDetail({ host, i18n, onUpdateHost }) {
|
||||
function HostDetail({ i18n, host }) {
|
||||
const {
|
||||
created,
|
||||
description,
|
||||
id,
|
||||
modified,
|
||||
name,
|
||||
variables,
|
||||
summary_fields: {
|
||||
inventory,
|
||||
recent_jobs,
|
||||
kind,
|
||||
created_by,
|
||||
modified_by,
|
||||
user_capabilities,
|
||||
},
|
||||
} = host;
|
||||
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const { id: inventoryId, hostId: inventoryHostId } = useParams();
|
||||
const [isLoading, setIsloading] = useState(false);
|
||||
const [deletionError, setDeletionError] = useState(false);
|
||||
|
||||
const recentPlaybookJobs = recent_jobs.map(job => ({ ...job, type: 'job' }));
|
||||
const history = useHistory();
|
||||
|
||||
const handleHostDelete = async () => {
|
||||
setIsloading(true);
|
||||
try {
|
||||
await HostsAPI.destroy(id);
|
||||
setIsloading(false);
|
||||
const url = pathname.startsWith('/inventories')
|
||||
? `/inventories/inventory/${inventoryId}/hosts/`
|
||||
: `/hosts`;
|
||||
history.push(url);
|
||||
history.push('/hosts');
|
||||
} catch (err) {
|
||||
setDeletionError(err);
|
||||
} finally {
|
||||
setIsloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -66,77 +60,71 @@ function HostDetail({ host, i18n, onUpdateHost }) {
|
||||
</AlertModal>
|
||||
);
|
||||
}
|
||||
|
||||
const recentPlaybookJobs = recent_jobs.map(job => ({ ...job, type: 'job' }));
|
||||
return (
|
||||
<CardBody>
|
||||
<HostToggle
|
||||
host={host}
|
||||
onToggle={enabled =>
|
||||
onUpdateHost({
|
||||
...host,
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
css="padding-bottom: 40px"
|
||||
/>
|
||||
<HostToggle host={host} css="padding-bottom: 40px" />
|
||||
<DetailList gutter="sm">
|
||||
<Detail label={i18n._(t`Name`)} value={name} />
|
||||
<Detail
|
||||
value={<Sparkline jobs={recentPlaybookJobs} />}
|
||||
label={i18n._(t`Activity`)}
|
||||
value={<Sparkline jobs={recentPlaybookJobs} />}
|
||||
/>
|
||||
<Detail label={i18n._(t`Description`)} value={description} />
|
||||
{inventory && (
|
||||
<Detail
|
||||
label={i18n._(t`Inventory`)}
|
||||
value={
|
||||
<Link
|
||||
to={`/inventories/${
|
||||
kind === 'smart' ? 'smart_inventory' : 'inventory'
|
||||
}/${inventoryId}/details`}
|
||||
>
|
||||
{inventory.name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Detail
|
||||
label={i18n._(t`Inventory`)}
|
||||
value={
|
||||
<Link to={`/inventories/inventory/${inventory.id}/details`}>
|
||||
{inventory.name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
<UserDateDetail
|
||||
date={created}
|
||||
label={i18n._(t`Created`)}
|
||||
user={created_by}
|
||||
/>
|
||||
<UserDateDetail
|
||||
date={modified}
|
||||
label={i18n._(t`Last Modified`)}
|
||||
user={modified_by}
|
||||
date={modified}
|
||||
/>
|
||||
<VariablesDetail
|
||||
value={host.variables}
|
||||
rows={4}
|
||||
label={i18n._(t`Variables`)}
|
||||
rows={4}
|
||||
value={variables}
|
||||
/>
|
||||
</DetailList>
|
||||
<CardActionsRow>
|
||||
{user_capabilities && user_capabilities.edit && (
|
||||
{user_capabilities?.edit && (
|
||||
<Button
|
||||
aria-label={i18n._(t`edit`)}
|
||||
component={Link}
|
||||
to={
|
||||
pathname.startsWith('/inventories')
|
||||
? `/inventories/inventory/${inventoryId}/hosts/${inventoryHostId}/edit`
|
||||
: `/hosts/${id}/edit`
|
||||
}
|
||||
to={`/hosts/${id}/edit`}
|
||||
>
|
||||
{i18n._(t`Edit`)}
|
||||
</Button>
|
||||
)}
|
||||
{user_capabilities && user_capabilities.delete && (
|
||||
{user_capabilities?.delete && (
|
||||
<DeleteButton
|
||||
onConfirm={() => handleHostDelete()}
|
||||
modalTitle={i18n._(t`Delete Host`)}
|
||||
name={host.name}
|
||||
name={name}
|
||||
/>
|
||||
)}
|
||||
</CardActionsRow>
|
||||
{deletionError && (
|
||||
<AlertModal
|
||||
isOpen={deletionError}
|
||||
variant="error"
|
||||
title={i18n._(t`Error!`)}
|
||||
onClose={() => setDeletionError(null)}
|
||||
>
|
||||
{i18n._(t`Failed to delete host.`)}
|
||||
<ErrorDetail error={deletionError} />
|
||||
</AlertModal>
|
||||
)}
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,88 @@
|
||||
import React from 'react';
|
||||
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||
|
||||
import HostDetail from './HostDetail';
|
||||
import { HostsAPI } from '@api';
|
||||
|
||||
import mockHost from '../data.host.json';
|
||||
|
||||
jest.mock('@api');
|
||||
|
||||
describe('<HostDetail />', () => {
|
||||
const mockHost = {
|
||||
id: 1,
|
||||
name: 'Foo',
|
||||
description: 'Bar',
|
||||
inventory: 1,
|
||||
created: '2015-07-07T17:21:26.429745Z',
|
||||
modified: '2019-08-11T19:47:37.980466Z',
|
||||
variables: '---',
|
||||
summary_fields: {
|
||||
inventory: {
|
||||
id: 1,
|
||||
name: 'test inventory',
|
||||
},
|
||||
user_capabilities: {
|
||||
edit: true,
|
||||
},
|
||||
recent_jobs: [],
|
||||
},
|
||||
};
|
||||
let wrapper;
|
||||
|
||||
test('initially renders succesfully', () => {
|
||||
mountWithContexts(<HostDetail host={mockHost} />);
|
||||
describe('User has edit permissions', () => {
|
||||
beforeAll(() => {
|
||||
wrapper = mountWithContexts(<HostDetail host={mockHost} />);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render Details', async () => {
|
||||
function assertDetail(label, value) {
|
||||
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
|
||||
expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
|
||||
}
|
||||
|
||||
assertDetail('Name', 'localhost');
|
||||
assertDetail('Description', 'a good description');
|
||||
assertDetail('Inventory', 'Mikes Inventory');
|
||||
assertDetail('Created', '10/28/2019, 9:26:54 PM');
|
||||
assertDetail('Last Modified', '10/29/2019, 8:18:41 PM');
|
||||
});
|
||||
|
||||
test('should show edit button for users with edit permission', () => {
|
||||
const editButton = wrapper.find('Button[aria-label="edit"]');
|
||||
expect(editButton.text()).toEqual('Edit');
|
||||
expect(editButton.prop('to')).toBe('/hosts/2/edit');
|
||||
});
|
||||
|
||||
test('expected api call is made for delete', async () => {
|
||||
await act(async () => {
|
||||
wrapper.find('DeleteButton').invoke('onConfirm')();
|
||||
});
|
||||
expect(HostsAPI.destroy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Error dialog shown for failed deletion', async () => {
|
||||
HostsAPI.destroy.mockImplementationOnce(() =>
|
||||
Promise.reject(new Error())
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper.find('DeleteButton').invoke('onConfirm')();
|
||||
});
|
||||
await waitForElement(
|
||||
wrapper,
|
||||
'Modal[title="Error!"]',
|
||||
el => el.length === 1
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper.find('Modal[title="Error!"]').invoke('onClose')();
|
||||
});
|
||||
await waitForElement(
|
||||
wrapper,
|
||||
'Modal[title="Error!"]',
|
||||
el => el.length === 0
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('should render Details', async () => {
|
||||
const wrapper = mountWithContexts(<HostDetail host={mockHost} />);
|
||||
const testParams = [
|
||||
{ label: 'Name', value: 'Foo' },
|
||||
{ label: 'Description', value: 'Bar' },
|
||||
{ label: 'Inventory', value: 'test inventory' },
|
||||
{ label: 'Created', value: '7/7/2015, 5:21:26 PM' },
|
||||
{ label: 'Last Modified', value: '8/11/2019, 7:47:37 PM' },
|
||||
];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const { label, value } of testParams) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const detail = await waitForElement(wrapper, `Detail[label="${label}"]`);
|
||||
expect(detail.find('dt').text()).toBe(label);
|
||||
expect(detail.find('dd').text()).toBe(value);
|
||||
}
|
||||
});
|
||||
describe('User has read-only permissions', () => {
|
||||
beforeAll(() => {
|
||||
const readOnlyHost = { ...mockHost };
|
||||
readOnlyHost.summary_fields.user_capabilities.edit = false;
|
||||
|
||||
test('should show edit button for users with edit permission', async () => {
|
||||
const wrapper = mountWithContexts(<HostDetail host={mockHost} />);
|
||||
const editButton = wrapper.find('Button[aria-label="edit"]');
|
||||
expect(editButton.text()).toEqual('Edit');
|
||||
expect(editButton.prop('to')).toBe('/hosts/1/edit');
|
||||
});
|
||||
wrapper = mountWithContexts(<HostDetail host={mockHost} />);
|
||||
});
|
||||
|
||||
test('should hide edit button for users without edit permission', async () => {
|
||||
const readOnlyHost = { ...mockHost };
|
||||
readOnlyHost.summary_fields.user_capabilities.edit = false;
|
||||
const wrapper = mountWithContexts(<HostDetail host={readOnlyHost} />);
|
||||
await waitForElement(wrapper, 'HostDetail');
|
||||
expect(wrapper.find('Button[aria-label="edit"]').length).toBe(0);
|
||||
afterAll(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should hide edit button for users without edit permission', async () => {
|
||||
expect(wrapper.find('Button[aria-label="edit"]').length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,31 +1,14 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { CardBody } from '@components/Card';
|
||||
import HostForm from '@components/HostForm';
|
||||
import { HostsAPI } from '@api';
|
||||
import HostForm from '../shared';
|
||||
|
||||
function HostEdit({ host }) {
|
||||
const [formError, setFormError] = useState(null);
|
||||
const hostsMatch = useRouteMatch('/hosts/:id/edit');
|
||||
const inventoriesMatch = useRouteMatch(
|
||||
'/inventories/inventory/:id/hosts/:hostId/edit'
|
||||
);
|
||||
const detailsUrl = `/hosts/${host.id}/details`;
|
||||
const history = useHistory();
|
||||
let detailsUrl;
|
||||
|
||||
if (hostsMatch) {
|
||||
detailsUrl = `/hosts/${hostsMatch.params.id}/details`;
|
||||
}
|
||||
|
||||
if (inventoriesMatch) {
|
||||
const kind =
|
||||
host.summary_fields.inventory.kind === 'smart'
|
||||
? 'smart_inventory'
|
||||
: 'inventory';
|
||||
detailsUrl = `/inventories/${kind}/${inventoriesMatch.params.id}/hosts/${inventoriesMatch.params.hostId}/details`;
|
||||
}
|
||||
|
||||
const handleSubmit = async values => {
|
||||
try {
|
||||
|
||||
@@ -1,49 +1,70 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { HostsAPI } from '@api';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import mockHost from '../data.host.json';
|
||||
import HostEdit from './HostEdit';
|
||||
|
||||
jest.mock('@api');
|
||||
|
||||
describe('<HostEdit />', () => {
|
||||
const mockData = {
|
||||
id: 1,
|
||||
name: 'Foo',
|
||||
description: 'Bar',
|
||||
inventory: 1,
|
||||
variables: '---',
|
||||
summary_fields: {
|
||||
inventory: {
|
||||
id: 1,
|
||||
name: 'test inventory',
|
||||
},
|
||||
},
|
||||
let wrapper;
|
||||
let history;
|
||||
|
||||
const updatedHostData = {
|
||||
name: 'new name',
|
||||
description: 'new description',
|
||||
variables: '---\nfoo: bar',
|
||||
};
|
||||
|
||||
test('handleSubmit should call api update', () => {
|
||||
const wrapper = mountWithContexts(<HostEdit host={mockData} />);
|
||||
|
||||
const updatedHostData = {
|
||||
name: 'new name',
|
||||
description: 'new description',
|
||||
variables: '---\nfoo: bar',
|
||||
};
|
||||
wrapper.find('HostForm').prop('handleSubmit')(updatedHostData);
|
||||
|
||||
expect(HostsAPI.update).toHaveBeenCalledWith(1, updatedHostData);
|
||||
beforeAll(async () => {
|
||||
history = createMemoryHistory();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<HostEdit host={mockHost} />, {
|
||||
context: { router: { history } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should navigate to host detail when cancel is clicked', () => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/hosts/1/edit'],
|
||||
});
|
||||
const wrapper = mountWithContexts(<HostEdit host={mockData} />, {
|
||||
context: { router: { history } },
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
|
||||
test('handleSubmit should call api update', async () => {
|
||||
await act(async () => {
|
||||
wrapper.find('HostForm').prop('handleSubmit')(updatedHostData);
|
||||
});
|
||||
expect(HostsAPI.update).toHaveBeenCalledWith(2, updatedHostData);
|
||||
});
|
||||
|
||||
expect(history.location.pathname).toEqual('/hosts/1/details');
|
||||
test('should navigate to host detail when cancel is clicked', async () => {
|
||||
await act(async () => {
|
||||
wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
|
||||
});
|
||||
expect(history.location.pathname).toEqual('/hosts/2/details');
|
||||
});
|
||||
|
||||
test('should navigate to host detail after successful submission', async () => {
|
||||
await act(async () => {
|
||||
wrapper.find('HostForm').invoke('handleSubmit')(updatedHostData);
|
||||
});
|
||||
expect(wrapper.find('FormSubmitError').length).toBe(0);
|
||||
expect(history.location.pathname).toEqual('/hosts/2/details');
|
||||
});
|
||||
|
||||
test('failed form submission should show an error message', async () => {
|
||||
const error = {
|
||||
response: {
|
||||
data: { detail: 'An error occurred' },
|
||||
},
|
||||
};
|
||||
HostsAPI.update.mockImplementationOnce(() => Promise.reject(error));
|
||||
await act(async () => {
|
||||
wrapper.find('HostForm').invoke('handleSubmit')(mockHost);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('FormSubmitError').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,18 +51,6 @@
|
||||
"id": 1,
|
||||
"failed": false
|
||||
},
|
||||
"created_by": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"first_name": "",
|
||||
"last_name": ""
|
||||
},
|
||||
"modified_by": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"first_name": "",
|
||||
"last_name": ""
|
||||
},
|
||||
"user_capabilities": {
|
||||
"edit": true,
|
||||
"delete": true
|
||||
|
||||
Reference in New Issue
Block a user