diff --git a/awx/ui_next/src/components/FormField/TextAndCheckboxField.jsx b/awx/ui_next/src/components/FormField/TextAndCheckboxField.jsx index e3ebe93acb..4d7a1c6f43 100644 --- a/awx/ui_next/src/components/FormField/TextAndCheckboxField.jsx +++ b/awx/ui_next/src/components/FormField/TextAndCheckboxField.jsx @@ -1,16 +1,19 @@ -import React, { useState } from 'react'; -import { useField, useFormikContext } from 'formik'; +import React from 'react'; +import { useField } from 'formik'; import { t } from '@lingui/macro'; -import { FormGroup, TextInput, Button } from '@patternfly/react-core'; +import { + FormGroup, + TextInput, + Button, + InputGroup as PFInputGroup, + Tooltip, +} from '@patternfly/react-core'; import PFCheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon'; import styled from 'styled-components'; import Popover from '../Popover'; -const InputWrapper = styled.span` - && { - display: flex; - padding-bottom: 5px; - } +const InputGroup = styled(PFInputGroup)` + padding-bottom: 5px; `; const CheckIcon = styled(PFCheckIcon)` color: var(--pf-c-button--m-plain--disabled--Color); @@ -18,34 +21,17 @@ const CheckIcon = styled(PFCheckIcon)` props.isSelected && `color: var(--pf-c-button--m-secondary--active--Color)`}; `; -function TextAndCheckboxField({ - id, - label, - helperText, - isRequired, - isValid, - tooltip, - name, - rows, - ...rest -}) { - const { values: formikValues } = useFormikContext(); +function TextAndCheckboxField({ label, helperText, tooltip }) { const [choicesField, choicesMeta, choicesHelpers] = useField('choices'); - // const [fields, setFields] = useState(choicesField.value.split('\n')); - // const [defaultValue, setDefaultValue] = useState( - // formikValues.default.split('\n') - // ); - const [, , defaultHelpers] = useField('default'); - - const [isNewValueChecked, setIsNewValueChecked] = useState(false); - console.log('set'); + const [typeField] = useField('type'); + const [defaultField, , defaultHelpers] = useField('default'); const handleCheckboxChange = v => defaultSplit.includes(v) ? defaultHelpers.setValue(defaultSplit.filter(d => d !== v).join('\n')) - : defaultHelpers.setValue(formikValues.default.concat(`\n${v}`)); + : defaultHelpers.setValue(defaultField.value.concat(`\n${v}`)); const choicesSplit = choicesField.value.split('\n'); - const defaultSplit = formikValues.default.split('\n'); + const defaultSplit = defaultField.value?.split('\n'); return ( } > - {choicesSplit - .map((v, i) => ( - - { - defaultHelpers.setValue( - defaultSplit.filter(d => d !== v).join('\n') - ); + {choicesSplit.map((v, i) => ( + + { + if (e.key === 'Enter' && i === choicesSplit.length - 1) { + choicesHelpers.setValue(choicesField.value.concat('\n')); + } - const newFields = choicesSplit - .map((choice, index) => (i === index ? value : choice)) + if (e.key === 'Backspace' && v.length <= 1) { + const removeEmptyField = choicesSplit + .filter((choice, index) => index !== i) .join('\n'); + choicesHelpers.setValue(removeEmptyField); + } + }} + value={v} + onChange={value => { + defaultHelpers.setValue( + defaultSplit.filter(d => d !== v).join('\n') + ); - return value === '' - ? choicesHelpers.setValue( - choicesSplit.filter(d => d !== v).join('\n') - ) - : choicesHelpers.setValue(newFields); - }} - /> + const newFields = choicesSplit + .map((choice, index) => (i === index ? value : choice)) + .join('\n'); + + return value === '' + ? choicesHelpers.setValue( + choicesSplit.filter(d => d !== v).join('\n') + ) + : choicesHelpers.setValue(newFields); + }} + /> + - - )) - .concat( - - { - choicesHelpers.setValue([...choicesSplit, value].join('\n')); - }} - /> - - - )} + + + ))} ); } diff --git a/awx/ui_next/src/components/FormField/TextAndCheckboxField.test.jsx b/awx/ui_next/src/components/FormField/TextAndCheckboxField.test.jsx new file mode 100644 index 0000000000..5db9cc954e --- /dev/null +++ b/awx/ui_next/src/components/FormField/TextAndCheckboxField.test.jsx @@ -0,0 +1,168 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { Formik } from 'formik'; +import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; +import TextAndCheckboxField from './TextAndCheckboxField'; + +describe('', () => { + test('should activate default values, multiselect', async () => { + let wrapper; + + act(() => { + wrapper = mountWithContexts( + + + + ); + }); + await act(async () => + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(0) + .prop('onChange')('alex') + ); + wrapper.update(); + expect( + wrapper + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(false); + await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')()); + wrapper.update(); + expect( + wrapper + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(true); + await act(async () => + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(0) + .prop('onKeyDown')({ key: 'Enter' }) + ); + wrapper.update(); + expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe( + 3 + ); + await act(async () => + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(1) + .prop('onChange')('spencer') + ); + wrapper.update(); + expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe( + 3 + ); + await act(() => wrapper.find('Button[ouiaId="spencer"]').prop('onClick')()); + wrapper.update(); + expect( + wrapper + .find('Button[ouiaId="spencer"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(true); + await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')()); + wrapper.update(); + expect( + wrapper + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(false); + }); + + test('should select default, multiplechoice', async () => { + let wrapper; + + act(() => { + wrapper = mountWithContexts( + + + + ); + }); + await act(async () => + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(0) + .prop('onChange')('alex') + ); + wrapper.update(); + expect( + wrapper + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(false); + await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')()); + wrapper.update(); + expect( + wrapper + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(true); + expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe( + 3 + ); + await act(async () => + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(0) + .prop('onKeyDown')({ key: 'Enter' }) + ); + wrapper.update(); + await act(async () => + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(1) + .prop('onChange')('spencer') + ); + wrapper.update(); + expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe( + 3 + ); + await act(() => wrapper.find('Button[ouiaId="spencer"]').prop('onClick')()); + wrapper.update(); + + expect( + wrapper + .find('Button[ouiaId="spencer"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(true); + expect( + wrapper + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(false); + }); +}); diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.jsx index 99be1cffca..9eb2d6ebed 100644 --- a/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.jsx +++ b/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.jsx @@ -104,20 +104,6 @@ function SurveyQuestionForm({ handleCancel, submitError, }) { - const defaultIsNotAvailable = choices => { - return defaultValue => { - let errorMessage; - const found = [...defaultValue].every(dA => { - return choices.indexOf(dA) > -1; - }); - - if (!found) { - errorMessage = t`Default choice must be answered from the choices listed.`; - } - return errorMessage; - }; - }; - return ( )} {['multiplechoice', 'multiselect'].includes(formik.values.type) && ( - <> - - - + )} diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.test.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.test.jsx index ac52082b43..a063afabc2 100644 --- a/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.test.jsx +++ b/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.test.jsx @@ -17,12 +17,15 @@ const noop = () => {}; async function selectType(wrapper, type) { await act(async () => { - wrapper.find('AnsibleSelect#question-type').invoke('onChange')({ - target: { - name: 'type', - value: type, + wrapper.find('AnsibleSelect#question-type').invoke('onChange')( + { + target: { + name: 'type', + value: type, + }, }, - }); + type + ); }); wrapper.update(); } @@ -146,12 +149,15 @@ describe('', () => { }); await selectType(wrapper, 'multiplechoice'); - expect(wrapper.find('FormField#question-options').prop('type')).toEqual( - 'textarea' - ); - expect(wrapper.find('FormField#question-default').prop('type')).toEqual( - 'text' + expect(wrapper.find('TextAndCheckboxField').length).toBe(1); + expect(wrapper.find('TextAndCheckboxField').find('TextInput').length).toBe( + 1 ); + expect( + wrapper + .find('TextAndCheckboxField') + .find('Button[aria-label="Click to toggle default value"]').length + ).toBe(1); }); test('should provide fields for multi-select question', async () => { @@ -168,12 +174,15 @@ describe('', () => { }); await selectType(wrapper, 'multiselect'); - expect(wrapper.find('FormField#question-options').prop('type')).toEqual( - 'textarea' - ); - expect(wrapper.find('FormField#question-default').prop('type')).toEqual( - 'textarea' + expect(wrapper.find('TextAndCheckboxField').length).toBe(1); + expect(wrapper.find('TextAndCheckboxField').find('TextInput').length).toBe( + 1 ); + expect( + wrapper + .find('TextAndCheckboxField') + .find('Button[aria-label="Click to toggle default value"]').length + ).toBe(1); }); test('should provide fields for integer question', async () => { @@ -225,7 +234,7 @@ describe('', () => { wrapper.find('FormField#question-default input').prop('type') ).toEqual('number'); }); - test('should not throw validation error', async () => { + test('should activate default values, multiselect', async () => { let wrapper; act(() => { @@ -239,25 +248,71 @@ describe('', () => { }); await selectType(wrapper, 'multiselect'); await act(async () => - wrapper.find('textarea#question-options').simulate('change', { - target: { value: 'a \n b', name: 'choices' }, - }) + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(0) + .prop('onChange')('alex') ); - await act(async () => - wrapper.find('textarea#question-options').simulate('change', { - target: { value: 'b \n a', name: 'default' }, - }) - ); - wrapper.find('FormField#question-default').prop('validate')('b \n a', {}); wrapper.update(); expect( wrapper - .find('FormGroup[fieldId="question-default"]') - .prop('helperTextInvalid') - ).toBe(undefined); + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(false); + await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')()); + wrapper.update(); + expect( + wrapper + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(true); + await act(async () => + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(0) + .prop('onKeyDown')({ key: 'Enter' }) + ); + wrapper.update(); + expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe( + 2 + ); + await act(async () => + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(1) + .prop('onChange')('spencer') + ); + wrapper.update(); + expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe( + 2 + ); + await act(() => wrapper.find('Button[ouiaId="spencer"]').prop('onClick')()); + wrapper.update(); + expect( + wrapper + .find('Button[ouiaId="spencer"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(true); + await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')()); + wrapper.update(); + expect( + wrapper + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(false); }); - test('should throw validation error', async () => { + test('should select default, multiplechoice', async () => { let wrapper; act(() => { @@ -269,23 +324,68 @@ describe('', () => { /> ); }); - await selectType(wrapper, 'multiselect'); + await selectType(wrapper, 'multiplechoice'); await act(async () => - wrapper.find('textarea#question-options').simulate('change', { - target: { value: 'a \n b', name: 'choices' }, - }) + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(0) + .prop('onChange')('alex') ); - await act(async () => - wrapper.find('textarea#question-default').simulate('change', { - target: { value: 'c', name: 'default' }, - }) - ); - wrapper.find('FormField#question-default').prop('validate')('c', {}); wrapper.update(); expect( wrapper - .find('FormGroup[fieldId="question-default"]') - .prop('helperTextInvalid') - ).toBe('Default choice must be answered from the choices listed.'); + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(false); + await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')()); + wrapper.update(); + expect( + wrapper + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(true); + expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe( + 1 + ); + await act(async () => + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(0) + .prop('onKeyDown')({ key: 'Enter' }) + ); + wrapper.update(); + await act(async () => + wrapper + .find('TextAndCheckboxField') + .find('TextAndCheckboxField') + .find('TextInput') + .at(1) + .prop('onChange')('spencer') + ); + wrapper.update(); + expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe( + 2 + ); + await act(() => wrapper.find('Button[ouiaId="spencer"]').prop('onClick')()); + wrapper.update(); + + expect( + wrapper + .find('Button[ouiaId="spencer"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(true); + expect( + wrapper + .find('Button[ouiaId="alex"]') + .find('CheckIcon') + .prop('isSelected') + ).toBe(false); }); });