Skip to content

Commit 13b80a4

Browse files
committed
fix(input/testing): inconsistently reading name from input with ngModel
If an input has a `name` binding and an `ngModel`, the input harness won't be able to read the name from the DOM, because `ngModel` doesn't proxy it. These changes add the proxy behavior to the `MatInput` directive, similarly to what we we're doing for `required`, `placeholder`, `readonly` etc. Fixes #18624.
1 parent 2a97418 commit 13b80a4

File tree

4 files changed

+36
-13
lines changed

4 files changed

+36
-13
lines changed

src/material-experimental/mdc-input/input.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {MatInput as BaseMatInput} from '@angular/material/input';
3131
'[id]': 'id',
3232
'[disabled]': 'disabled',
3333
'[required]': 'required',
34+
'[attr.name]': 'name',
3435
'[attr.placeholder]': 'placeholder',
3536
'[attr.readonly]': 'readonly && !_isNativeSelect || null',
3637
'[attr.aria-describedby]': '_ariaDescribedby || null',

src/material/input/input.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const _MatInputMixinBase: CanUpdateErrorStateCtor & typeof MatInputBase =
8080
'[attr.placeholder]': 'placeholder',
8181
'[disabled]': 'disabled',
8282
'[required]': 'required',
83+
'[attr.name]': 'name || null',
8384
'[attr.readonly]': 'readonly && !_isNativeSelect || null',
8485
'[attr.aria-describedby]': '_ariaDescribedby || null',
8586
'[attr.aria-invalid]': 'errorState',
@@ -166,6 +167,12 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
166167
*/
167168
@Input() placeholder: string;
168169

