Updated frontend email validation to use @wordpress/url (https://github.com/woocommerce/woocommerce-admin/pull/8197)

* Updated frontend email validation to use @wordpress/url

- added testing for StoreDetails
- changed basic email validation to use @wordpress/url isEmail
This commit is contained in:
RJ 2022-01-20 16:40:20 +08:00 committed by GitHub
parent 8af4fe6311
commit 49bf6a5c81
5 changed files with 565 additions and 53 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: Fix
changed email validation in Store Details onboarding task to more closely match PHP backend validation. #8197

View File

@ -25,6 +25,7 @@ import {
import { recordEvent } from '@woocommerce/tracks';
import { Text } from '@woocommerce/experimental';
import { Icon, info } from '@wordpress/icons';
import { isEmail } from '@wordpress/url';
/**
* Internal dependencies
@ -56,7 +57,7 @@ const LoadingPlaceholder = () => (
</div>
);
class StoreDetails extends Component {
export class StoreDetails extends Component {
constructor( props ) {
super( props );
@ -205,11 +206,7 @@ class StoreDetails extends Component {
const validateAddress = getStoreAddressValidator( locale );
const errors = validateAddress( values );
if (
values.storeEmail &&
values.storeEmail.trim().length &&
values.storeEmail.indexOf( '@' ) === -1
) {
if ( ! isEmail( values.storeEmail ) ) {
errors.storeEmail = __(
'Invalid email address',
'woocommerce-admin'

View File

@ -0,0 +1,426 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StoreDetails Snapshot test should match saved snapshot 1`] = `
Object {
"asFragment": [Function],
"baseElement": <body>
<p
class="a11y-speak-intro-text"
hidden="hidden"
id="a11y-speak-intro-text"
style="position: absolute;margin: -1px;padding: 0;height: 1px;width: 1px;overflow: hidden;clip: rect(1px, 1px, 1px, 1px);-webkit-clip-path: inset(50%);clip-path: inset(50%);border: 0;word-wrap: normal !important;"
>
Notifications
</p>
<div
aria-atomic="true"
aria-live="assertive"
aria-relevant="additions text"
class="a11y-speak-region"
id="a11y-speak-assertive"
style="position: absolute;margin: -1px;padding: 0;height: 1px;width: 1px;overflow: hidden;clip: rect(1px, 1px, 1px, 1px);-webkit-clip-path: inset(50%);clip-path: inset(50%);border: 0;word-wrap: normal !important;"
/>
<div
aria-atomic="true"
aria-live="polite"
aria-relevant="additions text"
class="a11y-speak-region"
id="a11y-speak-polite"
style="position: absolute;margin: -1px;padding: 0;height: 1px;width: 1px;overflow: hidden;clip: rect(1px, 1px, 1px, 1px);-webkit-clip-path: inset(50%);clip-path: inset(50%);border: 0;word-wrap: normal !important;"
/>
<div>
<div
class="woocommerce-profile-wizard__store-details"
>
<div
class="woocommerce-profile-wizard__step-header"
>
<h2
class="css-1ahfdc3-Text e15wbhsk0"
size="20"
>
Welcome to WooCommerce
</h2>
<p
class="css-1f0yw52-Text e15wbhsk0"
>
Tell us about your store and we'll get you set up in no time
<button
aria-label="Learn more about store details"
class="components-button is-tertiary"
type="button"
>
<svg
aria-hidden="true"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"
/>
</svg>
</button>
</p>
</div>
<div
class="components-card is-size-medium css-1xs3c37-CardUI e1q7k77g0"
>
<div
class="components-card__body is-size-medium css-xmjzce-BodyUI e1q7k77g3"
>
<span
class="components-spinner"
/>
<div>
<div
class="components-base-control muriel-component muriel-input-text with-value"
>
<div
class="components-base-control__field"
>
<label
class="components-base-control__label"
for="inspector-text-control-0"
>
Email address
</label>
<input
autocomplete="email"
checked=""
class="components-text-control__input"
id="inspector-text-control-0"
placeholder="Email address"
required=""
type="text"
value="wordpress@example.com"
/>
</div>
</div>
</div>
<div
class="components-flex__item css-1s295sp-Item eboqfv51"
>
<div
class="woocommerce-profile-wizard__newsletter-signup"
>
<div
class="components-base-control css-wdf2ti-Wrapper e1puf3u0"
>
<div
class="components-base-control__field css-11vcxb9-StyledField e1puf3u1"
>
<span
class="components-checkbox-control__input-container"
>
<input
checked=""
class="components-checkbox-control__input"
id="inspector-checkbox-control-0"
type="checkbox"
value="true"
/>
<svg
aria-hidden="true"
class="components-checkbox-control__checked"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18.3 5.6L9.9 16.9l-4.6-3.4-.9 1.2 5.8 4.3 9.3-12.6z"
/>
</svg>
</span>
<label
class="components-checkbox-control__label"
for="inspector-checkbox-control-0"
>
Get tips, product updates and inspiration straight to your mailbox.
<span
class="woocommerce-profile-wizard__powered-by-mailchimp"
>
Powered by Mailchimp
</span>
</label>
</div>
</div>
</div>
</div>
</div>
<div
class="components-flex components-card__footer is-size-medium e1q7k77g4 css-1fpr7ir-Flex-FooterUI eboqfv50"
>
<button
class="components-button is-primary"
disabled=""
type="button"
>
Continue
</button>
</div>
</div>
<div
class="woocommerce-profile-wizard__footer"
>
<button
class="components-button woocommerce-profile-wizard__footer-link is-link"
type="button"
>
Skip setup store details
</button>
<button
aria-label="Manual setup is only recommended for
experienced WooCommerce users or developers."
class="components-button is-tertiary"
type="button"
>
<svg
aria-hidden="true"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"
/>
</svg>
</button>
</div>
</div>
</div>
</body>,
"container": <div>
<div
class="woocommerce-profile-wizard__store-details"
>
<div
class="woocommerce-profile-wizard__step-header"
>
<h2
class="css-1ahfdc3-Text e15wbhsk0"
size="20"
>
Welcome to WooCommerce
</h2>
<p
class="css-1f0yw52-Text e15wbhsk0"
>
Tell us about your store and we'll get you set up in no time
<button
aria-label="Learn more about store details"
class="components-button is-tertiary"
type="button"
>
<svg
aria-hidden="true"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"
/>
</svg>
</button>
</p>
</div>
<div
class="components-card is-size-medium css-1xs3c37-CardUI e1q7k77g0"
>
<div
class="components-card__body is-size-medium css-xmjzce-BodyUI e1q7k77g3"
>
<span
class="components-spinner"
/>
<div>
<div
class="components-base-control muriel-component muriel-input-text with-value"
>
<div
class="components-base-control__field"
>
<label
class="components-base-control__label"
for="inspector-text-control-0"
>
Email address
</label>
<input
autocomplete="email"
checked=""
class="components-text-control__input"
id="inspector-text-control-0"
placeholder="Email address"
required=""
type="text"
value="wordpress@example.com"
/>
</div>
</div>
</div>
<div
class="components-flex__item css-1s295sp-Item eboqfv51"
>
<div
class="woocommerce-profile-wizard__newsletter-signup"
>
<div
class="components-base-control css-wdf2ti-Wrapper e1puf3u0"
>
<div
class="components-base-control__field css-11vcxb9-StyledField e1puf3u1"
>
<span
class="components-checkbox-control__input-container"
>
<input
checked=""
class="components-checkbox-control__input"
id="inspector-checkbox-control-0"
type="checkbox"
value="true"
/>
<svg
aria-hidden="true"
class="components-checkbox-control__checked"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18.3 5.6L9.9 16.9l-4.6-3.4-.9 1.2 5.8 4.3 9.3-12.6z"
/>
</svg>
</span>
<label
class="components-checkbox-control__label"
for="inspector-checkbox-control-0"
>
Get tips, product updates and inspiration straight to your mailbox.
<span
class="woocommerce-profile-wizard__powered-by-mailchimp"
>
Powered by Mailchimp
</span>
</label>
</div>
</div>
</div>
</div>
</div>
<div
class="components-flex components-card__footer is-size-medium e1q7k77g4 css-1fpr7ir-Flex-FooterUI eboqfv50"
>
<button
class="components-button is-primary"
disabled=""
type="button"
>
Continue
</button>
</div>
</div>
<div
class="woocommerce-profile-wizard__footer"
>
<button
class="components-button woocommerce-profile-wizard__footer-link is-link"
type="button"
>
Skip setup store details
</button>
<button
aria-label="Manual setup is only recommended for
experienced WooCommerce users or developers."
class="components-button is-tertiary"
type="button"
>
<svg
aria-hidden="true"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"
/>
</svg>
</button>
</div>
</div>
</div>,
"debug": [Function],
"findAllByAltText": [Function],
"findAllByDisplayValue": [Function],
"findAllByLabelText": [Function],
"findAllByPlaceholderText": [Function],
"findAllByRole": [Function],
"findAllByTestId": [Function],
"findAllByText": [Function],
"findAllByTitle": [Function],
"findByAltText": [Function],
"findByDisplayValue": [Function],
"findByLabelText": [Function],
"findByPlaceholderText": [Function],
"findByRole": [Function],
"findByTestId": [Function],
"findByText": [Function],
"findByTitle": [Function],
"getAllByAltText": [Function],
"getAllByDisplayValue": [Function],
"getAllByLabelText": [Function],
"getAllByPlaceholderText": [Function],
"getAllByRole": [Function],
"getAllByTestId": [Function],
"getAllByText": [Function],
"getAllByTitle": [Function],
"getByAltText": [Function],
"getByDisplayValue": [Function],
"getByLabelText": [Function],
"getByPlaceholderText": [Function],
"getByRole": [Function],
"getByTestId": [Function],
"getByText": [Function],
"getByTitle": [Function],
"queryAllByAltText": [Function],
"queryAllByDisplayValue": [Function],
"queryAllByLabelText": [Function],
"queryAllByPlaceholderText": [Function],
"queryAllByRole": [Function],
"queryAllByTestId": [Function],
"queryAllByText": [Function],
"queryAllByTitle": [Function],
"queryByAltText": [Function],
"queryByDisplayValue": [Function],
"queryByLabelText": [Function],
"queryByPlaceholderText": [Function],
"queryByRole": [Function],
"queryByTestId": [Function],
"queryByText": [Function],
"queryByTitle": [Function],
"rerender": [Function],
"unmount": [Function],
}
`;

View File

@ -0,0 +1,83 @@
/**
* External dependencies
*/
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
import { StoreDetails } from '../';
const testProps = {
query: {
page: 'wc-admin',
path: '/setup-wizard',
},
step: {
key: 'store-details',
label: 'Store Details',
isComplete: false,
},
initialValues: {
addressLine1: '',
addressLine2: '',
city: '',
countryState: '',
postCode: '',
isAgreeMarketing: true,
storeEmail: 'wordpress@example.com',
},
getLocale: jest.fn(),
isLoading: false,
};
describe( 'StoreDetails', () => {
describe( 'Snapshot test', () => {
test( 'should match saved snapshot', () => {
const container = render( <StoreDetails { ...testProps } /> );
expect( container ).toMatchSnapshot();
} );
} );
describe( 'Email validation test cases', () => {
// test cases taken from wordpress php is_email test cases
// https://github.com/WordPress/wordpress-develop/blob/2648a5f984b8abf06872151898e3a61d3458a628/tests/phpunit/tests/formatting/isEmail.php
test.each( [
'khaaaaaaaaaaaaaaan!',
'http://bob.example.com/',
"sif i'd give u it, spamer!1",
'com.exampleNOSPAMbob',
'bob@your mom',
'a@b.c',
] )( 'should fail email validation when given %s', async ( email ) => {
const container = render( <StoreDetails { ...testProps } /> );
const emailInput = container.getByLabelText( 'Email address' );
await userEvent.clear( emailInput );
await userEvent.type( emailInput, email );
// validation is triggered onChange but error message only renders on blur
// react testing lib doesn't have a "blur" event that can be triggered so this does the job of triggering the error message rendering
userEvent.tab();
expect(
container.queryByText( 'Invalid email address' )
).toBeInTheDocument();
} );
test.each( [
'bob@example.com',
'phil@example.info',
// 'ace@204.32.222.14', this testcase passes for the backend validation but fails here, following up in a PR to fix this in @wordpress/url
'kevin@many.subdomains.make.a.happy.man.edu',
'a@b.co',
'bill+ted@example.com',
] )( 'should pass email validation when given %s', async ( email ) => {
const container = render( <StoreDetails { ...testProps } /> );
const emailInput = container.getByLabelText( 'Email address' );
await userEvent.clear( emailInput );
await userEvent.type( emailInput, email );
userEvent.tab();
expect(
container.queryByText( 'Invalid email address' )
).toBeNull();
} );
} );
} );

View File

@ -1,6 +1,6 @@
{
"name": "@woocommerce/admin-library",
"version": "3.1.0-dev",
"version": "3.2.0-dev",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -9420,6 +9420,16 @@
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz",
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
},
"@types/yauzl": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
"integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==",
"dev": true,
"optional": true,
"requires": {
"@types/node": "*"
}
},
"@typescript-eslint/eslint-plugin": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.1.tgz",
@ -15642,18 +15652,6 @@
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"dependencies": {
"@types/yauzl": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
"integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==",
"dev": true,
"optional": true,
"requires": {
"@types/node": "*"
}
}
}
},
"file-entry-cache": {
@ -16825,36 +16823,6 @@
"tar-fs": "^2.0.0",
"unbzip2-stream": "^1.3.3",
"ws": "^7.2.3"
},
"dependencies": {
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true
},
"tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"dev": true,
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"unbzip2-stream": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
"integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
"dev": true,
"requires": {
"buffer": "^5.2.1",
"through": "^2.3.8"
}
}
}
},
"read-pkg": {
@ -17795,7 +17763,9 @@
"dev": true
},
"ansi-regex": {
"version": "5.0.1"
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
"version": "3.2.1",
@ -32985,7 +32955,9 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"json-schema": {
"version": "0.4.0"
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
},
"json-schema-traverse": {
"version": "0.4.1",
@ -33039,6 +33011,8 @@
},
"jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@ -36032,6 +36006,8 @@
},
"nth-check": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz",
"integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==",
"requires": {
"boolbase": "^1.0.0"
}
@ -38132,6 +38108,8 @@
},
"prismjs": {
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==",
"dev": true
},
"process": {
@ -39536,11 +39514,13 @@
},
"refractor": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.5.0.tgz",
"integrity": "sha512-QwPJd3ferTZ4cSPPjdP5bsYHMytwWYnAN5EEnLtGvkqp/FCCnGsBgxrm9EuIDnjUC3Uc/kETtvVi7fSIVC74Dg==",
"dev": true,
"requires": {
"hastscript": "^6.0.0",
"parse-entities": "^2.0.0",
"prismjs": "~1.24.0"
"prismjs": "~1.25.0"
}
},
"regenerate": {
@ -42447,6 +42427,18 @@
}
}
},
"tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"dev": true,
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
@ -43357,6 +43349,16 @@
"which-boxed-primitive": "^1.0.2"
}
},
"unbzip2-stream": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
"integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
"dev": true,
"requires": {
"buffer": "^5.2.1",
"through": "^2.3.8"
}
},
"unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",