Skip to content

fix(material/testing): Fix minor issue preventing the label filter from working for some implementations #31596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions src/material/datepicker/testing/date-range-input-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<string | null> {
// 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string | null> {
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;
Expand Down
23 changes: 20 additions & 3 deletions src/material/input/testing/input-harness.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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+/);
Expand Down Expand Up @@ -239,9 +249,11 @@ describe('MatInputHarness', () => {
<input matInput placeholder="Favorite food" value="Sushi" name="favorite-food">
</mat-form-field>

<label for="favorite-drink-input">Favorite drink</label>
<mat-form-field>
<input
matInput
id="favorite-drink-input"
[type]="inputType()"
[readonly]="readonly()"
[disabled]="disabled()"
Expand All @@ -250,7 +262,12 @@ describe('MatInputHarness', () => {
</mat-form-field>

<mat-form-field>
<textarea id="myTextarea" matInput placeholder="Leave a comment"></textarea>
<textarea
id="myTextarea"
matInput
placeholder="Leave a comment"
aria-label="Comment box"
></textarea>
</mat-form-field>

<mat-form-field>
Expand Down
Loading