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:
parent
85c7f7691e
commit
751cd83ae3
|
@ -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 {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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}});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue