angular/packages/forms/signals/test/node/api/structure.spec.ts
Miles Malerba 60c8e2779c refactor(forms): preserve logic order when apply is used on root path
In some cases the logic order was not preserved properly when using `apply`. In particular this occurs when some logic is registered on a child of the root, followed by an apply to the root, followed by further logic registered on a child. In this case the final registered logic wound up running before the applied logic.

This happened because `FieldPathNode` for a child path was caching its `LogicNodeBuilder` at creation time. This meant that if the parent's `LogicNodeBuilder` changed (e.g., due to an `apply` call), the child would still be using the old one.

This commit fixes the issue by dynamically resolving the `LogicNodeBuilder` for a child path whenever it is accessed.

(cherry picked from commit fa13a167f1)
2025-10-29 10:40:09 +01:00

53 lines
1.7 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 {Injector, signal} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {apply, form, required, schema} from '@angular/forms/signals';
describe('structure APIs', () => {
describe('apply', () => {
it('should maintain order when applying to child of root path', () => {
const s = schema<{a: string}>((p) => {
required(p.a, {message: 'before'});
apply(p.a, (prop) => {
required(prop, {message: 'apply'});
});
required(p.a, {message: 'after'});
});
const data = signal({a: ''});
const f = form(data, s, {injector: TestBed.inject(Injector)});
expect(f.a().errors()).toEqual([
jasmine.objectContaining({message: 'before'}),
jasmine.objectContaining({message: 'apply'}),
jasmine.objectContaining({message: 'after'}),
]);
});
it('should maintain order when applying to root path', () => {
const s = schema<{a: {b: string}}>((p) => {
required(p.a.b, {message: 'before'});
apply(p, (prop) => {
required(prop.a.b, {message: 'apply'});
});
required(p.a.b, {message: 'after'});
});
const data = signal({a: {b: ''}});
const f = form(data, s, {injector: TestBed.inject(Injector)});
expect(f.a.b().errors()).toEqual([
jasmine.objectContaining({message: 'before'}),
jasmine.objectContaining({message: 'apply'}),
jasmine.objectContaining({message: 'after'}),
]);
});
});
});