diff --git a/README.md b/README.md index 49ad3bc..ad1f295 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,7 @@ Indicates the `type` of validator to use. Recognised type values are: - `url`: Must be of type `url`. - `hex`: Must be of type `hex`. - `email`: Must be of type `email`. +- `tel`: Must be of type `tel`. - `any`: Can be any type. #### Required diff --git a/src/interface.ts b/src/interface.ts index c7d66cd..11353d4 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -15,6 +15,7 @@ export type RuleType = | 'url' | 'hex' | 'email' + | 'tel' | 'pattern' | 'any'; @@ -146,6 +147,7 @@ export interface ValidateMessages { float?: ValidateMessage<[FullField, Type]>; regexp?: ValidateMessage<[FullField, Type]>; email?: ValidateMessage<[FullField, Type]>; + tel?: ValidateMessage<[FullField, Type]>; url?: ValidateMessage<[FullField, Type]>; hex?: ValidateMessage<[FullField, Type]>; }; diff --git a/src/messages.ts b/src/messages.ts index 6f32b99..35072a0 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -23,6 +23,7 @@ export function newMessages(): InternalValidateMessages { float: '%s is not a %s', regexp: '%s is not a valid %s', email: '%s is not a valid %s', + tel: '%s is not a valid %s', url: '%s is not a valid %s', hex: '%s is not a valid %s', }, diff --git a/src/rule/type.ts b/src/rule/type.ts index 6da27c9..d44729c 100644 --- a/src/rule/type.ts +++ b/src/rule/type.ts @@ -12,6 +12,13 @@ const pattern = { // '^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', // 'i', // ), + /** + * Phone number regex, support country code, brackets, spaces, and dashes (or non-breaking hyphen \u2011). + * @see https://regexr.com/3c53v + * @see https://ihateregex.io/expr/phone/ + * @see https://developers.google.com/style/phone-numbers using non-breaking hyphen \u2011 + */ + tel: /^(\+[0-9]{1,3}[-\s\u2011]?)?(\([0-9]{1,4}\)[-\s\u2011]?)?([0-9]+[-\s\u2011]?)*[0-9]+$/, hex: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i, }; @@ -58,6 +65,9 @@ const types = { email(value: Value) { return typeof value === 'string' && value.length <= 320 && !!value.match(pattern.email); }, + tel(value: Value) { + return typeof value === 'string' && value.length <= 32 && !!value.match(pattern.tel); + }, url(value: Value) { return typeof value === 'string' && value.length <= 2048 && !!value.match(getUrlRegex()); }, @@ -79,6 +89,7 @@ const type: ExecuteRule = (rule, value, source, errors, options) => { 'object', 'method', 'email', + 'tel', 'number', 'date', 'url', diff --git a/src/util.ts b/src/util.ts index 78383f4..7b8d62c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -91,7 +91,8 @@ function isNativeStringType(type: string) { type === 'hex' || type === 'email' || type === 'date' || - type === 'pattern' + type === 'pattern' || + type === 'tel' ); } diff --git a/src/validator/index.ts b/src/validator/index.ts index e4e3c5b..4fea62e 100644 --- a/src/validator/index.ts +++ b/src/validator/index.ts @@ -30,6 +30,7 @@ export default { url: type, hex: type, email: type, + tel: type, required, any, }; diff --git a/tests/tel.spec.ts b/tests/tel.spec.ts new file mode 100644 index 0000000..6ab2eb8 --- /dev/null +++ b/tests/tel.spec.ts @@ -0,0 +1,165 @@ +import Schema from '../src'; + +describe('tel', () => { + it('works for empty string', done => { + new Schema({ + v: { + type: 'tel', + }, + }).validate( + { + v: '', + }, + errors => { + expect(errors).toBe(null); + done(); + }, + ); + }); + + it('works for china mobile phone number', done => { + new Schema({ + v: { + type: 'tel', + }, + }).validate( + { + v: '13156451303', + }, + errors => { + expect(errors).toBe(null); + done(); + }, + ); + }); + + it('works for china mobile phone number with country code', done => { + new Schema({ + v: { + type: 'tel', + }, + }).validate( + { + v: '+8613156451303', + }, + errors => { + expect(errors).toBe(null); + done(); + }, + ); + }); + + it('works for china mobile phone number with spaces', done => { + new Schema({ + v: { + type: 'tel', + }, + }).validate( + { + v: '+86 131 5645 1303', + }, + errors => { + expect(errors).toBe(null); + done(); + }, + ); + }); + + it('works for us phone number with dashes', done => { + new Schema({ + v: { + type: 'tel', + }, + }).validate( + { + v: '415-555-0132', + }, + errors => { + expect(errors).toBe(null); + done(); + }, + ); + }); + + it('works for us phone number with brackets, dashes, and spaces', done => { + new Schema({ + v: { + type: 'tel', + }, + }).validate( + { + v: '(123) 456-7890', + }, + errors => { + expect(errors).toBe(null); + done(); + }, + ); + }); + + it('works for us phone number with nonbreaking hyphen', done => { + new Schema({ + v: { + type: 'tel', + }, + }).validate( + { + v: '415‑555‑0132', + }, + errors => { + expect(errors).toBe(null); + done(); + }, + ); + }); + + it('forbid multiple spaces in a row', done => { + new Schema({ + v: { + type: 'tel', + }, + }).validate( + { + v: '123 456', + }, + errors => { + expect(errors[0].message).toBe('v is not a valid tel'); + done(); + }, + ); + }); + + it('forbid multiple dashes in a row', done => { + new Schema({ + v: { + type: 'tel', + }, + }).validate( + { + v: '123---456', + }, + errors => { + expect(errors[0].message).toBe('v is not a valid tel'); + done(); + }, + ); + }); + + it('works for required empty string', done => { + new Schema({ + v: { + type: 'tel', + required: true, + }, + }).validate( + { + v: '', + }, + errors => { + expect(errors.length).toBe(1); + expect(errors[0].message).toBe('v is required'); + done(); + }, + ); + }); +});