update JobTemplateForm tests

This commit is contained in:
Keith Grant
2019-09-03 14:35:04 -07:00
parent 4f546be87a
commit 8b1ca12d8f
6 changed files with 135 additions and 67 deletions

View File

@@ -16,6 +16,7 @@ function CheckboxField({ id, name, label, tooltip, validate, ...rest }) {
validate={validate} validate={validate}
render={({ field }) => ( render={({ field }) => (
<Checkbox <Checkbox
aria-label={label}
label={ label={
<span> <span>
{label} {label}

View File

@@ -57,7 +57,7 @@ FormField.propTypes = {
type: PropTypes.string, type: PropTypes.string,
validate: PropTypes.func, validate: PropTypes.func,
isRequired: PropTypes.bool, isRequired: PropTypes.bool,
tooltip: PropTypes.string, tooltip: PropTypes.node,
}; };
FormField.defaultProps = { FormField.defaultProps = {

View File

@@ -12,10 +12,11 @@ const getInstanceGroups = async params => InstanceGroupsAPI.read(params);
class InstanceGroupsLookup extends React.Component { class InstanceGroupsLookup extends React.Component {
render() { render() {
const { value, tooltip, onChange, i18n } = this.props; const { value, tooltip, onChange, className, i18n } = this.props;
return ( return (
<FormGroup <FormGroup
className={className}
label={ label={
<Fragment> <Fragment>
{i18n._(t`Instance Groups`)}{' '} {i18n._(t`Instance Groups`)}{' '}

View File

@@ -92,6 +92,32 @@ const mockRelatedProjectPlaybooks = [
'vault.yml', 'vault.yml',
]; ];
const mockInstanceGroups = [
{
id: 1,
type: 'instance_group',
url: '/api/v2/instance_groups/1/',
related: {
jobs: '/api/v2/instance_groups/1/jobs/',
instances: '/api/v2/instance_groups/1/instances/',
},
name: 'tower',
capacity: 59,
committed_capacity: 0,
consumed_capacity: 0,
percent_capacity_remaining: 100.0,
jobs_running: 0,
jobs_total: 3,
instances: 1,
controller: null,
is_controller: false,
is_isolated: false,
policy_instance_percentage: 100,
policy_instance_minimum: 0,
policy_instance_list: [],
},
];
JobTemplatesAPI.readCredentials.mockResolvedValue({ JobTemplatesAPI.readCredentials.mockResolvedValue({
data: mockRelatedCredentials, data: mockRelatedCredentials,
}); });
@@ -101,12 +127,25 @@ ProjectsAPI.readPlaybooks.mockResolvedValue({
LabelsAPI.read.mockResolvedValue({ data: { results: [] } }); LabelsAPI.read.mockResolvedValue({ data: { results: [] } });
describe('<JobTemplateEdit />', () => { describe('<JobTemplateEdit />', () => {
test('initially renders successfully', async done => { beforeEach(() => {
LabelsAPI.read.mockResolvedValue({ data: { results: [] } });
JobTemplatesAPI.readCredentials.mockResolvedValue({
data: mockRelatedCredentials,
});
JobTemplatesAPI.readInstanceGroups.mockReturnValue({
data: { results: mockInstanceGroups },
});
});
afterEach(() => {
jest.clearAllMocks();
});
test('initially renders successfully', async () => {
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<JobTemplateEdit template={mockJobTemplate} /> <JobTemplateEdit template={mockJobTemplate} />
); );
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
done();
}); });
test('handleSubmit should call api update', async done => { test('handleSubmit should call api update', async done => {

View File

@@ -157,23 +157,6 @@ class JobTemplateForm extends Component {
newLabel => newLabel.name !== label newLabel => newLabel.name !== label
); );
this.setState({ newLabels: filteredLabels }); this.setState({ newLabels: filteredLabels });
} else if (typeof label === 'string') {
setFieldValue('newLabels', [
...newLabels,
{
name: label,
organization: template.summary_fields.inventory.organization_id,
},
]);
this.setState({
newLabels: [
...newLabels,
{
name: label,
organization: template.summary_fields.inventory.organization_id,
},
],
});
} else { } else {
setFieldValue('newLabels', [ setFieldValue('newLabels', [
...newLabels, ...newLabels,
@@ -182,7 +165,12 @@ class JobTemplateForm extends Component {
this.setState({ this.setState({
newLabels: [ newLabels: [
...newLabels, ...newLabels,
{ name: label.name, associate: true, id: label.id }, {
name: label.name,
associate: true,
id: label.id,
organization: template.summary_fields.inventory.organization_id,
},
], ],
}); });
} }
@@ -311,6 +299,12 @@ class JobTemplateForm extends Component {
{ value: '3', key: '3', label: i18n._(t`3 (Debug)`) }, { value: '3', key: '3', label: i18n._(t`3 (Debug)`) },
{ value: '4', key: '4', label: i18n._(t`4 (Connection Debug)`) }, { value: '4', key: '4', label: i18n._(t`4 (Connection Debug)`) },
]; ];
let callbackUrl;
if (template && template.related) {
const { origin } = document.location;
const path = template.related.callback || `${template.url}callback`;
callbackUrl = `${origin}${path}`;
}
if (hasContentLoading) { if (hasContentLoading) {
return ( return (
@@ -477,6 +471,7 @@ class JobTemplateForm extends Component {
id="template-forks" id="template-forks"
name="forks" name="forks"
type="number" type="number"
min="0"
label={i18n._(t`Forks`)} label={i18n._(t`Forks`)}
tooltip={ tooltip={
<span> <span>
@@ -568,6 +563,7 @@ class JobTemplateForm extends Component {
/> />
</FormRow> </FormRow>
<InstanceGroupsLookup <InstanceGroupsLookup
css="margin-top: 20px"
value={relatedInstanceGroups} value={relatedInstanceGroups}
onChange={this.handleInstanceGroupsChange} onChange={this.handleInstanceGroupsChange}
tooltip={i18n._( tooltip={i18n._(
@@ -579,6 +575,7 @@ class JobTemplateForm extends Component {
render={({ field, form }) => ( render={({ field, form }) => (
<FormGroup <FormGroup
label={i18n._(t`Job Tags`)} label={i18n._(t`Job Tags`)}
css="margin-top: 20px"
fieldId="template-job-tags" fieldId="template-job-tags"
> >
<Tooltip <Tooltip
@@ -603,6 +600,7 @@ class JobTemplateForm extends Component {
render={({ field, form }) => ( render={({ field, form }) => (
<FormGroup <FormGroup
label={i18n._(t`Skip Tags`)} label={i18n._(t`Skip Tags`)}
css="margin-top: 20px"
fieldId="template-skip-tags" fieldId="template-skip-tags"
> >
<Tooltip <Tooltip
@@ -622,7 +620,12 @@ class JobTemplateForm extends Component {
</FormGroup> </FormGroup>
)} )}
/> />
<GridFormGroup isInline label={i18n._(t`Options`)}> <GridFormGroup
fieldId="template-option-checkboxes"
isInline
label={i18n._(t`Options`)}
css="margin-top: 20px"
>
<CheckboxField <CheckboxField
id="option-privilege-escalation" id="option-privilege-escalation"
name="become_enabled" name="become_enabled"
@@ -641,7 +644,7 @@ class JobTemplateForm extends Component {
position="right" position="right"
content={i18n._( content={i18n._(
t`Enables creation of a provisioning callback URL. Using t`Enables creation of a provisioning callback URL. Using
the URL a host can contact {{BRAND_NAME}} and request a the URL a host can contact BRAND_NAME and request a
configuration update using this job template.` configuration update using this job template.`
)} )}
> >
@@ -677,19 +680,22 @@ class JobTemplateForm extends Component {
<div <div
css={` css={`
${allowCallbacks ? '' : 'display: none'} ${allowCallbacks ? '' : 'display: none'}
margin-top: 20px;
`} `}
> >
<FormRow> <FormRow>
<FormGroup {callbackUrl && (
label={i18n._(t`Provisioning Callback URL`)} <FormGroup
fieldId="template-callback-url" label={i18n._(t`Provisioning Callback URL`)}
> fieldId="template-callback-url"
<TextInput >
id="template-callback-url" <TextInput
isDisabled id="template-callback-url"
value={`${document.location.origin}${template.related.callback}`} isDisabled
/> value={callbackUrl}
</FormGroup> />
</FormGroup>
)}
<FormField <FormField
id="template-host-config-key" id="template-host-config-key"
name="host_config_key" name="host_config_key"

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import { sleep } from '@testUtils/testUtils'; import { sleep } from '@testUtils/testUtils';
import JobTemplateForm, { _JobTemplateForm } from './JobTemplateForm'; import JobTemplateForm, { _JobTemplateForm } from './JobTemplateForm';
import { LabelsAPI } from '@api'; import { LabelsAPI, JobTemplatesAPI } from '@api';
jest.mock('@api'); jest.mock('@api');
@@ -29,17 +29,45 @@ describe('<JobTemplateForm />', () => {
labels: { results: [{ name: 'Sushi', id: 1 }, { name: 'Major', id: 2 }] }, labels: { results: [{ name: 'Sushi', id: 1 }, { name: 'Major', id: 2 }] },
}, },
}; };
const mockInstanceGroups = [
{
id: 1,
type: 'instance_group',
url: '/api/v2/instance_groups/1/',
related: {
jobs: '/api/v2/instance_groups/1/jobs/',
instances: '/api/v2/instance_groups/1/instances/',
},
name: 'tower',
capacity: 59,
committed_capacity: 0,
consumed_capacity: 0,
percent_capacity_remaining: 100.0,
jobs_running: 0,
jobs_total: 3,
instances: 1,
controller: null,
is_controller: false,
is_isolated: false,
policy_instance_percentage: 100,
policy_instance_minimum: 0,
policy_instance_list: [],
},
];
beforeEach(() => { beforeEach(() => {
LabelsAPI.read.mockReturnValue({ LabelsAPI.read.mockReturnValue({
data: mockData.summary_fields.labels, data: mockData.summary_fields.labels,
}); });
JobTemplatesAPI.readInstanceGroups.mockReturnValue({
data: { results: mockInstanceGroups },
});
}); });
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('initially renders successfully', async done => { test('should render labels MultiSelect', async () => {
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<JobTemplateForm <JobTemplateForm
template={mockData} template={mockData}
@@ -47,19 +75,18 @@ describe('<JobTemplateForm />', () => {
handleCancel={jest.fn()} handleCancel={jest.fn()}
/> />
); );
await waitForElement(wrapper, 'Form', el => el.length === 0);
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
expect(LabelsAPI.read).toHaveBeenCalled(); expect(LabelsAPI.read).toHaveBeenCalled();
expect(JobTemplatesAPI.readInstanceGroups).toHaveBeenCalled();
wrapper.update();
expect( expect(
wrapper wrapper
.find('FormGroup[fieldId="template-labels"] MultiSelect Chip') .find('FormGroup[fieldId="template-labels"] MultiSelect')
.first() .prop('associatedItems')
.text() ).toEqual(mockData.summary_fields.labels.results);
).toEqual('Sushi');
done();
}); });
test('should update form values on input changes', async done => { test('should update form values on input changes', async () => {
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<JobTemplateForm <JobTemplateForm
template={mockData} template={mockData}
@@ -96,10 +123,9 @@ describe('<JobTemplateForm />', () => {
target: { value: 'new baz type', name: 'playbook' }, target: { value: 'new baz type', name: 'playbook' },
}); });
expect(form.state('values').playbook).toEqual('new baz type'); expect(form.state('values').playbook).toEqual('new baz type');
done();
}); });
test('should call handleSubmit when Submit button is clicked', async done => { test('should call handleSubmit when Submit button is clicked', async () => {
const handleSubmit = jest.fn(); const handleSubmit = jest.fn();
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<JobTemplateForm <JobTemplateForm
@@ -113,10 +139,9 @@ describe('<JobTemplateForm />', () => {
wrapper.find('button[aria-label="Save"]').simulate('click'); wrapper.find('button[aria-label="Save"]').simulate('click');
await sleep(1); await sleep(1);
expect(handleSubmit).toBeCalled(); expect(handleSubmit).toBeCalled();
done();
}); });
test('should call handleCancel when Cancel button is clicked', async done => { test('should call handleCancel when Cancel button is clicked', async () => {
const handleCancel = jest.fn(); const handleCancel = jest.fn();
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<JobTemplateForm <JobTemplateForm
@@ -129,10 +154,9 @@ describe('<JobTemplateForm />', () => {
expect(handleCancel).not.toHaveBeenCalled(); expect(handleCancel).not.toHaveBeenCalled();
wrapper.find('button[aria-label="Cancel"]').prop('onClick')(); wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
expect(handleCancel).toBeCalled(); expect(handleCancel).toBeCalled();
done();
}); });
test('should call loadRelatedProjectPlaybooks when project value changes', async done => { test('should call loadRelatedProjectPlaybooks when project value changes', async () => {
const loadRelatedProjectPlaybooks = jest.spyOn( const loadRelatedProjectPlaybooks = jest.spyOn(
_JobTemplateForm.prototype, _JobTemplateForm.prototype,
'loadRelatedProjectPlaybooks' 'loadRelatedProjectPlaybooks'
@@ -150,15 +174,10 @@ describe('<JobTemplateForm />', () => {
name: 'project', name: 'project',
}); });
expect(loadRelatedProjectPlaybooks).toHaveBeenCalledWith(10); expect(loadRelatedProjectPlaybooks).toHaveBeenCalledWith(10);
done();
}); });
test('handleNewLabel should arrange new labels properly', async done => { test('handleNewLabel should arrange new labels properly', async () => {
const handleNewLabel = jest.spyOn( const event = { key: 'Enter' };
_JobTemplateForm.prototype,
'handleNewLabel'
);
const event = { key: 'Enter', preventDefault: () => {} };
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<JobTemplateForm <JobTemplateForm
template={mockData} template={mockData}
@@ -167,22 +186,25 @@ describe('<JobTemplateForm />', () => {
/> />
); );
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
const multiSelect = wrapper.find('MultiSelect'); const multiSelect = wrapper.find(
'FormGroup[fieldId="template-labels"] MultiSelect'
);
const component = wrapper.find('JobTemplateForm'); const component = wrapper.find('JobTemplateForm');
wrapper.setState({ newLabels: [], loadedLabels: [], removedLabels: [] }); wrapper.setState({ newLabels: [], loadedLabels: [], removedLabels: [] });
multiSelect.setState({ input: 'Foo' }); multiSelect.setState({ input: 'Foo' });
component.find('input[aria-label="labels"]').prop('onKeyDown')(event); component
expect(handleNewLabel).toHaveBeenCalledWith('Foo'); .find('FormGroup[fieldId="template-labels"] input[aria-label="labels"]')
.prop('onKeyDown')(event);
component.instance().handleNewLabel({ name: 'Bar', id: 2 }); component.instance().handleNewLabel({ name: 'Bar', id: 2 });
expect(component.state().newLabels).toEqual([ const newLabels = component.state('newLabels');
{ name: 'Foo', organization: 1 }, expect(newLabels).toHaveLength(2);
{ associate: true, id: 2, name: 'Bar' }, expect(newLabels[0].name).toEqual('Foo');
]); expect(newLabels[0].organization).toEqual(1);
done();
}); });
test('disassociateLabel should arrange new labels properly', async done => {
test('disassociateLabel should arrange new labels properly', async () => {
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<JobTemplateForm <JobTemplateForm
template={mockData} template={mockData}
@@ -203,6 +225,5 @@ describe('<JobTemplateForm />', () => {
component.instance().removeLabel({ name: 'Sushi', id: 1 }); component.instance().removeLabel({ name: 'Sushi', id: 1 });
expect(component.state().newLabels.length).toBe(0); expect(component.state().newLabels.length).toBe(0);
expect(component.state().removedLabels.length).toBe(1); expect(component.state().removedLabels.length).toBe(1);
done();
}); });
}); });