Skip to content

Commit f5e087b

Browse files
devversionjelbourn
authored andcommitted
feat(material-experimental): test harness for dialog (#16709)
Resolves COMP-180.
1 parent dd548be commit f5e087b

File tree

6 files changed

+246
-1
lines changed

6 files changed

+246
-1
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
/src/material-experimental/mdc-card/** @mmalerba
9595
/src/material-experimental/mdc-checkbox/** @mmalerba
9696
/src/material-experimental/mdc-chips/** @mmalerba
97+
/src/material-experimental/mdc-dialog/** @devversion
9798
/src/material-experimental/mdc-helpers/** @mmalerba
9899
# Note to implementer: please repossess
99100
/src/material-experimental/mdc-input/** @devversion

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {UnitTestElement} from './unit-test-element';
1414

1515
/** A `HarnessEnvironment` implementation for Angular's Testbed. */
1616
export class TestbedHarnessEnvironment extends HarnessEnvironment<Element> {
17-
protected constructor(rawRootElement: Element, private _fixture: ComponentFixture<unknown>) {
17+
constructor(rawRootElement: Element, private _fixture: ComponentFixture<unknown>) {
1818
super(rawRootElement);
1919
}
2020

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
4+
5+
ts_library(
6+
name = "harness",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
deps = [
12+
"//src/cdk-experimental/testing",
13+
"//src/material/dialog",
14+
],
15+
)
16+
17+
ng_test_library(
18+
name = "harness_tests",
19+
srcs = glob(["**/*.spec.ts"]),
20+
deps = [
21+
":harness",
22+
"//src/cdk-experimental/testing",
23+
"//src/cdk-experimental/testing/testbed",
24+
"//src/material/dialog",
25+
"@npm//@angular/platform-browser",
26+
],
27+
)
28+
29+
ng_web_test_suite(
30+
name = "tests",
31+
deps = [":harness_tests"],
32+
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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 type DialogHarnessFilters = {
10+
id?: string;
11+
};
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import {HarnessLoader} from '@angular/cdk-experimental/testing';
2+
import {TestbedHarnessEnvironment} from '@angular/cdk-experimental/testing/testbed';
3+
import {Component, TemplateRef, ViewChild} from '@angular/core';
4+
import {ComponentFixture, TestBed} from '@angular/core/testing';
5+
import {MatDialog, MatDialogConfig, MatDialogModule} from '@angular/material/dialog';
6+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
7+
import {MatDialogHarness} from './dialog-harness';
8+
9+
let fixture: ComponentFixture<DialogHarnessTest>;
10+
let loader: HarnessLoader;
11+
let dialogHarness: typeof MatDialogHarness;
12+
13+
describe('MatDialogHarness', () => {
14+
describe('non-MDC-based', () => {
15+
beforeEach(async () => {
16+
await TestBed
17+
.configureTestingModule({
18+
imports: [MatDialogModule, NoopAnimationsModule],
19+
declarations: [DialogHarnessTest],
20+
})
21+
.compileComponents();
22+
23+
fixture = TestBed.createComponent(DialogHarnessTest);
24+
fixture.detectChanges();
25+
loader = new TestbedHarnessEnvironment(document.body, fixture);
26+
dialogHarness = MatDialogHarness;
27+
});
28+
29+
runTests();
30+
});
31+
32+
describe(
33+
'MDC-based',
34+
() => {
35+
// TODO: run tests for MDC based radio-button once implemented.
36+
});
37+
});
38+
39+
/** Shared tests to run on both the original and MDC-based radio-button's. */
40+
function runTests() {
41+
it('should load harness for dialog', async () => {
42+
fixture.componentInstance.open();
43+
const dialogs = await loader.getAllHarnesses(dialogHarness);
44+
expect(dialogs.length).toBe(1);
45+
});
46+
47+
it('should load harness for dialog with specific id', async () => {
48+
fixture.componentInstance.open({id: 'my-dialog'});
49+
fixture.componentInstance.open({id: 'other'});
50+
let dialogs = await loader.getAllHarnesses(dialogHarness);
51+
expect(dialogs.length).toBe(2);
52+
53+
dialogs = await loader.getAllHarnesses(dialogHarness.with({id: 'my-dialog'}));
54+
expect(dialogs.length).toBe(1);
55+
});
56+
57+
it('should be able to get id of dialog', async () => {
58+
fixture.componentInstance.open({id: 'my-dialog'});
59+
fixture.componentInstance.open({id: 'other'});
60+
const dialogs = await loader.getAllHarnesses(dialogHarness);
61+
expect(await dialogs[0].getId()).toBe('my-dialog');
62+
expect(await dialogs[1].getId()).toBe('other');
63+
});
64+
65+
it('should be able to get role of dialog', async () => {
66+
fixture.componentInstance.open({role: 'alertdialog'});
67+
fixture.componentInstance.open({role: 'dialog'});
68+
fixture.componentInstance.open({role: undefined});
69+
const dialogs = await loader.getAllHarnesses(dialogHarness);
70+
expect(await dialogs[0].getRole()).toBe('alertdialog');
71+
expect(await dialogs[1].getRole()).toBe('dialog');
72+
expect(await dialogs[2].getRole()).toBe(null);
73+
});
74+
75+
it('should be able to get aria-label of dialog', async () => {
76+
fixture.componentInstance.open();
77+
fixture.componentInstance.open({ariaLabel: 'Confirm purchase.'});
78+
const dialogs = await loader.getAllHarnesses(dialogHarness);
79+
expect(await dialogs[0].getAriaLabel()).toBe(null);
80+
expect(await dialogs[1].getAriaLabel()).toBe('Confirm purchase.');
81+
});
82+
83+
it('should be able to get aria-labelledby of dialog', async () => {
84+
fixture.componentInstance.open();
85+
fixture.componentInstance.open({ariaLabelledBy: 'dialog-label'});
86+
const dialogs = await loader.getAllHarnesses(dialogHarness);
87+
expect(await dialogs[0].getAriaLabelledby()).toBe(null);
88+
expect(await dialogs[1].getAriaLabelledby()).toBe('dialog-label');
89+
});
90+
91+
it('should be able to get aria-describedby of dialog', async () => {
92+
fixture.componentInstance.open();
93+
fixture.componentInstance.open({ariaDescribedBy: 'dialog-description'});
94+
const dialogs = await loader.getAllHarnesses(dialogHarness);
95+
expect(await dialogs[0].getAriaDescribedby()).toBe(null);
96+
expect(await dialogs[1].getAriaDescribedby()).toBe('dialog-description');
97+
});
98+
99+
it('should be able to close dialog', async () => {
100+
fixture.componentInstance.open({disableClose: true});
101+
fixture.componentInstance.open();
102+
let dialogs = await loader.getAllHarnesses(dialogHarness);
103+
104+
expect(dialogs.length).toBe(2);
105+
await dialogs[0].close();
106+
107+
dialogs = await loader.getAllHarnesses(dialogHarness);
108+
expect(dialogs.length).toBe(1);
109+
110+
// should be a noop since "disableClose" is set to "true".
111+
await dialogs[0].close();
112+
dialogs = await loader.getAllHarnesses(dialogHarness);
113+
expect(dialogs.length).toBe(1);
114+
});
115+
}
116+
117+
@Component({
118+
template: `
119+
<ng-template>
120+
Hello from the dialog!
121+
</ng-template>
122+
`
123+
})
124+
class DialogHarnessTest {
125+
@ViewChild(TemplateRef, {static: false}) dialogTmpl: TemplateRef<any>;
126+
127+
constructor(readonly dialog: MatDialog) {}
128+
129+
open(config?: MatDialogConfig) {
130+
return this.dialog.open(this.dialogTmpl, config);
131+
}
132+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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, TestKey} from '@angular/cdk-experimental/testing';
10+
import {DialogRole} from '@angular/material/dialog';
11+
import {DialogHarnessFilters} from './dialog-harness-filters';
12+
13+
/**
14+
* Harness for interacting with a standard MatDialog in tests.
15+
* @dynamic
16+
*/
17+
export class MatDialogHarness extends ComponentHarness {
18+
// Developers can provide a custom component or template for the
19+
// dialog. The canonical dialog parent is the "MatDialogContainer".
20+
static hostSelector = '.mat-dialog-container';
21+
22+
/**
23+
* Gets a `HarnessPredicate` that can be used to search for a dialog with
24+
* specific attributes.
25+
* @param options Options for narrowing the search:
26+
* - `id` finds a dialog with specific id.
27+
* @return a `HarnessPredicate` configured with the given options.
28+
*/
29+
static with(options: DialogHarnessFilters = {}): HarnessPredicate<MatDialogHarness> {
30+
return new HarnessPredicate(MatDialogHarness)
31+
.addOption('id', options.id, async (harness, id) => (await harness.getId()) === id);
32+
}
33+
34+
/** Gets the id of the dialog. */
35+
async getId(): Promise<string|null> {
36+
const id = await (await this.host()).getAttribute('id');
37+
// In case no id has been specified, the "id" property always returns
38+
// an empty string. To make this method more explicit, we return null.
39+
return id !== '' ? id : null;
40+
}
41+
42+
/** Gets the role of the dialog. */
43+
async getRole(): Promise<DialogRole|null> {
44+
return (await this.host()).getAttribute('role') as Promise<DialogRole|null>;
45+
}
46+
47+
/** Gets the value of the dialog's "aria-label" attribute. */
48+
async getAriaLabel(): Promise<string|null> {
49+
return (await this.host()).getAttribute('aria-label');
50+
}
51+
52+
/** Gets the value of the dialog's "aria-labelledby" attribute. */
53+
async getAriaLabelledby(): Promise<string|null> {
54+
return (await this.host()).getAttribute('aria-labelledby');
55+
}
56+
57+
/** Gets the value of the dialog's "aria-describedby" attribute. */
58+
async getAriaDescribedby(): Promise<string|null> {
59+
return (await this.host()).getAttribute('aria-describedby');
60+
}
61+
62+
/**
63+
* Closes the dialog by pressing escape. Note that this method cannot
64+
* be used if "disableClose" has been set to true for the dialog.
65+
*/
66+
async close(): Promise<void> {
67+
await (await this.host()).sendKeys(TestKey.ESCAPE);
68+
}
69+
}

0 commit comments

Comments
 (0)