Improves Tests and addresses other PR Issues

This commit is contained in:
Alex Corey
2019-11-21 13:29:12 -05:00
parent 19180a1bc4
commit 83caf99c58
6 changed files with 162 additions and 100 deletions

View File

@@ -43,20 +43,29 @@ function InventoryAdd({ history, i18n }) {
}; };
const handleSubmit = async values => { const handleSubmit = async values => {
const {
instanceGroups,
organization,
insights_credential,
...remainingValues
} = values;
try { try {
let response; const {
if (values.instance_groups) { data: { id: inventoryId },
response = await InventoriesAPI.create(values); } = await InventoriesAPI.create({
const associatePromises = values.instance_groups.map(async ig => organization: organization.id,
InventoriesAPI.associateInstanceGroup(response.data.id, ig.id) insights_credential: insights_credential.id,
...remainingValues,
});
if (instanceGroups) {
const associatePromises = instanceGroups.map(async ig =>
InventoriesAPI.associateInstanceGroup(inventoryId, ig.id)
); );
await Promise.all([response, ...associatePromises]); await Promise.all(associatePromises);
} else {
response = await InventoriesAPI.create(values);
} }
const url = history.location.pathname.search('smart') const url = history.location.pathname.search('smart')
? `/inventories/smart_inventory/${response.data.id}/details` ? `/inventories/smart_inventory/${inventoryId}/details`
: `/inventories/inventory/${response.data.id}/details`; : `/inventories/inventory/${inventoryId}/details`;
history.push(`${url}`); history.push(`${url}`);
} catch (err) { } catch (err) {
@@ -75,11 +84,11 @@ function InventoryAdd({ history, i18n }) {
<Card> <Card>
<CardHeader <CardHeader
style={{ style={{
'padding-right': '10px', paddingRight: '10px',
'padding-top': '10px', paddingTop: '10px',
'padding-bottom': '0', paddingBottom: '0',
textAlign: 'right',
}} }}
className="at-u-textRight"
> >
<Tooltip content={i18n._(t`Close`)} position="top"> <Tooltip content={i18n._(t`Close`)} position="top">
<CardCloseButton onClick={handleCancel} /> <CardCloseButton onClick={handleCancel} />
@@ -87,8 +96,8 @@ function InventoryAdd({ history, i18n }) {
</CardHeader> </CardHeader>
<CardBody> <CardBody>
<InventoryForm <InventoryForm
handleCancel={handleCancel} onCancel={handleCancel}
handleSubmit={handleSubmit} onSubmit={handleSubmit}
credentialTypeId={credentialTypeId} credentialTypeId={credentialTypeId}
/> />
</CardBody> </CardBody>

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history'; import { createMemoryHistory } from 'history';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import { sleep } from '@testUtils/testUtils';
import { InventoriesAPI, CredentialTypesAPI } from '@api'; import { InventoriesAPI, CredentialTypesAPI } from '@api';
import InventoryAdd from './InventoryAdd'; import InventoryAdd from './InventoryAdd';
@@ -18,6 +19,7 @@ CredentialTypesAPI.read.mockResolvedValue({
], ],
}, },
}); });
InventoriesAPI.create.mockResolvedValue({ data: { id: 13 } });
describe('<InventoryAdd />', () => { describe('<InventoryAdd />', () => {
let wrapper; let wrapper;
@@ -39,18 +41,27 @@ describe('<InventoryAdd />', () => {
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
}); });
test('handleSubmit should call the api', async () => { test('handleSubmit should call the api', async () => {
const instanceGroups = [{ name: 'Bizz', id: 1 }, { name: 'Buzz', id: 2 }];
await waitForElement(wrapper, 'isLoading', el => el.length === 0); await waitForElement(wrapper, 'isLoading', el => el.length === 0);
wrapper.update(); wrapper.find('InventoryForm').prop('onSubmit')({
await act(async () => { name: 'new Foo',
wrapper.find('InventoryForm').prop('handleSubmit')({ organization: { id: 2 },
name: 'Foo', insights_credential: { id: 47 },
id: 1, instanceGroups,
organization: 2,
});
}); });
await sleep(1);
expect(InventoriesAPI.create).toHaveBeenCalledTimes(1); expect(InventoriesAPI.create).toHaveBeenCalledWith({
name: 'new Foo',
organization: 2,
insights_credential: 47,
});
instanceGroups.map(IG =>
expect(InventoriesAPI.associateInstanceGroup).toHaveBeenCalledWith(
13,
IG.id
)
);
}); });
test('handleCancel should return the user back to the inventories list', async () => { test('handleCancel should return the user back to the inventories list', async () => {
await waitForElement(wrapper, 'isLoading', el => el.length === 0); await waitForElement(wrapper, 'isLoading', el => el.length === 0);

View File

@@ -14,7 +14,7 @@ import { getAddedAndRemoved } from '../../../util/lists';
function InventoryEdit({ history, i18n, inventory }) { function InventoryEdit({ history, i18n, inventory }) {
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [instanceGroups, setInstanceGroups] = useState(null); const [associatedInstanceGroups, setInstanceGroups] = useState(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [credentialTypeId, setCredentialTypeId] = useState(null); const [credentialTypeId, setCredentialTypeId] = useState(null);
@@ -50,26 +50,31 @@ function InventoryEdit({ history, i18n, inventory }) {
}; };
const handleSubmit = async values => { const handleSubmit = async values => {
const {
instanceGroups,
insights_credential,
organization,
...remainingValues
} = values;
try { try {
if (values.instance_groups) { await InventoriesAPI.update(inventory.id, {
insights_credential: insights_credential.id,
organization: organization.id,
...remainingValues,
});
if (instanceGroups) {
const { added, removed } = getAddedAndRemoved( const { added, removed } = getAddedAndRemoved(
instanceGroups, associatedInstanceGroups,
values.instance_groups instanceGroups
); );
const update = InventoriesAPI.update(inventory.id, values);
const associatePromises = added.map(async ig => const associatePromises = added.map(async ig =>
InventoriesAPI.associateInstanceGroup(inventory.id, ig.id) InventoriesAPI.associateInstanceGroup(inventory.id, ig.id)
); );
const disAssociatePromises = removed.map(async ig => const disassociatePromises = removed.map(async ig =>
InventoriesAPI.disassociateInstanceGroup(inventory.id, ig.id) InventoriesAPI.disassociateInstanceGroup(inventory.id, ig.id)
); );
await Promise.all([ await Promise.all([...associatePromises, ...disassociatePromises]);
update,
...associatePromises,
...disAssociatePromises,
]);
} else {
await InventoriesAPI.update(inventory.id, values);
} }
} catch (err) { } catch (err) {
setError(err); setError(err);
@@ -90,11 +95,11 @@ function InventoryEdit({ history, i18n, inventory }) {
<> <>
<CardHeader <CardHeader
style={{ style={{
'padding-right': '10px', paddingRight: '10px',
'padding-top': '10px', paddingTop: '10px',
'padding-bottom': '0', paddingBottom: '0',
textAlign: 'right',
}} }}
className="at-u-textRight"
> >
<Tooltip content={i18n._(t`Close`)} position="top"> <Tooltip content={i18n._(t`Close`)} position="top">
<CardCloseButton onClick={handleCancel} /> <CardCloseButton onClick={handleCancel} />
@@ -102,10 +107,10 @@ function InventoryEdit({ history, i18n, inventory }) {
</CardHeader> </CardHeader>
<CardBody> <CardBody>
<InventoryForm <InventoryForm
handleCancel={handleCancel} onCancel={handleCancel}
handleSubmit={handleSubmit} onSubmit={handleSubmit}
inventory={inventory} inventory={inventory}
instanceGroups={instanceGroups} instanceGroups={associatedInstanceGroups}
credentialTypeId={credentialTypeId} credentialTypeId={credentialTypeId}
/> />
</CardBody> </CardBody>

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history'; import { createMemoryHistory } from 'history';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import { sleep } from '@testUtils/testUtils';
import { InventoriesAPI, CredentialTypesAPI } from '@api'; import { InventoriesAPI, CredentialTypesAPI } from '@api';
import InventoryEdit from './InventoryEdit'; import InventoryEdit from './InventoryEdit';
@@ -59,15 +60,15 @@ CredentialTypesAPI.read.mockResolvedValue({
], ],
}, },
}); });
const associatedInstanceGroups = [
{
id: 1,
name: 'Foo',
},
];
InventoriesAPI.readInstanceGroups.mockResolvedValue({ InventoriesAPI.readInstanceGroups.mockResolvedValue({
data: { data: {
results: [ results: associatedInstanceGroups,
{
id: 1,
name: 'Foo',
},
],
}, },
}); });
@@ -89,24 +90,39 @@ describe('<InventoryEdit />', () => {
test('initially renders successfully', async () => { test('initially renders successfully', async () => {
expect(wrapper.find('InventoryEdit').length).toBe(1); expect(wrapper.find('InventoryEdit').length).toBe(1);
}); });
test('called InventoriesAPI.readInstanceGroups', async () => { test('called InventoriesAPI.readInstanceGroups', async () => {
expect(InventoriesAPI.readInstanceGroups).toBeCalledWith(1); expect(InventoriesAPI.readInstanceGroups).toBeCalledWith(1);
await waitForElement(wrapper, 'isLoading', el => el.length === 0);
}); });
test('handleCancel returns the user to the inventories list', async () => { test('handleCancel returns the user to the inventories list', async () => {
await waitForElement(wrapper, 'isLoading', el => el.length === 0); await waitForElement(wrapper, 'isLoading', el => el.length === 0);
wrapper.find('CardCloseButton').simulate('click'); wrapper.find('CardCloseButton').simulate('click');
expect(history.location.pathname).toEqual('/inventories'); expect(history.location.pathname).toEqual('/inventories');
}); });
test('handleSubmit should post to the api', async () => { test('handleSubmit should post to the api', async () => {
await waitForElement(wrapper, 'isLoading', el => el.length === 0); await waitForElement(wrapper, 'isLoading', el => el.length === 0);
wrapper.find('InventoryForm').prop('handleSubmit')({ const instanceGroups = [{ name: 'Bizz', id: 2 }, { name: 'Buzz', id: 3 }];
wrapper.find('InventoryForm').prop('onSubmit')({
name: 'Foo', name: 'Foo',
id: 1, id: 13,
organization: 2, organization: { id: 1 },
insights_credential: { id: 13 },
instanceGroups,
}); });
wrapper.update(); await sleep(0);
instanceGroups.map(IG =>
expect(InventoriesAPI.update).toHaveBeenCalledTimes(1); expect(InventoriesAPI.associateInstanceGroup).toHaveBeenCalledWith(
1,
IG.id
)
);
associatedInstanceGroups.map(async aIG =>
expect(InventoriesAPI.disassociateInstanceGroup).toHaveBeenCalledWith(
1,
aIG.id
)
);
}); });
}); });

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React from 'react';
import { Formik, Field } from 'formik'; import { Formik, Field } from 'formik';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -17,33 +17,29 @@ import CredentialLookup from '@components/Lookup/CredentialLookup';
function InventoryForm({ function InventoryForm({
inventory = {}, inventory = {},
i18n, i18n,
handleCancel, onCancel,
handleSubmit, onSubmit,
instanceGroups, instanceGroups,
credentialTypeId, credentialTypeId,
}) { }) {
const [organization, setOrganization] = useState(
inventory.summary_fields ? inventory.summary_fields.organization : null
);
const [insights_credential, setInsights_Credential] = useState(
inventory.summary_fields
? inventory.summary_fields.insights_credential
: null
);
const initialValues = { const initialValues = {
name: inventory.name || '', name: inventory.name || '',
description: inventory.description || '', description: inventory.description || '',
variables: inventory.variables || '---', variables: inventory.variables || '---',
organization: organization ? organization.id : null, organization:
instance_groups: instanceGroups || [], (inventory.summary_fields && inventory.summary_fields.organization) ||
insights_credential: insights_credential ? insights_credential.id : '', null,
instanceGroups: instanceGroups || [],
insights_credential:
(inventory.summary_fields &&
inventory.summary_fields.insights_credential) ||
null,
}; };
return ( return (
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
onSubmit={values => { onSubmit={values => {
handleSubmit(values); onSubmit(values);
}} }}
render={formik => ( render={formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}> <Form autoComplete="off" onSubmit={formik.handleSubmit}>
@@ -70,7 +66,7 @@ function InventoryForm({
i18n._(t`Select a value for this field`), i18n._(t`Select a value for this field`),
i18n i18n
)} )}
render={({ form }) => ( render={({ form, field }) => (
<OrganizationLookup <OrganizationLookup
helperTextInvalid={form.errors.organization} helperTextInvalid={form.errors.organization}
isValid={ isValid={
@@ -78,29 +74,30 @@ function InventoryForm({
} }
onBlur={() => form.setFieldTouched('organization')} onBlur={() => form.setFieldTouched('organization')}
onChange={value => { onChange={value => {
form.setFieldValue('organization', value.id); form.setFieldValue('organization', value);
setOrganization(value);
}} }}
value={organization} value={field.value}
required required
/> />
)} )}
/> />
</FormRow>
<FormRow>
<Field <Field
id="inventory-insights_credential" id="inventory-insights_credential"
label={i18n._(t`Insights Credential`)} label={i18n._(t`Insights Credential`)}
name="insights_credential" name="insights_credential"
render={({ form }) => ( render={({ field, form }) => (
<CredentialLookup <CredentialLookup
label={i18n._(t`Insights Credential`)} label={i18n._(t`Insights Credential`)}
credentialTypeId={credentialTypeId} credentialTypeId={credentialTypeId}
onChange={value => { onChange={value => {
form.setFieldValue('insights_credential', value.id); // TODO: BELOW SHOULD BE REFACTORED AND REMOVED ONCE THE LOOKUP REFACTOR
setInsights_Credential(value); // GOES INTO PLACE.
if (value[0] === field.value) {
return form.setFieldValue('insights_credential', null);
}
return form.setFieldValue('insights_credential', value);
}} }}
value={insights_credential} value={field.value}
/> />
)} )}
/> />
@@ -109,12 +106,12 @@ function InventoryForm({
<Field <Field
id="inventory-instanceGroups" id="inventory-instanceGroups"
label={i18n._(t`Instance Groups`)} label={i18n._(t`Instance Groups`)}
name="instance_groups" name="instanceGroups"
render={({ field, form }) => ( render={({ field, form }) => (
<InstanceGroupsLookup <InstanceGroupsLookup
value={field.value} value={field.value}
onChange={value => { onChange={value => {
form.setFieldValue('instance_groups', value); form.setFieldValue('instanceGroups', value);
}} }}
/> />
)} )}
@@ -132,7 +129,7 @@ function InventoryForm({
</FormRow> </FormRow>
<FormRow> <FormRow>
<FormActionGroup <FormActionGroup
onCancel={handleCancel} onCancel={onCancel}
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
/> />
</FormRow> </FormRow>
@@ -147,11 +144,10 @@ InventoryForm.proptype = {
handleCancel: func.isRequired, handleCancel: func.isRequired,
instanceGroups: shape(), instanceGroups: shape(),
inventory: shape(), inventory: shape(),
credentialTypeId: number, credentialTypeId: number.isRequired,
}; };
InventoryForm.defaultProps = { InventoryForm.defaultProps = {
credentialTypeId: 14,
inventory: {}, inventory: {},
instanceGroups: [], instanceGroups: [],
}; };

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '@testUtils/enzymeHelpers'; import { mountWithContexts } from '@testUtils/enzymeHelpers';
import { sleep } from '@testUtils/testUtils';
import InventoryForm from './InventoryForm'; import InventoryForm from './InventoryForm';
@@ -48,15 +49,18 @@ const inventory = {
const instanceGroups = [{ name: 'Foo', id: 1 }, { name: 'Bar', id: 2 }]; const instanceGroups = [{ name: 'Foo', id: 1 }, { name: 'Bar', id: 2 }];
describe('<InventoryForm />', () => { describe('<InventoryForm />', () => {
let wrapper; let wrapper;
let handleCancel; let onCancel;
let onSubmit;
beforeEach(() => { beforeEach(() => {
handleCancel = jest.fn(); onCancel = jest.fn();
onSubmit = jest.fn();
wrapper = mountWithContexts( wrapper = mountWithContexts(
<InventoryForm <InventoryForm
handleCancel={handleCancel} onCancel={onCancel}
handleSubmit={jest.fn()} onSubmit={onSubmit}
inventory={inventory} inventory={inventory}
instanceGroups={instanceGroups} instanceGroups={instanceGroups}
credentialTypeId={14}
/> />
); );
}); });
@@ -76,16 +80,23 @@ describe('<InventoryForm />', () => {
); );
expect(wrapper.find('VariablesField[label="Variables"]').length).toBe(1); expect(wrapper.find('VariablesField[label="Variables"]').length).toBe(1);
}); });
test('should update from values onChange', () => { test('should update from values onChange', async () => {
const form = wrapper.find('Formik'); const form = wrapper.find('Formik');
act(() => { act(() => {
wrapper.find('OrganizationLookup').invoke('onBlur')(); wrapper.find('OrganizationLookup').invoke('onBlur')();
wrapper.find('OrganizationLookup').invoke('onChange')({ wrapper.find('OrganizationLookup').invoke('onChange')({
id: 1, id: 3,
name: 'organization', name: 'organization',
}); });
}); });
expect(form.state('values').organization).toEqual(1); expect(form.state('values').organization).toEqual({
id: 3,
name: 'organization',
});
wrapper.find('input#inventory-name').simulate('change', {
target: { value: 'new Foo', name: 'name' },
});
expect(form.state('values').name).toEqual('new Foo');
act(() => { act(() => {
wrapper.find('CredentialLookup').invoke('onBlur')(); wrapper.find('CredentialLookup').invoke('onBlur')();
wrapper.find('CredentialLookup').invoke('onChange')({ wrapper.find('CredentialLookup').invoke('onChange')({
@@ -93,12 +104,26 @@ describe('<InventoryForm />', () => {
name: 'credential', name: 'credential',
}); });
}); });
expect(form.state('values').insights_credential).toEqual(10); expect(form.state('values').insights_credential).toEqual({
id: 10,
name: 'credential',
});
form.find('button[aria-label="Save"]').simulate('click');
await sleep(1);
expect(onSubmit).toHaveBeenCalledWith({
description: '',
insights_credential: { id: 10, name: 'credential' },
instanceGroups: [{ id: 1, name: 'Foo' }, { id: 2, name: 'Bar' }],
name: 'new Foo',
organization: { id: 3, name: 'organization' },
variables: '---',
});
}); });
test('should call handleCancel when Cancel button is clicked', async () => { test('should call handleCancel when Cancel button is clicked', async () => {
expect(handleCancel).not.toHaveBeenCalled(); expect(onCancel).not.toHaveBeenCalled();
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')(); wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
expect(handleCancel).toBeCalled(); expect(onCancel).toBeCalled();
}); });
}); });