170+
/**
171+
* Name of the input.
172+
* @docs-private
173+
*/
174+
@Input() name: string;
175+
169176
/**
170177
* Implemented as part of MatFormFieldControl.
171178
* @docs-private

src/material/input/testing/shared.spec.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {HarnessLoader} from '@angular/cdk/testing';
22
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
33
import {Component} from '@angular/core';
44
import {ComponentFixture, TestBed} from '@angular/core/testing';
5-
import {ReactiveFormsModule} from '@angular/forms';
5+
import {FormsModule} from '@angular/forms';
66
import {MatInputModule} from '@angular/material/input';
77
import {getSupportedInputTypes} from '@angular/cdk/platform';
88
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
@@ -17,7 +17,7 @@ export function runHarnessTests(
1717
beforeEach(async () => {
1818
await TestBed
1919
.configureTestingModule({
20-
imports: [NoopAnimationsModule, inputModule, ReactiveFormsModule],
20+
imports: [NoopAnimationsModule, inputModule, FormsModule],
2121
declarations: [InputHarnessTest],
2222
})
2323
.compileComponents();
@@ -29,7 +29,7 @@ export function runHarnessTests(
2929

3030
it('should load all input harnesses', async () => {
3131
const inputs = await loader.getAllHarnesses(inputHarness);
32-
expect(inputs.length).toBe(6);
32+
expect(inputs.length).toBe(7);
3333
});
3434

3535
it('should load input with specific id', async () => {
@@ -50,37 +50,40 @@ export function runHarnessTests(
5050

5151
it('should be able to get id of input', async () => {
5252
const inputs = await loader.getAllHarnesses(inputHarness);
53-
expect(inputs.length).toBe(6);
53+
expect(inputs.length).toBe(7);
5454
expect(await inputs[0].getId()).toMatch(/mat-input-\d+/);
5555
expect(await inputs[1].getId()).toMatch(/mat-input-\d+/);
5656
expect(await inputs[2].getId()).toBe('myTextarea');
5757
expect(await inputs[3].getId()).toBe('nativeControl');
5858
expect(await inputs[4].getId()).toMatch(/mat-input-\d+/);
59+
expect(await inputs[5].getId()).toBe('has-ng-model');
5960
});
6061

6162
it('should be able to get name of input', async () => {
6263
const inputs = await loader.getAllHarnesses(inputHarness);
63-
expect(inputs.length).toBe(6);
64+
expect(inputs.length).toBe(7);
6465
expect(await inputs[0].getName()).toBe('favorite-food');
6566
expect(await inputs[1].getName()).toBe('');
6667
expect(await inputs[2].getName()).toBe('');
6768
expect(await inputs[3].getName()).toBe('');
6869
expect(await inputs[4].getName()).toBe('');
70+
expect(await inputs[5].getName()).toBe('has-ng-model');
6971
});
7072

7173
it('should be able to get value of input', async () => {
7274
const inputs = await loader.getAllHarnesses(inputHarness);
73-
expect(inputs.length).toBe(6);
75+
expect(inputs.length).toBe(7);
7476
expect(await inputs[0].getValue()).toBe('Sushi');
7577
expect(await inputs[1].getValue()).toBe('');
7678
expect(await inputs[2].getValue()).toBe('');
7779
expect(await inputs[3].getValue()).toBe('');
7880
expect(await inputs[4].getValue()).toBe('');
81+
expect(await inputs[5].getValue()).toBe('');
7982
});
8083

8184
it('should be able to set value of input', async () => {
8285
const inputs = await loader.getAllHarnesses(inputHarness);
83-
expect(inputs.length).toBe(6);
86+
expect(inputs.length).toBe(7);
8487
expect(await inputs[0].getValue()).toBe('Sushi');
8588
expect(await inputs[1].getValue()).toBe('');
8689
expect(await inputs[3].getValue()).toBe('');
@@ -99,13 +102,14 @@ export function runHarnessTests(
99102

100103
it('should be able to get disabled state', async () => {
101104
const inputs = await loader.getAllHarnesses(inputHarness);
102-
expect(inputs.length).toBe(6);
105+
expect(inputs.length).toBe(7);
103106

104107
expect(await inputs[0].isDisabled()).toBe(false);
105108
expect(await inputs[1].isDisabled()).toBe(false);
106109
expect(await inputs[2].isDisabled()).toBe(false);
107110
expect(await inputs[3].isDisabled()).toBe(false);
108111
expect(await inputs[4].isDisabled()).toBe(false);
112+
expect(await inputs[5].isDisabled()).toBe(false);
109113

110114
fixture.componentInstance.disabled = true;
111115

@@ -114,13 +118,14 @@ export function runHarnessTests(
114118

115119
it('should be able to get readonly state', async () => {
116120
const inputs = await loader.getAllHarnesses(inputHarness);
117-
expect(inputs.length).toBe(6);
121+
expect(inputs.length).toBe(7);
118122

119123
expect(await inputs[0].isReadonly()).toBe(false);
120124
expect(await inputs[1].isReadonly()).toBe(false);
121125
expect(await inputs[2].isReadonly()).toBe(false);
122126
expect(await inputs[3].isReadonly()).toBe(false);
123127
expect(await inputs[4].isReadonly()).toBe(false);
128+
expect(await inputs[5].isReadonly()).toBe(false);
124129

125130
fixture.componentInstance.readonly = true;
126131

@@ -129,13 +134,14 @@ export function runHarnessTests(
129134

130135
it('should be able to get required state', async () => {
131136
const inputs = await loader.getAllHarnesses(inputHarness);
132-
expect(inputs.length).toBe(6);
137+
expect(inputs.length).toBe(7);
133138

134139
expect(await inputs[0].isRequired()).toBe(false);
135140
expect(await inputs[1].isRequired()).toBe(false);
136141
expect(await inputs[2].isRequired()).toBe(false);
137142
expect(await inputs[3].isRequired()).toBe(false);
138143
expect(await inputs[4].isRequired()).toBe(false);
144+
expect(await inputs[5].isRequired()).toBe(false);
139145

140146
fixture.componentInstance.required = true;
141147

@@ -144,22 +150,24 @@ export function runHarnessTests(
144150

145151
it('should be able to get placeholder of input', async () => {
146152
const inputs = await loader.getAllHarnesses(inputHarness);
147-
expect(inputs.length).toBe(6);
153+
expect(inputs.length).toBe(7);
148154
expect(await inputs[0].getPlaceholder()).toBe('Favorite food');
149155
expect(await inputs[1].getPlaceholder()).toBe('');
150156
expect(await inputs[2].getPlaceholder()).toBe('Leave a comment');
151157
expect(await inputs[3].getPlaceholder()).toBe('Native control');
152158
expect(await inputs[4].getPlaceholder()).toBe('');
159+
expect(await inputs[5].getPlaceholder()).toBe('');
153160
});
154161

155162
it('should be able to get type of input', async () => {
156163
const inputs = await loader.getAllHarnesses(inputHarness);
157-
expect(inputs.length).toBe(6);
164+
expect(inputs.length).toBe(7);
158165
expect(await inputs[0].getType()).toBe('text');
159166
expect(await inputs[1].getType()).toBe('number');
160167
expect(await inputs[2].getType()).toBe('textarea');
161168
expect(await inputs[3].getType()).toBe('text');
162169
expect(await inputs[4].getType()).toBe('textarea');
170+
expect(await inputs[5].getType()).toBe('text');
163171

164172
fixture.componentInstance.inputType = 'text';
165173

@@ -234,6 +242,10 @@ function getActiveElementTagName() {
234242
</select>
235243
</mat-form-field>
236244
245+
<mat-form-field>
246+
<input [(ngModel)]="ngModelValue" [name]="ngModelName" id="has-ng-model" matNativeControl>
247+
</mat-form-field>
248+
237249
<mat-form-field>
238250
<input matNativeControl placeholder="Color control" id="colorControl" type="color">
239251
</mat-form-field>
@@ -244,4 +256,6 @@ class InputHarnessTest {
244256
readonly = false;
245257
disabled = false;
246258
required = false;
259+
ngModelValue = '';
260+
ngModelName = 'has-ng-model';
247261
}

tools/public_api_guard/material/input.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export declare class MatInput extends _MatInputMixinBase implements MatFormField
2727
focused: boolean;
2828
get id(): string;
2929
set id(value: string);
30+
name: string;
3031
ngControl: NgControl;
3132
placeholder: string;
3233
get readonly(): boolean;
@@ -58,7 +59,7 @@ export declare class MatInput extends _MatInputMixinBase implements MatFormField
5859
static ngAcceptInputType_readonly: BooleanInput;
5960
static ngAcceptInputType_required: BooleanInput;
6061
static ngAcceptInputType_value: any;
61-
static ɵdir: i0.ɵɵDirectiveDefWithMeta<MatInput, "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", ["matInput"], { "disabled": "disabled"; "id": "id"; "placeholder": "placeholder"; "required": "required"; "type": "type"; "errorStateMatcher": "errorStateMatcher"; "value": "value"; "readonly": "readonly"; }, {}, never>;
62+
static ɵdir: i0.ɵɵDirectiveDefWithMeta<MatInput, "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", ["matInput"], { "disabled": "disabled"; "id": "id"; "placeholder": "placeholder"; "name": "name"; "required": "required"; "type": "type"; "errorStateMatcher": "errorStateMatcher"; "value": "value"; "readonly": "readonly"; }, {}, never>;
6263
static ɵfac: i0.ɵɵFactoryDef<MatInput, [null, null, { optional: true; self: true; }, { optional: true; }, { optional: true; }, null, { optional: true; self: true; }, null, null]>;
6364
}
6465

0 commit comments

Comments
 (0)