From d6cbbcf804c079c2d224fa00f7b7fcc3b62b7453 Mon Sep 17 00:00:00 2001 From: Alice Q Date: Wed, 23 Jul 2025 18:53:38 +0000 Subject: [PATCH] fix(material/testing): Modify input filtering to more broadly search for a label, including fallback functionality that checks `mat-label` --- .../testing/date-range-input-harness.ts | 28 +++++++++---------- .../control/form-field-control-harness.ts | 25 ++++++++--------- .../input/testing/input-harness.spec.ts | 23 +++++++++++++-- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/material/datepicker/testing/date-range-input-harness.ts b/src/material/datepicker/testing/date-range-input-harness.ts index 4504717007fe..d1cbee84b930 100644 --- a/src/material/datepicker/testing/date-range-input-harness.ts +++ b/src/material/datepicker/testing/date-range-input-harness.ts @@ -48,8 +48,6 @@ export class MatEndDateHarness extends MatDatepickerInputHarnessBase { export class MatDateRangeInputHarness extends DatepickerTriggerHarnessBase { static hostSelector = '.mat-date-range-input'; - private readonly _floatingLabelSelector = '.mdc-floating-label'; - /** * Gets a `HarnessPredicate` that can be used to search for a `MatDateRangeInputHarness` * that meets certain criteria. @@ -91,26 +89,28 @@ export class MatDateRangeInputHarness extends DatepickerTriggerHarnessBase { return this.locatorFor(MatEndDateHarness)(); } - /** Gets the floating label text for the range input, if it exists. */ + /** + * Gets the label for the range input, if it exists. This might be provided by a label element or + * by the `aria-label` attribute. + */ async getLabel(): Promise { - // Copied from MatFormFieldControlHarnessBase since this class cannot extend two classes + // Directly copied from MatFormFieldControlHarnessBase. This class already has a parent so it + // cannot extend MatFormFieldControlHarnessBase for the functionality. const documentRootLocator = this.documentRootLocatorFactory(); const labelId = await (await this.host()).getAttribute('aria-labelledby'); + const labelText = await (await this.host()).getAttribute('aria-label'); const hostId = await (await this.host()).getAttribute('id'); if (labelId) { - // First option, try to fetch the label using the `aria-labelledby` - // attribute. - const labelEl = await documentRootLocator.locatorForOptional( - `${this._floatingLabelSelector}[id="${labelId}"]`, - )(); + // First, try to find the label by following [aria-labelledby] + const labelEl = await documentRootLocator.locatorForOptional(`[id="${labelId}"]`)(); return labelEl ? labelEl.text() : null; + } else if (labelText) { + // If that doesn't work, return [aria-label] if it exists + return labelText; } else if (hostId) { - // Fallback option, try to match the id of the input with the `for` - // attribute of the label. - const labelEl = await documentRootLocator.locatorForOptional( - `${this._floatingLabelSelector}[for="${hostId}"]`, - )(); + // Finally, search the DOM for a label that points to the host element + const labelEl = await documentRootLocator.locatorForOptional(`[for="${hostId}"]`)(); return labelEl ? labelEl.text() : null; } return null; diff --git a/src/material/form-field/testing/control/form-field-control-harness.ts b/src/material/form-field/testing/control/form-field-control-harness.ts index 39f2874e090b..ad357f061bad 100644 --- a/src/material/form-field/testing/control/form-field-control-harness.ts +++ b/src/material/form-field/testing/control/form-field-control-harness.ts @@ -18,27 +18,26 @@ export abstract class MatFormFieldControlHarness extends ComponentHarness {} * Shared behavior for `MatFormFieldControlHarness` implementations */ export abstract class MatFormFieldControlHarnessBase extends MatFormFieldControlHarness { - private readonly _floatingLabelSelector = '.mdc-floating-label'; - - /** Gets the text content of the floating label, if it exists. */ + /** + * Gets the label for the control, if it exists. This might be provided by a label element or by + * the `aria-label` attribute. + */ async getLabel(): Promise { const documentRootLocator = this.documentRootLocatorFactory(); const labelId = await (await this.host()).getAttribute('aria-labelledby'); + const labelText = await (await this.host()).getAttribute('aria-label'); const hostId = await (await this.host()).getAttribute('id'); if (labelId) { - // First option, try to fetch the label using the `aria-labelledby` - // attribute. - const labelEl = await documentRootLocator.locatorForOptional( - `${this._floatingLabelSelector}[id="${labelId}"]`, - )(); + // First, try to find the label by following [aria-labelledby] + const labelEl = await documentRootLocator.locatorForOptional(`[id="${labelId}"]`)(); return labelEl ? labelEl.text() : null; + } else if (labelText) { + // If that doesn't work, return [aria-label] if it exists + return labelText; } else if (hostId) { - // Fallback option, try to match the id of the input with the `for` - // attribute of the label. - const labelEl = await documentRootLocator.locatorForOptional( - `${this._floatingLabelSelector}[for="${hostId}"]`, - )(); + // Finally, search the DOM for a label that points to the host element + const labelEl = await documentRootLocator.locatorForOptional(`[for="${hostId}"]`)(); return labelEl ? labelEl.text() : null; } return null; diff --git a/src/material/input/testing/input-harness.spec.ts b/src/material/input/testing/input-harness.spec.ts index 3d93514de9fa..88b5609ee145 100644 --- a/src/material/input/testing/input-harness.spec.ts +++ b/src/material/input/testing/input-harness.spec.ts @@ -38,11 +38,21 @@ describe('MatInputHarness', () => { expect(inputs.length).toBe(1); }); - it('should load input with a specific label', async () => { + it('should load input with a specific floating label', async () => { const inputs = await loader.getAllHarnesses(MatInputHarness.with({label: 'Favorite food'})); expect(inputs.length).toBe(1); }); + it('should load input with a specific external label', async () => { + const inputs = await loader.getAllHarnesses(MatInputHarness.with({label: 'Favorite drink'})); + expect(inputs.length).toBe(1); + }); + + it('should load input with a specific aria label', async () => { + const inputs = await loader.getAllHarnesses(MatInputHarness.with({label: 'Comment box'})); + expect(inputs.length).toBe(1); + }); + it('should load input with a value that matches a regex', async () => { const inputs = await loader.getAllHarnesses(MatInputHarness.with({value: /shi$/})); expect(inputs.length).toBe(1); @@ -66,7 +76,7 @@ describe('MatInputHarness', () => { const inputs = await loader.getAllHarnesses(MatInputHarness); expect(inputs.length).toBe(7); expect(await inputs[0].getId()).toMatch(/mat-input-\w+\d+/); - expect(await inputs[1].getId()).toMatch(/mat-input-\w+\d+/); + expect(await inputs[1].getId()).toMatch('favorite-drink-input'); expect(await inputs[2].getId()).toBe('myTextarea'); expect(await inputs[3].getId()).toBe('nativeControl'); expect(await inputs[4].getId()).toMatch(/mat-input-\w+\d+/); @@ -239,9 +249,11 @@ describe('MatInputHarness', () => { + { - +