mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Remove the `customError` function and `CustomValidationError` type. These were made obsolete by support for returning plain object literals as custom errors. This also catches few `field` properties that were missed in the renaming to `fieldTree`.
129 lines
4.1 KiB
TypeScript
129 lines
4.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
import {computed, Injector, signal, type Signal} from '@angular/core';
|
|
import {TestBed} from '@angular/core/testing';
|
|
import {disabled, validate} from '../../src/api/rules';
|
|
import {applyEach, applyWhen, applyWhenValue, form, schema} from '../../src/api/structure';
|
|
import type {FieldTree, Schema} from '../../src/api/types';
|
|
|
|
interface TreeData {
|
|
level: number;
|
|
next: TreeData | null;
|
|
}
|
|
|
|
function narrowed<TModel, TNarrowed extends TModel>(
|
|
fieldTree: FieldTree<TModel> | undefined,
|
|
guard: (value: TModel) => value is TNarrowed,
|
|
): Signal<FieldTree<TNarrowed> | undefined> {
|
|
return computed(
|
|
() =>
|
|
fieldTree && (guard(fieldTree().value()) ? (fieldTree as FieldTree<TNarrowed>) : undefined),
|
|
);
|
|
}
|
|
|
|
function isNonNull<T>(t: T): t is NonNullable<T> {
|
|
return t !== null;
|
|
}
|
|
|
|
describe('recursive schema logic', () => {
|
|
it('should support recursive logic', () => {
|
|
const s = schema<TreeData>((p) => {
|
|
disabled(p.level, ({valueOf}) => {
|
|
return valueOf(p.level) % 2 === 0;
|
|
});
|
|
applyWhenValue(p.next, isNonNull, s);
|
|
});
|
|
const f = form<TreeData>(
|
|
signal({level: 0, next: {level: 1, next: {level: 2, next: {level: 3, next: null}}}}),
|
|
s,
|
|
{injector: TestBed.inject(Injector)},
|
|
);
|
|
expect(f.level().disabled()).toBe(true);
|
|
expect(narrowed(f.next, isNonNull)()?.level().disabled()).toBe(false);
|
|
expect(narrowed(narrowed(f.next, isNonNull)()?.next, isNonNull)()?.level().disabled()).toBe(
|
|
true,
|
|
);
|
|
expect(
|
|
narrowed(narrowed(narrowed(f.next, isNonNull)()?.next, isNonNull)()?.next, isNonNull)()
|
|
?.level()
|
|
.disabled(),
|
|
).toBe(false);
|
|
});
|
|
|
|
it('should support co-recursive logic', () => {
|
|
const s1: Schema<TreeData> = schema((p) => {
|
|
disabled(p.level, ({valueOf}) => valueOf(p.level) % 2 === 0);
|
|
applyWhenValue(p.next, isNonNull, s2);
|
|
});
|
|
const s2: Schema<TreeData> = schema((p) => {
|
|
disabled(p.level, ({valueOf}) => valueOf(p.level) % 2 === 0);
|
|
applyWhenValue(p.next, isNonNull, s1);
|
|
});
|
|
const f = form<TreeData>(
|
|
signal({
|
|
level: 0,
|
|
next: {level: 1, next: {level: 2, next: {level: 3, next: null!}}},
|
|
}),
|
|
s1,
|
|
{injector: TestBed.inject(Injector)},
|
|
);
|
|
expect(f.level().disabled()).toBe(true);
|
|
expect(narrowed(f.next, isNonNull)()?.level().disabled()).toBe(false);
|
|
expect(narrowed(narrowed(f.next, isNonNull)()?.next, isNonNull)()?.level().disabled()).toBe(
|
|
true,
|
|
);
|
|
expect(
|
|
narrowed(narrowed(narrowed(f.next, isNonNull)()?.next, isNonNull)()?.next, isNonNull)()
|
|
?.level()
|
|
.disabled(),
|
|
).toBe(false);
|
|
});
|
|
|
|
it('should support recursive logic with arrays', () => {
|
|
interface Dom {
|
|
tag: string;
|
|
children: Dom[];
|
|
}
|
|
|
|
const domSchema = schema<Dom>((p) => {
|
|
applyEach(p.children, domSchema);
|
|
applyWhen(
|
|
p.children,
|
|
({valueOf}) => valueOf(p.tag) === 'table',
|
|
(children) => {
|
|
applyEach(children, (c) => {
|
|
validate(c.tag, ({value}) => (value() !== 'tr' ? {kind: 'invalid-child'} : undefined));
|
|
});
|
|
},
|
|
);
|
|
applyWhen(
|
|
p.children,
|
|
({valueOf}) => valueOf(p.tag) === 'tr',
|
|
(children) => {
|
|
applyEach(children, (c) => {
|
|
validate(c.tag, ({value}) => (value() !== 'td' ? {kind: 'invalid-child'} : undefined));
|
|
});
|
|
},
|
|
);
|
|
});
|
|
|
|
const data = signal<Dom>({tag: 'div', children: [{tag: 'span', children: []}]});
|
|
const f = form(data, domSchema, {injector: TestBed.inject(Injector)});
|
|
expect(f().valid()).toBe(true);
|
|
|
|
data.set({tag: 'table', children: [{tag: 'span', children: []}]});
|
|
expect(f().valid()).toBe(false);
|
|
|
|
data.set({tag: 'table', children: [{tag: 'tr', children: [{tag: 'span', children: []}]}]});
|
|
expect(f().valid()).toBe(false);
|
|
|
|
data.set({tag: 'table', children: [{tag: 'tr', children: [{tag: 'td', children: []}]}]});
|
|
expect(f().valid()).toBe(true);
|
|
});
|
|
});
|