Skip to content

Commit 2aaa9d4

Browse files
authored
test: provide a test component that opens components in a dialog (#24522)
* test: provide a test component that opens components in a dialog * test: update goldens * test: protected static, remove DI, noop animations * test: add typing * test: make generics optional * test: update goldens
1 parent 2fc6b92 commit 2aaa9d4

File tree

9 files changed

+350
-0
lines changed

9 files changed

+350
-0
lines changed

src/material-experimental/mdc-dialog/testing/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ ts_library(
99
exclude = ["**/*.spec.ts"],
1010
),
1111
deps = [
12+
"//src/cdk/overlay",
1213
"//src/cdk/testing",
14+
"//src/material-experimental/mdc-dialog",
1315
"//src/material/dialog/testing",
16+
"@npm//@angular/core",
17+
"@npm//@angular/platform-browser",
1418
],
1519
)
1620

@@ -29,6 +33,7 @@ ng_test_library(
2933
":testing",
3034
"//src/material-experimental/mdc-dialog",
3135
"//src/material/dialog/testing:harness_tests_lib",
36+
"@npm//@angular/platform-browser",
3237
],
3338
)
3439

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {Component, Inject} from '@angular/core';
2+
import {fakeAsync, TestBed, flush} from '@angular/core/testing';
3+
import {
4+
MatTestDialogOpenerModule,
5+
MatTestDialogOpener,
6+
} from '@angular/material-experimental/mdc-dialog/testing';
7+
import {
8+
MAT_DIALOG_DATA,
9+
MatDialogRef,
10+
MatDialogState,
11+
} from '@angular/material-experimental/mdc-dialog';
12+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
13+
14+
describe('MDC-based MatTestDialogOpener', () => {
15+
beforeEach(fakeAsync(() => {
16+
TestBed.configureTestingModule({
17+
imports: [MatTestDialogOpenerModule, NoopAnimationsModule],
18+
declarations: [ExampleComponent],
19+
});
20+
21+
TestBed.compileComponents();
22+
}));
23+
24+
it('should open a dialog when created', fakeAsync(() => {
25+
const fixture = TestBed.createComponent(MatTestDialogOpener.withComponent(ExampleComponent));
26+
flush();
27+
expect(fixture.componentInstance.dialogRef.getState()).toBe(MatDialogState.OPEN);
28+
expect(document.querySelector('mat-dialog-container')).toBeTruthy();
29+
}));
30+
31+
it('should throw an error if no dialog component is provided', () => {
32+
expect(() => TestBed.createComponent(MatTestDialogOpener)).toThrow(
33+
Error('MatTestDialogOpener does not have a component provided.'),
34+
);
35+
});
36+
37+
it('should pass data to the component', fakeAsync(() => {
38+
const config = {data: 'test'};
39+
TestBed.createComponent(MatTestDialogOpener.withComponent(ExampleComponent, config));
40+
flush();
41+
const dialogContainer = document.querySelector('mat-dialog-container');
42+
expect(dialogContainer!.innerHTML).toContain('Data: test');
43+
}));
44+
45+
it('should get closed result data', fakeAsync(() => {
46+
const config = {data: 'test'};
47+
const fixture = TestBed.createComponent(
48+
MatTestDialogOpener.withComponent<ExampleComponent, ExampleDialogResult>(
49+
ExampleComponent,
50+
config,
51+
),
52+
);
53+
flush();
54+
const closeButton = document.querySelector('#close-btn') as HTMLElement;
55+
closeButton.click();
56+
flush();
57+
expect(fixture.componentInstance.closedResult).toEqual({reason: 'closed'});
58+
}));
59+
});
60+
61+
interface ExampleDialogResult {
62+
reason: string;
63+
}
64+
65+
/** Simple component for testing MatTestDialogOpener. */
66+
@Component({
67+
template: `
68+
Data: {{data}}
69+
<button id="close-btn" (click)="close()">Close</button>
70+
`,
71+
})
72+
class ExampleComponent {
73+
constructor(
74+
public dialogRef: MatDialogRef<ExampleComponent, ExampleDialogResult>,
75+
@Inject(MAT_DIALOG_DATA) public data: any,
76+
) {}
77+
78+
close() {
79+
this.dialogRef.close({reason: 'closed'});
80+
}
81+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 {ComponentType} from '@angular/cdk/overlay';
10+
import {ChangeDetectionStrategy, Component, NgModule, ViewEncapsulation} from '@angular/core';
11+
import {_MatTestDialogOpenerBase} from '@angular/material/dialog/testing';
12+
import {
13+
MatDialog,
14+
MatDialogContainer,
15+
MatDialogModule,
16+
MatDialogConfig,
17+
} from '@angular/material-experimental/mdc-dialog';
18+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
19+
20+
/** Test component that immediately opens a dialog when bootstrapped. */
21+
@Component({
22+
selector: 'mat-test-dialog-opener',
23+
template: '',
24+
changeDetection: ChangeDetectionStrategy.OnPush,
25+
encapsulation: ViewEncapsulation.None,
26+
})
27+
export class MatTestDialogOpener<T = unknown, R = unknown> extends _MatTestDialogOpenerBase<
28+
MatDialogContainer,
29+
T,
30+
R
31+
> {
32+
constructor(dialog: MatDialog) {
33+
super(dialog);
34+
}
35+
36+
/** Static method that prepares this class to open the provided component. */
37+
static withComponent<T = unknown, R = unknown>(
38+
component: ComponentType<T>,
39+
config?: MatDialogConfig,
40+
) {
41+
_MatTestDialogOpenerBase.component = component;
42+
_MatTestDialogOpenerBase.config = config;
43+
return MatTestDialogOpener as ComponentType<MatTestDialogOpener<T, R>>;
44+
}
45+
}
46+
47+
@NgModule({
48+
declarations: [MatTestDialogOpener],
49+
imports: [MatDialogModule, NoopAnimationsModule],
50+
})
51+
export class MatTestDialogOpenerModule {}

src/material-experimental/mdc-dialog/testing/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88

99
export {DialogHarnessFilters} from '@angular/material/dialog/testing';
1010
export {MatDialogHarness, MatDialogSection} from './dialog-harness';
11+
export * from './dialog-opener';

src/material/dialog/testing/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ ts_library(
1010
),
1111
deps = [
1212
"//src/cdk/coercion",
13+
"//src/cdk/overlay",
1314
"//src/cdk/testing",
1415
"//src/material/dialog",
16+
"@npm//@angular/core",
17+
"@npm//@angular/platform-browser",
18+
"@npm//rxjs",
1519
],
1620
)
1721

