Browse Source

Fixed the validation on submit. Added simple animation.

Snow 8 years ago
parent
commit
425c8d27b9
3 changed files with 129 additions and 95 deletions
  1. 10 15
      src/__test__/Form.test.js
  2. 118 80
      src/components/Form/index.js
  3. 1 0
      src/components/Form/style.css

+ 10 - 15
src/__test__/Form.test.js

@@ -1,5 +1,5 @@
 import React from 'react';
-import Form, { Warning } from '../components/Form';
+import Form from '../components/Form';
 import { mount, shallow } from 'enzyme';
 
 describe('Place holder', () => {
@@ -51,18 +51,7 @@ describe('Place holder', () => {
 });
 
 describe('Validation', () => {
-  it('Display a warning when isValid prop is false', () => {
-    const warning1 = shallow(
-      <Warning isValid={false} validationType="length" />
-    );
-    expect(warning1.find('span.warning').length).toBe(1);
-    const warning2 = shallow(
-      <Warning isValid={true} validationType="length" />
-    );
-    expect(warning2.find('span.warning').length).toBe(0);
-  });
-
-  it('Exceeding the min length shows a warning on submit', () => {
+  it('Not meeting the min length shows a warning on blur', () => {
     const formWindow = mount(<Form />);
     const inputField = formWindow.find('input').at(0);
     const name = inputField.prop('name');
@@ -76,7 +65,6 @@ describe('Validation', () => {
 
   it('Unmatched password shows a warning on blur', () => {
     const formWindow = mount(<Form />);
-    formWindow.find('input').last().simulate('submit');
     const inputField = formWindow.find('input').at(1);
     expect(inputField.prop('type')).toBe('password');
     const name = inputField.prop('name');
@@ -85,6 +73,13 @@ describe('Validation', () => {
       target: { name: name, value: newValue }
     });
     inputField.simulate('blur');
-    expect(formWindow.find('.warning').length).toBe(1);
+    expect(formWindow.find('.warning').length).toBeTruthy();
+  });
+  
+   it('Submitting an empty form shows all warnings', () => {
+    const formWindow = mount(<Form />);
+    formWindow.simulate('submit');
+    // two warnings on two pwd input fields
+     expect(formWindow.find('.warning').length).toBeTruthy();
   });
 });

+ 118 - 80
src/components/Form/index.js

@@ -1,84 +1,97 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Link } from 'react-router-dom';
+import { Motion, spring } from 'react-motion';
 import './style.css';
 
-const Warning = ({ isValid, validationType, length }) => {
-  switch (validationType) {
-    case 'length':
-      return isValid === undefined || isValid
-        ? null
-        : <span className="warning">
-            This field should at least have {length} characters.
-          </span>;
-    case 'passwordMatch':
-      return isValid === undefined || isValid
-        ? null
-        : <span className="warning">
-            Password don't match.
-          </span>;
-    default:
-      return null;
-  }
-};
-
-export { Warning };
-
-Warning.propTypes = {
-  isValid: PropTypes.bool,
-  validationType: PropTypes.string.isRequired,
-  length: PropTypes.number
-};
-
 export default class Form extends React.Component {
   static placeHolder = {
-    name: { message: 'Enter your name', color: 'grey' },
-    password: { message: '12345678', color: 'grey', isValid: true },
-    password2: { message: '12345678', color: 'grey' },
-    comments: { message: 'Enter your comment', color: 'grey' }
+    name: { message: 'Enter your name', color: 'grey', warnings: [] },
+    password: { message: '12345678', color: 'grey', warnings: [] },
+    password2: { message: '12345678', color: 'grey', warnings: [] },
+    comments: { message: 'Enter your comment', color: 'grey', warnings: [] }
   };
 
   state = {
     ...this.constructor.placeHolder
   };
 
-  // validateCenter = {
-  //   name: this.validateHelper.length('name', 5),
-  //   password2: this.validateHelper.passwordMatch('password'),
-  //   comments: this.validateHelper.length('comments', 20),};
+  validateRequired = name => {
+    const warning = (
+            <Motion defaultStyle={{x:0}} style={{ x: spring(1) }}>
+        {({x}) =>
+          <span className="warning" key="isRequired" style={{opacity:x}}>
+        This field is required.
+      </span>}
+           </Motion>
+    );
+    const color = this.state[name].color;
+    return this.state[name].message.length > 0 && color !== 'grey'
+      ? null
+      : warning;
+  };
 
   validateLength = (name, length) => {
-    const isValid = !(
-      this.state[name].message.length <= length ||
-      this.state[name].color === 'grey'
+    const warning = (
+      <Motion defaultStyle={{x:0}} style={{ x: spring(1) }}>
+        {({x}) =>
+          <span className="warning" key="minLength" style={{opacity: x}}>
+        This field should at least have {length} characters.
+      </span>}
+      </Motion>
     );
-    this.setState({
-      [name]: Object.assign({}, this.state[name], { isValid: isValid })
-    });
+    const color = this.state[name].color;
+    return this.state[name].message.length >= length && color !== 'grey'
+      ? null
+      : warning;
   };
 
-  validatePasswordMatch = () => {
-    const password1 = this.state.password.message;
-    const password2 = this.state.password2.message;
-    const isValid = !(
-      password1 !== password2 || this.state.password2.color === 'grey'
+  validatePasswordMatch = (name1, name2) => {
+    const password1 = this.state[name1].message;
+    const password2 = this.state[name2].message;
+    const warning = (
+      <span className="warning" key="pwdMatch">
+        Password don't match.
+      </span>
     );
-    this.setState({
-      password2: Object.assign({}, this.state.password2, { isValid: isValid })
-    });
+    const color1 = this.state[name1].color;
+    return password1 === password2 && color1 !== 'grey' ? null : warning;
   };
 
   handleSubmit = event => {
-    event.preventDefault();
-    // this.validateCenter();
-    // const keys = Object.keys(this.state)
-    // let isAllValid = true
-    // keys.forEach(key => {
-    // let status = (this.state[key].isValid === true) ? true : false
-    // isAllValid = status && isAllValid
-    //   })
+    // force validate all fields
+    const keys = Object.keys(this.state);
+    keys.forEach(name => {
+       const warnings = this.validateItem(name);
+if (name === 'password' || name === 'password2') {
+      // match two password input behavior
+      this.setState({
+        password: Object.assign({}, this.state.password, {
+          warnings
+        }),
+        password2: Object.assign({}, this.state.password2, {
+          warnings
+        })
+      });
+    } else {
+      this.setState({
+        [name]: Object.assign({}, this.state[name], {
+          warnings
+        })
+      });
+    }
+    });
+
+    const inValidFields = keys.filter(
+      name => this.state[name].warnings.length !== 0
+    );
+    if (inValidFields.length !== 0 || this.state.password.color === 'grey') {event.preventDefault()};
   };
 
+  wrapWarnings = (...theArgs) => {
+    const validWarnings = theArgs.filter(arg => arg !== null);
+    return validWarnings;
+  };
   handleFocus = event => {
     const target = event.target;
     const name = target.name;
@@ -93,27 +106,61 @@ export default class Form extends React.Component {
     }
   };
 
-  handleBlur = event => {
-    const target = event.target;
-    const name = target.name;
-    const message = this.constructor.placeHolder[name].message;
+  validateItem = name => {
+    let warnings;
     switch (name) {
       case 'name':
-        this.validateLength(name, 5);
+        warnings = this.wrapWarnings(
+          this.validateLength(name, 5),
+          this.validateRequired(name)
+        );
         break;
       case 'password':
       case 'password2':
-        this.validatePasswordMatch();
+        warnings = this.wrapWarnings(
+          this.validatePasswordMatch('password', 'password2'),
+          this.validateRequired(name)
+        );
         break;
       case 'comments':
-        this.validateLength(name, 10);
+        warnings = this.wrapWarnings(
+          this.validateLength(name, 20),
+          this.validateRequired(name)
+        );
+        break;
+      default:
         break;
     }
+    return warnings;
+  };
+  handleBlur = event => {
+    const target = event.target;
+    const name = target.name;
+    const message = this.constructor.placeHolder[name].message;
+
+    const warnings = this.validateItem(name);
     if (target.value.length === 0) {
       this.setState({
         [name]: Object.assign({}, this.state[name], {
           message: message,
-          color: 'grey'
+          color: 'grey',
+          warnings
+        })
+      });
+    } else if (name === 'password' || name === 'password2') {
+      // match two password input behavior
+      this.setState({
+        password: Object.assign({}, this.state.password, {
+          warnings
+        }),
+        password2: Object.assign({}, this.state.password2, {
+          warnings
+        })
+      });
+    } else {
+      this.setState({
+        [name]: Object.assign({}, this.state[name], {
+          warnings
         })
       });
     }
@@ -129,7 +176,7 @@ export default class Form extends React.Component {
   };
 
   renderInput = (type, name) => {
-    const borderColor = this.state[name].isValid === false ? 'red' : null;
+    const borderColor = this.state[name].warnings.length ? 'red' : null;
     const props = {
       type: type,
       name: name,
@@ -156,32 +203,23 @@ export default class Form extends React.Component {
           Name:
         </label>
         {this.renderInput('text', 'name')}
-        <Warning
-          isValid={this.state.name.isValid}
-          validationType="length"
-          length={5}
-        />
+        {this.state.name.warnings}
         <label htmlFor="password">
           Password:
         </label>
         {this.renderInput('password', 'password')}
+        {this.state.password.warnings}
         <label htmlFor="password2">
           Password:
         </label>
         {this.renderInput('password', 'password2')}
-        <Warning
-          isValid={this.state.password2.isValid}
-          validationType="passwordMatch"
-        />
+        {this.state.password2.warnings}
+
         <label htmlFor="comments">
           Comments:
         </label>
         {this.renderInput('textarea', 'comments')}
-        <Warning
-          isValid={this.state.comments.isValid}
-          validationType="length"
-          length={20}
-        />
+        {this.state.comments.warnings}
         <input type="submit" value="Submit" />
       </form>
     );

+ 1 - 0
src/components/Form/style.css

@@ -34,6 +34,7 @@ label {
     display: block;
     width: 400px;
     color: red;
+    font-size: 80%;
 }
 
 input[type=submit] {