1
0
mirror of https://github.com/ansible/awx.git synced 2024-10-30 22:21:13 +03:00

Merge pull request #5688 from keithjgrant/5235-variables-field-json

Upgrade to Formik 2.1.2

Reviewed-by: John Mitchell
             https://github.com/jlmitch5
This commit is contained in:
softwarefactory-project-zuul[bot] 2020-01-17 19:35:27 +00:00 committed by GitHub
commit 5e4c997c41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 485 additions and 486 deletions

View File

@ -3015,11 +3015,6 @@
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
"dev": true
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@ -5193,15 +5188,6 @@
"sha.js": "^2.4.8"
}
},
"create-react-context": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.3.tgz",
"integrity": "sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==",
"requires": {
"fbjs": "^0.8.0",
"gud": "^1.0.0"
}
},
"cross-spawn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
@ -6215,6 +6201,7 @@
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"dev": true,
"requires": {
"iconv-lite": "~0.4.13"
}
@ -7308,27 +7295,6 @@
"bser": "^2.0.0"
}
},
"fbjs": {
"version": "0.8.17",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
"integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
"requires": {
"core-js": "^1.0.0",
"isomorphic-fetch": "^2.1.1",
"loose-envify": "^1.0.0",
"object-assign": "^4.1.0",
"promise": "^7.1.1",
"setimmediate": "^1.0.5",
"ua-parser-js": "^0.7.18"
},
"dependencies": {
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
}
}
},
"fbjs-scripts": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/fbjs-scripts/-/fbjs-scripts-0.8.3.tgz",
@ -7648,19 +7614,47 @@
}
},
"formik": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/formik/-/formik-1.5.1.tgz",
"integrity": "sha512-FBWGBKQkcCE4d5b5l2fKccD9d1QxNxw/0bQTRvp3EjzA8Bnjmsm9H/Oy0375UA8P3FPmfJkF4cXLLdEqK7fP5A==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/formik/-/formik-2.1.2.tgz",
"integrity": "sha512-lbhyV8FQ/hkg4tsVf075Ad9/vDXVbSj6XLW8ooZtAZyNJK8MBYLf1fRJ6iEo2C0pODQneDboYpEUby7nsPW00Q==",
"requires": {
"create-react-context": "^0.2.2",
"deepmerge": "^2.1.1",
"hoist-non-react-statics": "^2.5.5",
"lodash": "^4.17.11",
"lodash-es": "^4.17.11",
"prop-types": "^15.6.1",
"hoist-non-react-statics": "^3.3.0",
"lodash": "^4.17.14",
"lodash-es": "^4.17.14",
"react-fast-compare": "^2.0.1",
"scheduler": "^0.18.0",
"tiny-warning": "^1.0.2",
"tslib": "^1.9.3"
"tslib": "^1.10.0"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw==",
"requires": {
"react-is": "^16.7.0"
}
},
"react-is": {
"version": "16.12.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
"integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q=="
},
"scheduler": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz",
"integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
},
"forwarded": {
@ -7774,7 +7768,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -7795,12 +7790,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -7815,17 +7812,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -7942,7 +7942,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -7954,6 +7955,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -7968,6 +7970,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -7975,12 +7978,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -7999,6 +8004,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -8079,7 +8085,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -8091,6 +8098,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -8176,7 +8184,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -8212,6 +8221,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -8231,6 +8241,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -8274,12 +8285,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -8678,7 +8691,8 @@
"hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
"dev": true
},
"home-or-tmp": {
"version": "2.0.0",
@ -9716,7 +9730,8 @@
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"dev": true
},
"is-string": {
"version": "1.0.4",
@ -9787,15 +9802,6 @@
}
}
},
"isomorphic-fetch": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
"requires": {
"node-fetch": "^1.0.1",
"whatwg-fetch": ">=0.10.0"
}
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@ -11487,9 +11493,9 @@
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"lodash-es": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz",
"integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q=="
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz",
"integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ=="
},
"lodash.debounce": {
"version": "4.0.8",
@ -12271,6 +12277,7 @@
"version": "1.6.3",
"resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz",
"integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=",
"dev": true,
"requires": {
"encoding": "^0.1.11",
"is-stream": "^1.0.1"
@ -13491,14 +13498,6 @@
"integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==",
"dev": true
},
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"requires": {
"asap": "~2.0.3"
}
},
"promise-inflight": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -14987,7 +14986,8 @@
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
"dev": true
},
"setprototypeof": {
"version": "1.1.0",
@ -16385,7 +16385,8 @@
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
"dev": true
},
"tty-browserify": {
"version": "0.0.0",
@ -16436,11 +16437,6 @@
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
"dev": true
},
"ua-parser-js": {
"version": "0.7.19",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz",
"integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ=="
},
"uglify-js": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
@ -17608,11 +17604,6 @@
"iconv-lite": "0.4.24"
}
},
"whatwg-fetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
},
"whatwg-mimetype": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz",

View File

@ -67,7 +67,7 @@
"codemirror": "^5.47.0",
"d3": "^5.12.0",
"dagre": "^0.8.4",
"formik": "^1.5.1",
"formik": "^2.1.2",
"has-ansi": "^3.0.0",
"html-entities": "^1.2.1",
"js-yaml": "^3.13.1",

View File

