mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(forms): allow custom controls to require pending input
* Allow custom controls to make `pending` a required input
* Refactor test for `pending` input to be consistent with other control
properties
* Test that `pending` inputs are reset when the field binding changes
(cherry picked from commit 1a4c3eb1d0)
This commit is contained in:
parent
79d16919a5
commit
ec110f170b
2 changed files with 92 additions and 43 deletions
|
|
@ -46,6 +46,7 @@ const formControlInputFields = [
|
|||
'hidden',
|
||||
'invalid',
|
||||
'name',
|
||||
'pending',
|
||||
'readonly',
|
||||
'touched',
|
||||
'max',
|
||||
|
|
|
|||
|
|
@ -545,6 +545,97 @@ describe('field directive', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('pending', () => {
|
||||
it('should bind to custom control', async () => {
|
||||
const {promise, resolve} = promiseWithResolvers<ValidationError[]>();
|
||||
|
||||
@Component({
|
||||
selector: 'custom-control',
|
||||
template: '<input #i [value]="value()" (input)="value.set(i.value)" />',
|
||||
})
|
||||
class CustomControl implements FormValueControl<string> {
|
||||
readonly value = model.required<string>();
|
||||
readonly pending = input.required<boolean>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: ` <custom-control [field]="f" /> `,
|
||||
imports: [CustomControl, Field],
|
||||
})
|
||||
class TestCmp {
|
||||
readonly data = signal('test');
|
||||
readonly f = form(this.data, (p) => {
|
||||
validateAsync(p, {
|
||||
params: () => [],
|
||||
factory: (params) =>
|
||||
resource({
|
||||
params,
|
||||
loader: () => promise,
|
||||
}),
|
||||
onSuccess: (results) => results,
|
||||
onError: () => null,
|
||||
});
|
||||
});
|
||||
readonly customControl = viewChild.required(CustomControl);
|
||||
}
|
||||
|
||||
const fixture = act(() => TestBed.createComponent(TestCmp));
|
||||
const comp = fixture.componentInstance;
|
||||
|
||||
expect(comp.customControl().pending()).toBe(true);
|
||||
|
||||
resolve([]);
|
||||
await promise;
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(comp.customControl().pending()).toBe(false);
|
||||
});
|
||||
|
||||
it('should be reset when field changes on custom control', async () => {
|
||||
const {promise, resolve} = promiseWithResolvers<ValidationError[]>();
|
||||
|
||||
@Component({selector: 'custom-control', template: ``})
|
||||
class CustomControl implements FormValueControl<string> {
|
||||
readonly value = model.required<string>();
|
||||
readonly pending = input.required<boolean>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
imports: [Field, CustomControl],
|
||||
template: `<custom-control [field]="field()" />`,
|
||||
})
|
||||
class TestCmp {
|
||||
readonly f = form(signal({x: '', y: ''}), (p) => {
|
||||
validateAsync(p.x, {
|
||||
params: () => [],
|
||||
factory: (params) =>
|
||||
resource({
|
||||
params,
|
||||
loader: () => promise,
|
||||
}),
|
||||
onSuccess: (results) => results,
|
||||
onError: () => null,
|
||||
});
|
||||
});
|
||||
readonly field = signal(this.f.x);
|
||||
readonly customControl = viewChild.required(CustomControl);
|
||||
}
|
||||
|
||||
const fixture = act(() => TestBed.createComponent(TestCmp));
|
||||
const component = fixture.componentInstance;
|
||||
|
||||
expect(component.customControl().pending()).toBe(true);
|
||||
|
||||
act(() => component.field.set(component.f.y));
|
||||
expect(component.customControl().pending()).toBe(false);
|
||||
|
||||
resolve([]);
|
||||
await promise;
|
||||
await fixture.whenStable();
|
||||
expect(component.customControl().pending()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readonly', () => {
|
||||
it('should bind to native control', () => {
|
||||
@Component({
|
||||
|
|
@ -2184,49 +2275,6 @@ describe('field directive', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should synchronize pending status', async () => {
|
||||
const {promise, resolve} = promiseWithResolvers<ValidationError[]>();
|
||||
|
||||
@Component({
|
||||
selector: 'my-input',
|
||||
template: '<input #i [value]="value()" (input)="value.set(i.value)" />',
|
||||
})
|
||||
class CustomInput implements FormValueControl<string> {
|
||||
value = model('');
|
||||
pending = input(false);
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: ` <my-input [field]="f" /> `,
|
||||
imports: [CustomInput, Field],
|
||||
})
|
||||
class PendingTestCmp {
|
||||
myInput = viewChild.required<CustomInput>(CustomInput);
|
||||
data = signal('test');
|
||||
f = form(this.data, (p) => {
|
||||
validateAsync(p, {
|
||||
params: () => [],
|
||||
factory: (params) =>
|
||||
resource({
|
||||
params,
|
||||
loader: () => promise,
|
||||
}),
|
||||
onSuccess: (results) => results,
|
||||
onError: () => null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const fix = act(() => TestBed.createComponent(PendingTestCmp));
|
||||
|
||||
expect(fix.componentInstance.myInput().pending()).toBe(true);
|
||||
|
||||
resolve([]);
|
||||
await promise;
|
||||
await fix.whenStable();
|
||||
expect(fix.componentInstance.myInput().pending()).toBe(false);
|
||||
});
|
||||
|
||||
it(`should mark field as touched on native control 'blur' event`, () => {
|
||||
@Component({
|
||||
imports: [Field],
|
||||
|
|
|
|||
Loading…
Reference in a new issue