Skip to content

Commit d6cbbcf

Browse files
committed
fix(material/testing): Modify input filtering to more broadly search for
a label, including fallback functionality that checks `mat-label`
1 parent 88d4ffd commit d6cbbcf

File tree

3 files changed

+46
-30
lines changed

3 files changed

+46
-30
lines changed

src/material/datepicker/testing/date-range-input-harness.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ export class MatEndDateHarness extends MatDatepickerInputHarnessBase {
4848
export class MatDateRangeInputHarness extends DatepickerTriggerHarnessBase {
4949
static hostSelector = '.mat-date-range-input';
5050

51-
private readonly _floatingLabelSelector = '.mdc-floating-label';
52-
5351
/**
5452
* Gets a `HarnessPredicate` that can be used to search for a `MatDateRangeInputHarness`
5553
* that meets certain criteria.
@@ -91,26 +89,28 @@ export class MatDateRangeInputHarness extends DatepickerTriggerHarnessBase {
9189
return this.locatorFor(MatEndDateHarness)();
9290
}
9391

94-
/** Gets the floating label text for the range input, if it exists. */
92+
/**
93+
* Gets the label for the range input, if it exists. This might be provided by a label element or
94+
* by the `aria-label` attribute.
95+
*/
9596
async getLabel(): Promise<string | null> {
96-
// Copied from MatFormFieldControlHarnessBase since this class cannot extend two classes
97+
// Directly copied from MatFormFieldControlHarnessBase. This class already has a parent so it
98+
// cannot extend MatFormFieldControlHarnessBase for the functionality.
9799
const documentRootLocator = this.documentRootLocatorFactory();
98100
const labelId = await (await this.host()).getAttribute('aria-labelledby');
101+
const labelText = await (await this.host()).getAttribute('aria-label');
99102
const hostId = await (await this.host()).getAttribute('id');
100103

101104
if (labelId) {
102-
// First option, try to fetch the label using the `aria-labelledby`
103-
// attribute.
104-
const labelEl = await documentRootLocator.locatorForOptional(
105-
`${this._floatingLabelSelector}[id="${labelId}"]`,
106-
)();
105+
// First, try to find the label by following [aria-labelledby]
106+
const labelEl = await documentRootLocator.locatorForOptional(`[id="${labelId}"]`)();
107107
return labelEl ? labelEl.text() : null;
108+
} else if (labelText) {
109+
// If that doesn't work, return [aria-label] if it exists
110+
return labelText;
108111
} else if (hostId) {
109-
// Fallback option, try to match the id of the input with the `for`
110-
// attribute of the label.
111-
const labelEl = await documentRootLocator.locatorForOptional(
112-
`${this._floatingLabelSelector}[for="${hostId}"]`,
113-
)();
112+
// Finally, search the DOM for a label that points to the host element
113+
const labelEl = await documentRootLocator.locatorForOptional(`[for="${hostId}"]`)();
114114
return labelEl ? labelEl.text() : null;
115115
}
116116
return null;

src/material/form-field/testing/control/form-field-control-harness.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,26 @@ export abstract class MatFormFieldControlHarness extends ComponentHarness {}
1818
* Shared behavior for `MatFormFieldControlHarness` implementations
1919
*/
2020
export abstract class MatFormFieldControlHarnessBase extends MatFormFieldControlHarness {
21-
private readonly _floatingLabelSelector = '.mdc-floating-label';
22-
23-
/** Gets the text content of the floating label, if it exists. */
21+
/**
22+
* Gets the label for the control, if it exists. This might be provided by a label element or by
23+
* the `aria-label` attribute.
24+
*/
2425
async getLabel(): Promise<string | null> {
2526
const documentRootLocator = this.documentRootLocatorFactory();
2627
const labelId = await (await this.host()).getAttribute('aria-labelledby');
28+
const labelText = await (await this.host()).getAttribute('aria-label');
2729
const hostId = await (await this.host()).getAttribute('id');
2830

2931
if (labelId) {
30-
// First option, try to fetch the label using the `aria-labelledby`
31-
// attribute.
32-
const labelEl = await documentRootLocator.locatorForOptional(
33-
`${this._floatingLabelSelector}[id="${labelId}"]`,
34-
)();
32+
// First, try to find the label by following [aria-labelledby]
33+
const labelEl = await documentRootLocator.locatorForOptional(`[id="${labelId}"]`)();
3534
return labelEl ? labelEl.text() : null;
35+
} else if (labelText) {
36+
// If that doesn't work, return [aria-label] if it exists
37+
return labelText;
3638
} else if (hostId) {
37-
// Fallback option, try to match the id of the input with the `for`
38-
// attribute of the label.
39-
const labelEl = await documentRootLocator.locatorForOptional(
40-
`${this._floatingLabelSelector}[for="${hostId}"]`,
41-
)();
39+
// Finally, search the DOM for a label that points to the host element
40+
const labelEl = await documentRootLocator.locatorForOptional(`[for="${hostId}"]`)();
4241
return labelEl ? labelEl.text() : null;
4342
}
4443
return null;

src/material/input/testing/input-harness.spec.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,21 @@ describe('MatInputHarness', () => {
3838
expect(inputs.length).toBe(1);
3939
});
4040

41-
it('should load input with a specific label', async () => {
41+
it('should load input with a specific floating label', async () => {
4242
const inputs = await loader.getAllHarnesses(MatInputHarness.with({label: 'Favorite food'}));
4343
expect(inputs.length).toBe(1);
4444
});
4545

46+
it('should load input with a specific external label', async () => {
47+
const inputs = await loader.getAllHarnesses(MatInputHarness.with({label: 'Favorite drink'}));
48+
expect(inputs.length).toBe(1);
49+
});
50+
51+
it('should load input with a specific aria label', async () => {
52+
const inputs = await loader.getAllHarnesses(MatInputHarness.with({label: 'Comment box'}));
53+
expect(inputs.length).toBe(1);
54+
});
55+
4656
it('should load input with a value that matches a regex', async () => {
4757
const inputs = await loader.getAllHarnesses(MatInputHarness.with({value: /shi$/}));
4858
expect(inputs.length).toBe(1);
@@ -66,7 +76,7 @@ describe('MatInputHarness', () => {
6676
const inputs = await loader.getAllHarnesses(MatInputHarness);
6777
expect(inputs.length).toBe(7);
6878
expect(await inputs[0].getId()).toMatch(/mat-input-\w+\d+/);
69-
expect(await inputs[1].getId()).toMatch(/mat-input-\w+\d+/);
79+
expect(await inputs[1].getId()).toMatch('favorite-drink-input');
7080
expect(await inputs[2].getId()).toBe('myTextarea');
7181
expect(await inputs[3].getId()).toBe('nativeControl');
7282
expect(await inputs[4].getId()).toMatch(/mat-input-\w+\d+/);
@@ -239,9 +249,11 @@ describe('MatInputHarness', () => {
239249
<input matInput placeholder="Favorite food" value="Sushi" name="favorite-food">
240250
</mat-form-field>
241251
252+
<label for="favorite-drink-input">Favorite drink</label>
242253
<mat-form-field>
243254
<input
244255
matInput
256+
id="favorite-drink-input"
245257
[type]="inputType()"
246258
[readonly]="readonly()"
247259
[disabled]="disabled()"
@@ -250,7 +262,12 @@ describe('MatInputHarness', () => {
250262
</mat-form-field>
251263
252264
<mat-form-field>
253-
<textarea id="myTextarea" matInput placeholder="Leave a comment"></textarea>
265+
<textarea
266+
id="myTextarea"
267+
matInput
268+
placeholder="Leave a comment"
269+
aria-label="Comment box"
270+
></textarea>
254271
</mat-form-field>
255272
256273
<mat-form-field>

0 commit comments

Comments
 (0)