Skip to content

Commit 784756d

Browse files
mmalerbajelbourn
authored andcommitted
feat(cdk-experimental/testing): add support for matching selector on TestElement (#16848)
1 parent 52c33c7 commit 784756d

39 files changed

+188
-113
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
@@ -142,5 +142,16 @@ export class ProtractorElement implements TestElement {
142142
return browser.executeScript(`return arguments[0][arguments[1]]`, this.element, name);
143143
}
144144

145+
async matchesSelector(selector: string): Promise<boolean> {
146+
return browser.executeScript(`
147+
return (Element.prototype.matches ||
148+
Element.prototype.matchesSelector ||
149+
Element.prototype.mozMatchesSelector ||
150+
Element.prototype.msMatchesSelector ||
151+
Element.prototype.oMatchesSelector ||
152+
Element.prototype.webkitMatchesSelector).call(arguments[0], arguments[1])
153+
`, this.element, selector);
154+
}
155+
145156
async forceStabilize(): Promise<void> {}
146157
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ export interface TestElement {
102102
/** Gets the value of a property of an element. */
103103
getProperty(name: string): Promise<any>;
104104

105+
/** Checks whether this element matches the given selector. */
106+
matchesSelector(selector: string): Promise<boolean>;
107+
105108
/**
106109
* Flushes change detection and async tasks.
107110
* In most cases it should not be necessary to call this. However, there may be some edge cases

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,17 @@ export class UnitTestElement implements TestElement {
138138
return (this.element as any)[name];
139139
}
140140

141+
async matchesSelector(selector: string): Promise<boolean> {
142+
await this._stabilize();
143+
const elementPrototype = Element.prototype as any;
144+
return (elementPrototype['matches'] ||
145+
elementPrototype['matchesSelector'] ||
146+
elementPrototype['mozMatchesSelector'] ||
147+
elementPrototype['msMatchesSelector'] ||
148+
elementPrototype['oMatchesSelector'] ||
149+
elementPrototype['webkitMatchesSelector']).call(this.element, selector);
150+
}
151+
141152
async forceStabilize(): Promise<void> {
142153
return this._stabilize();
143154
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export class MainComponentHarness extends ComponentHarness {
4949
readonly testLists = this.locatorForAll(SubComponentHarness.with({title: /test/}));
5050
readonly requiredFourIteamToolsLists =
5151
this.locatorFor(SubComponentHarness.with({title: 'List of test tools', itemCount: 4}));
52+
readonly lastList = this.locatorFor(SubComponentHarness.with({selector: ':last-child'}));
5253
readonly specaialKey = this.locatorFor('.special-key');
5354

5455
private _testTools = this.locatorFor(SubComponentHarness);

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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ describe('ProtractorHarnessEnvironment', () => {
260260
await input.sendKeys('Hello');
261261
expect(await input.getProperty('value')).toBe('Hello');
262262
});
263+
264+
it('should check if selector matches', async () => {
265+
const button = await harness.button();
266+
expect(await button.matchesSelector('button:not(.fake-class)')).toBe(true);
267+
expect(await button.matchesSelector('button:disabled')).toBe(false);
268+
});
263269
});
264270

265271
describe('HarnessPredicate', () => {
@@ -293,6 +299,11 @@ describe('ProtractorHarnessEnvironment', () => {
293299
expect(await (await testLists[1].title()).text()).toBe('List of test methods');
294300
});
295301

302+
it('should find subcomponents that match selector', async () => {
303+
const lastList = await harness.lastList();
304+
expect(await (await lastList.title()).text()).toBe('List of test methods');
305+
});
306+
296307
it('should error if predicate does not match but a harness is required', async () => {
297308
try {
298309
await harness.requiredFourIteamToolsLists();

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ 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());
@@ -277,6 +277,12 @@ describe('TestbedHarnessEnvironment', () => {
277277
await input.sendKeys('Hello');
278278
expect(await input.getProperty('value')).toBe('Hello');
279279
});
280+
281+
it('should check if selector matches', async () => {
282+
const button = await harness.button();
283+
expect(await button.matchesSelector('button:not(.fake-class)')).toBe(true);
284+
expect(await button.matchesSelector('button:disabled')).toBe(false);
285+
});
280286
});
281287

282288
describe('HarnessPredicate', () => {
@@ -311,6 +317,11 @@ describe('TestbedHarnessEnvironment', () => {
311317
expect(await (await testLists[1].title()).text()).toBe('List of test methods');
312318
});
313319

320+
it('should find subcomponents that match selector', async () => {
321+
const lastList = await harness.lastList();
322+
expect(await (await lastList.title()).text()).toBe('List of test methods');
323+
});
324+
314325
it('should error if predicate does not match but a harness is required', async () => {
315326
try {
316327
await harness.requiredFourIteamToolsLists();

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+
}

0 commit comments

Comments
 (0)