diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index 03900c5841bc..fb26eefdc970 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -349,7 +349,9 @@ export class CdkStepper implements OnDestroy { if (this._linear && index >= 0) { return steps.slice(0, index).some(step => { const control = step.stepControl; - const isIncomplete = control ? (control.invalid || control.pending) : !step.completed; + const isIncomplete = control ? + (control.invalid || control.pending || !step.interacted) : + !step.completed; return isIncomplete && !step.optional; }); } diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts index 98bdb031f657..834d66c4692a 100644 --- a/src/lib/stepper/stepper.spec.ts +++ b/src/lib/stepper/stepper.spec.ts @@ -1,25 +1,32 @@ -import {Directionality, Direction} from '@angular/cdk/bidi'; +import {Direction, Directionality} from '@angular/cdk/bidi'; import { + DOWN_ARROW, + END, ENTER, + HOME, LEFT_ARROW, RIGHT_ARROW, - UP_ARROW, - DOWN_ARROW, SPACE, - HOME, - END, + UP_ARROW, } from '@angular/cdk/keycodes'; +import {StepperOrientation} from '@angular/cdk/stepper'; import {dispatchKeyboardEvent} from '@angular/cdk/testing'; import {Component, DebugElement} from '@angular/core'; -import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing'; -import {AbstractControl, AsyncValidatorFn, FormControl, FormGroup, ReactiveFormsModule, - ValidationErrors, Validators} from '@angular/forms'; +import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing'; +import { + AbstractControl, + AsyncValidatorFn, + FormControl, + FormGroup, + ReactiveFormsModule, + ValidationErrors, + Validators +} from '@angular/forms'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {StepperOrientation} from '@angular/cdk/stepper'; +import {Observable} from 'rxjs/Observable'; import {map} from 'rxjs/operators/map'; import {take} from 'rxjs/operators/take'; -import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; import {MatStepperModule} from './index'; import {MatHorizontalStepper, MatStep, MatStepper, MatVerticalStepper} from './stepper'; @@ -44,6 +51,7 @@ describe('MatStepper', () => { SimpleStepperWithoutStepControl, SimpleStepperWithStepControlAndCompletedBinding, SimpleMatHorizontalStepperApp, + LinearStepperWithValidOptionalStep, ], providers: [ {provide: Directionality, useFactory: () => ({value: dir})} @@ -491,6 +499,8 @@ describe('MatStepper', () => { testComponent.oneGroup.get('oneCtrl')!.setValue('input'); testComponent.twoGroup.get('twoCtrl')!.setValue('input'); testComponent.validationTrigger.next(); + stepperComponent.selectedIndex = 1; + fixture.detectChanges(); stepperComponent.selectedIndex = 2; fixture.detectChanges(); @@ -559,6 +569,8 @@ describe('MatStepper', () => { testComponent.twoGroup.get('twoCtrl')!.setValue('input'); testComponent.threeGroup.get('threeCtrl')!.setValue('valid'); testComponent.validationTrigger.next(); + stepperComponent.selectedIndex = 1; + fixture.detectChanges(); stepperComponent.selectedIndex = 2; fixture.detectChanges(); stepperComponent.selectedIndex = 3; @@ -695,6 +707,43 @@ describe('MatStepper', () => { assertArrowKeyInteractionInRtl(fixture, stepHeaders); }); }); + + describe('valid step in linear stepper', () => { + let fixture: ComponentFixture; + let testComponent: LinearStepperWithValidOptionalStep; + let stepper: MatStepper; + + beforeEach(() => { + fixture = TestBed.createComponent(LinearStepperWithValidOptionalStep); + fixture.detectChanges(); + + testComponent = fixture.componentInstance; + stepper = fixture.debugElement + .query(By.css('mat-horizontal-stepper')).componentInstance; + }); + + it('must be visited if not optional', () => { + stepper.selectedIndex = 2; + fixture.detectChanges(); + expect(stepper.selectedIndex).toBe(0); + + stepper.selectedIndex = 1; + fixture.detectChanges(); + expect(stepper.selectedIndex).toBe(1); + + stepper.selectedIndex = 2; + fixture.detectChanges(); + expect(stepper.selectedIndex).toBe(2); + }); + + it('can be skipped entirely if optional', () => { + testComponent.step2Optional = true; + fixture.detectChanges(); + stepper.selectedIndex = 2; + fixture.detectChanges(); + expect(stepper.selectedIndex).toBe(2); + }); + }); }); /** Asserts that keyboard interaction works correctly. */ @@ -986,3 +1035,17 @@ class SimpleStepperWithStepControlAndCompletedBinding { ` }) class IconOverridesStepper {} + +@Component({ + template: ` + + + + + + ` +}) +class LinearStepperWithValidOptionalStep { + controls = [0, 0, 0].map(() => new FormControl()); + step2Optional = false; +}