@@ -43,6 +47,7 @@ ng_test_library(
4347
":harness_tests_lib",
4448
":testing",
4549
"//src/material/dialog",
50+
"@npm//@angular/platform-browser",
4651
],
4752
)
4853

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {Component, Inject} from '@angular/core';
2+
import {fakeAsync, flush, TestBed} from '@angular/core/testing';
3+
import {MatTestDialogOpenerModule, MatTestDialogOpener} from '@angular/material/dialog/testing';
4+
import {MAT_DIALOG_DATA, MatDialogRef, MatDialogState} from '@angular/material/dialog';
5+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
6+
7+
describe('MDC-based MatTestDialogOpener', () => {
8+
beforeEach(fakeAsync(() => {
9+
TestBed.configureTestingModule({
10+
imports: [MatTestDialogOpenerModule, NoopAnimationsModule],
11+
declarations: [ExampleComponent],
12+
});
13+
14+
TestBed.compileComponents();
15+
}));
16+
17+
it('should open a dialog when created', fakeAsync(() => {
18+
const fixture = TestBed.createComponent(MatTestDialogOpener.withComponent(ExampleComponent));
19+
flush();
20+
expect(fixture.componentInstance.dialogRef.getState()).toBe(MatDialogState.OPEN);
21+
expect(document.querySelector('mat-dialog-container')).toBeTruthy();
22+
}));
23+
24+
it('should throw an error if no dialog component is provided', () => {
25+
expect(() => TestBed.createComponent(MatTestDialogOpener)).toThrow(
26+
Error('MatTestDialogOpener does not have a component provided.'),
27+
);
28+
});
29+
30+
it('should pass data to the component', fakeAsync(() => {
31+
const config = {data: 'test'};
32+
TestBed.createComponent(MatTestDialogOpener.withComponent(ExampleComponent, config));
33+
flush();
34+
const dialogContainer = document.querySelector('mat-dialog-container');
35+
expect(dialogContainer!.innerHTML).toContain('Data: test');
36+
}));
37+
38+
it('should get closed result data', fakeAsync(() => {
39+
const config = {data: 'test'};
40+
const fixture = TestBed.createComponent(
41+
MatTestDialogOpener.withComponent<ExampleComponent, ExampleDialogResult>(
42+
ExampleComponent,
43+
config,
44+
),
45+
);
46+
flush();
47+
const closeButton = document.querySelector('#close-btn') as HTMLElement;
48+
closeButton.click();
49+
flush();
50+
expect(fixture.componentInstance.closedResult).toEqual({reason: 'closed'});
51+
}));
52+
});
53+
54+
interface ExampleDialogResult {
55+
reason: string;
56+
}
57+
58+
/** Simple component for testing MatTestDialogOpener. */
59+
@Component({
60+
template: `
61+
Data: {{data}}
62+
<button id="close-btn" (click)="close()">Close</button>
63+
`,
64+
})
65+
class ExampleComponent {
66+
constructor(
67+
public dialogRef: MatDialogRef<ExampleComponent, ExampleDialogResult>,
68+
@Inject(MAT_DIALOG_DATA) public data: any,
69+
) {}
70+
71+
close() {
72+
this.dialogRef.close({reason: 'closed'});
73+
}
74+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 {ComponentType} from '@angular/cdk/overlay';
10+
import {
11+
ChangeDetectionStrategy,
12+
Directive,
13+
Component,
14+
NgModule,
15+
OnDestroy,
16+
ViewEncapsulation,
17+
} from '@angular/core';
18+
import {
19+
_MatDialogBase,
20+
_MatDialogContainerBase,
21+
MatDialog,
22+
MatDialogConfig,
23+
MatDialogContainer,
24+
MatDialogModule,
25+
MatDialogRef,
26+
} from '@angular/material/dialog';
27+
import {Subscription} from 'rxjs';
28+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
29+
30+
/** Base class for a component that immediately opens a dialog when created. */
31+
@Directive()
32+
export class _MatTestDialogOpenerBase<C extends _MatDialogContainerBase, T, R>
33+
implements OnDestroy
34+
{
35+
/** Component that should be opened with the MatDialog `open` method. */
36+
protected static component: ComponentType<unknown> | undefined;
37+
38+
/** Config that should be provided to the MatDialog `open` method. */
39+
protected static config: MatDialogConfig | undefined;
40+
41+
/** MatDialogRef returned from the MatDialog `open` method. */
42+
dialogRef: MatDialogRef<T, R>;
43+
44+
/** Data passed to the `MatDialog` close method. */
45+
closedResult: R | undefined;
46+
47+
private readonly _afterClosedSubscription: Subscription;
48+
49+
constructor(public dialog: _MatDialogBase<C>) {
50+
if (!_MatTestDialogOpenerBase.component) {
51+
throw new Error(`MatTestDialogOpener does not have a component provided.`);
52+
}
53+
54+
this.dialogRef = this.dialog.open<T, R>(
55+
_MatTestDialogOpenerBase.component as ComponentType<T>,
56+
_MatTestDialogOpenerBase.config || {},
57+
);
58+
this._afterClosedSubscription = this.dialogRef.afterClosed().subscribe(result => {
59+
this.closedResult = result;
60+
});
61+
}
62+
63+
ngOnDestroy() {
64+
this._afterClosedSubscription.unsubscribe();
65+
_MatTestDialogOpenerBase.component = undefined;
66+
_MatTestDialogOpenerBase.config = undefined;
67+
}
68+
}
69+
70+
/** Test component that immediately opens a dialog when created. */
71+
@Component({
72+
selector: 'mat-test-dialog-opener',
73+
template: '',
74+
changeDetection: ChangeDetectionStrategy.OnPush,
75+
encapsulation: ViewEncapsulation.None,
76+
})
77+
export class MatTestDialogOpener<T = unknown, R = unknown> extends _MatTestDialogOpenerBase<
78+
MatDialogContainer,
79+
T,
80+
R
81+
> {
82+
constructor(dialog: MatDialog) {
83+
super(dialog);
84+
}
85+
86+
/** Static method that prepares this class to open the provided component. */
87+
static withComponent<T = unknown, R = unknown>(
88+
component: ComponentType<T>,
89+
config?: MatDialogConfig,
90+
) {
91+
_MatTestDialogOpenerBase.component = component;
92+
_MatTestDialogOpenerBase.config = config;
93+
return MatTestDialogOpener as ComponentType<MatTestDialogOpener<T, R>>;
94+
}
95+
}
96+
97+
@NgModule({
98+
declarations: [MatTestDialogOpener],
99+
imports: [MatDialogModule, NoopAnimationsModule],
100+
})
101+
export class MatTestDialogOpenerModule {}

src/material/dialog/testing/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88

99
export * from './dialog-harness';
1010
export * from './dialog-harness-filters';
11+
export * from './dialog-opener';

0 commit comments

Comments
 (0)