@@ -3,9 +3,14 @@ import {ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes';
33import { dispatchKeyboardEvent } from '@angular/cdk/testing' ;
44import { Component , DebugElement } from '@angular/core' ;
55import { async , ComponentFixture , TestBed , inject } from '@angular/core/testing' ;
6- import { FormControl , FormGroup , ReactiveFormsModule , Validators } from '@angular/forms' ;
6+ import { AbstractControl , AsyncValidatorFn , FormControl , FormGroup , ReactiveFormsModule ,
7+ ValidationErrors , Validators } from '@angular/forms' ;
78import { By } from '@angular/platform-browser' ;
89import { NoopAnimationsModule } from '@angular/platform-browser/animations' ;
10+ import { map } from 'rxjs/operators/map' ;
11+ import { take } from 'rxjs/operators/take' ;
12+ import { Observable } from 'rxjs/Observable' ;
13+ import { Subject } from 'rxjs/Subject' ;
914import { MatStepperModule } from './index' ;
1015import { MatHorizontalStepper , MatStep , MatStepper , MatVerticalStepper } from './stepper' ;
1116import { MatStepperNext , MatStepperPrevious } from './stepper-button' ;
@@ -156,17 +161,25 @@ describe('MatHorizontalStepper', () => {
156161 expect ( stepperComponent . linear ) . toBe ( true ) ;
157162 } ) ;
158163
159- it ( 'should not move to next step if current step is not valid ' , ( ) => {
164+ it ( 'should not move to next step if current step is invalid ' , ( ) => {
160165 expect ( testComponent . oneGroup . get ( 'oneCtrl' ) ! . value ) . toBe ( '' ) ;
161166 expect ( testComponent . oneGroup . get ( 'oneCtrl' ) ! . valid ) . toBe ( false ) ;
162167 expect ( testComponent . oneGroup . valid ) . toBe ( false ) ;
168+ expect ( testComponent . oneGroup . invalid ) . toBe ( true ) ;
163169 expect ( stepperComponent . selectedIndex ) . toBe ( 0 ) ;
164170
165171 let stepHeaderEl = fixture . debugElement
166172 . queryAll ( By . css ( '.mat-horizontal-stepper-header' ) ) [ 1 ] . nativeElement ;
167173 assertLinearStepperValidity ( stepHeaderEl , testComponent , fixture ) ;
168174 } ) ;
169175
176+ it ( 'should not move to next step if current step is pending' , ( ) => {
177+ let stepHeaderEl = fixture . debugElement
178+ . queryAll ( By . css ( '.mat-horizontal-stepper-header' ) ) [ 2 ] . nativeElement ;
179+
180+ assertLinearStepperPending ( stepHeaderEl , testComponent , fixture ) ;
181+ } ) ;
182+
170183 it ( 'should not focus step header upon click if it is not able to be selected' , ( ) => {
171184 assertStepHeaderBlurred ( fixture ) ;
172185 } ) ;
@@ -317,10 +330,11 @@ describe('MatVerticalStepper', () => {
317330 expect ( stepperComponent . linear ) . toBe ( true ) ;
318331 } ) ;
319332
320- it ( 'should not move to next step if current step is not valid ' , ( ) => {
333+ it ( 'should not move to next step if current step is invalid ' , ( ) => {
321334 expect ( testComponent . oneGroup . get ( 'oneCtrl' ) ! . value ) . toBe ( '' ) ;
322335 expect ( testComponent . oneGroup . get ( 'oneCtrl' ) ! . valid ) . toBe ( false ) ;
323336 expect ( testComponent . oneGroup . valid ) . toBe ( false ) ;
337+ expect ( testComponent . oneGroup . invalid ) . toBe ( true ) ;
324338 expect ( stepperComponent . selectedIndex ) . toBe ( 0 ) ;
325339
326340 let stepHeaderEl = fixture . debugElement
@@ -329,6 +343,13 @@ describe('MatVerticalStepper', () => {
329343 assertLinearStepperValidity ( stepHeaderEl , testComponent , fixture ) ;
330344 } ) ;
331345
346+ it ( 'should not move to next step if current step is pending' , ( ) => {
347+ let stepHeaderEl = fixture . debugElement
348+ . queryAll ( By . css ( '.mat-vertical-stepper-header' ) ) [ 2 ] . nativeElement ;
349+
350+ assertLinearStepperPending ( stepHeaderEl , testComponent , fixture ) ;
351+ } ) ;
352+
332353 it ( 'should not focus step header upon click if it is not able to be selected' , ( ) => {
333354 assertStepHeaderBlurred ( fixture ) ;
334355 } ) ;
@@ -617,6 +638,58 @@ function assertLinearStepperValidity(stepHeaderEl: HTMLElement,
617638 expect ( stepperComponent . selectedIndex ) . toBe ( 1 ) ;
618639}
619640
641+ /** Asserts that linear stepper does not allow step selection change if current step is pending. */
642+ function assertLinearStepperPending ( stepHeaderEl : HTMLElement ,
643+ testComponent :
644+ LinearMatHorizontalStepperApp |
645+ LinearMatVerticalStepperApp ,
646+ fixture : ComponentFixture < any > ) {
647+ let stepperComponent = fixture . debugElement . query ( By . directive ( MatStepper ) ) . componentInstance ;
648+ let nextButtonNativeEl = fixture . debugElement
649+ . queryAll ( By . directive ( MatStepperNext ) ) [ 1 ] . nativeElement ;
650+
651+ testComponent . oneGroup . get ( 'oneCtrl' ) ! . setValue ( 'input' ) ;
652+ testComponent . twoGroup . get ( 'twoCtrl' ) ! . setValue ( 'input' ) ;
653+ stepperComponent . selectedIndex = 1 ;
654+ fixture . detectChanges ( ) ;
655+ expect ( stepperComponent . selectedIndex ) . toBe ( 1 ) ;
656+
657+ // Step status = PENDING
658+ // Assert that linear stepper does not allow step selection change
659+ expect ( testComponent . twoGroup . pending ) . toBe ( true ) ;
660+
661+ stepHeaderEl . click ( ) ;
662+ fixture . detectChanges ( ) ;
663+
664+ expect ( stepperComponent . selectedIndex ) . toBe ( 1 ) ;
665+
666+ nextButtonNativeEl . click ( ) ;
667+ fixture . detectChanges ( ) ;
668+
669+ expect ( stepperComponent . selectedIndex ) . toBe ( 1 ) ;
670+
671+ // Trigger asynchronous validation
672+ testComponent . validationTrigger . next ( ) ;
673+ // Asynchronous validation completed:
674+ // Step status = VALID
675+ expect ( testComponent . twoGroup . pending ) . toBe ( false ) ;
676+ expect ( testComponent . twoGroup . valid ) . toBe ( true ) ;
677+
678+ stepHeaderEl . click ( ) ;
679+ fixture . detectChanges ( ) ;
680+
681+ expect ( stepperComponent . selectedIndex ) . toBe ( 2 ) ;
682+
683+ stepperComponent . selectedIndex = 1 ;
684+ fixture . detectChanges ( ) ;
685+ expect ( stepperComponent . selectedIndex ) . toBe ( 1 ) ;
686+
687+ nextButtonNativeEl . click ( ) ;
688+ fixture . detectChanges ( ) ;
689+
690+ expect ( stepperComponent . selectedIndex ) . toBe ( 2 ) ;
691+ }
692+
620693/** Asserts that step header focus is blurred if the step cannot be selected upon header click. */
621694function assertStepHeaderBlurred ( fixture : ComponentFixture < any > ) {
622695 let stepHeaderEl = fixture . debugElement
@@ -659,6 +732,7 @@ function assertOptionalStepValidity(testComponent:
659732
660733 testComponent . oneGroup . get ( 'oneCtrl' ) ! . setValue ( 'input' ) ;
661734 testComponent . twoGroup . get ( 'twoCtrl' ) ! . setValue ( 'input' ) ;
735+ testComponent . validationTrigger . next ( ) ;
662736 stepperComponent . selectedIndex = 2 ;
663737 fixture . detectChanges ( ) ;
664738
@@ -706,6 +780,18 @@ function assertCorrectStepIcon(fixture: ComponentFixture<any>,
706780 expect ( stepperComponent . _getIndicatorType ( 0 ) ) . toBe ( icon ) ;
707781}
708782
783+ function asyncValidator ( minLength : number , validationTrigger : Observable < any > ) : AsyncValidatorFn {
784+ return ( control : AbstractControl ) : Observable < ValidationErrors | null > => {
785+ return validationTrigger . pipe (
786+ map ( ( ) => {
787+ const success = control . value && control . value . length >= minLength ;
788+ return success ? null : { 'asyncValidation' : { } } ;
789+ } ) ,
790+ take ( 1 )
791+ ) ;
792+ } ;
793+ }
794+
709795@Component ( {
710796 template : `
711797 <mat-horizontal-stepper>
@@ -783,12 +869,14 @@ class LinearMatHorizontalStepperApp {
783869 twoGroup : FormGroup ;
784870 threeGroup : FormGroup ;
785871
872+ validationTrigger : Subject < any > = new Subject ( ) ;
873+
786874 ngOnInit ( ) {
787875 this . oneGroup = new FormGroup ( {
788876 oneCtrl : new FormControl ( '' , Validators . required )
789877 } ) ;
790878 this . twoGroup = new FormGroup ( {
791- twoCtrl : new FormControl ( '' , Validators . required )
879+ twoCtrl : new FormControl ( '' , Validators . required , asyncValidator ( 3 , this . validationTrigger ) )
792880 } ) ;
793881 this . threeGroup = new FormGroup ( {
794882 threeCtrl : new FormControl ( '' , Validators . pattern ( VALID_REGEX ) )
@@ -873,12 +961,14 @@ class LinearMatVerticalStepperApp {
873961 twoGroup : FormGroup ;
874962 threeGroup : FormGroup ;
875963
964+ validationTrigger : Subject < any > = new Subject ( ) ;
965+
876966 ngOnInit ( ) {
877967 this . oneGroup = new FormGroup ( {
878968 oneCtrl : new FormControl ( '' , Validators . required )
879969 } ) ;
880970 this . twoGroup = new FormGroup ( {
881- twoCtrl : new FormControl ( '' , Validators . required )
971+ twoCtrl : new FormControl ( '' , Validators . required , asyncValidator ( 3 , this . validationTrigger ) )
882972 } ) ;
883973 this . threeGroup = new FormGroup ( {
884974 threeCtrl : new FormControl ( '' , Validators . pattern ( VALID_REGEX ) )
0 commit comments