Skip to content

Commit 779cac3

Browse files
committed
feat(cdk-experimental/testing): add support for matching selector on TestElement
1 parent fa81811 commit 779cac3

32 files changed

+139
-55
lines changed

src/cdk-experimental/testing/component-harness.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@ export interface ComponentHarnessConstructor<T extends ComponentHarness> {
259259
hostSelector: string;
260260
}
261261

262+
export interface BaseHarnessFilters {
263+
selector?: string;
264+
}
265+
262266
/**
263267
* A class used to associate a ComponentHarness class with predicates functions that can be used to
264268
* filter instances of the class.
@@ -267,7 +271,14 @@ export class HarnessPredicate<T extends ComponentHarness> {
267271
private _predicates: AsyncPredicate<T>[] = [];
268272
private _descriptions: string[] = [];
269273

270-
constructor(public harnessType: ComponentHarnessConstructor<T>) {}
274+
constructor(public harnessType: ComponentHarnessConstructor<T>, options: BaseHarnessFilters) {
275+
const selector = options.selector;
276+
if (selector !== undefined) {
277+
this.add(`selector matches "${selector}"`, async item => {
278+
return (await item.host()).matchesSelector(selector);
279+
});
280+
}
281+
}
271282

272283
/**
273284
* Checks if a string matches the given pattern.

src/cdk-experimental/testing/harness-environment.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function _getErrorForMissingSelector(selector: string): Error {
2323
function _getErrorForMissingHarness<T extends ComponentHarness>(
2424
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): Error {
2525
const harnessPredicate =
26-
harnessType instanceof HarnessPredicate ? harnessType : new HarnessPredicate(harnessType);
26+
harnessType instanceof HarnessPredicate ? harnessType : new HarnessPredicate(harnessType, {});
2727
const {name, hostSelector} = harnessPredicate.harnessType;
2828
const restrictions = harnessPredicate.getDescription();
2929
let message = `Expected to find element for ${name} matching selector: "${hostSelector}"`;
@@ -160,7 +160,8 @@ export abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFac
160160
private async _getAllHarnesses<T extends ComponentHarness>(
161161
harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): Promise<T[]> {
162162
const harnessPredicate =
163-
harnessType instanceof HarnessPredicate ? harnessType : new HarnessPredicate(harnessType);
163+
harnessType instanceof HarnessPredicate ?
164+
harnessType : new HarnessPredicate(harnessType, {});
164165
const elements = await this.getAllRawElements(harnessPredicate.harnessType.hostSelector);
165166
return harnessPredicate.filter(elements.map(
166167
element => this.createComponentHarness(harnessPredicate.harnessType, element)));

src/cdk-experimental/testing/protractor/protractor-element.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,15 @@ export class ProtractorElement implements TestElement {
136136
const {x: left, y: top} = await this.element.getLocation();
137137
return {width, height, left, top};
138138
}
139+
140+
async matchesSelector(selector: string): Promise<boolean> {
141+
return browser.executeScript(`
142+
return (Element.prototype.matches ||
143+
Element.prototype.matchesSelector ||
144+
Element.prototype.mozMatchesSelector ||
145+
Element.prototype.msMatchesSelector ||
146+
Element.prototype.oMatchesSelector ||
147+
Element.prototype.webkitMatchesSelector).call(arguments[0], arguments[1])
148+
`, this.element, selector);
149+
}
139150
}

src/cdk-experimental/testing/test-element.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,7 @@ export interface TestElement {
101101

102102
/** Gets the dimensions of the element. */
103103
getDimensions(): Promise<ElementDimensions>;
104+
105+
/** Checks whether this element matches the given selector. */
106+
matchesSelector(selector: string): Promise<boolean>;
104107
}

