@@ -689,6 +689,35 @@ describe('Dialog', () => {
689689 expect ( dialog . getDialogById ( 'pizza' ) ) . toBe ( dialogRef ) ;
690690 } ) ;
691691
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+
692721 describe ( 'disableClose option' , ( ) => {
693722 it ( 'should prevent closing via clicks on the backdrop' , fakeAsync ( ( ) => {
694723 dialog . open ( PizzaMsg , {
@@ -778,6 +807,169 @@ describe('Dialog', () => {
778807 ) ;
779808 } ) ;
780809
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+
781973 describe ( 'hasBackdrop option' , ( ) => {
782974 it ( 'should have a backdrop' , ( ) => {
783975 dialog . open ( PizzaMsg , {
@@ -1273,6 +1465,10 @@ class PizzaMsg {
12731465 <h2>This is the title</h2>
12741466 ` ,
12751467 imports : [ DialogModule ] ,
1468+ host : {
1469+ // Avoids conflicting ID warning
1470+ 'id' : 'content-element-dialog' ,
1471+ } ,
12761472} )
12771473class ContentElementDialog {
12781474 closeButtonAriaLabel : string ;
@@ -1299,6 +1495,10 @@ class DialogWithInjectedData {
12991495@Component ( {
13001496 template : '<p>Pasta</p>' ,
13011497 imports : [ DialogModule ] ,
1498+ host : {
1499+ // Avoids conflicting ID warning
1500+ 'id' : 'dialog-without-focusable' ,
1501+ } ,
13021502} )
13031503class DialogWithoutFocusableElements { }
13041504
0 commit comments