fix(forms): the `min` and `max` validators should work correctly with `0` as a value (#42412)

Prior to this change the `min` and `max` validator directives would not
set the `min` and `max` attributes on the host element. The problem was
caused by the truthy check in host binding expression that was
calculated as `false` when `0` is used as a value. This commit updates
the logic to leverage nullish coalescing operator in these host binding
expressions, so `0` is treated as a valid value, thus the `min` and
`max` attributes are set correctly.

Partially closes #42267

PR Close #42412
This commit is contained in:
iRealNirmal 2021-05-29 10:21:30 +05:30 committed by Jessica Janiuk
parent 85c7f7691e
commit 751cd83ae3
3 changed files with 76 additions and 2 deletions

View File

@ -167,7 +167,7 @@ export const MAX_VALIDATOR: StaticProvider = {
selector: selector:
'input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]', 'input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]',
providers: [MAX_VALIDATOR], providers: [MAX_VALIDATOR],
host: {'[attr.max]': 'max ? max : null'} host: {'[attr.max]': 'max ?? null'}
}) })
export class MaxValidator extends AbstractValidatorDirective implements OnChanges { export class MaxValidator extends AbstractValidatorDirective implements OnChanges {
/** /**
@ -227,7 +227,7 @@ export const MIN_VALIDATOR: StaticProvider = {
selector: selector:
'input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]', 'input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]',
providers: [MIN_VALIDATOR], providers: [MIN_VALIDATOR],
host: {'[attr.min]': 'min ? min : null'} host: {'[attr.min]': 'min ?? null'}
}) })
export class MinValidator extends AbstractValidatorDirective implements OnChanges { export class MinValidator extends AbstractValidatorDirective implements OnChanges {
/** /**

View File

@ -2558,8 +2558,23 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
fixture.componentInstance.max = 1; fixture.componentInstance.max = 1;
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('max')).toEqual('1');
expect(form.valid).toBeFalse(); expect(form.valid).toBeFalse();
expect(form.controls.pin.errors).toEqual({max: {max: 1, actual: 2}}); expect(form.controls.pin.errors).toEqual({max: {max: 1, actual: 2}});
fixture.componentInstance.min = 0;
fixture.componentInstance.max = 0;
fixture.detectChanges();
expect(input.getAttribute('min')).toEqual('0');
expect(input.getAttribute('max')).toEqual('0');
expect(form.valid).toBeFalse();
expect(form.controls.pin.errors).toEqual({max: {max: 0, actual: 2}});
input.value = 0;
dispatchEvent(input, 'input');
fixture.detectChanges();
expect(form.valid).toBeTruthy();
expect(form.controls.pin.errors).toBeNull();
}); });
it('should validate max for float number', () => { it('should validate max for float number', () => {
@ -2573,6 +2588,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
const input = fixture.debugElement.query(By.css('input')).nativeElement; const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.componentInstance.form; const form = fixture.componentInstance.form;
expect(input.getAttribute('max')).toEqual('10.35');
expect(input.value).toEqual('10.25'); expect(input.value).toEqual('10.25');
expect(form.valid).toBeTruthy(); expect(form.valid).toBeTruthy();
expect(form.controls.pin.errors).toBeNull(); expect(form.controls.pin.errors).toBeNull();
@ -2586,6 +2602,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
fixture.componentInstance.max = 10.05; fixture.componentInstance.max = 10.05;
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('max')).toEqual('10.05');
expect(form.valid).toBeFalse(); expect(form.valid).toBeFalse();
expect(form.controls.pin.errors).toEqual({max: {max: 10.05, actual: 10.15}}); expect(form.controls.pin.errors).toEqual({max: {max: 10.05, actual: 10.15}});
@ -2618,6 +2635,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
fixture.componentInstance.max = 1; fixture.componentInstance.max = 1;
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('max')).toEqual('1');
expect(form.valid).toBeFalse(); expect(form.valid).toBeFalse();
expect(form.controls.pin.errors).toEqual({max: {max: 1, actual: 2}}); expect(form.controls.pin.errors).toEqual({max: {max: 1, actual: 2}});
}); });
@ -2644,8 +2662,23 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
fixture.componentInstance.min = 5; fixture.componentInstance.min = 5;
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('min')).toEqual('5');
expect(form.valid).toBeFalse(); expect(form.valid).toBeFalse();
expect(form.controls.pin.errors).toEqual({min: {min: 5, actual: 2}}); expect(form.controls.pin.errors).toEqual({min: {min: 5, actual: 2}});
fixture.componentInstance.min = 0;
input.value = -5;
dispatchEvent(input, 'input');
fixture.detectChanges();
expect(input.getAttribute('min')).toEqual('0');
expect(form.valid).toBeFalse();
expect(form.controls.pin.errors).toEqual({min: {min: 0, actual: -5}});
input.value = 0;
dispatchEvent(input, 'input');
fixture.detectChanges();
expect(form.valid).toBeTruthy();
expect(form.controls.pin.errors).toBeNull();
}); });
it('should validate min for float number', () => { it('should validate min for float number', () => {
@ -2660,6 +2693,8 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
const input = fixture.debugElement.query(By.css('input')).nativeElement; const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.componentInstance.form; const form = fixture.componentInstance.form;
expect(input.getAttribute('min')).toEqual('10.25');
expect(input.getAttribute('max')).toEqual('10.5');
expect(input.value).toEqual('10.25'); expect(input.value).toEqual('10.25');
expect(form.valid).toBeTruthy(); expect(form.valid).toBeTruthy();
expect(form.controls.pin.errors).toBeNull(); expect(form.controls.pin.errors).toBeNull();
@ -2672,6 +2707,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
fixture.componentInstance.min = 10.40; fixture.componentInstance.min = 10.40;
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('min')).toEqual('10.4');
expect(form.valid).toBeFalse(); expect(form.valid).toBeFalse();
expect(form.controls.pin.errors).toEqual({min: {min: 10.40, actual: 10.35}}); expect(form.controls.pin.errors).toEqual({min: {min: 10.40, actual: 10.35}});
@ -2704,6 +2740,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
fixture.componentInstance.min = 5; fixture.componentInstance.min = 5;
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('min')).toEqual('5');
expect(form.valid).toBeFalse(); expect(form.valid).toBeFalse();
expect(form.controls.pin.errors).toEqual({min: {min: 5, actual: 2}}); expect(form.controls.pin.errors).toEqual({min: {min: 5, actual: 2}});
}); });

View File

@ -1524,6 +1524,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
input.value = ''; input.value = '';
dispatchEvent(input, 'input'); dispatchEvent(input, 'input');
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('max')).toEqual('10');
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
expect(form.controls.max.errors).toBeNull(); expect(form.controls.max.errors).toBeNull();
@ -1538,6 +1539,21 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
fixture.detectChanges(); fixture.detectChanges();
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
expect(form.controls.max.errors).toBeNull(); expect(form.controls.max.errors).toBeNull();
fixture.componentInstance.max = 0;
fixture.detectChanges();
tick();
dispatchEvent(input, 'input');
fixture.detectChanges();
expect(input.getAttribute('max')).toEqual('0');
expect(form.valid).toEqual(false);
expect(form.controls.max.errors).toEqual({max: {max: 0, actual: 9}});
input.value = 0;
dispatchEvent(input, 'input');
fixture.detectChanges();
expect(form.valid).toEqual(true);
expect(form.controls.max.errors).toBeNull();
})); }));
it('should validate max for float number', fakeAsync(() => { it('should validate max for float number', fakeAsync(() => {
@ -1552,6 +1568,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
input.value = ''; input.value = '';
dispatchEvent(input, 'input'); dispatchEvent(input, 'input');
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('max')).toEqual('10.25');
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
expect(form.controls.max.errors).toBeNull(); expect(form.controls.max.errors).toBeNull();
@ -1586,6 +1603,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
input.value = '11'; input.value = '11';
dispatchEvent(input, 'input'); dispatchEvent(input, 'input');
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('max')).toEqual('10');
expect(form.valid).toEqual(false); expect(form.valid).toEqual(false);
expect(form.controls.max.errors).toEqual({max: {max: 10, actual: 11}}); expect(form.controls.max.errors).toEqual({max: {max: 10, actual: 11}});
@ -1635,6 +1653,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
input.value = ''; input.value = '';
dispatchEvent(input, 'input'); dispatchEvent(input, 'input');
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('min')).toEqual('10');
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
expect(form.controls.min.errors).toBeNull(); expect(form.controls.min.errors).toBeNull();
@ -1649,6 +1668,22 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
fixture.detectChanges(); fixture.detectChanges();
expect(form.valid).toEqual(false); expect(form.valid).toEqual(false);
expect(form.controls.min.errors).toEqual({min: {min: 10, actual: 9}}); expect(form.controls.min.errors).toEqual({min: {min: 10, actual: 9}});
fixture.componentInstance.min = 0;
fixture.detectChanges();
tick();
input.value = -5;
dispatchEvent(input, 'input');
fixture.detectChanges();
expect(input.getAttribute('min')).toEqual('0');
expect(form.valid).toEqual(false);
expect(form.controls.min.errors).toEqual({min: {min: 0, actual: -5}});
input.value = 0;
dispatchEvent(input, 'input');
fixture.detectChanges();
expect(form.valid).toEqual(true);
expect(form.controls.min.errors).toBeNull();
})); }));
it('should validate min for float number', fakeAsync(() => { it('should validate min for float number', fakeAsync(() => {
@ -1663,6 +1698,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
input.value = ''; input.value = '';
dispatchEvent(input, 'input'); dispatchEvent(input, 'input');
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('min')).toEqual('10.25');
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
expect(form.controls.min.errors).toBeNull(); expect(form.controls.min.errors).toBeNull();
@ -1696,6 +1732,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
input.value = '11'; input.value = '11';
dispatchEvent(input, 'input'); dispatchEvent(input, 'input');
fixture.detectChanges(); fixture.detectChanges();
expect(input.getAttribute('min')).toEqual('10');
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
expect(form.controls.min.errors).toBeNull(); expect(form.controls.min.errors).toBeNull();