From e997c6e576bc7b0d5c1f19cb5029c9ce5b5593e6 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 21 May 2020 02:22:32 +0300 Subject: [PATCH 1/8] feat: add custom required and email validators --- .../core/src/lib/tests/validators.spec.ts | 37 +++++++++++++++++++ .../packages/core/src/lib/validators/index.ts | 8 ++++ .../src/lib/validators/required.validator.ts | 25 +++++++++++++ npm/ng-packs/packages/core/src/public-api.ts | 1 + 4 files changed, 71 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts create mode 100644 npm/ng-packs/packages/core/src/lib/validators/index.ts create mode 100644 npm/ng-packs/packages/core/src/lib/validators/required.validator.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts new file mode 100644 index 0000000000..efe668005f --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts @@ -0,0 +1,37 @@ +import { FormControl } from '@angular/forms'; +import { validateRequired } from '../validators/required.validator'; + +describe('Validators', () => { + describe('Required Validator', () => { + const error = { required: true }; + + test.each` + input | options | expected + ${0} | ${undefined} | ${null} + ${false} | ${undefined} | ${null} + ${null} | ${undefined} | ${error} + ${undefined} | ${undefined} | ${error} + ${''} | ${undefined} | ${error} + ${''} | ${{}} | ${error} + ${''} | ${{ allowEmptyStrings: false }} | ${error} + ${''} | ${{ allowEmptyStrings: true }} | ${null} + `( + 'should return $expected when input is $input and options are $options', + ({ input, options, expected }) => { + const control = new FormControl(input, [validateRequired(options)]); + control.markAsDirty({ onlySelf: true }); + control.updateValueAndValidity({ onlySelf: true, emitEvent: false }); + + expect(control.errors).toEqual(expected); + }, + ); + + it('should return null when control is pristine', () => { + const invalidUrl = ''; + const control = new FormControl(invalidUrl, [validateRequired()]); + // control is not dirty + + expect(control.valid).toBe(true); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/validators/index.ts b/npm/ng-packs/packages/core/src/lib/validators/index.ts new file mode 100644 index 0000000000..0d0a6fb2e9 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/validators/index.ts @@ -0,0 +1,8 @@ +import { Validators } from '@angular/forms'; +import { validateRequired } from './required.validator'; +export * from './required.validator'; + +export const AbpValidators = { + email: () => Validators.email, + required: validateRequired, +}; diff --git a/npm/ng-packs/packages/core/src/lib/validators/required.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/required.validator.ts new file mode 100644 index 0000000000..f8d27a3ffd --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/validators/required.validator.ts @@ -0,0 +1,25 @@ +import { AbstractControl, ValidatorFn } from '@angular/forms'; + +export interface RequiredError { + required: true; +} + +export interface RequiredOptions { + allowEmptyStrings?: boolean; +} + +export function validateRequired({ allowEmptyStrings }: RequiredOptions = {}): ValidatorFn { + return (control: AbstractControl): RequiredError | null => { + return control.pristine || isValidRequired(control.value, allowEmptyStrings) + ? null + : { required: true }; + }; +} + +function isValidRequired(value: any, allowEmptyStrings: boolean): boolean { + if (value || value === 0 || value === false) return true; + + if (allowEmptyStrings && value === '') return true; + + return false; +} diff --git a/npm/ng-packs/packages/core/src/public-api.ts b/npm/ng-packs/packages/core/src/public-api.ts index 51ac130da2..56d4098d85 100644 --- a/npm/ng-packs/packages/core/src/public-api.ts +++ b/npm/ng-packs/packages/core/src/public-api.ts @@ -20,3 +20,4 @@ export * from './lib/states'; export * from './lib/strategies'; export * from './lib/tokens'; export * from './lib/utils'; +export * from './lib/validators'; From bfe5360656b297a354f8c33d8a37322baee01c17 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 21 May 2020 02:25:11 +0300 Subject: [PATCH 2/8] feat: add credit card validator --- .../core/src/lib/tests/validators.spec.ts | 39 +++++++++++++++++++ .../lib/validators/credit-card.validator.ts | 32 +++++++++++++++ .../packages/core/src/lib/validators/index.ts | 3 ++ 3 files changed, 74 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts index efe668005f..62c111a218 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts @@ -1,7 +1,46 @@ import { FormControl } from '@angular/forms'; +import { validateCreditCard } from '../validators/credit-card.validator'; import { validateRequired } from '../validators/required.validator'; describe('Validators', () => { + describe('Credit Card Validator', () => { + const error = { creditCardNumber: true }; + + test.each` + input | expected + ${'0'} | ${error} + ${'5105105105105100' /* Mastercard */} | ${null} + ${'5105105105105101' /* Mastercard */} | ${error} + ${'5105 1051 0510 5100'} | ${null} + ${'5105-1051-0510-5100'} | ${null} + ${'5105 - 1051 - 0510 - 5100'} | ${null} + ${'4111111111111111' /*Visa*/} | ${null} + ${'4111111111111112' /*Visa*/} | ${error} + ${'4012888888881881' /* Visa */} | ${null} + ${'4012888888881882' /* Visa */} | ${error} + ${'4222222222222' /* Visa */} | ${null} + ${'4222222222223' /* Visa */} | ${error} + ${'378282246310005' /* American Express */} | ${null} + ${'378282246310006' /* American Express */} | ${error} + ${'6011111111111117' /* Discover */} | ${null} + ${'6011111111111118' /* Discover */} | ${error} + `('should return $expected when input is $input', ({ input, expected }) => { + const control = new FormControl(input, [validateCreditCard()]); + control.markAsDirty({ onlySelf: true }); + control.updateValueAndValidity({ onlySelf: true, emitEvent: false }); + + expect(control.errors).toEqual(expected); + }); + + it('should return null when control is pristine', () => { + const invalidNumber = '5105105105105101'; + const control = new FormControl(invalidNumber, [validateCreditCard()]); + // control is not dirty + + expect(control.valid).toBe(true); + }); + }); + describe('Required Validator', () => { const error = { required: true }; diff --git a/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts new file mode 100644 index 0000000000..0436068d02 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts @@ -0,0 +1,32 @@ +import { AbstractControl, ValidatorFn } from '@angular/forms'; + +export interface CreditCardError { + creditCardNumber: true; +} + +export function validateCreditCard(): ValidatorFn { + return (control: AbstractControl): CreditCardError | null => { + if (control.pristine) return null; + + return isValidCreditCard(String(control.value)) ? null : { creditCardNumber: true }; + }; +} + +function isValidCreditCard(value: string): boolean { + value = value.replace(/[ -]/g, ''); + + if (!/^[0-9]{13,19}$/.test(value)) return false; + + let checksum = 0; + let multiplier = 1; + + for (let i = value.length; i > 0; i--) { + const digit = Number(value[i - 1]) * multiplier; + /* tslint:disable-next-line:no-bitwise */ + checksum += (digit % 10) + ~~(digit / 10); + + multiplier = (multiplier * 2) % 3; + } + + return checksum % 10 === 0; +} diff --git a/npm/ng-packs/packages/core/src/lib/validators/index.ts b/npm/ng-packs/packages/core/src/lib/validators/index.ts index 0d0a6fb2e9..d353ed0515 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/index.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/index.ts @@ -1,8 +1,11 @@ import { Validators } from '@angular/forms'; +import { validateCreditCard } from './credit-card.validator'; import { validateRequired } from './required.validator'; +export * from './credit-card.validator'; export * from './required.validator'; export const AbpValidators = { + creditCard: validateCreditCard, email: () => Validators.email, required: validateRequired, }; From bc66979a09bf42d40ff71647ab2dbeb2a950f714 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 21 May 2020 02:26:29 +0300 Subject: [PATCH 3/8] feat: add range validator --- .../core/src/lib/tests/validators.spec.ts | 37 +++++++++++++++++++ .../packages/core/src/lib/validators/index.ts | 3 ++ .../src/lib/validators/range.validator.ts | 30 +++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/validators/range.validator.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts index 62c111a218..c2bc3d1e10 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts @@ -1,4 +1,5 @@ import { FormControl } from '@angular/forms'; +import { validateRange } from '../validators'; import { validateCreditCard } from '../validators/credit-card.validator'; import { validateRequired } from '../validators/required.validator'; @@ -41,6 +42,42 @@ describe('Validators', () => { }); }); + describe('Range Validator', () => { + test.each` + input | options | expected + ${null} | ${undefined} | ${{ min: 0, max: Infinity }} + ${undefined} | ${undefined} | ${{ min: 0, max: Infinity }} + ${''} | ${undefined} | ${{ min: 0, max: Infinity }} + ${0} | ${undefined} | ${null} + ${Infinity} | ${undefined} | ${null} + ${null} | ${{ minimum: 0 }} | ${{ min: 0, max: Infinity }} + ${undefined} | ${{ minimum: 0 }} | ${{ min: 0, max: Infinity }} + ${''} | ${{ minimum: 0 }} | ${{ min: 0, max: Infinity }} + ${0} | ${{ minimum: 0 }} | ${null} + ${2} | ${{ minimum: 3, maximum: 5 }} | ${{ min: 3, max: 5 }} + ${3} | ${{ minimum: 3, maximum: 5 }} | ${null} + ${5} | ${{ minimum: 3, maximum: 5 }} | ${null} + ${6} | ${{ minimum: 3, maximum: 5 }} | ${{ min: 3, max: 5 }} + `( + 'should return $expected when input is $input and options are $options', + ({ input, options, expected }) => { + const control = new FormControl(input, [validateRange(options)]); + control.markAsDirty({ onlySelf: true }); + control.updateValueAndValidity({ onlySelf: true, emitEvent: false }); + + expect(control.errors).toEqual(expected); + }, + ); + + it('should return null when control is pristine', () => { + const invalidUrl = ''; + const control = new FormControl(invalidUrl, [validateRange({ minimum: 3 })]); + // control is not dirty + + expect(control.valid).toBe(true); + }); + }); + describe('Required Validator', () => { const error = { required: true }; diff --git a/npm/ng-packs/packages/core/src/lib/validators/index.ts b/npm/ng-packs/packages/core/src/lib/validators/index.ts index d353ed0515..df2a6f9c1b 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/index.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/index.ts @@ -1,11 +1,14 @@ import { Validators } from '@angular/forms'; import { validateCreditCard } from './credit-card.validator'; +import { validateRange } from './range.validator'; import { validateRequired } from './required.validator'; export * from './credit-card.validator'; +export * from './range.validator'; export * from './required.validator'; export const AbpValidators = { creditCard: validateCreditCard, email: () => Validators.email, + range: validateRange, required: validateRequired, }; diff --git a/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts new file mode 100644 index 0000000000..15a9862fcd --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts @@ -0,0 +1,30 @@ +import { AbstractControl, ValidatorFn } from '@angular/forms'; + +export interface RangeError { + max: number; + min: number; +} + +export interface RangeOptions { + maximum?: number; + minimum?: number; +} + +export function validateRange({ maximum = Infinity, minimum = 0 }: RangeOptions = {}): ValidatorFn { + return (control: AbstractControl): RangeError | null => { + if (control.pristine) return null; + + if (['', null, undefined].indexOf(control.value) > -1) return { min: minimum, max: maximum }; + + const value = Number(control.value); + return getMinError(value, minimum, maximum) || getMaxError(value, maximum, minimum); + }; +} + +function getMaxError(value: number, max: number, min: number): RangeError { + return value > max ? { max, min } : null; +} + +function getMinError(value: number, min: number, max: number): RangeError { + return value < min ? { min, max } : null; +} From 0c46e5958d7efbb69ddfbf3c83ed1a50806315da Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 21 May 2020 02:28:31 +0300 Subject: [PATCH 4/8] feat: add string length validator --- .../core/src/lib/tests/validators.spec.ts | 37 +++++++++++++++++++ .../packages/core/src/lib/validators/index.ts | 3 ++ .../lib/validators/string-length.validator.ts | 34 +++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts index c2bc3d1e10..7b911c3708 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts @@ -2,6 +2,7 @@ import { FormControl } from '@angular/forms'; import { validateRange } from '../validators'; import { validateCreditCard } from '../validators/credit-card.validator'; import { validateRequired } from '../validators/required.validator'; +import { validateStringLength } from '../validators/string-length.validator'; describe('Validators', () => { describe('Credit Card Validator', () => { @@ -110,4 +111,40 @@ describe('Validators', () => { expect(control.valid).toBe(true); }); }); + + describe('String Length Validator', () => { + test.each` + input | options | expected + ${null} | ${undefined} | ${null} + ${undefined} | ${undefined} | ${null} + ${''} | ${undefined} | ${null} + ${null} | ${{ minimumLength: 0 }} | ${null} + ${undefined} | ${{ minimumLength: 0 }} | ${null} + ${''} | ${{ minimumLength: 0 }} | ${null} + ${null} | ${{ minimumLength: 3 }} | ${{ minlength: 3 }} + ${undefined} | ${{ minimumLength: 3 }} | ${{ minlength: 3 }} + ${''} | ${{ minimumLength: 3 }} | ${{ minlength: 3 }} + ${'ab'} | ${{ minimumLength: 3 }} | ${{ minlength: 3 }} + ${'abp'} | ${{ minimumLength: 3 }} | ${null} + ${'abp'} | ${{ maximumLength: 2 }} | ${{ maxlength: 2 }} + ${'abp'} | ${{ maximumLength: 3 }} | ${null} + `( + 'should return $expected when input is $input and options are $options', + ({ input, options, expected }) => { + const control = new FormControl(input, [validateStringLength(options)]); + control.markAsDirty({ onlySelf: true }); + control.updateValueAndValidity({ onlySelf: true, emitEvent: false }); + + expect(control.errors).toEqual(expected); + }, + ); + + it('should return null when control is pristine', () => { + const invalidUrl = ''; + const control = new FormControl(invalidUrl, [validateStringLength({ minimumLength: 3 })]); + // control is not dirty + + expect(control.valid).toBe(true); + }); + }); }); diff --git a/npm/ng-packs/packages/core/src/lib/validators/index.ts b/npm/ng-packs/packages/core/src/lib/validators/index.ts index df2a6f9c1b..ad7c29ed83 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/index.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/index.ts @@ -2,13 +2,16 @@ import { Validators } from '@angular/forms'; import { validateCreditCard } from './credit-card.validator'; import { validateRange } from './range.validator'; import { validateRequired } from './required.validator'; +import { validateStringLength } from './string-length.validator'; export * from './credit-card.validator'; export * from './range.validator'; export * from './required.validator'; +export * from './string-length.validator'; export const AbpValidators = { creditCard: validateCreditCard, email: () => Validators.email, range: validateRange, required: validateRequired, + stringLength: validateStringLength, }; diff --git a/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts new file mode 100644 index 0000000000..24c98a2435 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts @@ -0,0 +1,34 @@ +import { AbstractControl, ValidatorFn } from '@angular/forms'; + +export interface StringLengthError { + maxlength?: number; + minlength?: number; +} + +export interface StringLengthOptions { + maximumLength?: number; + minimumLength?: number; +} + +export function validateStringLength({ + maximumLength = Infinity, + minimumLength = 0, +}: StringLengthOptions = {}): ValidatorFn { + return (control: AbstractControl): StringLengthError | null => { + if (control.pristine) return null; + + if (!control.value && minimumLength) return { minlength: minimumLength }; + + const value = String(control.value); + + return getMinLengthError(value, minimumLength) || getMaxLengthError(value, maximumLength); + }; +} + +function getMaxLengthError(value: string, maxlength: number): StringLengthError { + return value.length > maxlength ? { maxlength } : null; +} + +function getMinLengthError(value: string, minlength: number): StringLengthError { + return value.length < minlength ? { minlength } : null; +} From b6743ae350e3a39767bde96160d836f7610451a7 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 21 May 2020 02:28:50 +0300 Subject: [PATCH 5/8] feat: add url validator --- .../core/src/lib/tests/validators.spec.ts | 37 +++++++++++++++++++ .../packages/core/src/lib/validators/index.ts | 3 ++ .../core/src/lib/validators/url.validator.ts | 23 ++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/validators/url.validator.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts index 7b911c3708..4465c02788 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts @@ -3,6 +3,7 @@ import { validateRange } from '../validators'; import { validateCreditCard } from '../validators/credit-card.validator'; import { validateRequired } from '../validators/required.validator'; import { validateStringLength } from '../validators/string-length.validator'; +import { validateUrl } from '../validators/url.validator'; describe('Validators', () => { describe('Credit Card Validator', () => { @@ -147,4 +148,40 @@ describe('Validators', () => { expect(control.valid).toBe(true); }); }); + + describe('Url Validator', () => { + const error = { url: true }; + + test.each` + input | expected + ${'http://x'} | ${null} + ${'http:///x'} | ${error} + ${'https://x'} | ${null} + ${'https:///x'} | ${error} + ${'ftp://x'} | ${null} + ${'ftp:///x'} | ${error} + ${'http://x.com'} | ${null} + ${'http://x.photography'} | ${null} + ${'http://www.x.org'} | ${null} + ${'http://sub.x.gov.tr'} | ${null} + ${'x'} | ${error} + ${'x.com'} | ${error} + ${'www.x.org'} | ${error} + ${'sub.x.gov.tr'} | ${error} + `('should return $expected when input is $input', ({ input, expected }) => { + const control = new FormControl(input, [validateUrl()]); + control.markAsDirty({ onlySelf: true }); + control.updateValueAndValidity({ onlySelf: true, emitEvent: false }); + + expect(control.errors).toEqual(expected); + }); + + it('should return null when control is pristine', () => { + const invalidUrl = 'x'; + const control = new FormControl(invalidUrl, [validateUrl()]); + // control is not dirty + + expect(control.valid).toBe(true); + }); + }); }); diff --git a/npm/ng-packs/packages/core/src/lib/validators/index.ts b/npm/ng-packs/packages/core/src/lib/validators/index.ts index ad7c29ed83..26e0c08086 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/index.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/index.ts @@ -3,10 +3,12 @@ import { validateCreditCard } from './credit-card.validator'; import { validateRange } from './range.validator'; import { validateRequired } from './required.validator'; import { validateStringLength } from './string-length.validator'; +import { validateUrl } from './url.validator'; export * from './credit-card.validator'; export * from './range.validator'; export * from './required.validator'; export * from './string-length.validator'; +export * from './url.validator'; export const AbpValidators = { creditCard: validateCreditCard, @@ -14,4 +16,5 @@ export const AbpValidators = { range: validateRange, required: validateRequired, stringLength: validateStringLength, + url: validateUrl, }; diff --git a/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts new file mode 100644 index 0000000000..ef8ab4298c --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts @@ -0,0 +1,23 @@ +import { AbstractControl, ValidatorFn } from '@angular/forms'; + +export interface UrlError { + url: true; +} + +export function validateUrl(): ValidatorFn { + return (control: AbstractControl): UrlError | null => { + if (control.pristine) return null; + + return isValidUrl(control.value) ? null : { url: true }; + }; +} + +function isValidUrl(value: string): boolean { + if (/^http(s)?:\/\/[^/]/.test(value) || /^ftp:\/\/[^/]/.test(value)) { + const a = document.createElement('a'); + a.href = value; + return !!a.host; + } + + return false; +} From 38084bf914e94e319c65e2b45683a4fbed3f8a1e Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 21 May 2020 02:54:43 +0300 Subject: [PATCH 6/8] feat: return null when input is empty --- .../core/src/lib/tests/validators.spec.ts | 24 +++++++++---------- .../lib/validators/credit-card.validator.ts | 2 ++ .../src/lib/validators/range.validator.ts | 2 +- .../lib/validators/string-length.validator.ts | 2 +- .../core/src/lib/validators/url.validator.ts | 2 ++ 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts index 4465c02788..8b7e64f8db 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts @@ -11,6 +11,9 @@ describe('Validators', () => { test.each` input | expected + ${undefined} | ${null} + ${null} | ${null} + ${''} | ${null} ${'0'} | ${error} ${'5105105105105100' /* Mastercard */} | ${null} ${'5105105105105101' /* Mastercard */} | ${error} @@ -47,15 +50,13 @@ describe('Validators', () => { describe('Range Validator', () => { test.each` input | options | expected - ${null} | ${undefined} | ${{ min: 0, max: Infinity }} - ${undefined} | ${undefined} | ${{ min: 0, max: Infinity }} - ${''} | ${undefined} | ${{ min: 0, max: Infinity }} + ${null} | ${undefined} | ${null} + ${undefined} | ${undefined} | ${null} + ${''} | ${undefined} | ${null} ${0} | ${undefined} | ${null} ${Infinity} | ${undefined} | ${null} - ${null} | ${{ minimum: 0 }} | ${{ min: 0, max: Infinity }} - ${undefined} | ${{ minimum: 0 }} | ${{ min: 0, max: Infinity }} - ${''} | ${{ minimum: 0 }} | ${{ min: 0, max: Infinity }} - ${0} | ${{ minimum: 0 }} | ${null} + ${'-1'} | ${{ minimum: 0 }} | ${{ min: 0, max: Infinity }} + ${-1} | ${{ minimum: 0 }} | ${{ min: 0, max: Infinity }} ${2} | ${{ minimum: 3, maximum: 5 }} | ${{ min: 3, max: 5 }} ${3} | ${{ minimum: 3, maximum: 5 }} | ${null} ${5} | ${{ minimum: 3, maximum: 5 }} | ${null} @@ -119,12 +120,6 @@ describe('Validators', () => { ${null} | ${undefined} | ${null} ${undefined} | ${undefined} | ${null} ${''} | ${undefined} | ${null} - ${null} | ${{ minimumLength: 0 }} | ${null} - ${undefined} | ${{ minimumLength: 0 }} | ${null} - ${''} | ${{ minimumLength: 0 }} | ${null} - ${null} | ${{ minimumLength: 3 }} | ${{ minlength: 3 }} - ${undefined} | ${{ minimumLength: 3 }} | ${{ minlength: 3 }} - ${''} | ${{ minimumLength: 3 }} | ${{ minlength: 3 }} ${'ab'} | ${{ minimumLength: 3 }} | ${{ minlength: 3 }} ${'abp'} | ${{ minimumLength: 3 }} | ${null} ${'abp'} | ${{ maximumLength: 2 }} | ${{ maxlength: 2 }} @@ -154,6 +149,9 @@ describe('Validators', () => { test.each` input | expected + ${undefined} | ${null} + ${null} | ${null} + ${''} | ${null} ${'http://x'} | ${null} ${'http:///x'} | ${error} ${'https://x'} | ${null} diff --git a/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts index 0436068d02..8129badfb7 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts @@ -8,6 +8,8 @@ export function validateCreditCard(): ValidatorFn { return (control: AbstractControl): CreditCardError | null => { if (control.pristine) return null; + if (['', null, undefined].indexOf(control.value) > -1) return null; + return isValidCreditCard(String(control.value)) ? null : { creditCardNumber: true }; }; } diff --git a/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts index 15a9862fcd..6530477f53 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts @@ -14,7 +14,7 @@ export function validateRange({ maximum = Infinity, minimum = 0 }: RangeOptions return (control: AbstractControl): RangeError | null => { if (control.pristine) return null; - if (['', null, undefined].indexOf(control.value) > -1) return { min: minimum, max: maximum }; + if (['', null, undefined].indexOf(control.value) > -1) return null; const value = Number(control.value); return getMinError(value, minimum, maximum) || getMaxError(value, maximum, minimum); diff --git a/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts index 24c98a2435..5a874f7f34 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts @@ -17,7 +17,7 @@ export function validateStringLength({ return (control: AbstractControl): StringLengthError | null => { if (control.pristine) return null; - if (!control.value && minimumLength) return { minlength: minimumLength }; + if (['', null, undefined].indexOf(control.value) > -1) return null; const value = String(control.value); diff --git a/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts index ef8ab4298c..6eb3169837 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts @@ -8,6 +8,8 @@ export function validateUrl(): ValidatorFn { return (control: AbstractControl): UrlError | null => { if (control.pristine) return null; + if (['', null, undefined].indexOf(control.value) > -1) return null; + return isValidUrl(control.value) ? null : { url: true }; }; } From 69185cbc4044aa91eb7c9c4c50fea33ed831d1e1 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 21 May 2020 02:56:42 +0300 Subject: [PATCH 7/8] test: add a spec for email validator --- .../packages/core/src/lib/tests/validators.spec.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts index 8b7e64f8db..9ea0e74535 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts @@ -1,5 +1,5 @@ -import { FormControl } from '@angular/forms'; -import { validateRange } from '../validators'; +import { FormControl, Validators } from '@angular/forms'; +import { AbpValidators, validateRange } from '../validators'; import { validateCreditCard } from '../validators/credit-card.validator'; import { validateRequired } from '../validators/required.validator'; import { validateStringLength } from '../validators/string-length.validator'; @@ -47,6 +47,12 @@ describe('Validators', () => { }); }); + describe('Email Validator', () => { + it('should return email validator of Angular', () => { + expect(AbpValidators.email()).toBe(Validators.email); + }); + }); + describe('Range Validator', () => { test.each` input | options | expected From 4131e12007f8f99a4e172eb97867cf9a105eca36 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 21 May 2020 03:28:59 +0300 Subject: [PATCH 8/8] feat: sync validation errors with error messages --- .../core/src/lib/tests/validators.spec.ts | 16 ++++++------- .../lib/validators/credit-card.validator.ts | 4 ++-- .../packages/core/src/lib/validators/index.ts | 2 +- .../src/lib/validators/range.validator.ts | 10 ++++---- .../lib/validators/string-length.validator.ts | 16 ++++++++----- .../theme-basic/src/lib/theme-basic.module.ts | 24 +++++++++++-------- 6 files changed, 41 insertions(+), 31 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts index 9ea0e74535..699402751e 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts @@ -7,7 +7,7 @@ import { validateUrl } from '../validators/url.validator'; describe('Validators', () => { describe('Credit Card Validator', () => { - const error = { creditCardNumber: true }; + const error = { creditCard: true }; test.each` input | expected @@ -49,7 +49,7 @@ describe('Validators', () => { describe('Email Validator', () => { it('should return email validator of Angular', () => { - expect(AbpValidators.email()).toBe(Validators.email); + expect(AbpValidators.emailAddress()).toBe(Validators.email); }); }); @@ -61,12 +61,12 @@ describe('Validators', () => { ${''} | ${undefined} | ${null} ${0} | ${undefined} | ${null} ${Infinity} | ${undefined} | ${null} - ${'-1'} | ${{ minimum: 0 }} | ${{ min: 0, max: Infinity }} - ${-1} | ${{ minimum: 0 }} | ${{ min: 0, max: Infinity }} - ${2} | ${{ minimum: 3, maximum: 5 }} | ${{ min: 3, max: 5 }} + ${'-1'} | ${{ minimum: 0 }} | ${{ range: { min: 0, max: Infinity } }} + ${-1} | ${{ minimum: 0 }} | ${{ range: { min: 0, max: Infinity } }} + ${2} | ${{ minimum: 3, maximum: 5 }} | ${{ range: { min: 3, max: 5 } }} ${3} | ${{ minimum: 3, maximum: 5 }} | ${null} ${5} | ${{ minimum: 3, maximum: 5 }} | ${null} - ${6} | ${{ minimum: 3, maximum: 5 }} | ${{ min: 3, max: 5 }} + ${6} | ${{ minimum: 3, maximum: 5 }} | ${{ range: { min: 3, max: 5 } }} `( 'should return $expected when input is $input and options are $options', ({ input, options, expected }) => { @@ -126,9 +126,9 @@ describe('Validators', () => { ${null} | ${undefined} | ${null} ${undefined} | ${undefined} | ${null} ${''} | ${undefined} | ${null} - ${'ab'} | ${{ minimumLength: 3 }} | ${{ minlength: 3 }} + ${'ab'} | ${{ minimumLength: 3 }} | ${{ minlength: { requiredLength: 3 } }} ${'abp'} | ${{ minimumLength: 3 }} | ${null} - ${'abp'} | ${{ maximumLength: 2 }} | ${{ maxlength: 2 }} + ${'abp'} | ${{ maximumLength: 2 }} | ${{ maxlength: { requiredLength: 2 } }} ${'abp'} | ${{ maximumLength: 3 }} | ${null} `( 'should return $expected when input is $input and options are $options', diff --git a/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts index 8129badfb7..70de5eca77 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts @@ -1,7 +1,7 @@ import { AbstractControl, ValidatorFn } from '@angular/forms'; export interface CreditCardError { - creditCardNumber: true; + creditCard: true; } export function validateCreditCard(): ValidatorFn { @@ -10,7 +10,7 @@ export function validateCreditCard(): ValidatorFn { if (['', null, undefined].indexOf(control.value) > -1) return null; - return isValidCreditCard(String(control.value)) ? null : { creditCardNumber: true }; + return isValidCreditCard(String(control.value)) ? null : { creditCard: true }; }; } diff --git a/npm/ng-packs/packages/core/src/lib/validators/index.ts b/npm/ng-packs/packages/core/src/lib/validators/index.ts index 26e0c08086..4fcf06262d 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/index.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/index.ts @@ -12,7 +12,7 @@ export * from './url.validator'; export const AbpValidators = { creditCard: validateCreditCard, - email: () => Validators.email, + emailAddress: () => Validators.email, range: validateRange, required: validateRequired, stringLength: validateStringLength, diff --git a/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts index 6530477f53..c9e6edee6a 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/range.validator.ts @@ -1,8 +1,10 @@ import { AbstractControl, ValidatorFn } from '@angular/forms'; export interface RangeError { - max: number; - min: number; + range: { + max: number; + min: number; + }; } export interface RangeOptions { @@ -22,9 +24,9 @@ export function validateRange({ maximum = Infinity, minimum = 0 }: RangeOptions } function getMaxError(value: number, max: number, min: number): RangeError { - return value > max ? { max, min } : null; + return value > max ? { range: { max, min } } : null; } function getMinError(value: number, min: number, max: number): RangeError { - return value < min ? { min, max } : null; + return value < min ? { range: { min, max } } : null; } diff --git a/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts index 5a874f7f34..167240cfa6 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts @@ -1,8 +1,12 @@ import { AbstractControl, ValidatorFn } from '@angular/forms'; export interface StringLengthError { - maxlength?: number; - minlength?: number; + maxlength?: { + requiredLength: number; + }; + minlength?: { + requiredLength: number; + }; } export interface StringLengthOptions { @@ -25,10 +29,10 @@ export function validateStringLength({ }; } -function getMaxLengthError(value: string, maxlength: number): StringLengthError { - return value.length > maxlength ? { maxlength } : null; +function getMaxLengthError(value: string, requiredLength: number): StringLengthError { + return value.length > requiredLength ? { maxlength: { requiredLength } } : null; } -function getMinLengthError(value: string, minlength: number): StringLengthError { - return value.length < minlength ? { minlength } : null; +function getMinLengthError(value: string, requiredLength: number): StringLengthError { + return value.length < requiredLength ? { minlength: { requiredLength } } : null; } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts b/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts index f6395a93a7..5e50e6f33c 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts @@ -7,12 +7,12 @@ import { NgxsModule } from '@ngxs/store'; import { AccountLayoutComponent } from './components/account-layout/account-layout.component'; import { ApplicationLayoutComponent } from './components/application-layout/application-layout.component'; import { EmptyLayoutComponent } from './components/empty-layout/empty-layout.component'; -import { LayoutState } from './states/layout.state'; -import { ValidationErrorComponent } from './components/validation-error/validation-error.component'; -import { InitialService } from './services/initial.service'; import { LogoComponent } from './components/logo/logo.component'; -import { RoutesComponent } from './components/routes/routes.component'; import { NavItemsComponent } from './components/nav-items/nav-items.component'; +import { RoutesComponent } from './components/routes/routes.component'; +import { ValidationErrorComponent } from './components/validation-error/validation-error.component'; +import { InitialService } from './services/initial.service'; +import { LayoutState } from './states/layout.state'; export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, EmptyLayoutComponent]; @@ -34,15 +34,19 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt NgxValidateCoreModule.forRoot({ targetSelector: '.form-group', blueprints: { - email: 'AbpAccount::ThisFieldIsNotAValidEmailAddress.', - max: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]', + creditCard: 'AbpValidation::ThisFieldIsNotAValidCreditCardNumber.', + email: 'AbpValidation::ThisFieldIsNotAValidEmailAddress.', + max: 'AbpValidation::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]', maxlength: - 'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}[{{ requiredLength }}]', - min: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]', + 'AbpValidation::ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}[{{ requiredLength }}]', + min: 'AbpValidation::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]', minlength: - 'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}[{{ requiredLength }}]', - required: 'AbpAccount::ThisFieldIsRequired.', + 'AbpValidation::ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}[{{ requiredLength }}]', + ngbDate: 'AbpValidation::ThisFieldIsNotValid.', passwordMismatch: 'AbpIdentity::Identity.PasswordConfirmationFailed', + range: 'AbpValidation::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]', + required: 'AbpValidation::ThisFieldIsRequired.', + url: 'AbpValidation::ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl', }, errorTemplate: ValidationErrorComponent, }),