-
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): add test harness for snack-bar
- Loading branch information
1 parent
77b0e0e
commit 44d4f44
Showing
5 changed files
with
296 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
31 changes: 31 additions & 0 deletions
31
src/material-experimental/mdc-snack-bar/harness/BUILD.bazel
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,31 @@ | ||
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", | ||
], | ||
) | ||
|
||
ng_test_library( | ||
name = "harness_tests", | ||
srcs = glob(["**/*.spec.ts"]), | ||
deps = [ | ||
":harness", | ||
"//src/cdk-experimental/testing", | ||
"//src/cdk-experimental/testing/testbed", | ||
"//src/material/snack-bar", | ||
"@npm//@angular/platform-browser", | ||
], | ||
) | ||
|
||
ng_web_test_suite( | ||
name = "tests", | ||
deps = [":harness_tests"], | ||
) |
168 changes: 168 additions & 0 deletions
168
src/material-experimental/mdc-snack-bar/harness/snack-bar-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,168 @@ | ||
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 {MatSnackBar, MatSnackBarConfig, MatSnackBarModule} from '@angular/material/snack-bar'; | ||
import {NoopAnimationsModule} from '@angular/platform-browser/animations'; | ||
import {MatSnackBarHarness} from './snack-bar-harness'; | ||
|
||
let fixture: ComponentFixture<SnackbarHarnessTest>; | ||
let loader: HarnessLoader; | ||
let snackBarHarness: typeof MatSnackBarHarness; | ||
|
||
describe('MatSnackBarHarness', () => { | ||
describe('non-MDC-based', () => { | ||
beforeEach(async () => { | ||
await TestBed | ||
.configureTestingModule({ | ||
imports: [MatSnackBarModule, NoopAnimationsModule], | ||
declarations: [SnackbarHarnessTest], | ||
}) | ||
.compileComponents(); | ||
|
||
fixture = TestBed.createComponent(SnackbarHarnessTest); | ||
fixture.detectChanges(); | ||
// Note: we need to specify "document.body" as root element because | ||
// the snack-bar's will be added as immediate children of the body. | ||
loader = new TestbedHarnessEnvironment(document.body, fixture); | ||
snackBarHarness = MatSnackBarHarness; | ||
}); | ||
|
||
runTests(); | ||
}); | ||
|
||
describe( | ||
'MDC-based', | ||
() => { | ||
// TODO: run tests for MDC based snack-bar once implemented. | ||
}); | ||
}); | ||
|
||
/** Shared tests to run on both the original and MDC-based snack-bar's. */ | ||
function runTests() { | ||
it('should load harness for simple snack-bar', async () => { | ||
const snackBarRef = fixture.componentInstance.openSimple('Hello!', ''); | ||
let snackBars = await loader.getAllHarnesses(snackBarHarness); | ||
|
||
expect(snackBars.length).toBe(1); | ||
|
||
snackBarRef.dismiss(); | ||
snackBars = await loader.getAllHarnesses(snackBarHarness); | ||
expect(snackBars.length).toBe(0); | ||
}); | ||
|
||
it('should load harness for custom snack-bar', async () => { | ||
const snackBarRef = fixture.componentInstance.openCustom(); | ||
let snackBars = await loader.getAllHarnesses(snackBarHarness); | ||
|
||
expect(snackBars.length).toBe(1); | ||
|
||
snackBarRef.dismiss(); | ||
snackBars = await loader.getAllHarnesses(snackBarHarness); | ||
expect(snackBars.length).toBe(0); | ||
}); | ||
|
||
it('should be able to get role of snack-bar', async () => { | ||
fixture.componentInstance.openCustom(); | ||
let snackBar = await loader.getHarness(snackBarHarness); | ||
expect(await snackBar.getRole()).toBe('alert'); | ||
|
||
fixture.componentInstance.openCustom({politeness: 'polite'}); | ||
snackBar = await loader.getHarness(snackBarHarness); | ||
expect(await snackBar.getRole()).toBe('status'); | ||
|
||
fixture.componentInstance.openCustom({politeness: 'off'}); | ||
snackBar = await loader.getHarness(snackBarHarness); | ||
expect(await snackBar.getRole()).toBe(null); | ||
}); | ||
|
||
it('should be able to get message of simple snack-bar', async () => { | ||
fixture.componentInstance.openSimple('Subscribed to newsletter.'); | ||
let snackBar = await loader.getHarness(snackBarHarness); | ||
expect(await snackBar.getMessage()).toBe('Subscribed to newsletter.'); | ||
|
||
// For snack-bar's with custom template, the message cannot be | ||
// retrieved. We expect an error to be thrown. | ||
fixture.componentInstance.openCustom(); | ||
snackBar = await loader.getHarness(snackBarHarness); | ||
await expectAsyncError(() => snackBar.getMessage(), /custom content/); | ||
}); | ||
|
||
it('should be able to get action description of simple snack-bar', async () => { | ||
fixture.componentInstance.openSimple('Hello', 'Unsubscribe'); | ||
let snackBar = await loader.getHarness(snackBarHarness); | ||
expect(await snackBar.getActionDescription()).toBe('Unsubscribe'); | ||
|
||
// For snack-bar's with custom template, the action description | ||
// cannot be retrieved. We expect an error to be thrown. | ||
fixture.componentInstance.openCustom(); | ||
snackBar = await loader.getHarness(snackBarHarness); | ||
await expectAsyncError(() => snackBar.getActionDescription(), /custom content/); | ||
}); | ||
|
||
it('should be able to check whether simple snack-bar has action', async () => { | ||
fixture.componentInstance.openSimple('With action', 'Unsubscribe'); | ||
let snackBar = await loader.getHarness(snackBarHarness); | ||
expect(await snackBar.hasAction()).toBe(true); | ||
|
||
fixture.componentInstance.openSimple('No action'); | ||
snackBar = await loader.getHarness(snackBarHarness); | ||
expect(await snackBar.hasAction()).toBe(false); | ||
|
||
// For snack-bar's with custom template, the action cannot | ||
// be found. We expect an error to be thrown. | ||
fixture.componentInstance.openCustom(); | ||
snackBar = await loader.getHarness(snackBarHarness); | ||
await expectAsyncError(() => snackBar.hasAction(), /custom content/); | ||
}); | ||
|
||
it('should be able to dismiss simple snack-bar with action', async () => { | ||
const snackBarRef = fixture.componentInstance.openSimple('With action', 'Unsubscribe'); | ||
let snackBar = await loader.getHarness(snackBarHarness); | ||
let actionCount = 0; | ||
snackBarRef.onAction().subscribe(() => actionCount++); | ||
|
||
await snackBar.dismissWithAction(); | ||
expect(actionCount).toBe(1); | ||
|
||
fixture.componentInstance.openSimple('No action'); | ||
snackBar = await loader.getHarness(snackBarHarness); | ||
await expectAsyncError(() => snackBar.dismissWithAction(), /without action/); | ||
}); | ||
} | ||
|
||
/** | ||
* Expects the asynchronous function to throw an error that matches | ||
* the specified expectation. | ||
*/ | ||
async function expectAsyncError(fn: () => Promise<any>, expectation: RegExp) { | ||
let error: string|null = null; | ||
try { | ||
await fn(); | ||
} catch (e) { | ||
error = e.toString(); | ||
} | ||
expect(error).not.toBe(null); | ||
expect(error!).toMatch(expectation); | ||
} | ||
|
||
@Component({ | ||
template: ` | ||
<ng-template> | ||
My custom snack-bar. | ||
</ng-template> | ||
` | ||
}) | ||
class SnackbarHarnessTest { | ||
@ViewChild(TemplateRef, {static: false}) customTmpl: TemplateRef<any>; | ||
|
||
constructor(readonly snackBar: MatSnackBar) {} | ||
|
||
openSimple(message: string, action = '', config?: MatSnackBarConfig) { | ||
return this.snackBar.open(message, action, config); | ||
} | ||
|
||
openCustom(config?: MatSnackBarConfig) { | ||
return this.snackBar.openFromTemplate(this.customTmpl, config); | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
src/material-experimental/mdc-snack-bar/harness/snack-bar-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,95 @@ | ||
/** | ||
* @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} from '@angular/cdk-experimental/testing'; | ||
|
||
/** | ||
* Harness for interacting with a standard mat-snack-bar in tests. | ||
* @dynamic | ||
*/ | ||
export class MatSnackBarHarness extends ComponentHarness { | ||
// Developers can provide a custom component or template for the | ||
// snackbar. The canonical snack-bar parent is the "MatSnackBarContainer". | ||
static hostSelector = '.mat-snack-bar-container'; | ||
|
||
private _simpleSnackBar = this.locatorForOptional('.mat-simple-snackbar'); | ||
private _simpleSnackBarMessage = this.locatorFor('.mat-simple-snackbar > span'); | ||
private _simpleSnackBarActionButton = | ||
this.locatorForOptional('.mat-simple-snackbar-action > button'); | ||
|
||
/** | ||
* Gets the role of the snack-bar. The role of a snack-bar is determined based | ||
* on the ARIA politeness specified in the snack-bar config. | ||
*/ | ||
async getRole(): Promise<'alert'|'status'|null> { | ||
return (await this.host()).getAttribute('role') as Promise<'alert'|'status'|null>; | ||
} | ||
|
||
/** | ||
* Gets whether the snack-bar has an action. Method cannot be | ||
* used for snack-bar's with custom content. | ||
*/ | ||
async hasAction(): Promise<boolean> { | ||
await this._assertSimpleSnackBar(); | ||
return (await this._simpleSnackBarActionButton()) !== null; | ||
} | ||
|
||
/** | ||
* Gets the description of the snack-bar. Method cannot be | ||
* used for snack-bar's without action or with custom content. | ||
*/ | ||
async getActionDescription(): Promise<string> { | ||
await this._assertSimpleSnackBarWithAction(); | ||
return (await this._simpleSnackBarActionButton())!.text(); | ||
} | ||
|
||
|
||
/** | ||
* Dismisses the snack-bar by clicking the action button. Method cannot | ||
* be used for snack-bar's without action or with custom content. | ||
*/ | ||
async dismissWithAction(): Promise<void> { | ||
await this._assertSimpleSnackBarWithAction(); | ||
await (await this._simpleSnackBarActionButton())!.click(); | ||
} | ||
|
||
/** | ||
* Gets the message of the snack-bar. Method cannot be used for | ||
* snack-bar's with custom content. | ||
*/ | ||
async getMessage(): Promise<string> { | ||
await this._assertSimpleSnackBar(); | ||
return (await this._simpleSnackBarMessage()).text(); | ||
} | ||
|
||
/** | ||
* Asserts that the current snack-bar does not use custom content. Throws if | ||
* custom content is used. | ||
*/ | ||
private async _assertSimpleSnackBar(): Promise<void> { | ||
if (!await this._isSimpleSnackBar()) { | ||
throw new Error('Method cannot be used for snack-bar with custom content.'); | ||
} | ||
} | ||
|
||
/** | ||
* Asserts that the current snack-bar does not use custom content and has | ||
* an action defined. Otherwise an error will be thrown. | ||
*/ | ||
private async _assertSimpleSnackBarWithAction(): Promise<void> { | ||
await this._assertSimpleSnackBar(); | ||
if (!await this.hasAction()) { | ||
throw new Error('Method cannot be used for standard snack-bar without action.'); | ||
} | ||
} | ||
|
||
/** Gets whether the snack-bar is using the default content template. */ | ||
private async _isSimpleSnackBar(): Promise<boolean> { | ||
return await this._simpleSnackBar() !== null; | ||
} | ||
} |