test(forms): submit behavior while validation is pending

Ensure `submit()` behaves as expected while a form is pending.

- Submission is not blocked by pending validation.
- Submission errors prevent pending validation errors from appearing
  after they resolve on the same field.
- Submission errors don't prevent pending validation errors from
  appearing after they resolve on subfields.
This commit is contained in:
Leon Senft 2026-01-26 12:39:42 -08:00 committed by Andrew Scott
parent 4448356313
commit 01bfb83fc9

View file

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Injector, resource, signal} from '@angular/core';
import {ApplicationRef, Injector, resource, signal} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {
form,
@ -35,33 +35,125 @@ describe('submit', () => {
expect(f.first().errors()).toEqual([requiredError({fieldTree: f.first})]);
});
it('should not block on pending async validators', async () => {
const data = signal('');
const resolvers = promiseWithResolvers();
const f = form(
data,
(p) => {
validateAsync(p, {
params: ({value}) => value(),
factory: (params) =>
resource({
params,
loader: () => resolvers.promise,
}),
onSuccess: () => {},
onError: () => {},
});
},
{injector: TestBed.inject(Injector)},
);
describe('while pending', () => {
it('should not block', async () => {
const data = signal('');
const {promise} = promiseWithResolvers();
const f = form(
data,
(p) => {
validateAsync(p, {
params: ({value}) => value(),
factory: (params) =>
resource({
params,
loader: () => promise,
}),
onSuccess: () => {},
onError: () => {},
});
},
{injector: TestBed.inject(Injector)},
);
expect(f().pending()).toBe(true);
expect(f().pending()).toBe(true);
const submitSpy = jasmine.createSpy();
await submit(f, submitSpy);
const submitSpy = jasmine.createSpy();
await submit(f, submitSpy);
expect(f().pending()).toBe(true);
expect(submitSpy).toHaveBeenCalled();
expect(f().pending()).toBe(true);
expect(submitSpy).toHaveBeenCalled();
});
it('should retain submit errors after pending validation resolves', async () => {
const appRef = TestBed.inject(ApplicationRef);
const data = signal('foo');
const {promise, resolve} = promiseWithResolvers<boolean>();
const f = form(
data,
(p) => {
validateAsync(p, {
params: ({value}) => value(),
factory: (params) =>
resource({
params,
loader: () => promise,
}),
onSuccess: () => ({kind: 'async'}),
onError: (error) => fail(error),
});
},
{injector: TestBed.inject(Injector)},
);
await submit(f, async () => ({kind: 'submit'}));
expect(f().errorSummary()).toEqual([jasmine.objectContaining({kind: 'submit'})]);
resolve(true);
await appRef.whenStable();
expect(f().errorSummary()).toEqual([jasmine.objectContaining({kind: 'submit'})]);
});
it('should resolve pending validation on subfield', async () => {
const appRef = TestBed.inject(ApplicationRef);
const data = signal({first: 'foo', last: 'bar'});
const {promise, resolve} = promiseWithResolvers<boolean>();
const f = form(
data,
(p) => {
validateAsync(p.first, {
params: ({value}) => value(),
factory: (params) =>
resource({
params,
loader: () => promise,
}),
onSuccess: () => ({kind: 'async'}),
onError: (error) => fail(error),
});
},
{injector: TestBed.inject(Injector)},
);
await submit(f, async () => ({kind: 'submit'}));
expect(f().errorSummary()).toEqual([jasmine.objectContaining({kind: 'submit'})]);
resolve(true);
await appRef.whenStable();
expect(f().errorSummary()).toEqual([
jasmine.objectContaining({kind: 'submit', fieldTree: f}),
jasmine.objectContaining({kind: 'async', fieldTree: f.first}),
]);
});
it('should resolve pending validation after successful submit', async () => {
const appRef = TestBed.inject(ApplicationRef);
const data = signal('foo');
const {promise, resolve} = promiseWithResolvers();
const f = form(
data,
(p) => {
validateAsync(p, {
params: ({value}) => value(),
factory: (params) =>
resource({
params,
loader: () => promise,
}),
onSuccess: () => ({kind: 'async'}),
onError: (error) => fail(error),
});
},
{injector: TestBed.inject(Injector)},
);
await submit(f, async () => undefined);
expect(f().errorSummary()).toEqual([]);
resolve(true);
await appRef.whenStable();
expect(f().errorSummary()).toEqual([jasmine.objectContaining({kind: 'async'})]);
});
});
it('maps error to a field', async () => {