Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
Add client side postcode validation
Browse files Browse the repository at this point in the history
  • Loading branch information
nielslange committed Jan 10, 2023
1 parent 34146c3 commit b181485
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { ValidatedTextInput } from '@woocommerce/blocks-checkout';
import { ValidatedTextInput, isPostcode } from '@woocommerce/blocks-checkout';
import {
BillingCountryInput,
ShippingCountryInput,
Expand Down Expand Up @@ -216,6 +216,48 @@ const AddressForm = ( {
);
}

if ( field.key === 'postcode' ) {
return (
<ValidatedTextInput
id={ `${ id }-${ field.key }` }
key={ `${ id }-${ field.key }` }
value={ values.postcode }
label={ field.label }
required={ field.required }
requiredMessage={ __(
'Please provide a valid postcode',
'woo-gutenberg-products-block'
) }
onChange={ ( newValue: string ) =>
onChange( {
...values,
[ field.key ]: newValue,
} )
}
customValidation={ (
inputObject: HTMLInputElement
) => {
if (
! isPostcode( {
postcode: values.postcode,
country: values.country,
} )
) {
inputObject.setCustomValidity(
__(
'Please provide a valid postcode',
'woo-gutenberg-products-block'
)
);
return false;
}
return true;
} }
errorMessage={ field.errorMessage }
/>
);
}

return (
<ValidatedTextInput
key={ field.key }
Expand Down
2 changes: 1 addition & 1 deletion assets/js/base/components/combobox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface ComboboxProps {
autoComplete?: string;
className?: string;
errorId: string | null;
errorMessage?: string;
errorMessage?: string | undefined;
id: string;
instanceId?: string;
label: string;
Expand Down
2 changes: 1 addition & 1 deletion assets/js/base/components/state-input/StateInputProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface StateInputProps {
country: string;
onChange: ( value: string ) => void;
required?: boolean;
errorMessage?: string;
errorMessage?: string | undefined;
}

export type StateInputWithStatesProps = StateInputProps & {
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@
"dompurify": "^2.4.0",
"downshift": "6.1.7",
"html-react-parser": "3.0.4",
"postcode-validator": "3.7.0",
"react-number-format": "4.9.3",
"reakit": "1.3.11",
"snakecase-keys": "5.4.2",
Expand Down
29 changes: 2 additions & 27 deletions packages/checkout/utils/validation/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,2 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';

/**
* Ensures that a given value contains a string, or throws an error.
*/
export const mustContain = (
value: string,
requiredValue: string
): true | never => {
if ( ! value.includes( requiredValue ) ) {
throw Error(
sprintf(
/* translators: %1$s value passed to filter, %2$s : value that must be included. */
__(
'Returned value must include %1$s, you passed "%2$s"',
'woo-gutenberg-products-block'
),
requiredValue,
value
)
);
}
return true;
};
export { default as mustContain } from './mustContain';
export { default as isPostcode } from './isPostcode';
31 changes: 31 additions & 0 deletions packages/checkout/utils/validation/isPostcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* External dependencies
*/
import { POSTCODE_REGEXES } from 'postcode-validator/lib/cjs/postcode-regexes.js';

const getCustomRegexes = () => {
POSTCODE_REGEXES.set( 'BA', /^([7-8]{1})([0-9]{4})$/ );
POSTCODE_REGEXES.set(
'GB',
/^([A-Z]){1}([0-9]{1,2}|[A-Z][0-9][A-Z]|[A-Z][0-9]{2}|[A-Z][0-9]|[0-9][A-Z]){1}([ ])?([0-9][A-z]{2}){1}|BFPO(?:\s)?([0-9]{1,4})$|BFPO(c\/o[0-9]{1,3})$/i
);
POSTCODE_REGEXES.set( 'IN', /^[1-9]{1}[0-9]{2}\s{0,1}[0-9]{3}$/ );
POSTCODE_REGEXES.set( 'JP', /^([0-9]{3})([-]?)([0-9]{4})$/ );
POSTCODE_REGEXES.set( 'LI', /^(94[8-9][0-9])$/ );
POSTCODE_REGEXES.set( 'NL', /^([1-9][0-9]{3})(\s?)(?!SA|SD|SS)[A-Z]{2}$/i );
POSTCODE_REGEXES.set( 'SI', /^([1-9][0-9]{3})$/ );

return POSTCODE_REGEXES;
};
export interface IsPostcodeProps {
postcode: string;
country: string;
}

const isPostcode = ( { postcode, country }: IsPostcodeProps ) => {
const CUSTOM_POSTCODE_REGEXES = getCustomRegexes();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return CUSTOM_POSTCODE_REGEXES.get( country )!.test( postcode );
};

export default isPostcode;
26 changes: 26 additions & 0 deletions packages/checkout/utils/validation/mustContain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';

/**
* Ensures that a given value contains a string, or throws an error.
*/
const mustContain = ( value: string, requiredValue: string ): true | never => {
if ( ! value.includes( requiredValue ) ) {
throw Error(
sprintf(
/* translators: %1$s value passed to filter, %2$s : value that must be included. */
__(
'Returned value must include %1$s, you passed "%2$s"',
'woo-gutenberg-products-block'
),
requiredValue,
value
)
);
}
return true;
};

export default mustContain;
177 changes: 177 additions & 0 deletions packages/checkout/utils/validation/test/isPostcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/**
* Internal dependencies
*/
import isPostcode from '../isPostcode';
import type { IsPostcodeProps } from '../isPostcode';

describe( 'isPostcode', () => {
const cases = [
// Austrian postcodes
[ true, '1000', 'AT' ],
[ true, '9999', 'AT' ],
[ false, '0000', 'AT' ],
[ false, '10000', 'AT' ],

// Bosnian postcodes
[ true, '71000', 'BA' ],
[ true, '78256', 'BA' ],
[ true, '89240', 'BA' ],
[ false, '61000', 'BA' ],
[ false, '7850', 'BA' ],

// Belgian postcodes
[ true, '1111', 'BE' ],
[ false, '111', 'BE' ],
[ false, '11111', 'BE' ],

// Brazilian postcodes
[ true, '99999-999', 'BR' ],
[ true, '99999999', 'BR' ],
[ false, '99999 999', 'BR' ],
[ false, '99999-ABC', 'BR' ],

// Canadian postcodes
[ true, 'A9A 9A9', 'CA' ],
[ true, 'A9A9A9', 'CA' ],
[ true, 'a9a9a9', 'CA' ],
[ false, 'D0A 9A9', 'CA' ],
[ false, '99999', 'CA' ],
[ false, 'ABC999', 'CA' ],
[ false, '0A0A0A', 'CA' ],

// Swiss postcodes
[ true, '9999', 'CH' ],
[ false, '99999', 'CH' ],
[ false, 'ABCDE', 'CH' ],

// Czech postcodes
[ true, '160 00', 'CZ' ],
[ true, '16000', 'CZ' ],
[ false, '1600', 'CZ' ],

// German postcodes
[ true, '01234', 'DE' ],
[ true, '12345', 'DE' ],
[ false, '12 345', 'DE' ],
[ false, '1234', 'DE' ],

// Spanish postcodes
[ true, '03000', 'ES' ],
[ true, '08000', 'ES' ],
[ false, '08 000', 'ES' ],
[ false, '1234', 'ES' ],

// French postcodes
[ true, '01000', 'FR' ],
[ true, '99999', 'FR' ],
[ true, '01 000', 'FR' ],
[ false, '1234', 'FR' ],

// British postcodes
[ true, 'AA9A 9AA', 'GB' ],
[ true, 'A9A 9AA', 'GB' ],
[ true, 'A9 9AA', 'GB' ],
[ true, 'A99 9AA', 'GB' ],
[ true, 'AA99 9AA', 'GB' ],
[ true, 'BFPO 801', 'GB' ],
[ false, '99999', 'GB' ],
[ false, '9999 999', 'GB' ],
[ false, '999 999', 'GB' ],
[ false, '99 999', 'GB' ],
[ false, '9A A9A', 'GB' ],

// Hungarian postcodes
[ true, '1234', 'HU' ],
[ false, '123', 'HU' ],
[ false, '12345', 'HU' ],

// Irish postcodes
[ true, 'A65F4E2', 'IE' ],
[ true, 'A65 F4E2', 'IE' ],
[ true, 'A65-F4E2', 'IE' ],
[ false, 'B23F854', 'IE' ],

// Indian postcodes
[ true, '110001', 'IN' ],
[ true, '110 001', 'IN' ],
[ false, '11 0001', 'IN' ],
[ false, '1100 01', 'IN' ],

// Italian postcodes
[ true, '99999', 'IT' ],
[ false, '9999', 'IT' ],
[ false, 'ABC 999', 'IT' ],
[ false, 'ABC-999', 'IT' ],
[ false, 'ABC_123', 'IT' ],

// Japanese postcodes
[ true, '1340088', 'JP' ],
[ true, '134-0088', 'JP' ],
[ false, '1340-088', 'JP' ],
[ false, '12345', 'JP' ],
[ false, '0123', 'JP' ],

// Lichtenstein postcodes
[ true, '9485', 'LI' ],
[ true, '9486', 'LI' ],
[ true, '9499', 'LI' ],
[ false, '9585', 'LI' ],
[ false, '9385', 'LI' ],
[ false, '9475', 'LI' ],

// Dutch postcodes
[ true, '3852GC', 'NL' ],
[ true, '3852 GC', 'NL' ],
[ true, '3852 gc', 'NL' ],
[ false, '3852SA', 'NL' ],
[ false, '3852 SA', 'NL' ],
[ false, '3852 sa', 'NL' ],

// Polish postcodes
[ true, '00-001', 'PL' ],
[ true, '99-440', 'PL' ],
[ false, '000-01', 'PL' ],
[ false, '994-40', 'PL' ],
[ false, '00001', 'PL' ],
[ false, '99440', 'PL' ],

// Puerto Rican postcodes
[ true, '00901', 'PR' ],
[ true, '00617', 'PR' ],
[ true, '00602-1211', 'PR' ],
[ false, '1234', 'PR' ],
[ false, '0060-21211', 'PR' ],

// Portuguese postcodes
[ true, '1234-567', 'PT' ],
[ true, '2345-678', 'PT' ],
[ false, '123-4567', 'PT' ],
[ false, '234-5678', 'PT' ],

// Slovenian postcodes
[ true, '1234', 'SI' ],
[ true, '1000', 'SI' ],
[ true, '9876', 'SI' ],
[ false, '12345', 'SI' ],
[ false, '0123', 'SI' ],

// Slovak postcodes
[ true, '010 01', 'SK' ],
[ true, '01001', 'SK' ],
[ false, '01 001', 'SK' ],
[ false, '1234', 'SK' ],
[ false, '123456', 'SK' ],

// United States postcodes
[ true, '90210', 'US' ],
[ true, '99577-0727', 'US' ],
[ false, 'ABCDE', 'US' ],
[ false, 'ABCDE-9999', 'US' ],
];

test.each( cases )( '%s: %s for %s', ( result, postcode, country ) =>
expect( isPostcode( { postcode, country } as IsPostcodeProps ) ).toBe(
result
)
);
} );

0 comments on commit b181485

Please sign in to comment.