@ -13,9 +13,8 @@ function VariablesField({ id, name, label, readOnly }) {
const [mode, setMode] = useState(YAML_MODE);
return (
<Field
name={name}
render={({ field, form }) => (
<Field name={name}>
{({ field, form }) => (
<div className="pf-c-form__group">
<Split gutter="sm">
<SplitItem>
@ -60,7 +59,7 @@ function VariablesField({ id, name, label, readOnly }) {
) : null}
</div>
)}
/>
</Field>
);
}
VariablesField.propTypes = {

View File

@ -1,7 +1,7 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import { Formik } from 'formik';
import { sleep } from '../../../testUtils/testUtils';
import VariablesField from './VariablesField';
describe('VariablesField', () => {
@ -12,52 +12,52 @@ describe('VariablesField', () => {
it('should render code mirror input', () => {
const value = '---\n';
const wrapper = mount(
<Formik
initialValues={{ variables: value }}
render={() => (
<Formik initialValues={{ variables: value }}>
{() => (
<VariablesField id="the-field" name="variables" label="Variables" />
)}
/>
</Formik>
);
const codemirror = wrapper.find('Controlled');
expect(codemirror.prop('value')).toEqual(value);
});
it('should render yaml/json toggles', () => {
it('should render yaml/json toggles', async () => {
const value = '---\n';
const wrapper = mount(
<Formik
initialValues={{ variables: value }}
render={() => (
<Formik initialValues={{ variables: value }}>
{() => (
<VariablesField id="the-field" name="variables" label="Variables" />
)}
/>
</Formik>
);
const buttons = wrapper.find('Button');
expect(buttons).toHaveLength(2);
expect(buttons.at(0).prop('variant')).toEqual('primary');
expect(buttons.at(1).prop('variant')).toEqual('secondary');
buttons.at(1).simulate('click');
wrapper.update(0);
await act(async () => {
buttons.at(1).simulate('click');
});
wrapper.update();
expect(wrapper.find('CodeMirrorInput').prop('mode')).toEqual('javascript');
const buttons2 = wrapper.find('Button');
expect(buttons2.at(0).prop('variant')).toEqual('secondary');
expect(buttons2.at(1).prop('variant')).toEqual('primary');
buttons2.at(0).simulate('click');
wrapper.update(0);
await act(async () => {
buttons2.at(0).simulate('click');
});
wrapper.update();
expect(wrapper.find('CodeMirrorInput').prop('mode')).toEqual('yaml');
});
it('should set Formik error if yaml is invalid', () => {
it('should set Formik error if yaml is invalid', async () => {
const value = '---\nfoo bar\n';
const wrapper = mount(
<Formik
initialValues={{ variables: value }}
render={() => (
<Formik initialValues={{ variables: value }}>
{() => (
<VariablesField id="the-field" name="variables" label="Variables" />
)}
/>
</Formik>
);
wrapper
.find('Button')
@ -74,10 +74,8 @@ describe('VariablesField', () => {
const value = '---\nfoo: bar\n';
const handleSubmit = jest.fn();
const wrapper = mount(
<Formik
initialValues={{ variables: value }}
onSubmit={handleSubmit}
render={formik => (
<Formik initialValues={{ variables: value }} onSubmit={handleSubmit}>
{formik => (
<form onSubmit={formik.handleSubmit}>
<VariablesField id="the-field" name="variables" label="Variables" />
<button type="submit" id="submit">
@ -85,12 +83,14 @@ describe('VariablesField', () => {
</button>
</form>
)}
/>
</Formik>
);
wrapper.find('CodeMirrorInput').prop('onChange')('---\nnewval: changed');
wrapper.find('form').simulate('submit');
await sleep(1);
await sleep(1);
await act(async () => {
wrapper.find('CodeMirrorInput').invoke('onChange')(
'---\nnewval: changed'
);
wrapper.find('form').simulate('submit');
});
expect(handleSubmit).toHaveBeenCalled();
expect(handleSubmit.mock.calls[0][0]).toEqual({

View File

@ -11,10 +11,8 @@ const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
function CheckboxField({ id, name, label, tooltip, validate, ...rest }) {
return (
<Field
name={name}
validate={validate}
render={({ field }) => (
<Field name={name} validate={validate}>
{({ field }) => (
<Checkbox
aria-label={label}
label={
@ -37,7 +35,7 @@ function CheckboxField({ id, name, label, tooltip, validate, ...rest }) {
}}
/>
)}
/>
</Field>
);
}
CheckboxField.propTypes = {

View File

@ -22,10 +22,8 @@ function FormField(props) {
} = props;
return (
<Field
name={name}
validate={validate}
render={({ field, form }) => {
<Field name={name} validate={validate}>
{({ field, form }) => {
const isValid =
form && (!form.touched[field.name] || !form.errors[field.name]);
@ -59,7 +57,7 @@ function FormField(props) {
</FormGroup>
);
}}
/>
</Field>
);
}

View File

@ -22,10 +22,8 @@ function PasswordField(props) {
};
return (
<Field
name={name}
validate={validate}
render={({ field, form }) => {
<Field name={name} validate={validate}>
{({ field, form }) => {
const isValid =
form && (!form.touched[field.name] || !form.errors[field.name]);
return (
@ -65,7 +63,7 @@ function PasswordField(props) {
</FormGroup>
);
}}
/>
</Field>
);
}

View File

@ -11,10 +11,11 @@ describe('PasswordField', () => {
initialValues={{
password: '',
}}
render={() => (
>
{() => (
<PasswordField id="test-password" name="password" label="Password" />
)}
/>
</Formik>
);
expect(wrapper).toHaveLength(1);
});
@ -25,10 +26,11 @@ describe('PasswordField', () => {
initialValues={{
password: '',
}}
render={() => (
>
{() => (
<PasswordField id="test-password" name="password" label="Password" />
)}
/>
</Formik>
);
expect(wrapper.find('input').prop('type')).toBe('password');
expect(wrapper.find('EyeSlashIcon').length).toBe(1);

View File

@ -29,7 +29,8 @@ function HostForm({ handleSubmit, handleCancel, host, i18n }) {
variables: host.variables,
}}
onSubmit={handleSubmit}
render={formik => (
>
{formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormRow>
<FormField
@ -53,7 +54,8 @@ function HostForm({ handleSubmit, handleCancel, host, i18n }) {
i18n._(t`Select a value for this field`),
i18n
)}
render={({ form }) => (
>
{({ form }) => (
<InventoryLookup
value={inventory}
onBlur={() => form.setFieldTouched('inventory')}
@ -71,7 +73,7 @@ function HostForm({ handleSubmit, handleCancel, host, i18n }) {
error={form.errors.inventory}
/>
)}
/>
</Field>
)}
</FormRow>
<FormRow>
@ -87,7 +89,7 @@ function HostForm({ handleSubmit, handleCancel, host, i18n }) {
/>
</Form>
)}
/>
</Formik>
);
}

View File

@ -1,7 +1,6 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
import { sleep } from '@testUtils/testUtils';
import HostForm from './HostForm';
@ -31,25 +30,32 @@ describe('<HostForm />', () => {
jest.clearAllMocks();
});
test('changing inputs should update form values', () => {
const wrapper = mountWithContexts(
<HostForm
host={mockData}
handleSubmit={jest.fn()}
handleCancel={jest.fn()}
me={meConfig.me}
/>
);
test('changing inputs should update form values', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<HostForm
host={mockData}
handleSubmit={jest.fn()}
handleCancel={jest.fn()}
me={meConfig.me}
/>
);
});
const form = wrapper.find('Formik');
wrapper.find('input#host-name').simulate('change', {
target: { value: 'new foo', name: 'name' },
await act(async () => {
wrapper.find('input#host-name').simulate('change', {
target: { value: 'new foo', name: 'name' },
});
wrapper.find('input#host-description').simulate('change', {
target: { value: 'new bar', name: 'description' },
});
});
expect(form.state('values').name).toEqual('new foo');
wrapper.find('input#host-description').simulate('change', {
target: { value: 'new bar', name: 'description' },
});
expect(form.state('values').description).toEqual('new bar');
wrapper.update();
expect(wrapper.find('input#host-name').prop('value')).toEqual('new foo');
expect(wrapper.find('input#host-description').prop('value')).toEqual(
'new bar'
);
});
test('calls handleSubmit when form submitted', async () => {
@ -63,8 +69,9 @@ describe('<HostForm />', () => {
/>
);
expect(handleSubmit).not.toHaveBeenCalled();
wrapper.find('button[aria-label="Save"]').simulate('click');
await sleep(1);
await act(async () => {
wrapper.find('button[aria-label="Save"]').simulate('click');
});
expect(handleSubmit).toHaveBeenCalled();
});

View File

@ -3,8 +3,9 @@ import { Route } from 'react-router-dom';
import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import InventoryHostAdd from './InventoryHostAdd';
import { sleep } from '@testUtils/testUtils';
import { InventoriesAPI } from '@api';
import InventoryHostAdd from './InventoryHostAdd';
jest.mock('@api');
@ -49,45 +50,42 @@ describe('<InventoryHostAdd />', () => {
data: { ...mockHostData },
});
const formik = wrapper.find('Formik').instance();
await act(async () => {
const changeState = new Promise(resolve => {
formik.setState(
{
values: {
...mockHostData,
},
},
() => resolve()
);
wrapper.find('FormField[id="host-name"] input').simulate('change', {
target: { value: 'new name', name: 'name' },
});
await changeState;
});
await act(async () => {
wrapper.find('form').simulate('submit');
wrapper
.find('FormField[id="host-description"] input')
.simulate('change', {
target: { value: 'new description', name: 'description' },
});
wrapper.update();
await sleep(0);
wrapper.find('FormActionGroup').invoke('onSubmit')();
});
wrapper.update();
expect(InventoriesAPI.createHost).toHaveBeenCalledWith('1', mockHostData);
expect(InventoriesAPI.createHost).toHaveBeenCalledWith('1', {
name: 'new name',
description: 'new description',
variables: '---\n',
});
});
test('handleSubmit should throw an error', async () => {
InventoriesAPI.createHost.mockImplementationOnce(() =>
Promise.reject(new Error())
);
const formik = wrapper.find('Formik').instance();
await act(async () => {
const changeState = new Promise(resolve => {
formik.setState(
{
values: {
...mockHostData,
},
},
() => resolve()
);
wrapper.find('FormField[id="host-name"] input').simulate('change', {
target: { value: 'new name', name: 'name' },
});
await changeState;
wrapper
.find('FormField[id="host-description"] input')
.simulate('change', {
target: { value: 'new description', name: 'description' },
});
});
wrapper.update();
await act(async () => {
wrapper.find('form').simulate('submit');
});

View File

@ -41,7 +41,8 @@ function InventoryForm({
onSubmit={values => {
onSubmit(values);
}}
render={formik => (
>
{formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormRow>
<FormField
@ -66,7 +67,8 @@ function InventoryForm({
i18n._(t`Select a value for this field`),
i18n
)}
render={({ form, field }) => (
>
{({ form, field }) => (
<OrganizationLookup
helperTextInvalid={form.errors.organization}
isValid={
@ -82,12 +84,13 @@ function InventoryForm({
required
/>
)}
/>
</Field>
<Field
id="inventory-insights_credential"
label={i18n._(t`Insights Credential`)}
name="insights_credential"
render={({ field, form }) => (
>
{({ field, form }) => (
<CredentialLookup
label={i18n._(t`Insights Credential`)}
credentialTypeId={credentialTypeId}
@ -97,14 +100,15 @@ function InventoryForm({
value={field.value}
/>
)}
/>
</Field>
</FormRow>
<FormRow>
<Field
id="inventory-instanceGroups"
label={i18n._(t`Instance Groups`)}
name="instanceGroups"
render={({ field, form }) => (
>
{({ field, form }) => (
<InstanceGroupsLookup
value={field.value}
onChange={value => {
@ -112,7 +116,7 @@ function InventoryForm({
}}
/>
)}
/>
</Field>
</FormRow>
<FormRow>
<VariablesField
@ -132,7 +136,7 @@ function InventoryForm({
</FormRow>
</Form>
)}
/>
</Formik>
);
}

View File

@ -1,7 +1,6 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
import { sleep } from '@testUtils/testUtils';
import InventoryForm from './InventoryForm';
@ -64,12 +63,15 @@ describe('<InventoryForm />', () => {
/>
);
});
afterEach(() => {
wrapper.unmount();
});
test('Initially renders successfully', () => {
expect(wrapper.length).toBe(1);
});
test('should display form fields properly', () => {
expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
@ -80,45 +82,37 @@ describe('<InventoryForm />', () => {
);
expect(wrapper.find('VariablesField[label="Variables"]').length).toBe(1);
});
test('should update from values onChange', async () => {
const form = wrapper.find('Formik');
test('should update form values', async () => {
act(() => {
wrapper.find('OrganizationLookup').invoke('onBlur')();
wrapper.find('OrganizationLookup').invoke('onChange')({
id: 3,
name: 'organization',
});
});
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(() => {
wrapper.find('input#inventory-name').simulate('change', {
target: { value: 'new Foo', name: 'name' },
});
wrapper.find('CredentialLookup').invoke('onBlur')();
wrapper.find('CredentialLookup').invoke('onChange')({
id: 10,
name: 'credential',
});
});
expect(form.state('values').insights_credential).toEqual({
wrapper.update();
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
id: 3,
name: 'organization',
});
expect(wrapper.find('input#inventory-name').prop('value')).toEqual(
'new Foo'
);
expect(wrapper.find('CredentialLookup').prop('value')).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 () => {

View File

@ -28,10 +28,8 @@ function InventoryGroupForm({
return (
<Card className="awx-c-card">
<CardBody>
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
render={formik => (
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
{formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormRow css="grid-template-columns: repeat(auto-fit, minmax(300px, 500px));">
<FormField
@ -63,7 +61,7 @@ function InventoryGroupForm({
{error ? <div>error</div> : null}
</Form>
)}
/>
</Formik>
</CardBody>
</Card>
);

View File

@ -19,7 +19,8 @@ function InventoryHostForm({ handleSubmit, handleCancel, host, i18n }) {
variables: host.variables,
}}
onSubmit={handleSubmit}
render={formik => (
>
{formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormRow>
<FormField
@ -46,11 +47,13 @@ function InventoryHostForm({ handleSubmit, handleCancel, host, i18n }) {
</FormRow>
<FormActionGroup
onCancel={handleCancel}
onSubmit={formik.handleSubmit}
onSubmit={() => {
formik.handleSubmit();
}}
/>
</Form>
)}
/>
</Formik>
);
}

View File

@ -43,8 +43,9 @@ describe('<InventoryHostform />', () => {
test('should call handleSubmit when Submit button is clicked', async () => {
expect(handleSubmit).not.toHaveBeenCalled();
wrapper.find('button[aria-label="Save"]').simulate('click');
await sleep(1);
await act(async () => {
wrapper.find('button[aria-label="Save"]').simulate('click');
});
expect(handleSubmit).toHaveBeenCalled();
});

View File

@ -91,7 +91,8 @@ function OrganizationForm({ organization, i18n, me, onCancel, onSubmit }) {
max_hosts: organization.max_hosts || '0',
}}
onSubmit={handleSubmit}
render={formik => (
>
{formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormRow>
<FormField
@ -132,9 +133,8 @@ function OrganizationForm({ organization, i18n, me, onCancel, onSubmit }) {
isDisabled={!me.is_superuser}
/>
{custom_virtualenvs && custom_virtualenvs.length > 1 && (
<Field
name="custom_virtualenv"
render={({ field }) => (
<Field name="custom_virtualenv">
{({ field }) => (
<FormGroup
fieldId="org-custom-virtualenv"
label={i18n._(t`Ansible Environment`)}
@ -151,7 +151,7 @@ function OrganizationForm({ organization, i18n, me, onCancel, onSubmit }) {
/>
</FormGroup>
)}
/>
</Field>
)}
</FormRow>
<InstanceGroupsLookup
@ -167,7 +167,7 @@ function OrganizationForm({ organization, i18n, me, onCancel, onSubmit }) {
/>
</Form>
)}
/>
</Formik>
);
}

View File

@ -97,24 +97,7 @@ describe('<ProjectAdd />', () => {
wrapper = mountWithContexts(<ProjectAdd />);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
const formik = wrapper.find('Formik').instance();
await act(async () => {
const changeState = new Promise(resolve => {
formik.setState(
{
values: {
...projectData,
},
},
() => resolve()
);
});
await changeState;
});
await act(async () => {
wrapper.find('form').simulate('submit');
});
wrapper.update();
wrapper.find('ProjectForm').invoke('handleSubmit')(projectData);
expect(ProjectsAPI.create).toHaveBeenCalledTimes(1);
});

View File

@ -201,7 +201,8 @@ function ProjectForm({ project, ...props }) {
scm_url: project.scm_url || '',
}}
onSubmit={handleSubmit}
render={formik => (
>
{formik => (
<Form
autoComplete="off"
onSubmit={formik.handleSubmit}
@ -228,7 +229,8 @@ function ProjectForm({ project, ...props }) {
i18n._(t`Select a value for this field`),
i18n
)}
render={({ form }) => (
>
{({ form }) => (
<OrganizationLookup
helperTextInvalid={form.errors.organization}
isValid={
@ -243,14 +245,15 @@ function ProjectForm({ project, ...props }) {
required
/>
)}
/>
</Field>
<Field
name="scm_type"
validate={required(
i18n._(t`Select a value for this field`),
i18n
)}
render={({ field, form }) => (
>
{({ field, form }) => (
<FormGroup
fieldId="project-scm-type"
helperTextInvalid={form.errors.scm_type}
@ -286,7 +289,7 @@ function ProjectForm({ project, ...props }) {
/>
</FormGroup>
)}
/>
</Field>
{formik.values.scm_type !== '' && (
<ScmTypeFormRow>
<SubFormTitle size="md">
@ -345,9 +348,8 @@ function ProjectForm({ project, ...props }) {
{({ custom_virtualenvs }) =>
custom_virtualenvs &&
custom_virtualenvs.length > 1 && (
<Field
name="custom_virtualenv"
render={({ field }) => (
<Field name="custom_virtualenv">
{({ field }) => (
<FormGroup
fieldId="project-custom-virtualenv"
label={i18n._(t`Ansible Environment`)}
@ -378,7 +380,7 @@ function ProjectForm({ project, ...props }) {
/>
</FormGroup>
)}
/>
</Field>
)
}
</Config>
@ -389,7 +391,7 @@ function ProjectForm({ project, ...props }) {
/>
</Form>
)}
/>
</Formik>
)}
</Config>
);

View File

@ -1,7 +1,6 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import { sleep } from '@testUtils/testUtils';
import ProjectForm from './ProjectForm';
import { CredentialTypesAPI, ProjectsAPI } from '@api';
@ -130,19 +129,11 @@ describe('<ProjectForm />', () => {
);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
const formik = wrapper.find('Formik').instance();
await act(async () => {
const changeState = new Promise(resolve => {
formik.setState(
{
values: {
...mockData,
},
},
() => resolve()
);
});
await changeState;
await wrapper.find('AnsibleSelect[id="scm_type"]').invoke('onChange')(
null,
'git'
);
});
wrapper.update();
expect(wrapper.find('FormGroup[label="SCM URL"]').length).toBe(1);
@ -166,59 +157,58 @@ describe('<ProjectForm />', () => {
);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
const form = wrapper.find('Formik');
act(() => {
wrapper.find('OrganizationLookup').invoke('onBlur')();
wrapper.find('OrganizationLookup').invoke('onChange')({
id: 1,
name: 'organization',
});
});
expect(form.state('values').organization).toEqual(1);
act(() => {
wrapper.find('CredentialLookup').invoke('onBlur')();
wrapper.find('CredentialLookup').invoke('onChange')({
id: 10,
name: 'credential',
});
});
expect(form.state('values').credential).toEqual(10);
wrapper.update();
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
id: 1,
name: 'organization',
});
expect(wrapper.find('CredentialLookup').prop('value')).toEqual({
id: 10,
name: 'credential',
});
});
test('should display insights credential lookup when scm type is "Insights"', async () => {
test('should display insights credential lookup when scm type is "insights"', async () => {
await act(async () => {
wrapper = mountWithContexts(
<ProjectForm handleSubmit={jest.fn()} handleCancel={jest.fn()} />
);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
const formik = wrapper.find('Formik').instance();
await act(async () => {
const changeState = new Promise(resolve => {
formik.setState(
{
values: {
...mockData,
scm_type: 'insights',
},
},
() => resolve()
);
});
await changeState;
await wrapper.find('AnsibleSelect[id="scm_type"]').invoke('onChange')(
null,
'insights'
);
});
wrapper.update();
expect(wrapper.find('FormGroup[label="Insights Credential"]').length).toBe(
1
);
act(() => {
await act(async () => {
wrapper.find('CredentialLookup').invoke('onBlur')();
wrapper.find('CredentialLookup').invoke('onChange')({
id: 123,
name: 'credential',
});
});
expect(formik.state.values.credential).toEqual(123);
wrapper.update();
expect(wrapper.find('CredentialLookup').prop('value')).toEqual({
id: 123,
name: 'credential',
});
});
test('manual subform should display expected fields', async () => {
@ -289,10 +279,8 @@ describe('<ProjectForm />', () => {
const scmTypeSelect = wrapper.find(
'FormGroup[label="SCM Type"] FormSelect'
);
const formik = wrapper.find('Formik').instance();
expect(formik.state.values.scm_url).toEqual('');
await act(async () => {
scmTypeSelect.props().onChange('hg', { target: { name: 'Mercurial' } });
scmTypeSelect.invoke('onChange')('hg', { target: { name: 'Mercurial' } });
});
wrapper.update();
await act(async () => {
@ -300,7 +288,8 @@ describe('<ProjectForm />', () => {
target: { value: 'baz', name: 'scm_url' },
});
});
expect(formik.state.values.scm_url).toEqual('baz');
wrapper.update();
expect(wrapper.find('input#project-scm-url').prop('value')).toEqual('baz');
await act(async () => {
scmTypeSelect
.props()
@ -311,7 +300,7 @@ describe('<ProjectForm />', () => {
scmTypeSelect.props().onChange('svn', { target: { name: 'Subversion' } });
});
wrapper.update();
expect(formik.state.values.scm_url).toEqual('');
expect(wrapper.find('input#project-scm-url').prop('value')).toEqual('');
});
test('should call handleSubmit when Submit button is clicked', async () => {
@ -327,8 +316,9 @@ describe('<ProjectForm />', () => {
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
expect(handleSubmit).not.toHaveBeenCalled();
wrapper.find('button[aria-label="Save"]').simulate('click');
await sleep(1);
await act(async () => {
wrapper.find('button[aria-label="Save"]').simulate('click');
});
expect(handleSubmit).toBeCalled();
});

View File

@ -16,7 +16,8 @@ const InsightsSubForm = ({
<Field
name="credential"
validate={required(i18n._(t`Select a value for this field`), i18n)}
render={({ form }) => (
>
{({ form }) => (
<CredentialLookup
credentialTypeId={credential.typeId}
label={i18n._(t`Insights Credential`)}
@ -31,7 +32,7 @@ const InsightsSubForm = ({
required
/>
)}
/>
</Field>
<ScmTypeOptions hideAllowOverride scmUpdateOnLaunch={scmUpdateOnLaunch} />
</>
);

View File

@ -76,7 +76,8 @@ const ManualSubForm = ({
<Field
name="local_path"
validate={required(i18n._(t`Select a value for this field`), i18n)}
render={({ field, form }) => (
>
{({ field, form }) => (
<FormGroup
fieldId="project-local-path"
helperTextInvalid={form.errors.local_path}
@ -99,7 +100,7 @@ const ManualSubForm = ({
/>
</FormGroup>
)}
/>
</Field>
)}
</>
);

View File

@ -42,9 +42,8 @@ export const BranchFormField = withI18n()(({ i18n, label }) => (
export const ScmCredentialFormField = withI18n()(
({ i18n, credential, onCredentialSelection }) => (
<Field
name="credential"
render={({ form }) => (
<Field name="credential">
{({ form }) => (
<CredentialLookup
credentialTypeId={credential.typeId}
label={i18n._(t`SCM Credential`)}
@ -55,7 +54,7 @@ export const ScmCredentialFormField = withI18n()(
}}
/>
)}
/>
</Field>
)
);

View File

@ -24,7 +24,8 @@ function TeamForm(props) {
organization: team.organization || '',
}}
onSubmit={handleSubmit}
render={formik => (
>
{formik => (
<Form
autoComplete="off"
onSubmit={formik.handleSubmit}
@ -51,7 +52,8 @@ function TeamForm(props) {
i18n._(t`Select a value for this field`),
i18n
)}
render={({ form }) => (
>
{({ form }) => (
<OrganizationLookup
helperTextInvalid={form.errors.organization}
isValid={
@ -66,7 +68,7 @@ function TeamForm(props) {
required
/>
)}
/>
</Field>
</FormRow>
<FormActionGroup
onCancel={handleCancel}
@ -74,7 +76,7 @@ function TeamForm(props) {
/>
</Form>
)}
/>
</Formik>
);
}

View File

@ -1,7 +1,6 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import { sleep } from '@testUtils/testUtils';
import TeamForm from './TeamForm';
@ -42,23 +41,28 @@ describe('<TeamForm />', () => {
);
});
const form = wrapper.find('Formik');
wrapper.find('input#team-name').simulate('change', {
target: { value: 'new foo', name: 'name' },
});
expect(form.state('values').name).toEqual('new foo');
wrapper.find('input#team-description').simulate('change', {
target: { value: 'new bar', name: 'description' },
});
expect(form.state('values').description).toEqual('new bar');
act(() => {
wrapper.find('input#team-name').simulate('change', {
target: { value: 'new foo', name: 'name' },
});
wrapper.find('input#team-description').simulate('change', {
target: { value: 'new bar', name: 'description' },
});
wrapper.find('OrganizationLookup').invoke('onBlur')();
wrapper.find('OrganizationLookup').invoke('onChange')({
id: 2,
name: 'Other Org',
});
});
expect(form.state('values').organization).toEqual(2);
wrapper.update();
expect(wrapper.find('input#team-name').prop('value')).toEqual('new foo');
expect(wrapper.find('input#team-description').prop('value')).toEqual(
'new bar'
);
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
id: 2,
name: 'Other Org',
});
});
test('should call handleSubmit when Submit button is clicked', async () => {
@ -75,8 +79,9 @@ describe('<TeamForm />', () => {
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
expect(handleSubmit).not.toHaveBeenCalled();
wrapper.find('button[aria-label="Save"]').simulate('click');
await sleep(1);
await act(async () => {
wrapper.find('button[aria-label="Save"]').simulate('click');
});
expect(handleSubmit).toBeCalled();
});

View File

@ -2,7 +2,6 @@ import React from 'react';
import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import { sleep } from '@testUtils/testUtils';
import JobTemplateAdd from './JobTemplateAdd';
import { JobTemplatesAPI, LabelsAPI } from '@api';
@ -100,25 +99,37 @@ describe('<JobTemplateAdd />', () => {
wrapper = mountWithContexts(<JobTemplateAdd />);
});
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
const formik = wrapper.find('Formik').instance();
await act(async () => {
const changeState = new Promise(resolve => {
formik.setState(
{
values: {
...jobTemplateData,
labels: [],
instanceGroups: [],
},
},
() => resolve()
);
act(() => {
wrapper.find('input#template-name').simulate('change', {
target: { value: 'Foo', name: 'name' },
});
await changeState;
wrapper.find('AnsibleSelect#template-job-type').invoke('onChange')('run');
wrapper.find('InventoryLookup').invoke('onChange')({
id: 1,
organization: 1,
});
wrapper.find('ProjectLookup').invoke('onChange')({
id: 2,
name: 'project',
});
wrapper.update();
wrapper
.find('PlaybookSelect')
.prop('field')
.onChange({
target: { value: 'Bar', name: 'playbook' },
});
});
wrapper.update();
await act(async () => {
wrapper.find('form').simulate('submit');
});
wrapper.update();
expect(JobTemplatesAPI.create).toHaveBeenCalledWith({
...jobTemplateData,
description: '',
become_enabled: false,
});
wrapper.find('form').simulate('submit');
await sleep(1);
expect(JobTemplatesAPI.create).toHaveBeenCalledWith(jobTemplateData);
});
test('should navigate to job template detail after form submission', async () => {
@ -136,36 +147,34 @@ describe('<JobTemplateAdd />', () => {
context: { router: { history } },
});
});
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
act(() => {
wrapper.find('input#template-name').simulate('change', {
target: { value: 'Foo', name: 'name' },
});
wrapper.find('AnsibleSelect#template-job-type').invoke('onChange')('run');
wrapper.find('InventoryLookup').invoke('onChange')({
id: 1,
organization: 1,
});
wrapper.find('ProjectLookup').invoke('onChange')({
id: 2,
name: 'project',
});
wrapper.update();
wrapper
.find('PlaybookSelect')
.prop('field')
.onChange({
target: { value: 'Bar', name: 'playbook' },
});
});
wrapper.update();
await act(async () => {
wrapper.find('form').simulate('submit');
});
wrapper.update();
const updatedTemplateData = {
name: 'new name',
description: 'new description',
job_type: 'check',
};
const labels = [
{ id: 3, name: 'Foo', isNew: true },
{ id: 4, name: 'Bar', isNew: true },
{ id: 5, name: 'Maple' },
{ id: 6, name: 'Tree' },
];
JobTemplatesAPI.update.mockResolvedValue({
data: { ...updatedTemplateData },
});
const formik = wrapper.find('Formik').instance();
const changeState = new Promise(resolve => {
const values = {
...jobTemplateData,
...updatedTemplateData,
labels,
instanceGroups: [],
};
formik.setState({ values }, () => resolve());
});
await changeState;
await wrapper.find('JobTemplateForm').invoke('handleSubmit')(
jobTemplateData
);
await sleep(0);
expect(history.location.pathname).toEqual(
'/templates/job_template/1/details'
);

View File

@ -184,11 +184,9 @@ describe('<JobTemplateEdit />', () => {
<JobTemplateEdit template={mockJobTemplate} />
);
});
await waitForElement(wrapper, 'JobTemplateForm', e => e.length === 1);
const updatedTemplateData = {
name: 'new name',
description: 'new description',
job_type: 'check',
inventory: 1,
};
const labels = [
{ id: 3, name: 'Foo', isNew: true },
@ -196,32 +194,35 @@ describe('<JobTemplateEdit />', () => {
{ id: 5, name: 'Maple' },
{ id: 6, name: 'Tree' },
];
JobTemplatesAPI.update.mockResolvedValue({
data: { ...updatedTemplateData },
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
act(() => {
wrapper.find('input#template-name').simulate('change', {
target: { value: 'new name', name: 'name' },
});
wrapper.find('AnsibleSelect#template-job-type').invoke('onChange')(
'check'
);
wrapper.find('InventoryLookup').invoke('onChange')({
id: 1,
organization: 1,
});
wrapper.find('LabelSelect').invoke('onChange')(labels);
});
const formik = wrapper.find('Formik').instance();
const changeState = await act(
() =>
new Promise(resolve => {
const values = {
...mockJobTemplate,
...updatedTemplateData,
labels,
instanceGroups: [],
};
formik.setState({ values }, () => resolve());
})
);
await changeState;
wrapper.update();
await act(async () => {
wrapper.find('button[aria-label="Save"]').simulate('click');
});
await sleep(0);
expect(JobTemplatesAPI.update).toHaveBeenCalledWith(1, {
const expected = {
...mockJobTemplate,
...updatedTemplateData,
});
become_enabled: false,
};
delete expected.summary_fields;
delete expected.id;
delete expected.type;
expect(JobTemplatesAPI.update).toHaveBeenCalledWith(1, expected);
expect(JobTemplatesAPI.disassociateLabel).toHaveBeenCalledTimes(2);
expect(JobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(2);
expect(JobTemplatesAPI.generateLabel).toHaveBeenCalledTimes(2);

View File

@ -205,7 +205,8 @@ class JobTemplateForm extends Component {
name="job_type"
validate={required(null, i18n)}
onBlur={handleBlur}
render={({ form, field }) => {
>
{({ form, field }) => {
const isValid = !form.touched.job_type || !form.errors.job_type;
return (
<FormGroup
@ -230,11 +231,12 @@ class JobTemplateForm extends Component {
</FormGroup>
);
}}
/>
</Field>
<Field
name="inventory"
validate={required(i18n._(t`Select a value for this field`), i18n)}
render={({ form }) => (
>
{({ form }) => (
<InventoryLookup
value={inventory}
onBlur={() => form.setFieldTouched('inventory')}
@ -252,11 +254,9 @@ class JobTemplateForm extends Component {
error={form.errors.inventory}
/>
)}
/>
<Field
name="project"
validate={this.handleProjectValidation()}
render={({ form }) => (
</Field>
<Field name="project" validate={this.handleProjectValidation()}>
{({ form }) => (
<ProjectLookup
value={project}
onBlur={() => form.setFieldTouched('project')}
@ -268,12 +268,13 @@ class JobTemplateForm extends Component {
required
/>
)}
/>
</Field>
<Field
name="playbook"
validate={required(i18n._(t`Select a value for this field`), i18n)}
onBlur={handleBlur}
render={({ field, form }) => {
>
{({ field, form }) => {
const isValid = !form.touched.playbook || !form.errors.playbook;
return (
<FormGroup
@ -299,12 +300,11 @@ class JobTemplateForm extends Component {
</FormGroup>
);
}}
/>
</Field>
</FormRow>
<FormRow>
<Field
name="labels"
render={({ field }) => (
<Field name="labels">
{({ field }) => (
<FormGroup label={i18n._(t`Labels`)} fieldId="template-labels">
<FieldTooltip
content={i18n._(t`Optional labels that describe this job template,
@ -318,13 +318,11 @@ class JobTemplateForm extends Component {
/>
</FormGroup>
)}
/>
</Field>
</FormRow>
<FormRow>
<Field
name="credentials"
fieldId="template-credentials"
render={({ field }) => (
<Field name="credentials" fieldId="template-credentials">
{({ field }) => (
<MultiCredentialsLookup
value={field.value}
onChange={newCredentials =>
@ -336,7 +334,7 @@ class JobTemplateForm extends Component {
)}
/>
)}
/>
</Field>
</FormRow>
<AdvancedFieldsWrapper label="Advanced">
<FormRow>
@ -369,9 +367,8 @@ class JobTemplateForm extends Component {
playbook. Multiple patterns are allowed. Refer to Ansible
documentation for more information and examples on patterns.`)}
/>
<Field
name="verbosity"
render={({ field }) => (
<Field name="verbosity">
{({ field }) => (
<FormGroup
fieldId="template-verbosity"
label={i18n._(t`Verbosity`)}
@ -387,7 +384,7 @@ class JobTemplateForm extends Component {
/>
</FormGroup>
)}
/>
</Field>
<FormField
id="template-job-slicing"
name="job_slice_count"
@ -408,9 +405,8 @@ class JobTemplateForm extends Component {
before the task is canceled. Defaults to 0 for no job
timeout.`)}
/>
<Field
name="diff_mode"
render={({ field, form }) => (
<Field name="diff_mode">
{({ field, form }) => (
<FormGroup
fieldId="template-show-changes"
label={i18n._(t`Show Changes`)}
@ -432,11 +428,10 @@ class JobTemplateForm extends Component {
</div>
</FormGroup>
)}
/>
</Field>
</FormRow>
<Field
name="instanceGroups"
render={({ field, form }) => (
<Field name="instanceGroups">
{({ field, form }) => (
<InstanceGroupsLookup
css="margin-top: 20px"
value={field.value}
@ -445,10 +440,9 @@ class JobTemplateForm extends Component {
to run on.`)}
/>
)}
/>
<Field
name="job_tags"
render={({ field, form }) => (
</Field>
<Field name="job_tags">
{({ field, form }) => (
<FormGroup
label={i18n._(t`Job Tags`)}
css="margin-top: 20px"
@ -467,10 +461,9 @@ class JobTemplateForm extends Component {
/>
</FormGroup>
)}
/>
<Field
name="skip_tags"
render={({ field, form }) => (
</Field>
<Field name="skip_tags">
{({ field, form }) => (
<FormGroup
label={i18n._(t`Skip Tags`)}
css="margin-top: 20px"
@ -489,7 +482,7 @@ class JobTemplateForm extends Component {
/>
</FormGroup>
)}
/>
</Field>
<GridFormGroup
fieldId="template-option-checkboxes"
isInline

View File

@ -128,43 +128,59 @@ describe('<JobTemplateForm />', () => {
});
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
const form = wrapper.find('Formik');
wrapper.find('input#template-name').simulate('change', {
target: { value: 'new foo', name: 'name' },
});
expect(form.state('values').name).toEqual('new foo');
wrapper.find('input#template-description').simulate('change', {
target: { value: 'new bar', name: 'description' },
});
expect(form.state('values').description).toEqual('new bar');
wrapper.find('AnsibleSelect[name="job_type"]').simulate('change', {
target: { value: 'new job type', name: 'job_type' },
});
expect(form.state('values').job_type).toEqual('new job type');
wrapper.find('InventoryLookup').invoke('onChange')({
id: 3,
name: 'inventory',
});
expect(form.state('values').inventory).toEqual(3);
await act(async () => {
wrapper.find('input#template-name').simulate('change', {
target: { value: 'new foo', name: 'name' },
});
wrapper.find('input#template-description').simulate('change', {
target: { value: 'new bar', name: 'description' },
});
wrapper.find('AnsibleSelect[name="job_type"]').simulate('change', {
target: { value: 'new job type', name: 'job_type' },
});
wrapper.find('InventoryLookup').invoke('onChange')({
id: 3,
name: 'inventory',
});
wrapper.find('ProjectLookup').invoke('onChange')({
id: 4,
name: 'project',
});
});
expect(form.state('values').project).toEqual(4);
wrapper.find('AnsibleSelect[name="playbook"]').simulate('change', {
target: { value: 'new baz type', name: 'playbook' },
});
expect(form.state('values').playbook).toEqual('new baz type');
await act(async () => {
wrapper.find('AnsibleSelect[name="playbook"]').simulate('change', {
target: { value: 'new baz type', name: 'playbook' },
});
wrapper
.find('CredentialChip')
.at(0)
.prop('onClick')();
});
expect(form.state('values').credentials).toEqual([
{ id: 2, kind: 'ssh', name: 'Bar' },
wrapper.update();
expect(wrapper.find('input#template-name').prop('value')).toEqual(
'new foo'
);
expect(wrapper.find('input#template-description').prop('value')).toEqual(
'new bar'
);
expect(
wrapper.find('AnsibleSelect[name="job_type"]').prop('value')
).toEqual('new job type');
expect(wrapper.find('InventoryLookup').prop('value')).toEqual({
id: 3,
name: 'inventory',
});
expect(wrapper.find('ProjectLookup').prop('value')).toEqual({
id: 4,
name: 'project',
});
expect(
wrapper.find('AnsibleSelect[name="playbook"]').prop('value')
).toEqual('new baz type');
expect(wrapper.find('MultiCredentialsLookup').prop('value')).toEqual([
{
id: 2,
kind: 'ssh',
name: 'Bar',
},
]);
});

View File

@ -76,7 +76,8 @@ function UserForm(props) {
user_type: userType,
}}
onSubmit={handleValidateAndSubmit}
render={formik => (
>
{formik => (
<Form
autoComplete="off"
onSubmit={formik.handleSubmit}
@ -141,7 +142,8 @@ function UserForm(props) {
i18n._(t`Select a value for this field`),
i18n
)}
render={({ form }) => (
>
{({ form }) => (
<OrganizationLookup
helperTextInvalid={form.errors.organization}
isValid={
@ -156,11 +158,10 @@ function UserForm(props) {
required
/>
)}
/>
</Field>
)}
<Field
name="user_type"
render={({ form, field }) => {
<Field name="user_type">
{({ form, field }) => {
const isValid =
!form.touched.user_type || !form.errors.user_type;
return (
@ -180,7 +181,7 @@ function UserForm(props) {
</FormGroup>
);
}}
/>
</Field>
</FormRow>
<FormActionGroup
onCancel={handleCancel}
@ -188,7 +189,7 @@ function UserForm(props) {
/>
</Form>
)}
/>
</Formik>
);
}

View File

@ -1,7 +1,6 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import { sleep } from '@testUtils/testUtils';
import UserForm from './UserForm';
import { UsersAPI } from '@api';
import mockData from '../data.user.json';
@ -77,15 +76,18 @@ describe('<UserForm />', () => {
);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
const form = wrapper.find('Formik');
act(() => {
await act(async () => {
wrapper.find('OrganizationLookup').invoke('onBlur')();
wrapper.find('OrganizationLookup').invoke('onChange')({
id: 1,
name: 'organization',
});
});
expect(form.state('values').organization).toEqual(1);
wrapper.update();
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
id: 1,
name: 'organization',
});
});
test('password fields are required on add', async () => {
@ -133,8 +135,9 @@ describe('<UserForm />', () => {
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
expect(handleSubmit).not.toHaveBeenCalled();
wrapper.find('button[aria-label="Save"]').simulate('click');
await sleep(1);
await act(async () => {
wrapper.find('button[aria-label="Save"]').simulate('click');
});
expect(handleSubmit).toBeCalled();
});