mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(forms): test that minLength/maxLength properties are propagated to controls (#63884)
Ensure that minLength and maxLength are only bound to native input elements that support them. PR Close #63884
This commit is contained in:
parent
acd7c83597
commit
71e8672837
2 changed files with 147 additions and 3 deletions
|
|
@ -388,9 +388,11 @@ function updateNativeControl(tNode: TNode, lView: LView, control: ɵControl<unkn
|
|||
}
|
||||
|
||||
// TODO: https://github.com/orgs/angular/projects/60/views/1?pane=issue&itemId=131711472
|
||||
// * use tag and type attribute to determine which of these properties to bind.
|
||||
setOptionalAttribute(renderer, input, 'maxLength', state.maxLength());
|
||||
setOptionalAttribute(renderer, input, 'minLength', state.minLength());
|
||||
// * cache this in `tNode.flags`.
|
||||
if (isTextInput(input)) {
|
||||
setOptionalAttribute(renderer, input, 'maxLength', state.maxLength());
|
||||
setOptionalAttribute(renderer, input, 'minLength', state.minLength());
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if a given value is a Date or null */
|
||||
|
|
@ -413,6 +415,18 @@ function isNumericInput(control: NativeControlElement) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether `control` is a text-based input.
|
||||
*
|
||||
* This is not the same as an input with `type="text"`, but rather any input that accepts
|
||||
* text-based input which includes numeric types.
|
||||
*/
|
||||
function isTextInput(
|
||||
control: NativeControlElement,
|
||||
): control is HTMLInputElement | HTMLTextAreaElementNarrowed {
|
||||
return !(control instanceof HTMLSelectElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value from a native control element.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -429,6 +429,136 @@ describe('control directive', () => {
|
|||
expect(element.min).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('maxLength', () => {
|
||||
it('native control', () => {
|
||||
@Component({
|
||||
imports: [Control],
|
||||
template: `<textarea [control]="f"></textarea>`,
|
||||
})
|
||||
class TestCmp {
|
||||
readonly maxLength = signal(20);
|
||||
readonly f = form(signal(''), (p) => {
|
||||
maxLength(p, this.maxLength);
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = act(() => TestBed.createComponent(TestCmp));
|
||||
const element = fixture.nativeElement.firstChild as HTMLTextAreaElement;
|
||||
expect(element.maxLength).toBe(20);
|
||||
|
||||
act(() => fixture.componentInstance.maxLength.set(15));
|
||||
expect(element.maxLength).toBe(15);
|
||||
});
|
||||
|
||||
it('custom control', () => {
|
||||
@Component({selector: 'custom-control', template: ``})
|
||||
class CustomControl {
|
||||
readonly value = model('');
|
||||
readonly maxLength = input<number | null>(null);
|
||||
}
|
||||
|
||||
@Component({
|
||||
imports: [Control, CustomControl],
|
||||
template: `<custom-control [control]="f" />`,
|
||||
})
|
||||
class TestCmp {
|
||||
readonly maxLength = signal(10);
|
||||
readonly f = form(signal(''), (p) => {
|
||||
maxLength(p, this.maxLength);
|
||||
});
|
||||
readonly customControl = viewChild.required(CustomControl);
|
||||
}
|
||||
|
||||
const fixture = act(() => TestBed.createComponent(TestCmp));
|
||||
const component = fixture.componentInstance;
|
||||
expect(component.customControl().maxLength()).toBe(10);
|
||||
|
||||
act(() => component.maxLength.set(5));
|
||||
expect(component.customControl().maxLength()).toBe(5);
|
||||
});
|
||||
|
||||
it('is not set on a native control that does not support it', () => {
|
||||
@Component({
|
||||
imports: [Control],
|
||||
template: `<select [control]="f"></select>`,
|
||||
})
|
||||
class TestCmp {
|
||||
readonly f = form(signal(''), (p) => {
|
||||
maxLength(p, 10);
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = act(() => TestBed.createComponent(TestCmp));
|
||||
const element = fixture.nativeElement.firstChild as HTMLSelectElement;
|
||||
expect(element.getAttribute('maxLength')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('minLength', () => {
|
||||
it('native control', () => {
|
||||
@Component({
|
||||
imports: [Control],
|
||||
template: `<textarea [control]="f"></textarea>`,
|
||||
})
|
||||
class TestCmp {
|
||||
readonly minLength = signal(20);
|
||||
readonly f = form(signal(''), (p) => {
|
||||
minLength(p, this.minLength);
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = act(() => TestBed.createComponent(TestCmp));
|
||||
const element = fixture.nativeElement.firstChild as HTMLTextAreaElement;
|
||||
expect(element.minLength).toBe(20);
|
||||
|
||||
act(() => fixture.componentInstance.minLength.set(15));
|
||||
expect(element.minLength).toBe(15);
|
||||
});
|
||||
|
||||
it('custom control', () => {
|
||||
@Component({selector: 'custom-control', template: ``})
|
||||
class CustomControl {
|
||||
readonly value = model('');
|
||||
readonly minLength = input<number | null>(null);
|
||||
}
|
||||
|
||||
@Component({
|
||||
imports: [Control, CustomControl],
|
||||
template: `<custom-control [control]="f" />`,
|
||||
})
|
||||
class TestCmp {
|
||||
readonly minLength = signal(10);
|
||||
readonly f = form(signal(''), (p) => {
|
||||
minLength(p, this.minLength);
|
||||
});
|
||||
readonly customControl = viewChild.required(CustomControl);
|
||||
}
|
||||
|
||||
const fixture = act(() => TestBed.createComponent(TestCmp));
|
||||
const component = fixture.componentInstance;
|
||||
expect(component.customControl().minLength()).toBe(10);
|
||||
|
||||
act(() => component.minLength.set(5));
|
||||
expect(component.customControl().minLength()).toBe(5);
|
||||
});
|
||||
|
||||
it('is not set on a native control that does not support it', () => {
|
||||
@Component({
|
||||
imports: [Control],
|
||||
template: `<select [control]="f"></select>`,
|
||||
})
|
||||
class TestCmp {
|
||||
readonly f = form(signal(''), (p) => {
|
||||
minLength(p, 10);
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = act(() => TestBed.createComponent(TestCmp));
|
||||
const element = fixture.nativeElement.firstChild as HTMLSelectElement;
|
||||
expect(element.getAttribute('minLength')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('synchronizes a basic form with a custom control', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue