From 49bf6a5c81c465cdd00f4f3fb279b06a11a03fdd Mon Sep 17 00:00:00 2001 From: RJ <27843274+rjchow@users.noreply.github.com> Date: Thu, 20 Jan 2022 16:40:20 +0800 Subject: [PATCH] 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 --- ...fix-8155-improve-frontend-email-validation | 4 + .../steps/store-details/index.js | 9 +- .../test/__snapshots__/index.js.snap | 426 ++++++++++++++++++ .../steps/store-details/test/index.js | 83 ++++ plugins/woocommerce-admin/package-lock.json | 96 ++-- 5 files changed, 565 insertions(+), 53 deletions(-) create mode 100644 plugins/woocommerce-admin/changelogs/fix-8155-improve-frontend-email-validation create mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/__snapshots__/index.js.snap create mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/index.js diff --git a/plugins/woocommerce-admin/changelogs/fix-8155-improve-frontend-email-validation b/plugins/woocommerce-admin/changelogs/fix-8155-improve-frontend-email-validation new file mode 100644 index 00000000000..e7b2f2d7e68 --- /dev/null +++ b/plugins/woocommerce-admin/changelogs/fix-8155-improve-frontend-email-validation @@ -0,0 +1,4 @@ +Significance: patch +Type: Fix + +changed email validation in Store Details onboarding task to more closely match PHP backend validation. #8197 diff --git a/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js index 1ddeae95f6f..3d11a7d5be2 100644 --- a/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js +++ b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js @@ -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 = () => ( ); -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' diff --git a/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/__snapshots__/index.js.snap b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/__snapshots__/index.js.snap new file mode 100644 index 00000000000..f65b44cf25c --- /dev/null +++ b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/__snapshots__/index.js.snap @@ -0,0 +1,426 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`StoreDetails Snapshot test should match saved snapshot 1`] = ` +Object { + "asFragment": [Function], + "baseElement": + +
+
+
+
+
+

+ Welcome to WooCommerce +

+

+ Tell us about your store and we'll get you set up in no time + +

+
+
+
+ +
+
+
+ + +
+
+
+
+ +
+
+ +
+ +
+
+ , + "container":
+
+
+

+ Welcome to WooCommerce +

+

+ Tell us about your store and we'll get you set up in no time + +

+
+
+
+ +
+
+
+ + +
+
+
+
+ +
+
+ +
+ +
+
, + "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], +} +`; diff --git a/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/index.js b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/index.js new file mode 100644 index 00000000000..50950976ba6 --- /dev/null +++ b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/index.js @@ -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( ); + 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( ); + 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( ); + const emailInput = container.getByLabelText( 'Email address' ); + await userEvent.clear( emailInput ); + await userEvent.type( emailInput, email ); + userEvent.tab(); + expect( + container.queryByText( 'Invalid email address' ) + ).toBeNull(); + } ); + } ); +} ); diff --git a/plugins/woocommerce-admin/package-lock.json b/plugins/woocommerce-admin/package-lock.json index cc7f945dc25..59a6f7769c7 100644 --- a/plugins/woocommerce-admin/package-lock.json +++ b/plugins/woocommerce-admin/package-lock.json @@ -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", @@ -45132,4 +45134,4 @@ "dev": true } } -} \ No newline at end of file +}