src/cdk-experimental/testing/testbed/unit-test-element.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,15 @@ export class UnitTestElement implements TestElement {
139139
await this._stabilize();
140140
return this.element.getBoundingClientRect();
141141
}
142+
143+
async matchesSelector(selector: string): Promise<boolean> {
144+
await this._stabilize();
145+
const elementPrototype = Element.prototype as any;
146+
return (elementPrototype['matches'] ||
147+
elementPrototype['matchesSelector'] ||
148+
elementPrototype['mozMatchesSelector'] ||
149+
elementPrototype['msMatchesSelector'] ||
150+
elementPrototype['oMatchesSelector'] ||
151+
elementPrototype['webkitMatchesSelector']).call(this.element, selector);
152+
}
142153
}

src/cdk-experimental/testing/tests/harnesses/sub-component-harness.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ComponentHarness, HarnessPredicate} from '../../component-harness';
9+
import {BaseHarnessFilters, ComponentHarness, HarnessPredicate} from '../../component-harness';
1010
import {TestElement} from '../../test-element';
1111

12+
export interface SubComponentHarnessFilters extends BaseHarnessFilters {
13+
title?: string | RegExp;
14+
itemCount?: number;
15+
}
16+
1217
/** @dynamic */
1318
export class SubComponentHarness extends ComponentHarness {
1419
static readonly hostSelector = 'test-sub';
1520

16-
static with(options: {title?: string | RegExp, itemCount?: number} = {}) {
17-
return new HarnessPredicate(SubComponentHarness)
21+
static with(options: SubComponentHarnessFilters = {}) {
22+
return new HarnessPredicate(SubComponentHarness, options)
1823
.addOption('title', options.title,
1924
async (harness, title) =>
2025
HarnessPredicate.stringMatches((await harness.title()).text(), title))

src/cdk-experimental/testing/tests/protractor.e2e.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,12 @@ describe('ProtractorHarnessEnvironment', () => {
254254
expect(await (await browser.switchTo().activeElement()).getText())
255255
.not.toBe(await button.text());
256256
});
257+
258+
it('should check if selector matches', async () => {
259+
const button = await harness.button();
260+
expect(await button.matchesSelector('button:not(.fake-class)')).toBe(true);
261+
expect(await button.matchesSelector('button:disabled')).toBe(false);
262+
});
257263
});
258264

259265
describe('HarnessPredicate', () => {

src/cdk-experimental/testing/tests/testbed.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,13 +264,19 @@ describe('TestbedHarnessEnvironment', () => {
264264
});
265265

266266
it('should focus and blur element', async () => {
267-
let button = await harness.button();
267+
const button = await harness.button();
268268
expect(activeElementText()).not.toBe(await button.text());
269269
await button.focus();
270270
expect(activeElementText()).toBe(await button.text());
271271
await button.blur();
272272
expect(activeElementText()).not.toBe(await button.text());
273273
});
274+
275+
it('should check if selector matches', async () => {
276+
const button = await harness.button();
277+
expect(await button.matchesSelector('button:not(.fake-class)')).toBe(true);
278+
expect(await button.matchesSelector('button:disabled')).toBe(false);
279+
});
274280
});
275281

276282
describe('HarnessPredicate', () => {

src/material-experimental/mdc-button/harness/button-harness-filters.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
export type ButtonHarnessFilters = {
10-
text?: string | RegExp
11-
};
9+
import {BaseHarnessFilters} from '@angular/cdk-experimental/testing';
10+
11+
export interface ButtonHarnessFilters extends BaseHarnessFilters {
12+
text?: string | RegExp;
13+
}

src/material-experimental/mdc-button/harness/button-harness.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ export class MatButtonHarness extends ComponentHarness {
3030
/**
3131
* Gets a `HarnessPredicate` that can be used to search for a button with specific attributes.
3232
* @param options Options for narrowing the search:
33+
* - `selector` finds a button whose host element matches the given selector.
3334
* - `text` finds a button with specific text content.
3435
* @return a `HarnessPredicate` configured with the given options.
3536
*/
3637
static with(options: ButtonHarnessFilters = {}): HarnessPredicate<MatButtonHarness> {
37-
return new HarnessPredicate(MatButtonHarness)
38+
return new HarnessPredicate(MatButtonHarness, options)
3839
.addOption('text', options.text,
3940
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
4041
}

0 commit comments

Comments
 (0)