-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(material-experimental): test harness for dialog (#16709)
Resolves COMP-180.
- Loading branch information
1 parent
dd548be
commit f5e087b
Showing
6 changed files
with
246 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package(default_visibility = ["//visibility:public"]) | ||
|
||
load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library") | ||
|
||
ts_library( | ||
name = "harness", | ||
srcs = glob( | ||
["**/*.ts"], | ||
exclude = ["**/*.spec.ts"], | ||
), | ||
deps = [ | ||
"//src/cdk-experimental/testing", | ||
"//src/material/dialog", | ||
], | ||
) | ||
|
||
ng_test_library( | ||
name = "harness_tests", | ||
srcs = glob(["**/*.spec.ts"]), | ||
deps = [ | ||
":harness", | ||
"//src/cdk-experimental/testing", | ||
"//src/cdk-experimental/testing/testbed", | ||
"//src/material/dialog", | ||
"@npm//@angular/platform-browser", | ||
], | ||
) | ||
|
||
ng_web_test_suite( | ||
name = "tests", | ||
deps = [":harness_tests"], | ||
) |
11 changes: 11 additions & 0 deletions
11
src/material-experimental/mdc-dialog/harness/dialog-harness-filters.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/** | ||
* @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 type DialogHarnessFilters = { | ||
id?: string; | ||
}; |
132 changes: 132 additions & 0 deletions
132
src/material-experimental/mdc-dialog/harness/dialog-harness.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import {HarnessLoader} from '@angular/cdk-experimental/testing'; | ||
import {TestbedHarnessEnvironment} from '@angular/cdk-experimental/testing/testbed'; | ||
import {Component, TemplateRef, ViewChild} from '@angular/core'; | ||
import {ComponentFixture, TestBed} from '@angular/core/testing'; | ||
import {MatDialog, MatDialogConfig, MatDialogModule} from '@angular/material/dialog'; | ||
import {NoopAnimationsModule} from '@angular/platform-browser/animations'; | ||
import {MatDialogHarness} from './dialog-harness'; | ||
|
||
let fixture: ComponentFixture<DialogHarnessTest>; | ||
let loader: HarnessLoader; | ||
let dialogHarness: typeof MatDialogHarness; | ||
|
||
describe('MatDialogHarness', () => { | ||
describe('non-MDC-based', () => { | ||
beforeEach(async () => { | ||
await TestBed | ||
.configureTestingModule({ | ||
imports: [MatDialogModule, NoopAnimationsModule], | ||
declarations: [DialogHarnessTest], | ||
}) | ||
.compileComponents(); | ||
|
||
fixture = TestBed.createComponent(DialogHarnessTest); | ||
fixture.detectChanges(); | ||
loader = new TestbedHarnessEnvironment(document.body, fixture); | ||
dialogHarness = MatDialogHarness; | ||
}); | ||
|
||
runTests(); | ||
}); | ||
|
||
describe( | ||
'MDC-based', | ||
() => { | ||
// TODO: run tests for MDC based radio-button once implemented. | ||
}); | ||
}); | ||
|
||
/** Shared tests to run on both the original and MDC-based radio-button's. */ | ||
function runTests() { | ||
it('should load harness for dialog', async () => { | ||
fixture.componentInstance.open(); | ||
const dialogs = await loader.getAllHarnesses(dialogHarness); | ||
expect(dialogs.length).toBe(1); | ||
}); | ||
|
||
it('should load harness for dialog with specific id', async () => { | ||
fixture.componentInstance.open({id: 'my-dialog'}); | ||
fixture.componentInstance.open({id: 'other'}); | ||
let dialogs = await loader.getAllHarnesses(dialogHarness); | ||
expect(dialogs.length).toBe(2); | ||
|
||
dialogs = await loader.getAllHarnesses(dialogHarness.with({id: 'my-dialog'})); | ||
expect(dialogs.length).toBe(1); | ||
}); | ||
|
||
it('should be able to get id of dialog', async () => { | ||
fixture.componentInstance.open({id: 'my-dialog'}); | ||
fixture.componentInstance.open({id: 'other'}); | ||
const dialogs = await loader.getAllHarnesses(dialogHarness); | ||
expect(await dialogs[0].getId()).toBe('my-dialog'); | ||
expect(await dialogs[1].getId()).toBe('other'); | ||
}); | ||
|
||
it('should be able to get role of dialog', async () => { | ||
fixture.componentInstance.open({role: 'alertdialog'}); | ||
fixture.componentInstance.open({role: 'dialog'}); | ||
fixture.componentInstance.open({role: undefined}); | ||
const dialogs = await loader.getAllHarnesses(dialogHarness); | ||
expect(await dialogs[0].getRole()).toBe('alertdialog'); | ||
expect(await dialogs[1].getRole()).toBe('dialog'); | ||
expect(await dialogs[2].getRole()).toBe(null); | ||
}); | ||
|
||
it('should be able to get aria-label of dialog', async () => { | ||
fixture.componentInstance.open(); | ||
fixture.componentInstance.open({ariaLabel: 'Confirm purchase.'}); | ||
const dialogs = await loader.getAllHarnesses(dialogHarness); | ||
expect(await dialogs[0].getAriaLabel()).toBe(null); | ||
expect(await dialogs[1].getAriaLabel()).toBe('Confirm purchase.'); | ||
}); | ||
|
||
it('should be able to get aria-labelledby of dialog', async () => { | ||
fixture.componentInstance.open(); | ||
fixture.componentInstance.open({ariaLabelledBy: 'dialog-label'}); | ||
const dialogs = await loader.getAllHarnesses(dialogHarness); | ||
expect(await dialogs[0].getAriaLabelledby()).toBe(null); | ||
expect(await dialogs[1].getAriaLabelledby()).toBe('dialog-label'); | ||
}); | ||
|
||
it('should be able to get aria-describedby of dialog', async () => { | ||
fixture.componentInstance.open(); | ||
fixture.componentInstance.open({ariaDescribedBy: 'dialog-description'}); | ||
const dialogs = await loader.getAllHarnesses(dialogHarness); | ||
expect(await dialogs[0].getAriaDescribedby()).toBe(null); | ||
expect(await dialogs[1].getAriaDescribedby()).toBe('dialog-description'); | ||
}); | ||
|
||
it('should be able to close dialog', async () => { | ||
fixture.componentInstance.open({disableClose: true}); | ||
fixture.componentInstance.open(); | ||
let dialogs = await loader.getAllHarnesses(dialogHarness); | ||
|
||
expect(dialogs.length).toBe(2); | ||
await dialogs[0].close(); | ||
|
||
dialogs = await loader.getAllHarnesses(dialogHarness); | ||
expect(dialogs.length).toBe(1); | ||
|
||
// should be a noop since "disableClose" is set to "true". | ||
await dialogs[0].close(); | ||
dialogs = await loader.getAllHarnesses(dialogHarness); | ||
expect(dialogs.length).toBe(1); | ||
}); | ||
} | ||
|
||
@Component({ | ||
template: ` | ||
<ng-template> | ||
Hello from the dialog! | ||
</ng-template> | ||
` | ||
}) | ||
class DialogHarnessTest { | ||
@ViewChild(TemplateRef, {static: false}) dialogTmpl: TemplateRef<any>; | ||
|
||
constructor(readonly dialog: MatDialog) {} | ||
|
||
open(config?: MatDialogConfig) { | ||
return this.dialog.open(this.dialogTmpl, config); | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
src/material-experimental/mdc-dialog/harness/dialog-harness.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/** | ||
* @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, TestKey} from '@angular/cdk-experimental/testing'; | ||
import {DialogRole} from '@angular/material/dialog'; | ||
import {DialogHarnessFilters} from './dialog-harness-filters'; | ||
|
||
/** | ||
* Harness for interacting with a standard MatDialog in tests. | ||
* @dynamic | ||
*/ | ||
export class MatDialogHarness extends ComponentHarness { | ||
// Developers can provide a custom component or template for the | ||
// dialog. The canonical dialog parent is the "MatDialogContainer". | ||
static hostSelector = '.mat-dialog-container'; | ||
|
||
/** | ||
* Gets a `HarnessPredicate` that can be used to search for a dialog with | ||
* specific attributes. | ||
* @param options Options for narrowing the search: | ||
* - `id` finds a dialog with specific id. | ||
* @return a `HarnessPredicate` configured with the given options. | ||
*/ | ||
static with(options: DialogHarnessFilters = {}): HarnessPredicate<MatDialogHarness> { | ||
return new HarnessPredicate(MatDialogHarness) | ||
.addOption('id', options.id, async (harness, id) => (await harness.getId()) === id); | ||
} | ||
|
||
/** Gets the id of the dialog. */ | ||
async getId(): Promise<string|null> { | ||
const id = await (await this.host()).getAttribute('id'); | ||
// In case no id has been specified, the "id" property always returns | ||
// an empty string. To make this method more explicit, we return null. | ||
return id !== '' ? id : null; | ||
} | ||
|
||
/** Gets the role of the dialog. */ | ||
async getRole(): Promise<DialogRole|null> { | ||
return (await this.host()).getAttribute('role') as Promise<DialogRole|null>; | ||
} | ||
|
||
/** Gets the value of the dialog's "aria-label" attribute. */ | ||
async getAriaLabel(): Promise<string|null> { | ||
return (await this.host()).getAttribute('aria-label'); | ||
} | ||
|
||
/** Gets the value of the dialog's "aria-labelledby" attribute. */ | ||
async getAriaLabelledby(): Promise<string|null> { | ||
return (await this.host()).getAttribute('aria-labelledby'); | ||
} | ||
|
||
/** Gets the value of the dialog's "aria-describedby" attribute. */ | ||
async getAriaDescribedby(): Promise<string|null> { | ||
return (await this.host()).getAttribute('aria-describedby'); | ||
} | ||
|
||
/** | ||
* Closes the dialog by pressing escape. Note that this method cannot | ||
* be used if "disableClose" has been set to true for the dialog. | ||
*/ | ||
async close(): Promise<void> { | ||
await (await this.host()).sendKeys(TestKey.ESCAPE); | ||
} | ||
} |