diff --git a/src/cdk/testing/protractor/protractor-element.ts b/src/cdk/testing/protractor/protractor-element.ts index 4c4f70c489ec..afb7510cd3ec 100644 --- a/src/cdk/testing/protractor/protractor-element.ts +++ b/src/cdk/testing/protractor/protractor-element.ts @@ -94,6 +94,12 @@ export class ProtractorElement implements TestElement { .perform(); } + async mouseAway(): Promise { + return browser.actions() + .mouseMove(await this.element.getWebElement(), {x: -1, y: -1}) + .perform(); + } + async sendKeys(...keys: (string | TestKey)[]): Promise; async sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise; async sendKeys(...modifiersAndKeys: any[]): Promise { diff --git a/src/cdk/testing/test-element.ts b/src/cdk/testing/test-element.ts index 56ad7be4e732..8678b1e096ef 100644 --- a/src/cdk/testing/test-element.ts +++ b/src/cdk/testing/test-element.ts @@ -83,6 +83,9 @@ export interface TestElement { /** Hovers the mouse over the element. */ hover(): Promise; + /** Moves the mouse away from the element. */ + mouseAway(): Promise; + /** * Sends the given string to the input as a series of key presses. Also fires input events * and attempts to add the string to the Element's value. diff --git a/src/cdk/testing/testbed/unit-test-element.ts b/src/cdk/testing/testbed/unit-test-element.ts index a1eded0f7b2a..ec557ac1fd11 100644 --- a/src/cdk/testing/testbed/unit-test-element.ts +++ b/src/cdk/testing/testbed/unit-test-element.ts @@ -102,6 +102,12 @@ export class UnitTestElement implements TestElement { await this._stabilize(); } + async mouseAway(): Promise { + await this._stabilize(); + dispatchMouseEvent(this.element, 'mouseleave'); + await this._stabilize(); + } + async sendKeys(...keys: (string | TestKey)[]): Promise; async sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise; async sendKeys(...modifiersAndKeys: any[]): Promise { diff --git a/src/cdk/testing/tests/test-main-component.ts b/src/cdk/testing/tests/test-main-component.ts index 2c4d9fd551bb..bf37225c07c0 100644 --- a/src/cdk/testing/tests/test-main-component.ts +++ b/src/cdk/testing/tests/test-main-component.ts @@ -24,8 +24,8 @@ import { templateUrl: 'test-main-component.html', host: { '[class.hovering]': '_isHovering', - '(mouseenter)': 'onMouseOver()', - '(mouseout)': 'onMouseOut()', + '(mouseenter)': 'onMouseEnter()', + '(mouseleave)': 'onMouseLeave()', }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, @@ -50,11 +50,11 @@ export class TestMainComponent implements OnDestroy { private _fakeOverlayElement: HTMLElement; - onMouseOver() { + onMouseEnter() { this._isHovering = true; } - onMouseOut() { + onMouseLeave() { this._isHovering = false; } diff --git a/src/cdk/testing/tests/testbed.spec.ts b/src/cdk/testing/tests/testbed.spec.ts index 59854ff67cbe..94a5ca110961 100644 --- a/src/cdk/testing/tests/testbed.spec.ts +++ b/src/cdk/testing/tests/testbed.spec.ts @@ -338,6 +338,18 @@ describe('TestbedHarnessEnvironment', () => { expect(classAttr).toContain('hovering'); }); + it('should be able to stop hovering', async () => { + const host = await harness.host(); + let classAttr = await host.getAttribute('class'); + expect(classAttr).not.toContain('hovering'); + await host.hover(); + classAttr = await host.getAttribute('class'); + expect(classAttr).toContain('hovering'); + await host.mouseAway(); + classAttr = await host.getAttribute('class'); + expect(classAttr).not.toContain('hovering'); + }); + it('should be able to getAttribute', async () => { const memoStr = ` This is an example that shows how to use component harness diff --git a/src/material/config.bzl b/src/material/config.bzl index ed921eab309c..4d9bf489cd74 100644 --- a/src/material/config.bzl +++ b/src/material/config.bzl @@ -58,6 +58,7 @@ entryPoints = [ "tabs/testing", "toolbar", "tooltip", + "tooltip/testing", "tree", "form-field/testing", "form-field/testing/control", diff --git a/src/material/tooltip/testing/BUILD.bazel b/src/material/tooltip/testing/BUILD.bazel new file mode 100644 index 000000000000..15d01b095733 --- /dev/null +++ b/src/material/tooltip/testing/BUILD.bazel @@ -0,0 +1,50 @@ +load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "testing", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + module_name = "@angular/material/tooltip/testing", + deps = [ + "//src/cdk/testing", + ], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) + +ng_test_library( + name = "harness_tests_lib", + srcs = ["shared.spec.ts"], + deps = [ + ":testing", + "//src/cdk/testing", + "//src/cdk/testing/testbed", + "//src/material/tooltip", + "@npm//@angular/platform-browser", + ], +) + +ng_test_library( + name = "unit_tests_lib", + srcs = glob( + ["**/*.spec.ts"], + exclude = ["shared.spec.ts"], + ), + deps = [ + ":harness_tests_lib", + ":testing", + "//src/material/tooltip", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [":unit_tests_lib"], +) diff --git a/src/material/tooltip/testing/index.ts b/src/material/tooltip/testing/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material/tooltip/testing/index.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './public-api'; diff --git a/src/material/tooltip/testing/public-api.ts b/src/material/tooltip/testing/public-api.ts new file mode 100644 index 000000000000..4e63e5bef9d0 --- /dev/null +++ b/src/material/tooltip/testing/public-api.ts @@ -0,0 +1,10 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './tooltip-harness'; +export * from './tooltip-harness-filters'; diff --git a/src/material/tooltip/testing/shared.spec.ts b/src/material/tooltip/testing/shared.spec.ts new file mode 100644 index 000000000000..5ba2daecacbc --- /dev/null +++ b/src/material/tooltip/testing/shared.spec.ts @@ -0,0 +1,67 @@ +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {Component} from '@angular/core'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {MatTooltipModule} from '@angular/material/tooltip'; +import {MatTooltipHarness} from '@angular/material/tooltip/testing/tooltip-harness'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; + +/** Shared tests to run on both the original and MDC-based tooltips. */ +export function runHarnessTests( + tooltipModule: typeof MatTooltipModule, tooltipHarness: typeof MatTooltipHarness) { + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [tooltipModule, NoopAnimationsModule], + declarations: [TooltipHarnessTest], + }).compileComponents(); + + fixture = TestBed.createComponent(TooltipHarnessTest); + fixture.detectChanges(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should load all tooltip harnesses', async () => { + const tooltips = await loader.getAllHarnesses(tooltipHarness); + expect(tooltips.length).toBe(2); + }); + + it('should be able to show a tooltip', async () => { + const tooltip = await loader.getHarness(tooltipHarness.with({selector: '#one'})); + expect(await tooltip.isOpen()).toBe(false); + await tooltip.show(); + expect(await tooltip.isOpen()).toBe(true); + }); + + it('should be able to hide a tooltip', async () => { + const tooltip = await loader.getHarness(tooltipHarness.with({selector: '#one'})); + expect(await tooltip.isOpen()).toBe(false); + await tooltip.show(); + expect(await tooltip.isOpen()).toBe(true); + await tooltip.hide(); + expect(await tooltip.isOpen()).toBe(false); + }); + + it('should be able to get the text of a tooltip', async () => { + const tooltip = await loader.getHarness(tooltipHarness.with({selector: '#one'})); + await tooltip.show(); + expect(await tooltip.getTooltipText()).toBe('Tooltip message'); + }); + + it('should return empty when getting the tooltip text while closed', async () => { + const tooltip = await loader.getHarness(tooltipHarness.with({selector: '#one'})); + expect(await tooltip.getTooltipText()).toBe(''); + }); +} + +@Component({ + template: ` + + + ` +}) +class TooltipHarnessTest { + message = 'Tooltip message'; +} diff --git a/src/material/tooltip/testing/tooltip-harness-filters.ts b/src/material/tooltip/testing/tooltip-harness-filters.ts new file mode 100644 index 000000000000..454fb24f3232 --- /dev/null +++ b/src/material/tooltip/testing/tooltip-harness-filters.ts @@ -0,0 +1,12 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {BaseHarnessFilters} from '@angular/cdk/testing'; + +/** A set of criteria that can be used to filter a list of `MatTooltipHarness` instances. */ +export interface TooltipHarnessFilters extends BaseHarnessFilters {} diff --git a/src/material/tooltip/testing/tooltip-harness.spec.ts b/src/material/tooltip/testing/tooltip-harness.spec.ts new file mode 100644 index 000000000000..eb1446ae478c --- /dev/null +++ b/src/material/tooltip/testing/tooltip-harness.spec.ts @@ -0,0 +1,7 @@ +import {MatTooltipModule} from '@angular/material/tooltip'; +import {runHarnessTests} from '@angular/material/tooltip/testing/shared.spec'; +import {MatTooltipHarness} from './tooltip-harness'; + +describe('Non-MDC-based MatTooltipHarness', () => { + runHarnessTests(MatTooltipModule, MatTooltipHarness); +}); diff --git a/src/material/tooltip/testing/tooltip-harness.ts b/src/material/tooltip/testing/tooltip-harness.ts new file mode 100644 index 000000000000..2a16dbf1dff9 --- /dev/null +++ b/src/material/tooltip/testing/tooltip-harness.ts @@ -0,0 +1,49 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; +import {TooltipHarnessFilters} from './tooltip-harness-filters'; + +/** Harness for interacting with a standard mat-tooltip in tests. */ +export class MatTooltipHarness extends ComponentHarness { + private _optionalPanel = this.documentRootLocatorFactory().locatorForOptional('.mat-tooltip'); + static hostSelector = '.mat-tooltip-trigger'; + + /** + * Gets a `HarnessPredicate` that can be used to search + * for a tooltip trigger with specific attributes. + * @param options Options for narrowing the search. + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: TooltipHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatTooltipHarness, options); + } + + /** Shows the tooltip. */ + async show(): Promise { + return (await this.host()).hover(); + } + + /** Hides the tooltip. */ + async hide(): Promise { + const host = await this.host(); + await host.mouseAway(); + await this.forceStabilize(); // Needed in order to flush the `hide` animation. + } + + /** Gets whether the tooltip is open. */ + async isOpen(): Promise { + return !!(await this._optionalPanel()); + } + + /** Gets a promise for the tooltip panel's text. */ + async getTooltipText(): Promise { + const panel = await this._optionalPanel(); + return panel ? panel.text() : ''; + } +} diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts index 9c3ba364958b..de0c35a1e6c1 100644 --- a/src/material/tooltip/tooltip.ts +++ b/src/material/tooltip/tooltip.ts @@ -131,6 +131,9 @@ export function MAT_TOOLTIP_DEFAULT_OPTIONS_FACTORY(): MatTooltipDefaultOptions @Directive({ selector: '[matTooltip]', exportAs: 'matTooltip', + host: { + 'class': 'mat-tooltip-trigger' + } }) export class MatTooltip implements OnDestroy, OnInit { _overlayRef: OverlayRef | null; @@ -331,7 +334,6 @@ export class MatTooltip implements OnDestroy, OnInit { } const overlayRef = this._createOverlay(); - this._detach(); this._portal = this._portal || new ComponentPortal(TooltipComponent, this._viewContainerRef); this._tooltipInstance = overlayRef.attach(this._portal).instance; diff --git a/tools/public_api_guard/cdk/testing.d.ts b/tools/public_api_guard/cdk/testing.d.ts index 22ca48b285b4..2ba7eb442b98 100644 --- a/tools/public_api_guard/cdk/testing.d.ts +++ b/tools/public_api_guard/cdk/testing.d.ts @@ -117,6 +117,7 @@ export interface TestElement { hover(): Promise; isFocused(): Promise; matchesSelector(selector: string): Promise; + mouseAway(): Promise; sendKeys(...keys: (string | TestKey)[]): Promise; sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise; text(): Promise; diff --git a/tools/public_api_guard/cdk/testing/protractor.d.ts b/tools/public_api_guard/cdk/testing/protractor.d.ts index 0761944d3d74..edd12012bc71 100644 --- a/tools/public_api_guard/cdk/testing/protractor.d.ts +++ b/tools/public_api_guard/cdk/testing/protractor.d.ts @@ -13,6 +13,7 @@ export declare class ProtractorElement implements TestElement { hover(): Promise; isFocused(): Promise; matchesSelector(selector: string): Promise; + mouseAway(): Promise; sendKeys(...keys: (string | TestKey)[]): Promise; sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise; text(): Promise; diff --git a/tools/public_api_guard/cdk/testing/testbed.d.ts b/tools/public_api_guard/cdk/testing/testbed.d.ts index a44594f0b0ef..f1e92711c8f0 100644 --- a/tools/public_api_guard/cdk/testing/testbed.d.ts +++ b/tools/public_api_guard/cdk/testing/testbed.d.ts @@ -30,6 +30,7 @@ export declare class UnitTestElement implements TestElement { hover(): Promise; isFocused(): Promise; matchesSelector(selector: string): Promise; + mouseAway(): Promise; sendKeys(...keys: (string | TestKey)[]): Promise; sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise; text(): Promise; diff --git a/tools/public_api_guard/material/tooltip/testing.d.ts b/tools/public_api_guard/material/tooltip/testing.d.ts new file mode 100644 index 000000000000..02a4ee66eee3 --- /dev/null +++ b/tools/public_api_guard/material/tooltip/testing.d.ts @@ -0,0 +1,11 @@ +export declare class MatTooltipHarness extends ComponentHarness { + getTooltipText(): Promise; + hide(): Promise; + isOpen(): Promise; + show(): Promise; + static hostSelector: string; + static with(options?: TooltipHarnessFilters): HarnessPredicate; +} + +export interface TooltipHarnessFilters extends BaseHarnessFilters { +}