mirror of https://github.com/abpframework/abp.git
committed by
GitHub
9 changed files with 380 additions and 10 deletions
@ -0,0 +1,191 @@ |
|||||
|
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'; |
||||
|
import { validateUrl } from '../validators/url.validator'; |
||||
|
|
||||
|
describe('Validators', () => { |
||||
|
describe('Credit Card Validator', () => { |
||||
|
const error = { creditCard: true }; |
||||
|
|
||||
|
test.each` |
||||
|
input | expected |
||||
|
${undefined} | ${null} |
||||
|
${null} | ${null} |
||||
|
${''} | ${null} |
||||
|
${'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('Email Validator', () => { |
||||
|
it('should return email validator of Angular', () => { |
||||
|
expect(AbpValidators.emailAddress()).toBe(Validators.email); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('Range Validator', () => { |
||||
|
test.each` |
||||
|
input | options | expected |
||||
|
${null} | ${undefined} | ${null} |
||||
|
${undefined} | ${undefined} | ${null} |
||||
|
${''} | ${undefined} | ${null} |
||||
|
${0} | ${undefined} | ${null} |
||||
|
${Infinity} | ${undefined} | ${null} |
||||
|
${'-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 }} | ${{ range: { 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 }; |
||||
|
|
||||
|
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); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('String Length Validator', () => { |
||||
|
test.each` |
||||
|
input | options | expected |
||||
|
${null} | ${undefined} | ${null} |
||||
|
${undefined} | ${undefined} | ${null} |
||||
|
${''} | ${undefined} | ${null} |
||||
|
${'ab'} | ${{ minimumLength: 3 }} | ${{ minlength: { requiredLength: 3 } }} |
||||
|
${'abp'} | ${{ minimumLength: 3 }} | ${null} |
||||
|
${'abp'} | ${{ maximumLength: 2 }} | ${{ maxlength: { requiredLength: 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); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('Url Validator', () => { |
||||
|
const error = { url: true }; |
||||
|
|
||||
|
test.each` |
||||
|
input | expected |
||||
|
${undefined} | ${null} |
||||
|
${null} | ${null} |
||||
|
${''} | ${null} |
||||
|
${'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); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,34 @@ |
|||||
|
import { AbstractControl, ValidatorFn } from '@angular/forms'; |
||||
|
|
||||
|
export interface CreditCardError { |
||||
|
creditCard: true; |
||||
|
} |
||||
|
|
||||
|
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 : { creditCard: 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; |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
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'; |
||||
|
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, |
||||
|
emailAddress: () => Validators.email, |
||||
|
range: validateRange, |
||||
|
required: validateRequired, |
||||
|
stringLength: validateStringLength, |
||||
|
url: validateUrl, |
||||
|
}; |
||||
@ -0,0 +1,32 @@ |
|||||
|
import { AbstractControl, ValidatorFn } from '@angular/forms'; |
||||
|
|
||||
|
export interface RangeError { |
||||
|
range: { |
||||
|
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 null; |
||||
|
|
||||
|
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 ? { range: { max, min } } : null; |
||||
|
} |
||||
|
|
||||
|
function getMinError(value: number, min: number, max: number): RangeError { |
||||
|
return value < min ? { range: { min, max } } : null; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
import { AbstractControl, ValidatorFn } from '@angular/forms'; |
||||
|
|
||||
|
export interface StringLengthError { |
||||
|
maxlength?: { |
||||
|
requiredLength: number; |
||||
|
}; |
||||
|
minlength?: { |
||||
|
requiredLength: 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 (['', null, undefined].indexOf(control.value) > -1) return null; |
||||
|
|
||||
|
const value = String(control.value); |
||||
|
|
||||
|
return getMinLengthError(value, minimumLength) || getMaxLengthError(value, maximumLength); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function getMaxLengthError(value: string, requiredLength: number): StringLengthError { |
||||
|
return value.length > requiredLength ? { maxlength: { requiredLength } } : null; |
||||
|
} |
||||
|
|
||||
|
function getMinLengthError(value: string, requiredLength: number): StringLengthError { |
||||
|
return value.length < requiredLength ? { minlength: { requiredLength } } : null; |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
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; |
||||
|
|
||||
|
if (['', null, undefined].indexOf(control.value) > -1) 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; |
||||
|
} |
||||
Loading…
Reference in new issue