diff --git a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.spec.ts b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.spec.ts index 3286b9215d..f3e8d0ba7a 100644 --- a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.spec.ts @@ -264,6 +264,153 @@ describe('PoInputBase:', () => { const validValues = [true, 'true', 1, ' ']; expectPropertiesValues(component, 'noAutocomplete', validValues, true); }); + + describe('p-mask-no-length-validation:', () => { + describe('with p-maxlength:', () => { + it('validate: should return maxlength false when `maskNoLengthValidation` is false and value exceeds maxlength', () => { + component.maxlength = 5; + component.maskNoLengthValidation = false; + component.getScreenValue = () => '123456'; + + const result = component.validate(new FormControl('123456')); + + expect(result).toEqual({ + maxlength: { + valid: false + } + }); + }); + + it('validate: should return null when `maskNoLengthValidation` is false and value is within maxlength', () => { + component.maxlength = 6; + component.maskNoLengthValidation = false; + component.getScreenValue = () => '123456'; + + const result = component.validate(new FormControl('123456')); + + expect(result).toBeNull(); + }); + + it('validate: should return maxlength false when `maskNoLengthValidation` is true, ignoring special characters, and value exceeds maxlength', () => { + component.maxlength = 5; + component.maskNoLengthValidation = true; + component.getScreenValue = () => '12-3456'; + + const result = component.validate(new FormControl('12-3456')); + + expect(result).toEqual({ + maxlength: { + valid: false + } + }); + }); + + it('validate: should return null when `maskNoLengthValidation` is true and alphanumeric value is within maxlength ignoring special characters', () => { + component.maxlength = 6; + component.maskNoLengthValidation = true; + component.getScreenValue = () => '12-345'; + + const result = component.validate(new FormControl('12-345')); + + expect(result).toBeNull(); + }); + + it('validate: should handle special characters-only input correctly when `maskNoLengthValidation` is true', () => { + component.maxlength = 1; + component.maskNoLengthValidation = true; + component.getScreenValue = () => '---'; + + const result = component.validate(new FormControl('---')); + + expect(result).toBeNull(); + }); + + it('validate: should handle null or undefined values gracefully', () => { + component.maxlength = 5; + component.maskNoLengthValidation = true; + + let result = component.validate(new FormControl(null)); + expect(result).toBeNull(); + + result = component.validate(new FormControl(undefined)); + expect(result).toBeNull(); + }); + }); + describe('with p-minlength:', () => { + it('validate: should return minlength false when `maskNoLengthValidation` is false and value is below minlength', () => { + component.minlength = 5; + component.maskNoLengthValidation = false; + component.getScreenValue = () => '123'; + + const result = component.validate(new FormControl('123')); + + expect(result).toEqual({ + minlength: { + valid: false + } + }); + }); + + it('validate: should return null when `maskNoLengthValidation` is false and value meets minlength', () => { + component.minlength = 3; + component.maskNoLengthValidation = false; + component.getScreenValue = () => '123'; + + const result = component.validate(new FormControl('123')); + + expect(result).toBeNull(); + }); + + it('validate: should return minlength false when `maskNoLengthValidation` is true, ignoring special characters, and value is below minlength', () => { + component.minlength = 5; + component.maskNoLengthValidation = true; + component.getScreenValue = () => '1-2-3'; + + const result = component.validate(new FormControl('1-2-3')); + + expect(result).toEqual({ + minlength: { + valid: false + } + }); + }); + + it('validate: should return null when `maskNoLengthValidation` is true and alphanumeric value meets minlength ignoring special characters', () => { + component.minlength = 3; + component.maskNoLengthValidation = true; + component.getScreenValue = () => '1-2-3'; + + const result = component.validate(new FormControl('1-2-3')); + + expect(result).toBeNull(); + }); + + it('validate: should handle special characters-only input correctly when `maskNoLengthValidation` is true', () => { + component.minlength = 1; + component.maskNoLengthValidation = true; + component.getScreenValue = () => '---'; + + const result = component.validate(new FormControl('---')); + + expect(result).toEqual({ + minlength: { + valid: false + } + }); + }); + + it('validate: should handle null or undefined values gracefully', () => { + component.minlength = 5; + component.maskNoLengthValidation = true; + + let result = component.validate(new FormControl(null)); + expect(result).toBeNull(); + + result = component.validate(new FormControl(undefined)); + expect(result).toBeNull(); + }); + }); + }); }); describe('Methods:', () => { diff --git a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts index 35a517c2d5..7a43ddc2f6 100644 --- a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts @@ -173,6 +173,29 @@ export abstract class PoInputBaseComponent implements ControlValueAccessor, Vali */ @Input({ alias: 'p-upper-case', transform: convertToBoolean }) upperCase: boolean = false; + /** + * @description + * + * Define se os caracteres especiais da máscara devem ser ignorados ao validar os comprimentos mínimo (`minLength`) e máximo (`maxLength`) do campo. + * + * - Quando `true`, apenas os caracteres alfanuméricos serão contabilizados para a validação dos comprimentos. + * - Quando `false`, todos os caracteres, incluindo os especiais da máscara, serão considerados na validação. + * + * Exemplo: + * ``` + * + * ``` + * - Entrada: `123-456` → Validação será aplicada somente aos números, ignorando o caractere especial `-`. + * + * @default `false` + */ + @Input({ alias: 'p-mask-no-length-validation', transform: convertToBoolean }) maskNoLengthValidation: boolean = false; + /** * @optional * @@ -487,7 +510,7 @@ export abstract class PoInputBaseComponent implements ControlValueAccessor, Vali }; } - if (maxlengpoailed(this.maxlength, this.getScreenValue())) { + if (maxlengpoailed(this.maxlength, this.getScreenValue(), this.maskNoLengthValidation)) { this.isInvalid = true; return { maxlength: { @@ -496,7 +519,7 @@ export abstract class PoInputBaseComponent implements ControlValueAccessor, Vali }; } - if (minlengpoailed(this.minlength, this.getScreenValue())) { + if (minlengpoailed(this.minlength, this.getScreenValue(), this.maskNoLengthValidation)) { this.isInvalid = true; return { minlength: { diff --git a/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.html b/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.html index 3f13c9f8aa..34c9888fc5 100644 --- a/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.html @@ -20,6 +20,7 @@ [p-readonly]="properties?.includes('readonly')" [p-upper-case]="properties?.includes('uppercase')" [p-show-required]="properties?.includes('showRequired')" + [p-mask-no-length-validation]="properties?.includes('maskNoLengthValidation')" (p-blur)="changeEvent('p-blur')" (p-change)="changeEvent('p-change')" (p-change-model)="changeEvent('p-change-model')" diff --git a/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.ts b/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.ts index a2e4c501bf..501a2651f9 100644 --- a/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.ts @@ -36,7 +36,8 @@ export class SamplePoInputLabsComponent implements OnInit { { value: 'required', label: 'Required' }, { value: 'requiredFieldErrorMessage', label: 'Required Field Error Message' }, { value: 'uppercase', label: 'Upper Case' }, - { value: 'showRequired', label: 'Show Required' } + { value: 'showRequired', label: 'Show Required' }, + { value: 'maskNoLengthValidation', label: 'Mask No Length Validation' } ]; ngOnInit() { diff --git a/projects/ui/src/lib/components/po-field/validators.spec.ts b/projects/ui/src/lib/components/po-field/validators.spec.ts index 0339b6d55b..5421de443a 100644 --- a/projects/ui/src/lib/components/po-field/validators.spec.ts +++ b/projects/ui/src/lib/components/po-field/validators.spec.ts @@ -5,7 +5,8 @@ import { patternFailed, minFailed, maxFailed, - dateFailed + dateFailed, + validateLength } from './validators'; describe('requiredFailed: ', () => { @@ -41,25 +42,41 @@ describe('requiredFailed: ', () => { }); describe('Function maxlengpoailed:', () => { - it('should return `true` if value.length is greater than `maxlength`', () => { - expect(maxlengpoailed(3, '1234')).toBeTruthy(); - expect(maxlengpoailed(3, '1234567')).toBeTruthy(); - expect(maxlengpoailed(5, 'abcdef')).toBeTruthy(); - expect(maxlengpoailed(10, 'abcdefghijk')).toBeTruthy(); - expect(maxlengpoailed(0, 'abc')).toBeTruthy(); - }); - - it('should return `false` if value.length is less or equal than `maxlength` or value or `maxlength` is invalid', () => { - expect(maxlengpoailed(3, '123')).toBeFalsy(); - expect(maxlengpoailed(1, '1')).toBeFalsy(); - expect(maxlengpoailed(0, '')).toBeFalsy(); - expect(maxlengpoailed(20, null)).toBeFalsy(); - expect(maxlengpoailed(20, undefined)).toBeFalsy(); - expect(maxlengpoailed(5, 'abcd')).toBeFalsy(); - expect(maxlengpoailed(2, 'a')).toBeFalsy(); - expect(maxlengpoailed(undefined, 'abc')).toBeFalsy(); - expect(maxlengpoailed(null, '123')).toBeFalsy(); - expect(maxlengpoailed(NaN, '123')).toBeFalsy(); + it('should return `true` if value.length is greater than `maxlength` without ignoring special characters', () => { + expect(maxlengpoailed(3, '1234', false)).toBeTruthy(); + expect(maxlengpoailed(5, 'abc-de', false)).toBeTruthy(); + expect(maxlengpoailed(7, 'abc@def!', false)).toBeTruthy(); + }); + + it('should return `false` if value.length is less or equal than `maxlength` without ignoring special characters', () => { + expect(maxlengpoailed(3, '123', false)).toBeFalsy(); + expect(maxlengpoailed(6, 'abc-def', false)).toBeTruthy(); + expect(maxlengpoailed(7, 'abcdef!', false)).toBeFalsy(); + }); + + it('should return `true` if alphanumeric value.length exceeds `maxlength` while ignoring special characters', () => { + expect(maxlengpoailed(3, '123-4', true)).toBeTruthy(); + expect(maxlengpoailed(3, 'abc@de!', true)).toBeTruthy(); + }); + + it('should return `false` if alphanumeric value.length is within `maxlength` while ignoring special characters', () => { + expect(maxlengpoailed(4, '123-4', true)).toBeFalsy(); + expect(maxlengpoailed(5, 'abc@d!', true)).toBeFalsy(); + }); + + it('should handle null or undefined values gracefully', () => { + expect(maxlengpoailed(3, null, true)).toBeFalsy(); + expect(maxlengpoailed(3, undefined, true)).toBeFalsy(); + }); + + it('should handle edge cases for special character-only inputs', () => { + expect(maxlengpoailed(1, '---', true)).toBeFalsy(); + expect(maxlengpoailed(1, '@@@', true)).toBeFalsy(); + }); + + it('should consider all characters when ignoring special characters is disabled', () => { + expect(maxlengpoailed(5, '123-4', false)).toBeFalsy(); + expect(maxlengpoailed(5, 'abc@!', false)).toBeFalsy(); }); }); @@ -85,6 +102,73 @@ describe('Function minlengpoailed:', () => { expect(minlengpoailed(NaN, '123')).toBeFalsy(); expect(minlengpoailed(0, '123')).toBeFalsy(); }); + + it('should correctly handle maskNoLengthValidation when true', () => { + expect(minlengpoailed(3, '1-2', true)).toBeTruthy(); + expect(minlengpoailed(3, '1-2-3', true)).toBeFalsy(); + expect(minlengpoailed(5, '12-345', true)).toBeFalsy(); + expect(minlengpoailed(5, '12--34', true)).toBeTruthy(); + }); + + it('should correctly handle maskNoLengthValidation when false', () => { + expect(minlengpoailed(3, '1-2', false)).toBeFalsy(); + expect(minlengpoailed(3, '1-2-3', false)).toBeFalsy(); + expect(minlengpoailed(5, '12-345', false)).toBeFalsy(); + expect(minlengpoailed(5, '12--34', false)).toBeFalsy(); + }); + + it('should return `false` for invalid values or limits regardless of maskNoLengthValidation', () => { + expect(minlengpoailed(undefined, '123', true)).toBeFalsy(); + expect(minlengpoailed(null, '123', true)).toBeFalsy(); + expect(minlengpoailed(3, null, true)).toBeFalsy(); + expect(minlengpoailed(3, undefined, true)).toBeFalsy(); + + expect(minlengpoailed(undefined, '123', false)).toBeFalsy(); + expect(minlengpoailed(null, '123', false)).toBeFalsy(); + expect(minlengpoailed(3, null, false)).toBeFalsy(); + expect(minlengpoailed(3, undefined, false)).toBeFalsy(); + }); +}); + +describe('Function validateLength:', () => { + it('should return true if value length exceeds limit and comparison is "max"', () => { + expect(validateLength(3, '1234', 'max')).toBeTruthy(); + expect(validateLength(5, 'abcdef', 'max')).toBeTruthy(); + }); + + it('should return false if value length is within limit and comparison is "max"', () => { + expect(validateLength(3, '123', 'max')).toBeFalsy(); + expect(validateLength(5, 'abcd', 'max')).toBeFalsy(); + }); + + it('should return true if value length is below limit and comparison is "min"', () => { + expect(validateLength(5, '1234', 'min')).toBeTruthy(); + expect(validateLength(3, '12', 'min')).toBeTruthy(); + }); + + it('should return false if value length meets or exceeds limit and comparison is "min"', () => { + expect(validateLength(3, '123', 'min')).toBeFalsy(); + expect(validateLength(5, 'abcdef', 'min')).toBeFalsy(); + }); + + it('should correctly handle maskNoLengthValidation when true', () => { + expect(validateLength(5, '1-2-3-4-5', 'max', true)).toBeFalsy(); + expect(validateLength(5, '1-2-3', 'min', true)).toBeTruthy(); + }); + + it('should return false for invalid values or limits', () => { + expect(validateLength(undefined, '123', 'max')).toBeFalsy(); + expect(validateLength(null, '123', 'max')).toBeFalsy(); + expect(validateLength(3, null, 'max')).toBeFalsy(); + expect(validateLength(3, undefined, 'max')).toBeFalsy(); + }); + + it('should return false if comparison is not "max" or "min"', () => { + expect(validateLength(3, '1234', 'invalidComparison' as any)).toBeFalsy(); + expect(validateLength(3, '1234', '' as any)).toBeFalsy(); + expect(validateLength(3, '1234', null)).toBeFalsy(); + expect(validateLength(3, '1234', undefined)).toBeFalsy(); + }); }); describe('Function patternFailed', () => { diff --git a/projects/ui/src/lib/components/po-field/validators.ts b/projects/ui/src/lib/components/po-field/validators.ts index f2ac9c63c3..aaa6481063 100644 --- a/projects/ui/src/lib/components/po-field/validators.ts +++ b/projects/ui/src/lib/components/po-field/validators.ts @@ -7,16 +7,46 @@ export function requiredFailed(required: boolean, disabled: boolean, value: stri return required && !disabled && !valid; } -export function maxlengpoailed(maxlength: number, value: string | number) { - const validMaxlength = maxlength || maxlength === 0; - const validValue = (value || value === 0) && value.toString(); - return validMaxlength && validValue && validValue.length > Number(maxlength); +export function maxlengpoailed( + maxlength: number, + value: string | number, + maskNoLengthValidation: boolean = false +): boolean { + return validateLength(maxlength, value, 'max', maskNoLengthValidation); +} + +export function minlengpoailed( + minlength: number, + value: string | number, + maskNoLengthValidation: boolean = false +): boolean { + return validateLength(minlength, value, 'min', maskNoLengthValidation); } -export function minlengpoailed(minlength: number, value: string | number) { - const validMinlength = minlength || minlength === 0; +export function validateLength( + limit: number, + value: string | number, + comparison: 'max' | 'min', + maskNoLengthValidation: boolean = false +): boolean { + if (!limit && limit !== 0) { + return false; + } + const validValue = (value || value === 0) && value.toString(); - return validMinlength && validValue && validValue.length < Number(minlength); + if (!validValue) { + return false; + } + + const processedValue = maskNoLengthValidation ? validValue.replace(/[^\w]/g, '') : validValue; + + if (comparison === 'max') { + return processedValue.length > Number(limit); + } else if (comparison === 'min') { + return processedValue.length < Number(limit); + } + + return false; } export function patternFailed(pattern: string, value: string) {