Skip to content

Commit e89ed31

Browse files
crisbetojelbourn
authored andcommitted
feat(tooltip/testing): add test harness for mat-tooltip
This is redo of #16676. Adds a test harness for `mat-tooltip`, as well as an API to move the pointer away from an element, as an opposite to `TestElement.hover`.
1 parent caad0b5 commit e89ed31

File tree

18 files changed

+253
-5
lines changed

18 files changed

+253
-5
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ export class ProtractorElement implements TestElement {
9999
.perform();
100100
}
101101

102+
async mouseAway(): Promise<void> {
103+
return browser.actions()
104+
.mouseMove(await this.element.getWebElement(), {x: -1, y: -1})
105+
.perform();
106+
}
107+
102108
async sendKeys(...keys: (string | TestKey)[]): Promise<void>;
103109
async sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
104110
async sendKeys(...modifiersAndKeys: any[]): Promise<void> {

src/cdk/testing/test-element.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ export interface TestElement {
8686
/** Hovers the mouse over the element. */
8787
hover(): Promise<void>;
8888

89+
/** Moves the mouse away from the element. */
90+
mouseAway(): Promise<void>;
91+
8992
/**
9093
* Sends the given string to the input as a series of key presses. Also fires input events
9194
* and attempts to add the string to the Element's value.

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ export class UnitTestElement implements TestElement {
119119
await this._stabilize();
120120
}
121121

122+
async mouseAway(): Promise<void> {
123+
await this._stabilize();
124+
dispatchMouseEvent(this.element, 'mouseleave');
125+
await this._stabilize();
126+
}
127+
122128
async sendKeys(...keys: (string | TestKey)[]): Promise<void>;
123129
async sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
124130
async sendKeys(...modifiersAndKeys: any[]): Promise<void> {

src/cdk/testing/tests/test-main-component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import {
2424
templateUrl: 'test-main-component.html',
2525
host: {
2626
'[class.hovering]': '_isHovering',
27-
'(mouseenter)': 'onMouseOver()',
28-
'(mouseout)': 'onMouseOut()',
27+
'(mouseenter)': 'onMouseEnter()',
28+
'(mouseleave)': 'onMouseLeave()',
2929
},
3030
encapsulation: ViewEncapsulation.None,
3131
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -50,11 +50,11 @@ export class TestMainComponent implements OnDestroy {
5050

5151
private _fakeOverlayElement: HTMLElement;
5252

53-
onMouseOver() {
53+
onMouseEnter() {
5454
this._isHovering = true;
5555
}
5656

57-
onMouseOut() {
57+
onMouseLeave() {
5858
this._isHovering = false;
5959
}
6060

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,18 @@ describe('TestbedHarnessEnvironment', () => {
338338
expect(classAttr).toContain('hovering');
339339
});
340340

341+
it('should be able to stop hovering', async () => {
342+
const host = await harness.host();
343+
let classAttr = await host.getAttribute('class');
344+
expect(classAttr).not.toContain('hovering');
345+
await host.hover();
346+
classAttr = await host.getAttribute('class');
347+
expect(classAttr).toContain('hovering');
348+
await host.mouseAway();
349+
classAttr = await host.getAttribute('class');
350+
expect(classAttr).not.toContain('hovering');
351+
});
352+
341353
it('should be able to getAttribute', async () => {
342354
const memoStr = `
343355
This is an example that shows how to use component harness

src/material/config.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ entryPoints = [
5858
"tabs/testing",
5959
"toolbar",
6060
"tooltip",
61+
"tooltip/testing",
6162
"tree",
6263
"form-field/testing",
6364
"form-field/testing/control",
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ts_library(
6+
name = "testing",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
module_name = "@angular/material/tooltip/testing",
12+
deps = [
13+
"//src/cdk/testing",
14+
],
15+
)
16+
17+
filegroup(
18+
name = "source-files",
19+
srcs = glob(["**/*.ts"]),
20+
)
21+
22+
ng_test_library(
23+
name = "harness_tests_lib",
24+
srcs = ["shared.spec.ts"],
25+
deps = [
26+
":testing",
27+
"//src/cdk/testing",
28+
"//src/cdk/testing/testbed",
29+
"//src/material/tooltip",
30+
"@npm//@angular/platform-browser",
31+
],
32+
)
33+
34+
ng_test_library(
35+
name = "unit_tests_lib",
36+
srcs = glob(
37+
["**/*.spec.ts"],
38+
exclude = ["shared.spec.ts"],
39+
),
40+
deps = [
41+
":harness_tests_lib",
42+
":testing",
43+
"//src/material/tooltip",
44+
],
45+
)
46+
47+
ng_web_test_suite(
48+
name = "unit_tests",
49+
deps = [":unit_tests_lib"],
50+
)

src/material/tooltip/testing/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export * from './public-api';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export * from './tooltip-harness';
10+
export * from './tooltip-harness-filters';
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {HarnessLoader} from '@angular/cdk/testing';
2+
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
3+
import {Component} from '@angular/core';
4+
import {ComponentFixture, TestBed} from '@angular/core/testing';
5+
import {MatTooltipModule} from '@angular/material/tooltip';
6+
import {MatTooltipHarness} from '@angular/material/tooltip/testing/tooltip-harness';
7+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
8+
9+
/** Shared tests to run on both the original and MDC-based tooltips. */
10+
export function runHarnessTests(
11+
tooltipModule: typeof MatTooltipModule, tooltipHarness: typeof MatTooltipHarness) {
12+
let fixture: ComponentFixture<TooltipHarnessTest>;
13+
let loader: HarnessLoader;
14+
15+
beforeEach(async () => {
16+
await TestBed.configureTestingModule({
17+
imports: [tooltipModule, NoopAnimationsModule],
18+
declarations: [TooltipHarnessTest],
19+
}).compileComponents();
20+
21+
fixture = TestBed.createComponent(TooltipHarnessTest);
22+
fixture.detectChanges();
23+
loader = TestbedHarnessEnvironment.loader(fixture);
24+
});
25+
26+
it('should load all tooltip harnesses', async () => {
27+
const tooltips = await loader.getAllHarnesses(tooltipHarness);
28+
expect(tooltips.length).toBe(2);
29+
});
30+
31+
it('should be able to show a tooltip', async () => {
32+
const tooltip = await loader.getHarness(tooltipHarness.with({selector: '#one'}));
33+
expect(await tooltip.isOpen()).toBe(false);
34+
await tooltip.show();
35+
expect(await tooltip.isOpen()).toBe(true);
36+
});
37+
38+
it('should be able to hide a tooltip', async () => {
39+
const tooltip = await loader.getHarness(tooltipHarness.with({selector: '#one'}));
40+
expect(await tooltip.isOpen()).toBe(false);
41+
await tooltip.show();
42+
expect(await tooltip.isOpen()).toBe(true);
43+
await tooltip.hide();
44+
expect(await tooltip.isOpen()).toBe(false);
45+
});
46+
47+
it('should be able to get the text of a tooltip', async () => {
48+
const tooltip = await loader.getHarness(tooltipHarness.with({selector: '#one'}));
49+
await tooltip.show();
50+
expect(await tooltip.getTooltipText()).toBe('Tooltip message');
51+
});
52+
53+
it('should return empty when getting the tooltip text while closed', async () => {
54+
const tooltip = await loader.getHarness(tooltipHarness.with({selector: '#one'}));
55+
expect(await tooltip.getTooltipText()).toBe('');
56+
});
57+
}
58+
59+
@Component({
60+
template: `
61+
<button [matTooltip]="message" id="one">Trigger 1</button>
62+
<button matTooltip="Static message" id="two">Trigger 2</button>
63+
`
64+
})
65+
class TooltipHarnessTest {
66+
message = 'Tooltip message';
67+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {BaseHarnessFilters} from '@angular/cdk/testing';
10+
11+
/** A set of criteria that can be used to filter a list of `MatTooltipHarness` instances. */
12+
export interface TooltipHarnessFilters extends BaseHarnessFilters {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {MatTooltipModule} from '@angular/material/tooltip';
2+
import {runHarnessTests} from '@angular/material/tooltip/testing/shared.spec';
3+
import {MatTooltipHarness} from './tooltip-harness';
4+
5+
describe('Non-MDC-based MatTooltipHarness', () => {
6+
runHarnessTests(MatTooltipModule, MatTooltipHarness);
7+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
10+
import {TooltipHarnessFilters} from './tooltip-harness-filters';
11+
12+
/** Harness for interacting with a standard mat-tooltip in tests. */
13+
export class MatTooltipHarness extends ComponentHarness {
14+
private _optionalPanel = this.documentRootLocatorFactory().locatorForOptional('.mat-tooltip');
15+
static hostSelector = '.mat-tooltip-trigger';
16+
17+
/**
18+
* Gets a `HarnessPredicate` that can be used to search
19+
* for a tooltip trigger with specific attributes.
20+
* @param options Options for narrowing the search.
21+
* @return a `HarnessPredicate` configured with the given options.
22+
*/
23+
static with(options: TooltipHarnessFilters = {}): HarnessPredicate<MatTooltipHarness> {
24+
return new HarnessPredicate(MatTooltipHarness, options);
25+
}
26+
27+
/** Shows the tooltip. */
28+
async show(): Promise<void> {
29+
return (await this.host()).hover();
30+
}
31+
32+
/** Hides the tooltip. */
33+
async hide(): Promise<void> {
34+
const host = await this.host();
35+
await host.mouseAway();
36+
await this.forceStabilize(); // Needed in order to flush the `hide` animation.
37+
}
38+
39+
/** Gets whether the tooltip is open. */
40+
async isOpen(): Promise<boolean> {
41+
return !!(await this._optionalPanel());
42+
}
43+
44+
/** Gets a promise for the tooltip panel's text. */
45+
async getTooltipText(): Promise<string> {
46+
const panel = await this._optionalPanel();
47+
return panel ? panel.text() : '';
48+
}
49+
}

src/material/tooltip/tooltip.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ export function MAT_TOOLTIP_DEFAULT_OPTIONS_FACTORY(): MatTooltipDefaultOptions
131131
@Directive({
132132
selector: '[matTooltip]',
133133
exportAs: 'matTooltip',
134+
host: {
135+
'class': 'mat-tooltip-trigger'
136+
}
134137
})
135138
export class MatTooltip implements OnDestroy, OnInit {
136139
_overlayRef: OverlayRef | null;
@@ -331,7 +334,6 @@ export class MatTooltip implements OnDestroy, OnInit {
331334
}
332335

333336
const overlayRef = this._createOverlay();
334-
335337
this._detach();
336338
this._portal = this._portal || new ComponentPortal(TooltipComponent, this._viewContainerRef);
337339
this._tooltipInstance = overlayRef.attach(this._portal).instance;

tools/public_api_guard/cdk/testing.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export interface TestElement {
118118
hover(): Promise<void>;
119119
isFocused(): Promise<boolean>;
120120
matchesSelector(selector: string): Promise<boolean>;
121+
mouseAway(): Promise<void>;
121122
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
122123
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
123124
text(): Promise<string>;

tools/public_api_guard/cdk/testing/protractor.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export declare class ProtractorElement implements TestElement {
1313
hover(): Promise<void>;
1414
isFocused(): Promise<boolean>;
1515
matchesSelector(selector: string): Promise<boolean>;
16+
mouseAway(): Promise<void>;
1617
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
1718
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
1819
text(): Promise<string>;

tools/public_api_guard/cdk/testing/testbed.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export declare class UnitTestElement implements TestElement {
3030
hover(): Promise<void>;
3131
isFocused(): Promise<boolean>;
3232
matchesSelector(selector: string): Promise<boolean>;
33+
mouseAway(): Promise<void>;
3334
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
3435
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
3536
text(): Promise<string>;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export declare class MatTooltipHarness extends ComponentHarness {
2+
getTooltipText(): Promise<string>;
3+
hide(): Promise<void>;
4+
isOpen(): Promise<boolean>;
5+
show(): Promise<void>;
6+
static hostSelector: string;
7+
static with(options?: TooltipHarnessFilters): HarnessPredicate<MatTooltipHarness>;
8+
}
9+
10+
export interface TooltipHarnessFilters extends BaseHarnessFilters {
11+
}

0 commit comments

Comments
 (0)