fixes bug where one can launch erronously, adds tests for that bug

This commit is contained in:
Alex Corey
2020-10-07 12:04:18 -04:00
parent f051c4d58a
commit b04be850b5
4 changed files with 55 additions and 4 deletions

View File

@@ -1,13 +1,26 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons';
import { Tooltip } from '@patternfly/react-core';
import { withFormik, useFormikContext } from 'formik'; import { withFormik, useFormikContext } from 'formik';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'styled-components';
import Wizard from '../Wizard'; import Wizard from '../Wizard';
import AdHocCredentialStep from './AdHocCredentialStep'; import AdHocCredentialStep from './AdHocCredentialStep';
import AdHocDetailsStep from './AdHocDetailsStep'; import AdHocDetailsStep from './AdHocDetailsStep';
const AlertText = styled.div`
color: var(--pf-global--danger-color--200);
font-weight: var(--pf-global--FontWeight--bold);
`;
const ExclamationCircleIcon = styled(PFExclamationCircleIcon)`
margin-left: 10px;
color: var(--pf-global--danger-color--100);
`;
function AdHocCommandsWizard({ function AdHocCommandsWizard({
onLaunch, onLaunch,
i18n, i18n,
@@ -19,7 +32,7 @@ function AdHocCommandsWizard({
const [currentStepId, setCurrentStepId] = useState(1); const [currentStepId, setCurrentStepId] = useState(1);
const [enableLaunch, setEnableLaunch] = useState(false); const [enableLaunch, setEnableLaunch] = useState(false);
const { values } = useFormikContext(); const { values, errors, touched } = useFormikContext();
const enabledNextOnDetailsStep = () => { const enabledNextOnDetailsStep = () => {
if (!values.module_name) { if (!values.module_name) {
@@ -36,11 +49,26 @@ function AdHocCommandsWizard({
} }
return undefined; // makes the linter happy; return undefined; // makes the linter happy;
}; };
const hasDetailsStepError = errors.module_args && touched.module_args;
const steps = [ const steps = [
{ {
id: 1, id: 1,
key: 1, key: 1,
name: i18n._(t`Details`), name: hasDetailsStepError ? (
<AlertText>
{i18n._(t`Details`)}
<Tooltip
position="right"
content={i18n._(t`This step contains errors`)}
trigger="click mouseenter focus"
>
<ExclamationCircleIcon />
</Tooltip>
</AlertText>
) : (
i18n._(t`Details`)
),
component: ( component: (
<AdHocDetailsStep <AdHocDetailsStep
moduleOptions={moduleOptions} moduleOptions={moduleOptions}
@@ -60,7 +88,7 @@ function AdHocCommandsWizard({
onEnableLaunch={() => setEnableLaunch(true)} onEnableLaunch={() => setEnableLaunch(true)}
/> />
), ),
enableNext: enableLaunch, enableNext: enableLaunch && Object.values(errors).length === 0,
nextButtonText: i18n._(t`Launch`), nextButtonText: i18n._(t`Launch`),
canJumpTo: currentStepId >= 2, canJumpTo: currentStepId >= 2,
}, },

View File

@@ -148,6 +148,20 @@ describe('<AdHocCommandsWizard/>', () => {
expect(onLaunch).toHaveBeenCalled(); expect(onLaunch).toHaveBeenCalled();
}); });
test('should show error in navigation bar', async () => {
await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0);
await act(async () => {
wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(
{},
'command'
);
wrapper.find('input#module_args').simulate('change', {
target: { value: '', name: 'module_args' },
});
});
waitForElement(wrapper, 'ExclamationCircleIcon', el => el.length > 0);
});
test('expect credential step to throw error', async () => { test('expect credential step to throw error', async () => {
CredentialsAPI.read.mockRejectedValue( CredentialsAPI.read.mockRejectedValue(

View File

@@ -65,6 +65,7 @@ function AdHocCredentialStep({ i18n, credentialTypeId, onEnableLaunch }) {
<FormGroup <FormGroup
fieldId="credential" fieldId="credential"
label={i18n._(t`Machine Credential`)} label={i18n._(t`Machine Credential`)}
aria-label={i18n._(t`Machine Credential`)}
isRequired isRequired
validated={ validated={
!credentialMeta.touched || !credentialMeta.error ? 'default' : 'error' !credentialMeta.touched || !credentialMeta.error ? 'default' : 'error'

View File

@@ -47,7 +47,7 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
moduleNameField.value === 'command' || moduleNameField.value === 'shell'; moduleNameField.value === 'command' || moduleNameField.value === 'shell';
const [, argumentsMeta, argumentsHelpers] = useField({ const [, argumentsMeta, argumentsHelpers] = useField({
name: 'module_args', name: 'module_args',
validate: argumentsRequired ? required(null, i18n) : null, validate: argumentsRequired && required(null, i18n),
}); });
const isValid = !argumentsMeta.error || !argumentsMeta.touched; const isValid = !argumentsMeta.error || !argumentsMeta.touched;
@@ -58,6 +58,7 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
<FormFullWidthLayout> <FormFullWidthLayout>
<FormGroup <FormGroup
fieldId="module_name" fieldId="module_name"
aria-label={i18n._(t`Module`)}
label={i18n._(t`Module`)} label={i18n._(t`Module`)}
isRequired isRequired
helperTextInvalid={moduleNameMeta.error} helperTextInvalid={moduleNameMeta.error}
@@ -103,9 +104,11 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
<FormField <FormField
id="module_args" id="module_args"
name="module_args" name="module_args"
aria-label={i18n._(t`Arguments`)}
type="text" type="text"
label={i18n._(t`Arguments`)} label={i18n._(t`Arguments`)}
validated={isValid ? 'default' : 'error'} validated={isValid ? 'default' : 'error'}
onBlur={() => argumentsHelpers.setTouched(true)}
placeholder={i18n._(t`Enter arguments`)} placeholder={i18n._(t`Enter arguments`)}
isRequired={ isRequired={
moduleNameField.value === 'command' || moduleNameField.value === 'command' ||
@@ -133,6 +136,7 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
/> />
<FormGroup <FormGroup
fieldId="verbosity" fieldId="verbosity"
aria-label={i18n._(t`Verbosity`)}
label={i18n._(t`Verbosity`)} label={i18n._(t`Verbosity`)}
isRequired isRequired
validated={ validated={
@@ -164,6 +168,7 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
name="limit" name="limit"
type="text" type="text"
label={i18n._(t`Limit`)} label={i18n._(t`Limit`)}
aria-label={i18n._(t`Limit`)}
tooltip={ tooltip={
<span> <span>
{i18n._( {i18n._(
@@ -185,6 +190,7 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
type="number" type="number"
min="0" min="0"
label={i18n._(t`Forks`)} label={i18n._(t`Forks`)}
aria-label={i18n._(t`Forks`)}
tooltip={ tooltip={
<span> <span>
{i18n._( {i18n._(
@@ -203,6 +209,7 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
<FormColumnLayout> <FormColumnLayout>
<FormGroup <FormGroup
label={i18n._(t`Show changes`)} label={i18n._(t`Show changes`)}
aria-label={i18n._(t`Show changes`)}
labelIcon={ labelIcon={
<FieldTooltip <FieldTooltip
content={i18n._( content={i18n._(
@@ -300,6 +307,7 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
</TooltipWrapper> </TooltipWrapper>
} }
label={i18n._(t`Extra variables`)} label={i18n._(t`Extra variables`)}
aria-label={i18n._(t`Extra variables`)}
/> />
</FormFullWidthLayout> </FormFullWidthLayout>
</FormColumnLayout> </FormColumnLayout>