Skip to content

Commit

Permalink
833 refactor the create account form (#835)
Browse files Browse the repository at this point in the history
* PWA-833: refactored validators on the create account page

* PWA-833: static fixes

* PWA-833: adjusted tests for the new validation rules

* PWA-833: static fixes

* Removes asyncValidator for email field

* Password and confirm password fields are required

* Fixes new validators to return undefined instead of null on success
  • Loading branch information
Vitaliy authored and dpatil-magento committed May 10, 2019
1 parent cb059fe commit 0ed9379
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 108 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const validateEmail = jest.fn();

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ exports[`renders the correct tree 1`] = `
</div>
<div>
<span>
<span />
Password
</span>
<span
Expand Down Expand Up @@ -119,6 +120,7 @@ exports[`renders the correct tree 1`] = `
</div>
<div>
<span>
<span />
Confirm Password
</span>
<span
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { Form } from 'informed';
import { createTestInstance } from '@magento/peregrine';

import CreateAccount from '../createAccount';
import { validators } from '../validators';

jest.mock('../validators');
jest.mock('src/util/formValidators');

const submitCallback = jest.fn();
export const submitCallback = jest.fn();

test('renders the correct tree', () => {
const tree = createTestInstance(<CreateAccount />).toJSON();
Expand All @@ -34,22 +33,6 @@ test('attaches the submit handler', () => {
expect(onSubmit).toBe(instance.handleSubmit);
});

test('executes validators on submit', async () => {
const { root } = createTestInstance(<CreateAccount />);

const form = root.findByType(Form);
const { formApi } = form.instance;

// touch fields, call validators, call onSubmit
act(() => {
formApi.submitForm();
});

for (const validator of validators.values()) {
expect(validator).toHaveBeenCalledTimes(2);
}
});

test('calls onSubmit if validation passes', async () => {
const { root } = createTestInstance(
<CreateAccount onSubmit={submitCallback} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RestApi } from '@magento/peregrine';

const { request } = RestApi.Magento2;

export const validateEmail = async value => {
try {
const body = {
customerEmail: value,
website_id: null
};

// response is a boolean
const available = await request('/rest/V1/customers/isEmailAvailable', {
method: 'POST',
body: JSON.stringify(body)
});

return !available ? 'This email address is not available.' : null;
} catch (error) {
throw 'An error occurred while looking up this email address.';
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ import Button from 'src/components/Button';
import Checkbox from 'src/components/Checkbox';
import Field from 'src/components/Field';
import TextInput from 'src/components/TextInput';
import { validators } from './validators';

import combine from 'src/util/combineValidators';
import {
validateEmail,
isRequired,
validatePassword,
validateConfirmPassword,
hasLengthAtLeast
} from 'src/util/formValidators';

import defaultClasses from './createAccount.css';

class CreateAccount extends Component {
Expand Down Expand Up @@ -81,40 +90,47 @@ class CreateAccount extends Component {
<TextInput
field="customer.firstname"
autoComplete="given-name"
validate={validators.get('firstName')}
validate={isRequired}
validateOnBlur
/>
</Field>
<Field label="Last Name" required={true}>
<TextInput
field="customer.lastname"
autoComplete="family-name"
validate={validators.get('lastName')}
validate={isRequired}
validateOnBlur
/>
</Field>
<Field label="Email" required={true}>
<TextInput
field="customer.email"
autoComplete="email"
validate={validators.get('email')}
validate={combine([isRequired, validateEmail])}
validateOnBlur
/>
</Field>
<Field label="Password">
<Field label="Password" required={true}>
<TextInput
field="password"
type="password"
autoComplete="new-password"
validate={validators.get('password')}
validate={combine([
isRequired,
[hasLengthAtLeast, 8],
validatePassword
])}
validateOnBlur
/>
</Field>
<Field label="Confirm Password">
<Field label="Confirm Password" required={true}>
<TextInput
field="confirm"
type="password"
validate={validators.get('confirm')}
validate={combine([
isRequired,
validateConfirmPassword
])}
validateOnBlur
/>
</Field>
Expand Down
69 changes: 0 additions & 69 deletions packages/venia-concept/src/components/CreateAccount/validators.js

This file was deleted.

5 changes: 5 additions & 0 deletions packages/venia-concept/src/util/__mocks__/formValidators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const validateEmail = jest.fn();
export const isRequired = jest.fn();
export const validatePassword = jest.fn();
export const validateConfirmPassword = jest.fn();
export const hasLengthAtLeast = jest.fn();
51 changes: 51 additions & 0 deletions packages/venia-concept/src/util/__tests__/formValidators.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,54 @@ describe('validateRegionCode', () => {
expect(typeof result).toBe('string');
});
});

describe('validatePassword', () => {
test('it returns undefined on success', () => {
const result = validators.validatePassword('123qwe_+*');

expect(result).toBeUndefined();
});

test('it returns a string on failure', () => {
const result = validators.validatePassword('1111');

expect(typeof result).toBe('string');
});
});

describe('validateConfirmPassword', () => {
test('it returns undefined on success', () => {
const values = {
password: 'qwerty12345'
};
const password = 'qwerty12345';
const result = validators.validateConfirmPassword(password, values);

expect(result).toBeUndefined();
});

test('it returns undefined on success with a password key', () => {
const values = {
password_key: 'qwerty12345'
};
const password = 'qwerty12345';
const passwordKey = 'password_key';
const result = validators.validateConfirmPassword(
password,
values,
passwordKey
);

expect(result).toBeUndefined();
});

test('it returns a string on failure', () => {
const values = {
password: 'qwertz12345'
};
const password = 'qwerty12345';
const result = validators.validateConfirmPassword(password, values);

expect(typeof result).toBe('string');
});
});
30 changes: 30 additions & 0 deletions packages/venia-concept/src/util/formValidators.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,33 @@ export const validateRegionCode = (value, values, countries) => {

return SUCCESS;
};

export const validatePassword = value => {
const count = {
lower: 0,
upper: 0,
digit: 0,
special: 0
};

for (const char of value) {
if (/[a-z]/.test(char)) count.lower++;
else if (/[A-Z]/.test(char)) count.upper++;
else if (/\d/.test(char)) count.digit++;
else if (/\S/.test(char)) count.special++;
}

if (Object.values(count).filter(Boolean).length < 3) {
return 'A password must contain at least 3 of the following: lowercase, uppercase, digits, special characters.';
}

return SUCCESS;
};

export const validateConfirmPassword = (
value,
values,
passwordKey = 'password'
) => {
return value === values[passwordKey] ? SUCCESS : 'Passwords must match.';
};

1 comment on commit 0ed9379

@vercel
Copy link

@vercel vercel bot commented on 0ed9379 May 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.