Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ff4633d

Browse files
mmalerbathePunderWoman
authored andcommitted
refactor(forms): allow passing number|string|null paths to min & max (#65212)
Relaxes the constraints on which paths can be used with the `min` & `max` validation rules, since people may want to validate a potentially-null number, or a numeric value represented as a string PR Close #65212
1 parent 05cf14b commit ff4633d

File tree

5 files changed

+74
-18
lines changed

5 files changed

+74
-18
lines changed

‎goldens/public-api/forms/signals/index.api.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ export type MapToErrorsFn<TValue, TResult, TPathKind extends PathKind = PathKind
274274
export const MAX: AggregateMetadataKey<number | undefined, number | undefined>;
275275

276276
// @public
277-
export function max<TPathKind extends PathKind = PathKind.Root>(path: SchemaPath<number, SchemaPathRules.Supported, TPathKind>, maxValue: number | LogicFn<number, number | undefined, TPathKind>, config?: BaseValidatorConfig<number, TPathKind>): void;
277+
export function max<TPathKind extends PathKind = PathKind.Root>(path: SchemaPath<number | string | null, SchemaPathRules.Supported, TPathKind>, maxValue: number | LogicFn<number | string | null, number | undefined, TPathKind>, config?: BaseValidatorConfig<number | string | null, TPathKind>): void;
278278

279279
// @public
280280
export const MAX_LENGTH: AggregateMetadataKey<number | undefined, number | undefined>;
@@ -335,7 +335,7 @@ export class MetadataKey<TValue> {
335335
export const MIN: AggregateMetadataKey<number | undefined, number | undefined>;
336336

337337
// @public
338-
export function min<TPathKind extends PathKind = PathKind.Root>(path: SchemaPath<number, SchemaPathRules.Supported, TPathKind>, minValue: number | LogicFn<number, number | undefined, TPathKind>, config?: BaseValidatorConfig<number, TPathKind>): void;
338+
export function min<TPathKind extends PathKind = PathKind.Root>(path: SchemaPath<number | string | null, SchemaPathRules.Supported, TPathKind>, minValue: number | LogicFn<number | string | null, number | undefined, TPathKind>, config?: BaseValidatorConfig<number | string | null, TPathKind>): void;
339339

340340
// @public
341341
export const MIN_LENGTH: AggregateMetadataKey<number | undefined, number | undefined>;

‎packages/forms/signals/src/api/validators/max.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {computed} from '@angular/core';
1010
import {aggregateMetadata, metadata, validate} from '../logic';
1111
import {MAX} from '../metadata';
12-
import {SchemaPath, SchemaPathRules, LogicFn, PathKind} from '../types';
12+
import {LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../types';
1313
import {maxError} from '../validation_errors';
1414
import {BaseValidatorConfig, getOption, isEmpty} from './util';
1515

@@ -30,9 +30,9 @@ import {BaseValidatorConfig, getOption, isEmpty} from './util';
3030
* @experimental 21.0.0
3131
*/
3232
export function max<TPathKind extends PathKind = PathKind.Root>(
33-
path: SchemaPath<number, SchemaPathRules.Supported, TPathKind>,
34-
maxValue: number | LogicFn<number, number | undefined, TPathKind>,
35-
config?: BaseValidatorConfig<number, TPathKind>,
33+
path: SchemaPath<number | string | null, SchemaPathRules.Supported, TPathKind>,
34+
maxValue: number | LogicFn<number | string | null, number | undefined, TPathKind>,
35+
config?: BaseValidatorConfig<number | string | null, TPathKind>,
3636
) {
3737
const MAX_MEMO = metadata(path, (ctx) =>
3838
computed(() => (typeof maxValue === 'number' ? maxValue : maxValue(ctx))),
@@ -46,7 +46,9 @@ export function max<TPathKind extends PathKind = PathKind.Root>(
4646
if (max === undefined || Number.isNaN(max)) {
4747
return undefined;
4848
}
49-
if (ctx.value() > max) {
49+
const value = ctx.value();
50+
const numValue = !value && value !== 0 ? NaN : Number(value); // Treat `''` and `null` as `NaN`
51+
if (numValue > max) {
5052
if (config?.error) {
5153
return getOption(config.error, ctx);
5254
} else {

‎packages/forms/signals/src/api/validators/min.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {computed} from '@angular/core';
1010
import {aggregateMetadata, metadata, validate} from '../logic';
1111
import {MIN} from '../metadata';
12-
import {SchemaPath, LogicFn, PathKind, SchemaPathRules} from '../types';
12+
import {LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../types';
1313
import {minError} from '../validation_errors';
1414
import {BaseValidatorConfig, getOption, isEmpty} from './util';
1515

@@ -30,9 +30,9 @@ import {BaseValidatorConfig, getOption, isEmpty} from './util';
3030
* @experimental 21.0.0
3131
*/
3232
export function min<TPathKind extends PathKind = PathKind.Root>(
33-
path: SchemaPath<number, SchemaPathRules.Supported, TPathKind>,
34-
minValue: number | LogicFn<number, number | undefined, TPathKind>,
35-
config?: BaseValidatorConfig<number, TPathKind>,
33+
path: SchemaPath<number | string | null, SchemaPathRules.Supported, TPathKind>,
34+
minValue: number | LogicFn<number | string | null, number | undefined, TPathKind>,
35+
config?: BaseValidatorConfig<number | string | null, TPathKind>,
3636
) {
3737
const MIN_MEMO = metadata(path, (ctx) =>
3838
computed(() => (typeof minValue === 'number' ? minValue : minValue(ctx))),
@@ -46,7 +46,9 @@ export function min<TPathKind extends PathKind = PathKind.Root>(
4646
if (min === undefined || Number.isNaN(min)) {
4747
return undefined;
4848
}
49-
if (ctx.value() < min) {
49+
const value = ctx.value();
50+
const numValue = !value && value !== 0 ? NaN : Number(value); // Treat `''` and `null` as `NaN`
51+
if (numValue < min) {
5052
if (config?.error) {
5153
return getOption(config.error, ctx);
5254
} else {

‎packages/forms/signals/test/node/api/validators/max.spec.ts‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('max validator', () => {
5959
(p) => {
6060
max(p.age, 5, {
6161
error: ({value}) => {
62-
return customError({kind: 'special-max', message: value().toString()});
62+
return customError({kind: 'special-max', message: value()?.toString()});
6363
},
6464
});
6565
},
@@ -104,7 +104,7 @@ describe('max validator', () => {
104104
error: ({value, valueOf}) => {
105105
return valueOf(p.name) === 'disabled'
106106
? []
107-
: customError({kind: 'special-max', message: value().toString()});
107+
: customError({kind: 'special-max', message: value()?.toString()});
108108
},
109109
});
110110
},
@@ -155,7 +155,7 @@ describe('max validator', () => {
155155
(p) => {
156156
max(p.age, 5, {
157157
error: ({value}) => {
158-
return customError({kind: 'special-max', message: value().toString()});
158+
return customError({kind: 'special-max', message: value()?.toString()});
159159
},
160160
});
161161
},
@@ -299,4 +299,30 @@ describe('max validator', () => {
299299
expect(f.age().errors()).toEqual([]);
300300
});
301301
});
302+
303+
it('should validate properly formatted strings', () => {
304+
const f = form(
305+
signal<number | string | null>('4'),
306+
(p) => {
307+
max(p, -10);
308+
},
309+
{injector: TestBed.inject(Injector)},
310+
);
311+
expect(f().errors()).toEqual([jasmine.objectContaining({kind: 'max'})]);
312+
});
313+
314+
it('should not validate improperly formatted strings or null', () => {
315+
const f = form(
316+
signal<number | string | null>('4f'),
317+
(p) => {
318+
max(p, -10);
319+
},
320+
{injector: TestBed.inject(Injector)},
321+
);
322+
expect(f().errors()).toEqual([]);
323+
f().value.set(null);
324+
expect(f().errors()).toEqual([]);
325+
f().value.set(4);
326+
expect(f().errors()).toEqual([jasmine.objectContaining({kind: 'max'})]);
327+
});
302328
});

‎packages/forms/signals/test/node/api/validators/min.spec.ts‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('min validator', () => {
5959
(p) => {
6060
min(p.age, 5, {
6161
error: ({value}) => {
62-
return customError({kind: 'special-min', message: value().toString()});
62+
return customError({kind: 'special-min', message: value()?.toString()});
6363
},
6464
});
6565
},
@@ -84,7 +84,7 @@ describe('min validator', () => {
8484
error: ({value}) => {
8585
return {
8686
kind: 'special-min',
87-
message: value().toString(),
87+
message: value()?.toString(),
8888
};
8989
},
9090
});
@@ -130,7 +130,7 @@ describe('min validator', () => {
130130
error: ({value, valueOf}) => {
131131
return valueOf(p.name) === 'disabled'
132132
? []
133-
: customError({kind: 'special-min', message: value().toString()});
133+
: customError({kind: 'special-min', message: value()?.toString()});
134134
},
135135
});
136136
},
@@ -308,4 +308,30 @@ describe('min validator', () => {
308308
expect(f.age().errors()).toEqual([]);
309309
});
310310
});
311+
312+
it('should validate properly formatted strings', () => {
313+
const f = form(
314+
signal<number | string | null>('4'),
315+
(p) => {
316+
min(p, 10);
317+
},
318+
{injector: TestBed.inject(Injector)},
319+
);
320+
expect(f().errors()).toEqual([jasmine.objectContaining({kind: 'min'})]);
321+
});
322+
323+
it('should not validate improperly formatted strings or null', () => {
324+
const f = form(
325+
signal<number | string | null>('4f'),
326+
(p) => {
327+
min(p, 10);
328+
},
329+
{injector: TestBed.inject(Injector)},
330+
);
331+
expect(f().errors()).toEqual([]);
332+
f().value.set(null);
333+
expect(f().errors()).toEqual([]);
334+
f().value.set(4);
335+
expect(f().errors()).toEqual([jasmine.objectContaining({kind: 'min'})]);
336+
});
311337
});

0 commit comments

Comments
 (0)