@@ -689,6 +689,35 @@ describe('Dialog', () => {
689
689
expect ( dialog . getDialogById ( 'pizza' ) ) . toBe ( dialogRef ) ;
690
690
} ) ;
691
691
692
+ it ( 'should recapture focus to the first tabbable element when clicking on the backdrop' , fakeAsync ( ( ) => {
693
+ // When testing focus, all of the elements must be in the DOM.
694
+ document . body . appendChild ( overlayContainerElement ) ;
695
+
696
+ dialog . open ( PizzaMsg , {
697
+ disableClose : true ,
698
+ viewContainerRef : testViewContainerRef ,
699
+ } ) ;
700
+
701
+ viewContainerFixture . detectChanges ( ) ;
702
+ flushMicrotasks ( ) ;
703
+
704
+ const backdrop = overlayContainerElement . querySelector ( '.cdk-overlay-backdrop' ) as HTMLElement ;
705
+ const input = overlayContainerElement . querySelector ( 'input' ) as HTMLInputElement ;
706
+
707
+ expect ( document . activeElement ) . withContext ( 'Expected input to be focused on open' ) . toBe ( input ) ;
708
+
709
+ input . blur ( ) ; // Programmatic clicks might not move focus so we simulate it.
710
+ backdrop . click ( ) ;
711
+ viewContainerFixture . detectChanges ( ) ;
712
+ flush ( ) ;
713
+
714
+ expect ( document . activeElement )
715
+ . withContext ( 'Expected input to stay focused after click' )
716
+ . toBe ( input ) ;
717
+
718
+ overlayContainerElement . remove ( ) ;
719
+ } ) ) ;
720
+
692
721
describe ( 'disableClose option' , ( ) => {
693
722
it ( 'should prevent closing via clicks on the backdrop' , fakeAsync ( ( ) => {
694
723
dialog . open ( PizzaMsg , {
@@ -778,6 +807,169 @@ describe('Dialog', () => {
778
807
) ;
779
808
} ) ;
780
809
810
+ describe ( 'closePredicate option' , ( ) => {
811
+ function getDialogs ( ) {
812
+ return overlayContainerElement . querySelectorAll ( 'cdk-dialog-container' ) ;
813
+ }
814
+
815
+ it ( 'should determine whether closing via the backdrop is allowed' , fakeAsync ( ( ) => {
816
+ let canClose = false ;
817
+ const closedSpy = jasmine . createSpy ( 'closed spy' ) ;
818
+ const ref = dialog . open ( PizzaMsg , {
819
+ closePredicate : ( ) => canClose ,
820
+ viewContainerRef : testViewContainerRef ,
821
+ } ) ;
822
+
823
+ ref . closed . subscribe ( closedSpy ) ;
824
+ viewContainerFixture . detectChanges ( ) ;
825
+
826
+ expect ( getDialogs ( ) . length ) . toBe ( 1 ) ;
827
+
828
+ let backdrop = overlayContainerElement . querySelector ( '.cdk-overlay-backdrop' ) as HTMLElement ;
829
+ backdrop . click ( ) ;
830
+ viewContainerFixture . detectChanges ( ) ;
831
+ flush ( ) ;
832
+
833
+ expect ( getDialogs ( ) . length ) . toBe ( 1 ) ;
834
+ expect ( closedSpy ) . not . toHaveBeenCalled ( ) ;
835
+
836
+ canClose = true ;
837
+ backdrop . click ( ) ;
838
+ viewContainerFixture . detectChanges ( ) ;
839
+ flush ( ) ;
840
+
841
+ expect ( getDialogs ( ) . length ) . toBe ( 0 ) ;
842
+ expect ( closedSpy ) . toHaveBeenCalledTimes ( 1 ) ;
843
+ } ) ) ;
844
+
845
+ it ( 'should determine whether closing via the escape key is allowed' , fakeAsync ( ( ) => {
846
+ let canClose = false ;
847
+ const closedSpy = jasmine . createSpy ( 'closed spy' ) ;
848
+ const ref = dialog . open ( PizzaMsg , {
849
+ closePredicate : ( ) => canClose ,
850
+ viewContainerRef : testViewContainerRef ,
851
+ } ) ;
852
+
853
+ ref . closed . subscribe ( closedSpy ) ;
854
+ viewContainerFixture . detectChanges ( ) ;
855
+
856
+ expect ( getDialogs ( ) . length ) . toBe ( 1 ) ;
857
+
858
+ dispatchKeyboardEvent ( document . body , 'keydown' , ESCAPE ) ;
859
+ viewContainerFixture . detectChanges ( ) ;
860
+ flush ( ) ;
861
+
862
+ expect ( getDialogs ( ) . length ) . toBe ( 1 ) ;
863
+ expect ( closedSpy ) . not . toHaveBeenCalled ( ) ;
864
+
865
+ canClose = true ;
866
+ dispatchKeyboardEvent ( document . body , 'keydown' , ESCAPE ) ;
867
+ viewContainerFixture . detectChanges ( ) ;
868
+ flush ( ) ;
869
+
870
+ expect ( getDialogs ( ) . length ) . toBe ( 0 ) ;
871
+ expect ( closedSpy ) . toHaveBeenCalledTimes ( 1 ) ;
872
+ } ) ) ;
873
+
874
+ it ( 'should determine whether closing via the `close` method is allowed' , fakeAsync ( ( ) => {
875
+ let canClose = false ;
876
+ const closedSpy = jasmine . createSpy ( 'closed spy' ) ;
877
+ const ref = dialog . open ( PizzaMsg , {
878
+ closePredicate : ( ) => canClose ,
879
+ viewContainerRef : testViewContainerRef ,
880
+ } ) ;
881
+
882
+ ref . closed . subscribe ( closedSpy ) ;
883
+ viewContainerFixture . detectChanges ( ) ;
884
+
885
+ expect ( getDialogs ( ) . length ) . toBe ( 1 ) ;
886
+
887
+ ref . close ( ) ;
888
+ viewContainerFixture . detectChanges ( ) ;
889
+ flush ( ) ;
890
+
891
+ expect ( getDialogs ( ) . length ) . toBe ( 1 ) ;
892
+ expect ( closedSpy ) . not . toHaveBeenCalled ( ) ;
893
+
894
+ canClose = true ;
895
+ ref . close ( 'hello' ) ;
896
+ viewContainerFixture . detectChanges ( ) ;
897
+ flush ( ) ;
898
+
899
+ expect ( getDialogs ( ) . length ) . toBe ( 0 ) ;
900
+ expect ( closedSpy ) . toHaveBeenCalledTimes ( 1 ) ;
901
+ expect ( closedSpy ) . toHaveBeenCalledWith ( 'hello' ) ;
902
+ } ) ) ;
903
+
904
+ it ( 'should not be closed by `closeAll` if not allowed by the predicate' , fakeAsync ( ( ) => {
905
+ let canClose = false ;
906
+ const config = { closePredicate : ( ) => canClose } ;
907
+ const spy = jasmine . createSpy ( 'afterAllClosed spy' ) ;
908
+ dialog . open ( PizzaMsg , config ) ;
909
+ viewContainerFixture . detectChanges ( ) ;
910
+ dialog . open ( PizzaMsg , config ) ;
911
+ viewContainerFixture . detectChanges ( ) ;
912
+ dialog . open ( PizzaMsg , config ) ;
913
+ viewContainerFixture . detectChanges ( ) ;
914
+
915
+ const subscription = dialog . afterAllClosed . subscribe ( spy ) ;
916
+ expect ( getDialogs ( ) . length ) . toBe ( 3 ) ;
917
+ expect ( dialog . openDialogs . length ) . toBe ( 3 ) ;
918
+
919
+ dialog . closeAll ( ) ;
920
+ viewContainerFixture . detectChanges ( ) ;
921
+ flush ( ) ;
922
+
923
+ expect ( getDialogs ( ) . length ) . toBe ( 3 ) ;
924
+ expect ( dialog . openDialogs . length ) . toBe ( 3 ) ;
925
+ expect ( spy ) . not . toHaveBeenCalled ( ) ;
926
+
927
+ canClose = true ;
928
+ dialog . closeAll ( ) ;
929
+ viewContainerFixture . detectChanges ( ) ;
930
+ flush ( ) ;
931
+
932
+ expect ( getDialogs ( ) . length ) . toBe ( 0 ) ;
933
+ expect ( dialog . openDialogs . length ) . toBe ( 0 ) ;
934
+ expect ( spy ) . toHaveBeenCalledTimes ( 1 ) ;
935
+
936
+ subscription . unsubscribe ( ) ;
937
+ } ) ) ;
938
+
939
+ it ( 'should recapture focus to the first tabbable element when clicking on the backdrop while the `closePredicate` is blocking the close sequence' , fakeAsync ( ( ) => {
940
+ // When testing focus, all of the elements must be in the DOM.
941
+ document . body . appendChild ( overlayContainerElement ) ;
942
+
943
+ dialog . open ( PizzaMsg , {
944
+ closePredicate : ( ) => false ,
945
+ viewContainerRef : testViewContainerRef ,
946
+ } ) ;
947
+
948
+ viewContainerFixture . detectChanges ( ) ;
949
+ flushMicrotasks ( ) ;
950
+
951
+ const backdrop = overlayContainerElement . querySelector (
952
+ '.cdk-overlay-backdrop' ,
953
+ ) as HTMLElement ;
954
+ const input = overlayContainerElement . querySelector ( 'input' ) as HTMLInputElement ;
955
+
956
+ expect ( document . activeElement )
957
+ . withContext ( 'Expected input to be focused on open' )
958
+ . toBe ( input ) ;
959
+
960
+ input . blur ( ) ; // Programmatic clicks might not move focus so we simulate it.
961
+ backdrop . click ( ) ;
962
+ viewContainerFixture . detectChanges ( ) ;
963
+ flush ( ) ;
964
+
965
+ expect ( document . activeElement )
966
+ . withContext ( 'Expected input to stay focused after click' )
967
+ . toBe ( input ) ;
968
+
969
+ overlayContainerElement . remove ( ) ;
970
+ } ) ) ;
971
+ } ) ;
972
+
781
973
describe ( 'hasBackdrop option' , ( ) => {
782
974
it ( 'should have a backdrop' , ( ) => {
783
975
dialog . open ( PizzaMsg , {
@@ -1273,6 +1465,10 @@ class PizzaMsg {
1273
1465
<h2>This is the title</h2>
1274
1466
` ,
1275
1467
imports : [ DialogModule ] ,
1468
+ host : {
1469
+ // Avoids conflicting ID warning
1470
+ 'id' : 'content-element-dialog' ,
1471
+ } ,
1276
1472
} )
1277
1473
class ContentElementDialog {
1278
1474
closeButtonAriaLabel : string ;
@@ -1299,6 +1495,10 @@ class DialogWithInjectedData {
1299
1495
@Component ( {
1300
1496
template : '<p>Pasta</p>' ,
1301
1497
imports : [ DialogModule ] ,
1498
+ host : {
1499
+ // Avoids conflicting ID warning
1500
+ 'id' : 'dialog-without-focusable' ,
1501
+ } ,
1302
1502
} )
1303
1503
class DialogWithoutFocusableElements { }
1304
1504
0 commit comments