Skip to content

Commit 6a732d3

Browse files
devversionmmalerba
authored andcommitted
feat(material/snack-bar): add test harness for snack-bar (#17127)
* feat(material/snack-bar): add test harness for snack-bar * Address feedback
1 parent a2ccacd commit 6a732d3

File tree

11 files changed

+379
-5
lines changed

11 files changed

+379
-5
lines changed

src/cdk/private/testing/BUILD.bazel

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package(default_visibility = ["//visibility:public"])
22

3-
load("//tools:defaults.bzl", "ts_library")
3+
load("//tools:defaults.bzl", "ng_test_library")
44

5-
ts_library(
5+
ng_test_library(
66
name = "testing",
77
srcs = glob(
88
["**/*.ts"],
99
exclude = ["**/*.spec.ts"],
1010
),
1111
module_name = "@angular/cdk/private/testing",
12-
deps = [
13-
"@npm//@angular/core",
14-
],
1512
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
/**
10+
* Expects the asynchronous function to throw an error that matches
11+
* the specified expectation.
12+
*/
13+
export async function expectAsyncError(fn: () => Promise<any>, expectation: RegExp) {
14+
let error: string|null = null;
15+
try {
16+
await fn();
17+
} catch (e) {
18+
error = e.toString();
19+
}
20+
expect(error).not.toBe(null);
21+
expect(error!).toMatch(expectation, 'Expected error to be thrown.');
22+
}

src/cdk/private/testing/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
export * from './expect-async-error';
910
export * from './wrapped-error-message';
1011
export * from './mock-ng-zone';
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("//tools:defaults.bzl", "ng_module", "ng_test_library", "ng_web_test_suite")
4+
5+
ng_module(
6+
name = "testing",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
module_name = "@angular/material/snack-bar/testing",
12+
deps = [
13+
"//src/cdk/testing",
14+
],
15+
)
16+
17+
ng_test_library(
18+
name = "harness_tests_lib",
19+
srcs = ["shared.spec.ts"],
20+
deps = [
21+
":testing",
22+
"//src/cdk/overlay",
23+
"//src/cdk/private/testing",
24+
"//src/cdk/testing",
25+
"//src/cdk/testing/testbed",
26+
"//src/material/snack-bar",
27+
"@npm//@angular/platform-browser",
28+
],
29+
)
30+
31+
ng_test_library(
32+
name = "unit_tests_lib",
33+
srcs = glob(
34+
["**/*.spec.ts"],
35+
exclude = ["shared.spec.ts"],
36+
),
37+
deps = [
38+
":harness_tests_lib",
39+
":testing",
40+
"//src/material/snack-bar",
41+
],
42+
)
43+
44+
ng_web_test_suite(
45+
name = "unit_tests",
46+
deps = [":unit_tests_lib"],
47+
)
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 './snack-bar-harness';
10+
export * from './snack-bar-harness-filters';
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import {OverlayContainer} from '@angular/cdk/overlay';
2+
import {expectAsyncError} from '@angular/cdk/private/testing';
3+
import {HarnessLoader} from '@angular/cdk/testing';
4+
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
5+
import {Component, TemplateRef, ViewChild} from '@angular/core';
6+
import {ComponentFixture, inject, TestBed} from '@angular/core/testing';
7+
import {MatSnackBar, MatSnackBarConfig, MatSnackBarModule} from '@angular/material/snack-bar';
8+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
9+
import {MatSnackBarHarness} from './snack-bar-harness';
10+
11+
/**
12+
* Function that can be used to run the shared snack-bar harness tests for either
13+
* the non-MDC or MDC based snack-bar harness.
14+
*/
15+
export function runHarnessTests(
16+
snackBarModule: typeof MatSnackBarModule,
17+
snackBarHarness: typeof MatSnackBarHarness) {
18+
let fixture: ComponentFixture<SnackbarHarnessTest>;
19+
let loader: HarnessLoader;
20+
let overlayContainer: OverlayContainer;
21+
22+
beforeEach(async () => {
23+
await TestBed.configureTestingModule({
24+
imports: [snackBarModule, NoopAnimationsModule],
25+
declarations: [SnackbarHarnessTest],
26+
}).compileComponents();
27+
28+
fixture = TestBed.createComponent(SnackbarHarnessTest);
29+
fixture.detectChanges();
30+
loader = TestbedHarnessEnvironment.documentRootLoader(fixture);
31+
inject([OverlayContainer], (oc: OverlayContainer) => {
32+
overlayContainer = oc;
33+
})();
34+
});
35+
36+
afterEach(() => {
37+
// Angular won't call this for us so we need to do it ourselves to avoid leaks.
38+
overlayContainer.ngOnDestroy();
39+
overlayContainer = null!;
40+
});
41+
42+
it('should load harness for simple snack-bar', async () => {
43+
const snackBarRef = fixture.componentInstance.openSimple('Hello!', '');
44+
let snackBars = await loader.getAllHarnesses(snackBarHarness);
45+
46+
expect(snackBars.length).toBe(1);
47+
48+
snackBarRef.dismiss();
49+
snackBars = await loader.getAllHarnesses(snackBarHarness);
50+
expect(snackBars.length).toBe(0);
51+
});
52+
53+
it('should load harness for custom snack-bar', async () => {
54+
const snackBarRef = fixture.componentInstance.openCustom();
55+
let snackBars = await loader.getAllHarnesses(snackBarHarness);
56+
57+
expect(snackBars.length).toBe(1);
58+
59+
snackBarRef.dismiss();
60+
snackBars = await loader.getAllHarnesses(snackBarHarness);
61+
expect(snackBars.length).toBe(0);
62+
});
63+
64+
it('should load snack-bar harness by selector', async () => {
65+
fixture.componentInstance.openSimple('Hello!', '', {panelClass: 'my-snack-bar'});
66+
const snackBars = await loader.getAllHarnesses(snackBarHarness.with({
67+
selector: '.my-snack-bar'
68+
}));
69+
expect(snackBars.length).toBe(1);
70+
});
71+
72+
it('should be able to get role of snack-bar', async () => {
73+
fixture.componentInstance.openCustom();
74+
let snackBar = await loader.getHarness(snackBarHarness);
75+
expect(await snackBar.getRole()).toBe('alert');
76+
77+
fixture.componentInstance.openCustom({politeness: 'polite'});
78+
snackBar = await loader.getHarness(snackBarHarness);
79+
expect(await snackBar.getRole()).toBe('status');
80+
81+
fixture.componentInstance.openCustom({politeness: 'off'});
82+
snackBar = await loader.getHarness(snackBarHarness);
83+
expect(await snackBar.getRole()).toBe(null);
84+
});
85+
86+
it('should be able to get message of simple snack-bar', async () => {
87+
fixture.componentInstance.openSimple('Subscribed to newsletter.');
88+
let snackBar = await loader.getHarness(snackBarHarness);
89+
expect(await snackBar.getMessage()).toBe('Subscribed to newsletter.');
90+
91+
// For snack-bar's with custom template, the message cannot be
92+
// retrieved. We expect an error to be thrown.
93+
fixture.componentInstance.openCustom();
94+
snackBar = await loader.getHarness(snackBarHarness);
95+
await expectAsyncError(() => snackBar.getMessage(), /custom content/);
96+
});
97+
98+
it('should be able to get action description of simple snack-bar', async () => {
99+
fixture.componentInstance.openSimple('Hello', 'Unsubscribe');
100+
let snackBar = await loader.getHarness(snackBarHarness);
101+
expect(await snackBar.getActionDescription()).toBe('Unsubscribe');
102+
103+
// For snack-bar's with custom template, the action description
104+
// cannot be retrieved. We expect an error to be thrown.
105+
fixture.componentInstance.openCustom();
106+
snackBar = await loader.getHarness(snackBarHarness);
107+
await expectAsyncError(() => snackBar.getActionDescription(), /custom content/);
108+
});
109+
110+
it('should be able to check whether simple snack-bar has action', async () => {
111+
fixture.componentInstance.openSimple('With action', 'Unsubscribe');
112+
let snackBar = await loader.getHarness(snackBarHarness);
113+
expect(await snackBar.hasAction()).toBe(true);
114+
115+
fixture.componentInstance.openSimple('No action');
116+
snackBar = await loader.getHarness(snackBarHarness);
117+
expect(await snackBar.hasAction()).toBe(false);
118+
119+
// For snack-bar's with custom template, the action cannot
120+
// be found. We expect an error to be thrown.
121+
fixture.componentInstance.openCustom();
122+
snackBar = await loader.getHarness(snackBarHarness);
123+
await expectAsyncError(() => snackBar.hasAction(), /custom content/);
124+
});
125+
126+
it('should be able to dismiss simple snack-bar with action', async () => {
127+
const snackBarRef = fixture.componentInstance.openSimple('With action', 'Unsubscribe');
128+
let snackBar = await loader.getHarness(snackBarHarness);
129+
let actionCount = 0;
130+
snackBarRef.onAction().subscribe(() => actionCount++);
131+
132+
await snackBar.dismissWithAction();
133+
expect(actionCount).toBe(1);
134+
135+
fixture.componentInstance.openSimple('No action');
136+
snackBar = await loader.getHarness(snackBarHarness);
137+
await expectAsyncError(() => snackBar.dismissWithAction(), /without action/);
138+
});
139+
}
140+
141+
@Component({
142+
template: `
143+
<ng-template>
144+
My custom snack-bar.
145+
</ng-template>
146+
`
147+
})
148+
class SnackbarHarnessTest {
149+
@ViewChild(TemplateRef, {static: false}) customTmpl: TemplateRef<any>;
150+
151+
constructor(readonly snackBar: MatSnackBar) {}
152+
153+
openSimple(message: string, action = '', config?: MatSnackBarConfig) {
154+
return this.snackBar.open(message, action, config);
155+
}
156+
157+
openCustom(config?: MatSnackBarConfig) {
158+
return this.snackBar.openFromTemplate(this.customTmpl, config);
159+
}
160+
}
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+
import {BaseHarnessFilters} from '@angular/cdk/testing';
10+
11+
export interface SnackBarHarnessFilters extends BaseHarnessFilters {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {MatSnackBarModule} from '@angular/material/snack-bar';
2+
import {runHarnessTests} from '@angular/material/snack-bar/testing/shared.spec';
3+
import {MatSnackBarHarness} from './snack-bar-harness';
4+
5+
describe('Non-MDC-based MatSnackBarHarness', () => {
6+
runHarnessTests(MatSnackBarModule, MatSnackBarHarness);
7+
});

0 commit comments

Comments
 (0)