add tests for feedback form, clean-up form

This commit is contained in:
seotts 2021-01-25 17:52:33 -05:00
parent f2a3dfda1b
commit 9df36b0fcf
4 changed files with 154 additions and 88 deletions

View file

@ -32,18 +32,15 @@ class MuteModal extends React.Component {
'handleNext', 'handleNext',
'handlePrevious', 'handlePrevious',
'handleGoToFeedback', 'handleGoToFeedback',
'handleFeedbackInput',
'handleFeedbackSubmit',
'handleSetFeedbackRef', 'handleSetFeedbackRef',
'handleValidSubmit',
'validateFeedback' 'validateFeedback'
]); ]);
this.numSteps = 2;
if (this.props.showWarning) { this.numSteps = this.props.showWarning ? steps.BAN_WARNING : steps.MUTE_INFO;
this.numSteps++;
}
this.state = { this.state = {
step: 0, step: 0
feedback: ''
}; };
} }
handleNext () { handleNext () {
@ -64,22 +61,16 @@ class MuteModal extends React.Component {
}); });
} }
handleFeedbackSubmit () { // called after feedback validation passes with no errors
const noError = !this.validateFeedback(this.state.feedback); handleValidSubmit (formData, formikBag) {
formikBag.setSubmitting(false); // formik makes us do this ourselves
if (noError) { /* eslint-disable no-console */
/* eslint-disable no-console */ console.log(formData.feedback);
console.log(this.state.feedback); /* eslint-enable no-console */
/* eslint-enable no-console */
this.setState({
step: steps.FEEDBACK_SENT
});
}
}
handleFeedbackInput (feedback) {
this.setState({ this.setState({
feedback: feedback step: steps.FEEDBACK_SENT
}); });
} }
@ -89,14 +80,24 @@ class MuteModal extends React.Component {
validateFeedback (feedback) { validateFeedback (feedback) {
if (feedback.length === 0) { if (feedback.length === 0) {
return 'Can\'t be empty'; return this.props.intl.formatMessage({id: 'comments.muted.feedbackEmpty'});
} }
return null; return null;
} }
render () { render () {
const finalStep = this.showWarning ? steps.BAN_WARNING : steps.MUTE_INFO; const feedbackPrompt = (
<p className="feedback-prompt">
<FormattedMessage
id="comments.muted.mistake"
values={{feedbackLink: (
<a onClick={this.handleGoToFeedback}>
<FormattedMessage id="comments.muted.feedbackLinkText" />
</a>
)}}
/>
</p>
);
return ( return (
<Modal <Modal
@ -142,16 +143,7 @@ class MuteModal extends React.Component {
)}} )}}
/> />
</p> </p>
<p> {this.state.step === this.numSteps ? feedbackPrompt : null}
<FormattedMessage
id="comments.muted.mistake"
values={{feedbackLink: (
<a onClick={this.handleGoToFeedback}>
<FormattedMessage id="comments.muted.feedbackLinkText" />
</a>
)}}
/>
</p>
</MuteStep> </MuteStep>
<MuteStep <MuteStep
bottomImg="/svgs/commenting/warning.svg" bottomImg="/svgs/commenting/warning.svg"
@ -168,6 +160,7 @@ class MuteModal extends React.Component {
)}} )}}
/> />
</p> </p>
{this.state.step === this.numSteps ? feedbackPrompt : null}
</MuteStep> </MuteStep>
<MuteStep <MuteStep
header={this.props.intl.formatMessage({id: 'comments.muted.mistakeHeader'})} header={this.props.intl.formatMessage({id: 'comments.muted.mistakeHeader'})}
@ -182,43 +175,49 @@ class MuteModal extends React.Component {
validate={this.validateFeedback} validate={this.validateFeedback}
validateOnBlur={false} validateOnBlur={false}
validateOnChange={false} validateOnChange={false}
onSubmit={this.handleValidSubmit}
> >
{props => { {props => {
const { const {
errors, errors,
handleSubmit,
setFieldError, setFieldError,
setFieldTouched, setFieldTouched,
setFieldValue, setFieldValue,
validateField validateField
} = props; } = props;
return ( return (
<FormikInput <form
autoCapitalize="off" id="feedback-form"
autoComplete="off" onSubmit={handleSubmit}
autoCorrect="off" >
className={classNames( <FormikInput
'compose-feedback', autoCapitalize="off"
)} autoComplete="off"
component="textarea" autoCorrect="off"
error={errors.feedback} className={classNames(
id="feedback" 'compose-feedback',
maxLength={MAX_FEEDBACK_LENGTH} )}
name="feedback" component="textarea"
rows={5} error={errors.feedback}
type="text" id="feedback"
validate={this.validateFeedback} maxLength={MAX_FEEDBACK_LENGTH}
validationClassName="validation-full-width-input" name="feedback"
/* eslint-disable react/jsx-no-bind */ rows={5}
onBlur={() => validateField('feedback')} type="text"
onChange={e => { validate={this.validateFeedback}
setFieldValue('feedback', e.target.value); validationClassName="validation-full-width-input"
setFieldTouched('feedback'); /* eslint-disable react/jsx-no-bind */
setFieldError('feedback', null); onBlur={() => validateField('feedback')}
this.handleFeedbackInput(e.target.value); onChange={e => {
}} setFieldValue('feedback', e.target.value);
/* eslint-enable react/jsx-no-bind */ setFieldTouched('feedback');
onSetRef={this.handleSetFeedbackRef} setFieldError('feedback', null);
/> }}
/* eslint-enable react/jsx-no-bind */
onSetRef={this.handleSetFeedbackRef}
/>
</form>
); );
}} }}
</Formik> </Formik>
@ -242,7 +241,7 @@ class MuteModal extends React.Component {
this.state.step === steps.USER_FEEDBACK ? 'feedback-nav' : 'mute-nav' this.state.step === steps.USER_FEEDBACK ? 'feedback-nav' : 'mute-nav'
)} )}
> >
{this.state.step >= finalStep ? ( {this.state.step >= this.numSteps ? (
<Button <Button
className={classNames('close-button')} className={classNames('close-button')}
onClick={this.props.onRequestClose} onClick={this.props.onRequestClose}
@ -281,7 +280,8 @@ class MuteModal extends React.Component {
className={classNames( className={classNames(
'send-button', 'send-button',
)} )}
onClick={this.handleFeedbackSubmit} form="feedback-form"
type="submit"
> >
<div className="action-button-text"> <div className="action-button-text">
<FormattedMessage id="general.send" /> <FormattedMessage id="general.send" />

View file

@ -60,16 +60,9 @@
padding: 24px; padding: 24px;
button { button {
// min-width: 100px;
margin: 0 4px; margin: 0 4px;
} }
// .action-button-text {
// span {
// text-align: center;
// }
// }
.close-button { .close-button {
background-color: $ui-dark-gray; background-color: $ui-dark-gray;
} }
@ -87,16 +80,15 @@
.feedback-text { .feedback-text {
text-align: center; text-align: center;
max-width: 360px;
} }
textarea, .row-with-tooltip { #feedback-form, textarea {
height: 180px; height: 180px;
width: 100%; width: 100%;
} }
textarea { textarea {
padding: 16px; padding: 1rem;
} }
.character-limit { .character-limit {
@ -104,7 +96,7 @@
} }
.validation-message { .validation-message {
top: 30%; top: 52px;
margin-left: 4rem; left: 36px;
} }
} }

View file

@ -365,6 +365,7 @@
"comments.muted.thanksFeedback": "Thanks for letting us know!", "comments.muted.thanksFeedback": "Thanks for letting us know!",
"comments.muted.thanksInfo": "Your feedback will help us make Scratch better.", "comments.muted.thanksInfo": "Your feedback will help us make Scratch better.",
"comments.muted.characterLimit": "500 characters max", "comments.muted.characterLimit": "500 characters max",
"comments.muted.feedbackEmpty": "Can't be empty",
"social.embedLabel": "Embed", "social.embedLabel": "Embed",
"social.copyEmbedLinkText": "Copy embed", "social.copyEmbedLinkText": "Copy embed",

View file

@ -33,19 +33,22 @@ describe('MuteModalTest', () => {
expect(component.find('button.back-button').exists()).toEqual(false); expect(component.find('button.back-button').exists()).toEqual(false);
}); });
// test('Mute Modal shows extra showWarning step', () => { test('Mute Modal shows extra showWarning step', () => {
// const component = mountWithIntl( const component = mountWithIntl(
// <MuteModal <MuteModal
// showWarning showWarning
// muteModalMessages={defaultMessages} muteModalMessages={defaultMessages}
// /> />
// ); );
// component.find('MuteModal').instance() component.find('MuteModal').instance()
// .setState({step: 2}); .setState({step: 1});
// component.update(); expect(component.find('button.next-button').exists()).toEqual(true);
// expect(component.find('MuteStep').prop('bottomImg')).toEqual('/svgs/commenting/warning.svg'); expect(component.find('button.next-button').getElements()[0].props.onClick)
// expect(component.find('MuteStep').prop('totalSteps')).toEqual(3); .toEqual(component.find('MuteModal').instance().handleNext);
// }); component.find('MuteModal').instance()
.handleNext();
expect(component.find('MuteModal').instance().state.step).toEqual(2);
});
test('Mute Modal shows back & close button on last step', () => { test('Mute Modal shows back & close button on last step', () => {
const component = mountWithIntl( const component = mountWithIntl(
@ -113,4 +116,74 @@ describe('MuteModalTest', () => {
component.instance().handlePrevious(); component.instance().handlePrevious();
expect(component.instance().state.step).toBe(0); expect(component.instance().state.step).toBe(0);
}); });
test('Mute modal asks for feedback', () => {
const component = mountWithIntl(
<MuteModal muteModalMessages={defaultMessages} />
);
component.find('MuteModal').instance()
.setState({step: 1});
component.update();
expect(component.find('p.feedback-prompt').exists()).toEqual(true);
});
test('Mute modal asks for feedback on extra showWarning step', () => {
const component = mountWithIntl(
<MuteModal
showWarning
muteModalMessages={defaultMessages}
/>
);
component.find('MuteModal').instance()
.setState({step: 1});
component.update();
expect(component.find('p.feedback-prompt').exists()).toEqual(false);
component.find('MuteModal').instance()
.setState({step: 2});
component.update();
expect(component.find('p.feedback-prompt').exists()).toEqual(true);
});
test('Mute modal handle go to feedback', () => {
const component = shallowWithIntl(
<MuteModal
muteModalMessages={defaultMessages}
/>
).dive();
component.instance().handleGoToFeedback();
expect(component.instance().state.step).toBe(3);
});
test('Mute modal empty feedback invalid', () => {
const component = shallowWithIntl(
<MuteModal
muteModalMessages={defaultMessages}
/>
).dive();
const emptyError = 'comments.muted.feedbackEmpty';
expect(component.instance().validateFeedback('')).toBe(emptyError);
});
test('Mute modal non-empty feedback valid', () => {
const component = shallowWithIntl(
<MuteModal
muteModalMessages={defaultMessages}
/>
).dive();
expect(component.instance().validateFeedback('some feedback here')).toBeNull();
});
test('Mute modal submit feedback gives thank you step', () => {
const component = shallowWithIntl(
<MuteModal
muteModalMessages={defaultMessages}
/>
).dive();
const mockFormikBag = {};
mockFormikBag.setSubmitting = jest.fn();
component.instance().handleValidSubmit({feedback: 'something'}, mockFormikBag);
expect(component.instance().state.step).toBe(4);
});
}); });