1
+ import { FocusMonitor , FocusOrigin } from '@angular/cdk/a11y' ;
1
2
import {
2
3
ComponentFixture ,
3
4
fakeAsync ,
@@ -31,7 +32,9 @@ import {A, ESCAPE} from '@angular/cdk/keycodes';
31
32
import {
32
33
dispatchKeyboardEvent ,
33
34
createKeyboardEvent ,
34
- dispatchEvent
35
+ dispatchEvent ,
36
+ patchElementFocus ,
37
+ dispatchMouseEvent
35
38
} from '@angular/cdk/testing/private' ;
36
39
import {
37
40
MAT_DIALOG_DATA ,
@@ -43,12 +46,12 @@ import {
43
46
} from './index' ;
44
47
import { Subject } from 'rxjs' ;
45
48
46
-
47
49
describe ( 'MatDialog' , ( ) => {
48
50
let dialog : MatDialog ;
49
51
let overlayContainer : OverlayContainer ;
50
52
let overlayContainerElement : HTMLElement ;
51
53
let scrolledSubject = new Subject ( ) ;
54
+ let focusMonitor : FocusMonitor ;
52
55
53
56
let testViewContainerRef : ViewContainerRef ;
54
57
let viewContainerFixture : ComponentFixture < ComponentWithChildViewContainer > ;
@@ -68,13 +71,14 @@ describe('MatDialog', () => {
68
71
TestBed . compileComponents ( ) ;
69
72
} ) ) ;
70
73
71
- beforeEach ( inject ( [ MatDialog , Location , OverlayContainer ] ,
72
- ( d : MatDialog , l : Location , oc : OverlayContainer ) => {
74
+ beforeEach ( inject ( [ MatDialog , Location , OverlayContainer , FocusMonitor ] ,
75
+ ( d : MatDialog , l : Location , oc : OverlayContainer , fm : FocusMonitor ) => {
73
76
dialog = d ;
74
77
mockLocation = l as SpyLocation ;
75
78
overlayContainer = oc ;
76
79
overlayContainerElement = oc . getContainerElement ( ) ;
77
- } ) ) ;
80
+ focusMonitor = fm ;
81
+ } ) ) ;
78
82
79
83
afterEach ( ( ) => {
80
84
overlayContainer . ngOnDestroy ( ) ;
@@ -1145,6 +1149,148 @@ describe('MatDialog', () => {
1145
1149
document . body . removeChild ( button ) ;
1146
1150
} ) ) ;
1147
1151
1152
+ it ( 'should re-focus the trigger via keyboard when closed via escape key' , fakeAsync ( ( ) => {
1153
+ const button = document . createElement ( 'button' ) ;
1154
+ let lastFocusOrigin : FocusOrigin = null ;
1155
+
1156
+ focusMonitor . monitor ( button , false )
1157
+ . subscribe ( focusOrigin => lastFocusOrigin = focusOrigin ) ;
1158
+
1159
+ document . body . appendChild ( button ) ;
1160
+ button . focus ( ) ;
1161
+
1162
+ // Patch the element focus after the initial and real focus, because otherwise the
1163
+ // `activeElement` won't be set, and the dialog won't be able to restore focus to an element.
1164
+ patchElementFocus ( button ) ;
1165
+
1166
+ dialog . open ( PizzaMsg , { viewContainerRef : testViewContainerRef } ) ;
1167
+
1168
+ tick ( 500 ) ;
1169
+ viewContainerFixture . detectChanges ( ) ;
1170
+
1171
+ expect ( lastFocusOrigin ! ) . toBeNull ( 'Expected the trigger button to be blurred' ) ;
1172
+
1173
+ dispatchKeyboardEvent ( document . body , 'keydown' , ESCAPE ) ;
1174
+
1175
+ flushMicrotasks ( ) ;
1176
+ viewContainerFixture . detectChanges ( ) ;
1177
+ tick ( 500 ) ;
1178
+
1179
+ expect ( lastFocusOrigin ! )
1180
+ . toBe ( 'keyboard' , 'Expected the trigger button to be focused via keyboard' ) ;
1181
+
1182
+ focusMonitor . stopMonitoring ( button ) ;
1183
+ document . body . removeChild ( button ) ;
1184
+ } ) ) ;
1185
+
1186
+ it ( 'should re-focus the trigger via mouse when backdrop has been clicked' , fakeAsync ( ( ) => {
1187
+ const button = document . createElement ( 'button' ) ;
1188
+ let lastFocusOrigin : FocusOrigin = null ;
1189
+
1190
+ focusMonitor . monitor ( button , false )
1191
+ . subscribe ( focusOrigin => lastFocusOrigin = focusOrigin ) ;
1192
+
1193
+ document . body . appendChild ( button ) ;
1194
+ button . focus ( ) ;
1195
+
1196
+ // Patch the element focus after the initial and real focus, because otherwise the
1197
+ // `activeElement` won't be set, and the dialog won't be able to restore focus to an element.
1198
+ patchElementFocus ( button ) ;
1199
+
1200
+ dialog . open ( PizzaMsg , { viewContainerRef : testViewContainerRef } ) ;
1201
+
1202
+ tick ( 500 ) ;
1203
+ viewContainerFixture . detectChanges ( ) ;
1204
+
1205
+ const backdrop = overlayContainerElement
1206
+ . querySelector ( '.cdk-overlay-backdrop' ) as HTMLElement ;
1207
+
1208
+ backdrop . click ( ) ;
1209
+ viewContainerFixture . detectChanges ( ) ;
1210
+ tick ( 500 ) ;
1211
+
1212
+ expect ( lastFocusOrigin ! )
1213
+ . toBe ( 'mouse' , 'Expected the trigger button to be focused via mouse' ) ;
1214
+
1215
+ focusMonitor . stopMonitoring ( button ) ;
1216
+ document . body . removeChild ( button ) ;
1217
+ } ) ) ;
1218
+
1219
+ it ( 'should re-focus via keyboard if the close button has been triggered through keyboard' ,
1220
+ fakeAsync ( ( ) => {
1221
+
1222
+ const button = document . createElement ( 'button' ) ;
1223
+ let lastFocusOrigin : FocusOrigin = null ;
1224
+
1225
+ focusMonitor . monitor ( button , false )
1226
+ . subscribe ( focusOrigin => lastFocusOrigin = focusOrigin ) ;
1227
+
1228
+ document . body . appendChild ( button ) ;
1229
+ button . focus ( ) ;
1230
+
1231
+ // Patch the element focus after the initial and real focus, because otherwise the
1232
+ // `activeElement` won't be set, and the dialog won't be able to restore focus to an element.
1233
+ patchElementFocus ( button ) ;
1234
+
1235
+ dialog . open ( ContentElementDialog , { viewContainerRef : testViewContainerRef } ) ;
1236
+
1237
+ tick ( 500 ) ;
1238
+ viewContainerFixture . detectChanges ( ) ;
1239
+
1240
+ const closeButton = overlayContainerElement
1241
+ . querySelector ( 'button[mat-dialog-close]' ) as HTMLElement ;
1242
+
1243
+ // Fake the behavior of pressing the SPACE key on a button element. Browsers fire a `click`
1244
+ // event with a MouseEvent, which has coordinates that are out of the element boundaries.
1245
+ dispatchMouseEvent ( closeButton , 'click' , 0 , 0 ) ;
1246
+
1247
+ viewContainerFixture . detectChanges ( ) ;
1248
+ tick ( 500 ) ;
1249
+
1250
+ expect ( lastFocusOrigin ! )
1251
+ . toBe ( 'keyboard' , 'Expected the trigger button to be focused via keyboard' ) ;
1252
+
1253
+ focusMonitor . stopMonitoring ( button ) ;
1254
+ document . body . removeChild ( button ) ;
1255
+ } ) ) ;
1256
+
1257
+ it ( 'should re-focus via mouse if the close button has been clicked' , fakeAsync ( ( ) => {
1258
+ const button = document . createElement ( 'button' ) ;
1259
+ let lastFocusOrigin : FocusOrigin = null ;
1260
+
1261
+ focusMonitor . monitor ( button , false )
1262
+ . subscribe ( focusOrigin => lastFocusOrigin = focusOrigin ) ;
1263
+
1264
+ document . body . appendChild ( button ) ;
1265
+ button . focus ( ) ;
1266
+
1267
+ // Patch the element focus after the initial and real focus, because otherwise the
1268
+ // `activeElement` won't be set, and the dialog won't be able to restore focus to an element.
1269
+ patchElementFocus ( button ) ;
1270
+
1271
+ dialog . open ( ContentElementDialog , { viewContainerRef : testViewContainerRef } ) ;
1272
+
1273
+ tick ( 500 ) ;
1274
+ viewContainerFixture . detectChanges ( ) ;
1275
+
1276
+ const closeButton = overlayContainerElement
1277
+ . querySelector ( 'button[mat-dialog-close]' ) as HTMLElement ;
1278
+
1279
+ // The dialog close button detects the focus origin by inspecting the click event. If
1280
+ // coordinates of the click are not present, it assumes that the click has been triggered
1281
+ // by keyboard.
1282
+ dispatchMouseEvent ( closeButton , 'click' , 10 , 10 ) ;
1283
+
1284
+ viewContainerFixture . detectChanges ( ) ;
1285
+ tick ( 500 ) ;
1286
+
1287
+ expect ( lastFocusOrigin ! )
1288
+ . toBe ( 'mouse' , 'Expected the trigger button to be focused via mouse' ) ;
1289
+
1290
+ focusMonitor . stopMonitoring ( button ) ;
1291
+ document . body . removeChild ( button ) ;
1292
+ } ) ) ;
1293
+
1148
1294
it ( 'should allow the consumer to shift focus in afterClosed' , fakeAsync ( ( ) => {
1149
1295
// Create a element that has focus before the dialog is opened.
1150
1296
let button = document . createElement ( 'button' ) ;
@@ -1167,7 +1313,7 @@ describe('MatDialog', () => {
1167
1313
1168
1314
tick ( 500 ) ;
1169
1315
viewContainerFixture . detectChanges ( ) ;
1170
- flushMicrotasks ( ) ;
1316
+ flush ( ) ;
1171
1317
1172
1318
expect ( document . activeElement ! . id ) . toBe ( 'input-to-be-focused' ,
1173
1319
'Expected that the trigger was refocused after the dialog is closed.' ) ;
0 commit comments