Skip to content

Commit 4b51b11

Browse files
committed
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.
1 parent d306737 commit 4b51b11

1 file changed

Lines changed: 113 additions & 42 deletions

File tree

packages/forms/signals/test/node/submit.spec.ts

Lines changed: 113 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {Injector, resource, signal} from '@angular/core';
9+
import {ApplicationRef, Injector, resource, signal} from '@angular/core';
1010
import {TestBed} from '@angular/core/testing';
1111
import {
1212
form,
@@ -35,54 +35,125 @@ describe('submit', () => {
3535
expect(f.first().errors()).toEqual([requiredError({fieldTree: f.first})]);
3636
});
3737

38-
it('should not block on pending async validators', async () => {
39-
const data = signal('');
40-
const resolvers = promiseWithResolvers();
41-
const f = form(
42-
data,
43-
(p) => {
44-
validateAsync(p, {
45-
params: ({value}) => value(),
46-
factory: (params) =>
47-
resource({
48-
params,
49-
loader: () => resolvers.promise,
50-
}),
51-
onSuccess: () => {},
52-
onError: () => {},
53-
});
54-
},
55-
{injector: TestBed.inject(Injector)},
56-
);
38+
describe('while pending', () => {
39+
it('should not block', async () => {
40+
const data = signal('');
41+
const {promise} = promiseWithResolvers();
42+
const f = form(
43+
data,
44+
(p) => {
45+
validateAsync(p, {
46+
params: ({value}) => value(),
47+
factory: (params) =>
48+
resource({
49+
params,
50+
loader: () => promise,
51+
}),
52+
onSuccess: () => {},
53+
onError: () => {},
54+
});
55+
},
56+
{injector: TestBed.inject(Injector)},
57+
);
5758

58-
expect(f().pending()).toBe(true);
59+
expect(f().pending()).toBe(true);
5960

60-
const submitSpy = jasmine.createSpy();
61-
await submit(f, submitSpy);
61+
const submitSpy = jasmine.createSpy();
62+
await submit(f, submitSpy);
6263

63-
expect(f().pending()).toBe(true);
64-
expect(submitSpy).toHaveBeenCalled();
65-
});
64+
expect(f().pending()).toBe(true);
65+
expect(submitSpy).toHaveBeenCalled();
66+
});
6667

67-
it('maps error to a field', async () => {
68-
const data = signal({first: '', last: ''});
69-
const f = form(
70-
data,
71-
(name) => {
72-
// first name required if last name specified
73-
required(name.first, {when: ({valueOf}) => valueOf(name.last) !== ''});
74-
},
75-
{injector: TestBed.inject(Injector)},
76-
);
68+
it('should retain submit errors after pending validation resolves', async () => {
69+
const appRef = TestBed.inject(ApplicationRef);
70+
const data = signal('foo');
71+
const {promise, resolve} = promiseWithResolvers<boolean>();
72+
const f = form(
73+
data,
74+
(p) => {
75+
validateAsync(p, {
76+
params: ({value}) => value(),
77+
factory: (params) =>
78+
resource({
79+
params,
80+
loader: () => promise,
81+
}),
82+
onSuccess: () => ({kind: 'async'}),
83+
onError: (error) => fail(error),
84+
});
85+
},
86+
{injector: TestBed.inject(Injector)},
87+
);
7788

78-
await submit(f, (form) => {
79-
return Promise.resolve({
80-
kind: 'lastName',
81-
fieldTree: form.last,
82-
});
89+
await submit(f, async () => ({kind: 'submit'}));
90+
expect(f().errorSummary()).toEqual([jasmine.objectContaining({kind: 'submit'})]);
91+
92+
resolve(true);
93+
await appRef.whenStable();
94+
expect(f().errorSummary()).toEqual([jasmine.objectContaining({kind: 'submit'})]);
8395
});
8496

85-
expect(f.last().errors()).toEqual([{kind: 'lastName', fieldTree: f.last}]);
97+
it('should resolve pending validation on subfield', async () => {
98+
const appRef = TestBed.inject(ApplicationRef);
99+
const data = signal({first: 'foo', last: 'bar'});
100+
const {promise, resolve} = promiseWithResolvers<boolean>();
101+
const f = form(
102+
data,
103+
(p) => {
104+
validateAsync(p.first, {
105+
params: ({value}) => value(),
106+
factory: (params) =>
107+
resource({
108+
params,
109+
loader: () => promise,
110+
}),
111+
onSuccess: () => ({kind: 'async'}),
112+
onError: (error) => fail(error),
113+
});
114+
},
115+
{injector: TestBed.inject(Injector)},
116+
);
117+
118+
await submit(f, async () => ({kind: 'submit'}));
119+
expect(f().errorSummary()).toEqual([jasmine.objectContaining({kind: 'submit'})]);
120+
121+
resolve(true);
122+
await appRef.whenStable();
123+
expect(f().errorSummary()).toEqual([
124+
jasmine.objectContaining({kind: 'submit', fieldTree: f}),
125+
jasmine.objectContaining({kind: 'async', fieldTree: f.first}),
126+
]);
127+
});
128+
129+
it('should resolve pending validation after successful submit', async () => {
130+
const appRef = TestBed.inject(ApplicationRef);
131+
const data = signal('foo');
132+
const {promise, resolve} = promiseWithResolvers();
133+
const f = form(
134+
data,
135+
(p) => {
136+
validateAsync(p, {
137+
params: ({value}) => value(),
138+
factory: (params) =>
139+
resource({
140+
params,
141+
loader: () => promise,
142+
}),
143+
onSuccess: () => ({kind: 'async'}),
144+
onError: (error) => fail(error),
145+
});
146+
},
147+
{injector: TestBed.inject(Injector)},
148+
);
149+
150+
await submit(f, async () => undefined);
151+
expect(f().errorSummary()).toEqual([]);
152+
153+
resolve(true);
154+
await appRef.whenStable();
155+
expect(f().errorSummary()).toEqual([jasmine.objectContaining({kind: 'async'})]);
156+
});
86157
});
87158

88159
it('maps errors to multiple fields', async () => {

0 commit comments

Comments
 (0)