1
+ import { FocusMonitor , FocusOrigin } from '@angular/cdk/a11y' ;
1
2
import {
2
3
ComponentFixture ,
3
4
fakeAsync ,
@@ -28,7 +29,11 @@ import {MatDialogContainer} from './dialog-container';
28
29
import { OverlayContainer , ScrollStrategy , Overlay } from '@angular/cdk/overlay' ;
29
30
import { ScrollDispatcher } from '@angular/cdk/scrolling' ;
30
31
import { A , ESCAPE } from '@angular/cdk/keycodes' ;
31
- import { dispatchKeyboardEvent , createKeyboardEvent } from '@angular/cdk/testing/private' ;
32
+ import {
33
+ dispatchKeyboardEvent ,
34
+ createKeyboardEvent ,
35
+ patchElementFocus , dispatchMouseEvent
36
+ } from '@angular/cdk/testing/private' ;
32
37
import {
33
38
MAT_DIALOG_DATA ,
34
39
MatDialog ,
@@ -39,12 +44,12 @@ import {
39
44
} from './index' ;
40
45
import { Subject } from 'rxjs' ;
41
46
42
-
43
47
describe ( 'MatDialog' , ( ) => {
44
48
let dialog : MatDialog ;
45
49
let overlayContainer : OverlayContainer ;
46
50
let overlayContainerElement : HTMLElement ;
47
51
let scrolledSubject = new Subject ( ) ;
52
+ let focusMonitor : FocusMonitor ;
48
53
49
54
let testViewContainerRef : ViewContainerRef ;
50
55
let viewContainerFixture : ComponentFixture < ComponentWithChildViewContainer > ;
@@ -64,13 +69,14 @@ describe('MatDialog', () => {
64
69
TestBed . compileComponents ( ) ;
65
70
} ) ) ;
66
71
67
- beforeEach ( inject ( [ MatDialog , Location , OverlayContainer ] ,
68
- ( d : MatDialog , l : Location , oc : OverlayContainer ) => {
72
+ beforeEach ( inject ( [ MatDialog , Location , OverlayContainer , FocusMonitor ] ,
73
+ ( d : MatDialog , l : Location , oc : OverlayContainer , fm : FocusMonitor ) => {
69
74
dialog = d ;
70
75
mockLocation = l as SpyLocation ;
71
76
overlayContainer = oc ;
72
77
overlayContainerElement = oc . getContainerElement ( ) ;
73
- } ) ) ;
78
+ focusMonitor = fm ;
79
+ } ) ) ;
74
80
75
81
afterEach ( ( ) => {
76
82
overlayContainer . ngOnDestroy ( ) ;
@@ -1090,6 +1096,148 @@ describe('MatDialog', () => {
1090
1096
document . body . removeChild ( button ) ;
1091
1097
} ) ) ;
1092
1098
1099
+ it ( 'should re-focus the trigger via keyboard when closed via escape key' , fakeAsync ( ( ) => {
1100
+ const button = document . createElement ( 'button' ) ;
1101
+ let lastFocusOrigin : FocusOrigin = null ;
1102
+
1103
+ focusMonitor . monitor ( button , false )
1104
+ . subscribe ( focusOrigin => lastFocusOrigin = focusOrigin ) ;
1105
+
1106
+ document . body . appendChild ( button ) ;
1107
+ button . focus ( ) ;
1108
+
1109
+ // Patch the element focus after the initial and real focus, because otherwise the
1110
+ // `activeElement` won't be set, and the dialog won't be able to restore focus to an element.
1111
+ patchElementFocus ( button ) ;
1112
+
1113
+ dialog . open ( PizzaMsg , { viewContainerRef : testViewContainerRef } ) ;
1114
+
1115
+ tick ( 500 ) ;
1116
+ viewContainerFixture . detectChanges ( ) ;
1117
+
1118
+ expect ( lastFocusOrigin ! ) . toBeNull ( 'Expected the trigger button to be blurred' ) ;
1119
+
1120
+ dispatchKeyboardEvent ( document . body , 'keydown' , ESCAPE ) ;
1121
+
1122
+ flushMicrotasks ( ) ;
1123
+ viewContainerFixture . detectChanges ( ) ;
1124
+ tick ( 500 ) ;
1125
+
1126
+ expect ( lastFocusOrigin ! )
1127
+ . toBe ( 'keyboard' , 'Expected the trigger button to be focused via keyboard' ) ;
1128
+
1129
+ focusMonitor . stopMonitoring ( button ) ;
1130
+ document . body . removeChild ( button ) ;
1131
+ } ) ) ;
1132
+
1133
+ it ( 'should re-focus the trigger via mouse when backdrop has been clicked' , fakeAsync ( ( ) => {
1134
+ const button = document . createElement ( 'button' ) ;
1135
+ let lastFocusOrigin : FocusOrigin = null ;
1136
+
1137
+ focusMonitor . monitor ( button , false )
1138
+ . subscribe ( focusOrigin => lastFocusOrigin = focusOrigin ) ;
1139
+
1140
+ document . body . appendChild ( button ) ;
1141
+ button . focus ( ) ;
1142
+
1143
+ // Patch the element focus after the initial and real focus, because otherwise the
1144
+ // `activeElement` won't be set, and the dialog won't be able to restore focus to an element.
1145
+ patchElementFocus ( button ) ;
1146
+
1147
+ dialog . open ( PizzaMsg , { viewContainerRef : testViewContainerRef } ) ;
1148
+
1149
+ tick ( 500 ) ;
1150
+ viewContainerFixture . detectChanges ( ) ;
1151
+
1152
+ const backdrop = overlayContainerElement
1153
+ . querySelector ( '.cdk-overlay-backdrop' ) as HTMLElement ;
1154
+
1155
+ backdrop . click ( ) ;
1156
+ viewContainerFixture . detectChanges ( ) ;
1157
+ tick ( 500 ) ;
1158
+
1159
+ expect ( lastFocusOrigin ! )
1160
+ . toBe ( 'mouse' , 'Expected the trigger button to be focused via mouse' ) ;
1161
+
1162
+ focusMonitor . stopMonitoring ( button ) ;
1163
+ document . body . removeChild ( button ) ;
1164
+ } ) ) ;
1165
+
1166
+ it ( 'should re-focus via keyboard if the close button has been triggered through keyboard' ,
1167
+ fakeAsync ( ( ) => {
1168
+
1169
+ const button = document . createElement ( 'button' ) ;
1170
+ let lastFocusOrigin : FocusOrigin = null ;
1171
+
1172
+ focusMonitor . monitor ( button , false )
1173
+ . subscribe ( focusOrigin => lastFocusOrigin = focusOrigin ) ;
1174
+
1175
+ document . body . appendChild ( button ) ;
1176
+ button . focus ( ) ;
1177
+
1178
+ // Patch the element focus after the initial and real focus, because otherwise the
1179
+ // `activeElement` won't be set, and the dialog won't be able to restore focus to an element.
1180
+ patchElementFocus ( button ) ;
1181
+
1182
+ dialog . open ( ContentElementDialog , { viewContainerRef : testViewContainerRef } ) ;
1183
+
1184
+ tick ( 500 ) ;
1185
+ viewContainerFixture . detectChanges ( ) ;
1186
+
1187
+ const closeButton = overlayContainerElement
1188
+ . querySelector ( 'button[mat-dialog-close]' ) as HTMLElement ;
1189
+
1190
+ // Fake the behavior of pressing the SPACE key on a button element. Browsers fire a `click`
1191
+ // event with a MouseEvent, which has coordinates that are out of the element boundaries.
1192
+ dispatchMouseEvent ( closeButton , 'click' , 0 , 0 ) ;
1193
+
1194
+ viewContainerFixture . detectChanges ( ) ;
1195
+ tick ( 500 ) ;
1196
+
1197
+ expect ( lastFocusOrigin ! )
1198
+ . toBe ( 'keyboard' , 'Expected the trigger button to be focused via keyboard' ) ;
1199
+
1200
+ focusMonitor . stopMonitoring ( button ) ;
1201
+ document . body . removeChild ( button ) ;
1202
+ } ) ) ;
1203
+
1204
+ it ( 'should re-focus via mouse if the close button has been clicked' , fakeAsync ( ( ) => {
1205
+ const button = document . createElement ( 'button' ) ;
1206
+ let lastFocusOrigin : FocusOrigin = null ;
1207
+
1208
+ focusMonitor . monitor ( button , false )
1209
+ . subscribe ( focusOrigin => lastFocusOrigin = focusOrigin ) ;
1210
+
1211
+ document . body . appendChild ( button ) ;
1212
+ button . focus ( ) ;
1213
+
1214
+ // Patch the element focus after the initial and real focus, because otherwise the
1215
+ // `activeElement` won't be set, and the dialog won't be able to restore focus to an element.
1216
+ patchElementFocus ( button ) ;
1217
+
1218
+ dialog . open ( ContentElementDialog , { viewContainerRef : testViewContainerRef } ) ;
1219
+
1220
+ tick ( 500 ) ;
1221
+ viewContainerFixture . detectChanges ( ) ;
1222
+
1223
+ const closeButton = overlayContainerElement
1224
+ . querySelector ( 'button[mat-dialog-close]' ) as HTMLElement ;
1225
+
1226
+ // The dialog close button detects the focus origin by inspecting the click event. If
1227
+ // coordinates of the click are not present, it assumes that the click has been triggered
1228
+ // by keyboard.
1229
+ dispatchMouseEvent ( closeButton , 'click' , 10 , 10 ) ;
1230
+
1231
+ viewContainerFixture . detectChanges ( ) ;
1232
+ tick ( 500 ) ;
1233
+
1234
+ expect ( lastFocusOrigin ! )
1235
+ . toBe ( 'mouse' , 'Expected the trigger button to be focused via mouse' ) ;
1236
+
1237
+ focusMonitor . stopMonitoring ( button ) ;
1238
+ document . body . removeChild ( button ) ;
1239
+ } ) ) ;
1240
+
1093
1241
it ( 'should allow the consumer to shift focus in afterClosed' , fakeAsync ( ( ) => {
1094
1242
// Create a element that has focus before the dialog is opened.
1095
1243
let button = document . createElement ( 'button' ) ;
@@ -1112,7 +1260,7 @@ describe('MatDialog', () => {
1112
1260
1113
1261
tick ( 500 ) ;
1114
1262
viewContainerFixture . detectChanges ( ) ;
1115
- flushMicrotasks ( ) ;
1263
+ flush ( ) ;
1116
1264
1117
1265
expect ( document . activeElement ! . id ) . toBe ( 'input-to-be-focused' ,
1118
1266
'Expected that the trigger was refocused after the dialog is closed.' ) ;
0 commit comments