fix(client): add validation to profile image URL (#41927)
* fix: fix broken image url after validation * feat: add tests for a broken image url submission * fix: reduce event related calls * Update cypress/integration/settings/image-picture-check.js Co-authored-by: Sem Bauke <46919888+Sembauke@users.noreply.github.com> * Update cypress/integration/settings/image-picture-check.js Co-authored-by: Ahmad Abdolsaheb <ahmad.abdolsaheb@gmail.com> Co-authored-by: Sem Bauke <46919888+Sembauke@users.noreply.github.com>
This commit is contained in:
@ -3,7 +3,9 @@ import PropTypes from 'prop-types';
|
|||||||
import {
|
import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
ControlLabel,
|
ControlLabel,
|
||||||
FormControl
|
FormControl,
|
||||||
|
HelpBlock,
|
||||||
|
Alert
|
||||||
} from '@freecodecamp/react-bootstrap';
|
} from '@freecodecamp/react-bootstrap';
|
||||||
|
|
||||||
import { FullWidthRow, Spacer } from '../helpers';
|
import { FullWidthRow, Spacer } from '../helpers';
|
||||||
@ -28,7 +30,7 @@ const propTypes = {
|
|||||||
class AboutSettings extends Component {
|
class AboutSettings extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.validationImage = new Image();
|
||||||
const { name = '', location = '', picture = '', about = '' } = props;
|
const { name = '', location = '', picture = '', about = '' } = props;
|
||||||
const values = {
|
const values = {
|
||||||
name,
|
name,
|
||||||
@ -39,7 +41,8 @@ class AboutSettings extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
formValues: { ...values },
|
formValues: { ...values },
|
||||||
originalValues: { ...values },
|
originalValues: { ...values },
|
||||||
formClicked: false
|
formClicked: false,
|
||||||
|
isPictureUrlValid: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,18 +72,25 @@ class AboutSettings extends Component {
|
|||||||
|
|
||||||
isFormPristine = () => {
|
isFormPristine = () => {
|
||||||
const { formValues, originalValues } = this.state;
|
const { formValues, originalValues } = this.state;
|
||||||
return Object.keys(originalValues)
|
return (
|
||||||
|
this.state.isPictureUrlValid === false ||
|
||||||
|
Object.keys(originalValues)
|
||||||
.map(key => originalValues[key] === formValues[key])
|
.map(key => originalValues[key] === formValues[key])
|
||||||
.every(bool => bool);
|
.every(bool => bool)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { formValues } = this.state;
|
const { formValues } = this.state;
|
||||||
const { submitNewAbout } = this.props;
|
const { submitNewAbout } = this.props;
|
||||||
|
if (this.state.isPictureUrlValid === true) {
|
||||||
return this.setState({ formClicked: true }, () =>
|
return this.setState({ formClicked: true }, () =>
|
||||||
submitNewAbout(formValues)
|
submitNewAbout(formValues)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleNameChange = e => {
|
handleNameChange = e => {
|
||||||
@ -103,8 +113,22 @@ class AboutSettings extends Component {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.validationImage.addEventListener('error', this.errorEvent);
|
||||||
|
this.validationImage.addEventListener('load', this.loadEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.validationImage.removeEventListener('load', this.loadEvent);
|
||||||
|
this.validationImage.removeEventListener('error', this.errorEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadEvent = () => this.setState({ isPictureUrlValid: true });
|
||||||
|
errorEvent = () => this.setState({ isPictureUrlValid: false });
|
||||||
|
|
||||||
handlePictureChange = e => {
|
handlePictureChange = e => {
|
||||||
const value = e.target.value.slice(0);
|
const value = e.target.value.slice(0);
|
||||||
|
this.validationImage.src = value;
|
||||||
return this.setState(state => ({
|
return this.setState(state => ({
|
||||||
formValues: {
|
formValues: {
|
||||||
...state.formValues,
|
...state.formValues,
|
||||||
@ -113,6 +137,19 @@ class AboutSettings extends Component {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
showImageValidationWarning = () => {
|
||||||
|
const { t } = this.props;
|
||||||
|
if (this.state.isPictureUrlValid === false) {
|
||||||
|
return (
|
||||||
|
<HelpBlock>
|
||||||
|
<Alert bsStyle='info'>{t('validation.url-not-image')}</Alert>
|
||||||
|
</HelpBlock>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
handleAboutChange = e => {
|
handleAboutChange = e => {
|
||||||
const value = e.target.value.slice(0);
|
const value = e.target.value.slice(0);
|
||||||
return this.setState(state => ({
|
return this.setState(state => ({
|
||||||
@ -164,6 +201,7 @@ class AboutSettings extends Component {
|
|||||||
type='url'
|
type='url'
|
||||||
value={picture}
|
value={picture}
|
||||||
/>
|
/>
|
||||||
|
{this.showImageValidationWarning()}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup controlId='about-about'>
|
<FormGroup controlId='about-about'>
|
||||||
<ControlLabel>
|
<ControlLabel>
|
||||||
|
39
cypress/integration/settings/image-picture-check.js
Normal file
39
cypress/integration/settings/image-picture-check.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/* global cy */
|
||||||
|
|
||||||
|
describe('Picture input field', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.login();
|
||||||
|
cy.visit('/settings');
|
||||||
|
// Setting aliases here
|
||||||
|
cy.get('input#about-picture').as('pictureInput');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be possible to type', () => {
|
||||||
|
cy.get('@pictureInput')
|
||||||
|
.clear({ force: true })
|
||||||
|
.type('twaha', { force: true })
|
||||||
|
.should('have.attr', 'value', 'twaha');
|
||||||
|
});
|
||||||
|
it('Show an error message if an incorrect url was submitted', () => {
|
||||||
|
cy.get('@pictureInput')
|
||||||
|
.clear({ force: true })
|
||||||
|
.type('https://s3.amazonaws.com/freecodecamp/camper-image', {
|
||||||
|
force: true
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
cy.contains('URL must link directly to an image file');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Can submit a correct URL', () => {
|
||||||
|
cy.get('@pictureInput')
|
||||||
|
.clear({ force: true })
|
||||||
|
.type(
|
||||||
|
'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png',
|
||||||
|
{
|
||||||
|
force: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
cy.wait(500);
|
||||||
|
cy.get('#camper-identity > .btn').should('not.be.disabled');
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user