ipsos-components
Version:
Material Design components for Angular
991 lines (809 loc) • 37.2 kB
text/typescript
import {Directionality} from '@angular/cdk/bidi';
import {ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes';
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 {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
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';
import {MatStepperNext, MatStepperPrevious} from './stepper-button';
import {MatStepperIntl} from './stepper-intl';
const VALID_REGEX = /valid/;
describe('MatHorizontalStepper', () => {
let dir = 'ltr';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatStepperModule, NoopAnimationsModule, ReactiveFormsModule],
declarations: [
SimpleMatHorizontalStepperApp,
SimplePreselectedMatHorizontalStepperApp,
LinearMatHorizontalStepperApp
],
providers: [
{provide: Directionality, useFactory: () => ({value: dir})}
]
});
TestBed.compileComponents();
}));
describe('basic horizontal stepper', () => {
let fixture: ComponentFixture<SimpleMatHorizontalStepperApp>;
beforeEach(() => {
fixture = TestBed.createComponent(SimpleMatHorizontalStepperApp);
fixture.detectChanges();
});
it('should default to the first step', () => {
let stepperComponent = fixture.debugElement
.query(By.css('mat-horizontal-stepper')).componentInstance;
expect(stepperComponent.selectedIndex).toBe(0);
});
it('should change selected index on header click', () => {
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
assertSelectionChangeOnHeaderClick(fixture, stepHeaders);
});
it('should set the "tablist" role on stepper', () => {
let stepperEl = fixture.debugElement.query(By.css('mat-horizontal-stepper')).nativeElement;
expect(stepperEl.getAttribute('role')).toBe('tablist');
});
it('should set the proper "aria-orientation"', () => {
let stepperEl = fixture.debugElement.query(By.css('mat-horizontal-stepper')).nativeElement;
expect(stepperEl.getAttribute('aria-orientation')).toBe('horizontal');
});
it('should set aria-expanded of content correctly', () => {
let stepContents = fixture.debugElement.queryAll(By.css(`.mat-horizontal-stepper-content`));
assertCorrectAriaExpandedAttribute(fixture, stepContents);
});
it('should display the correct label', () => {
assertCorrectStepLabel(fixture);
});
it('should go to next available step when the next button is clicked', () => {
assertNextStepperButtonClick(fixture);
});
it('should go to previous available step when the previous button is clicked', () => {
assertPreviousStepperButtonClick(fixture);
});
it('should set the correct step position for animation', () => {
assertCorrectStepAnimationDirection(fixture);
});
it('should support keyboard events to move and select focus', () => {
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
assertCorrectKeyboardInteraction(fixture, stepHeaders);
});
it('should not set focus on header of selected step if header is not clicked', () => {
assertStepHeaderFocusNotCalled(fixture);
});
it('should only be able to return to a previous step if it is editable', () => {
assertEditableStepChange(fixture);
});
it('should set create icon if step is editable and completed', () => {
assertCorrectStepIcon(fixture, true, 'edit');
});
it('should set done icon if step is not editable and is completed', () => {
assertCorrectStepIcon(fixture, false, 'done');
});
it('should re-render when the i18n labels change',
inject([MatStepperIntl], (intl: MatStepperIntl) => {
const header = fixture.debugElement.queryAll(By.css('mat-step-header'))[2].nativeElement;
const optionalLabel = header.querySelector('.mat-step-optional');
expect(optionalLabel).toBeTruthy();
expect(optionalLabel.textContent).toBe('Optional');
intl.optionalLabel = 'Valgfri';
intl.changes.next();
fixture.detectChanges();
expect(optionalLabel.textContent).toBe('Valgfri');
}));
});
describe('RTL', () => {
let fixture: ComponentFixture<SimpleMatHorizontalStepperApp>;
beforeEach(() => {
dir = 'rtl';
fixture = TestBed.createComponent(SimpleMatHorizontalStepperApp);
fixture.detectChanges();
});
it('should reverse arrow key focus in RTL mode', () => {
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
assertArrowKeyInteractionInRtl(fixture, stepHeaders);
});
it('should reverse animation in RTL mode', () => {
assertCorrectStepAnimationDirection(fixture, 'rtl');
});
});
describe('linear horizontal stepper', () => {
let fixture: ComponentFixture<LinearMatHorizontalStepperApp>;
let testComponent: LinearMatHorizontalStepperApp;
let stepperComponent: MatHorizontalStepper;
beforeEach(() => {
fixture = TestBed.createComponent(LinearMatHorizontalStepperApp);
fixture.detectChanges();
testComponent = fixture.componentInstance;
stepperComponent = fixture.debugElement
.query(By.css('mat-horizontal-stepper')).componentInstance;
});
it('should have true linear attribute', () => {
expect(stepperComponent.linear).toBe(true);
});
it('should not move to next step if current step is invalid', () => {
expect(testComponent.oneGroup.get('oneCtrl')!.value).toBe('');
expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false);
expect(testComponent.oneGroup.valid).toBe(false);
expect(testComponent.oneGroup.invalid).toBe(true);
expect(stepperComponent.selectedIndex).toBe(0);
let stepHeaderEl = fixture.debugElement
.queryAll(By.css('.mat-horizontal-stepper-header'))[1].nativeElement;
assertLinearStepperValidity(stepHeaderEl, testComponent, fixture);
});
it('should not move to next step if current step is pending', () => {
let stepHeaderEl = fixture.debugElement
.queryAll(By.css('.mat-horizontal-stepper-header'))[2].nativeElement;
assertLinearStepperPending(stepHeaderEl, testComponent, fixture);
});
it('should not focus step header upon click if it is not able to be selected', () => {
assertStepHeaderBlurred(fixture);
});
it('should be able to move to next step even when invalid if current step is optional', () => {
assertOptionalStepValidity(testComponent, fixture);
});
it('should not throw when there is a pre-defined selectedIndex', () => {
fixture.destroy();
let preselectedFixture = TestBed.createComponent(SimplePreselectedMatHorizontalStepperApp);
let debugElement = preselectedFixture.debugElement;
expect(() => preselectedFixture.detectChanges()).not.toThrow();
let stepHeaders = debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
assertSelectionChangeOnHeaderClick(preselectedFixture, stepHeaders);
});
});
});
describe('MatVerticalStepper', () => {
let dir = 'ltr';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatStepperModule, NoopAnimationsModule, ReactiveFormsModule],
declarations: [
SimpleMatVerticalStepperApp,
LinearMatVerticalStepperApp
],
providers: [
{provide: Directionality, useFactory: () => ({value: dir})}
]
});
TestBed.compileComponents();
}));
describe('basic vertical stepper', () => {
let fixture: ComponentFixture<SimpleMatVerticalStepperApp>;
beforeEach(() => {
fixture = TestBed.createComponent(SimpleMatVerticalStepperApp);
fixture.detectChanges();
});
it('should default to the first step', () => {
let stepperComponent = fixture.debugElement
.query(By.css('mat-vertical-stepper')).componentInstance;
expect(stepperComponent.selectedIndex).toBe(0);
});
it('should change selected index on header click', () => {
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
assertSelectionChangeOnHeaderClick(fixture, stepHeaders);
});
it('should set the "tablist" role on stepper', () => {
let stepperEl = fixture.debugElement.query(By.css('mat-vertical-stepper')).nativeElement;
expect(stepperEl.getAttribute('role')).toBe('tablist');
});
it('should set the proper "aria-orientation"', () => {
let stepperEl = fixture.debugElement.query(By.css('mat-vertical-stepper')).nativeElement;
expect(stepperEl.getAttribute('aria-orientation')).toBe('vertical');
});
it('should set aria-expanded of content correctly', () => {
let stepContents = fixture.debugElement.queryAll(By.css(`.mat-vertical-stepper-content`));
assertCorrectAriaExpandedAttribute(fixture, stepContents);
});
it('should display the correct label', () => {
assertCorrectStepLabel(fixture);
});
it('should go to next available step when the next button is clicked', () => {
assertNextStepperButtonClick(fixture);
});
it('should go to previous available step when the previous button is clicked', () => {
assertPreviousStepperButtonClick(fixture);
});
it('should set the correct step position for animation', () => {
assertCorrectStepAnimationDirection(fixture);
});
it('should support keyboard events to move and select focus', () => {
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
assertCorrectKeyboardInteraction(fixture, stepHeaders);
});
it('should not set focus on header of selected step if header is not clicked', () => {
assertStepHeaderFocusNotCalled(fixture);
});
it('should only be able to return to a previous step if it is editable', () => {
assertEditableStepChange(fixture);
});
it('should set create icon if step is editable and completed', () => {
assertCorrectStepIcon(fixture, true, 'edit');
});
it('should set done icon if step is not editable and is completed', () => {
assertCorrectStepIcon(fixture, false, 'done');
});
});
describe('RTL', () => {
let fixture: ComponentFixture<SimpleMatVerticalStepperApp>;
beforeEach(() => {
dir = 'rtl';
fixture = TestBed.createComponent(SimpleMatVerticalStepperApp);
fixture.detectChanges();
});
it('should reverse arrow key focus in RTL mode', () => {
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
assertArrowKeyInteractionInRtl(fixture, stepHeaders);
});
it('should reverse animation in RTL mode', () => {
assertCorrectStepAnimationDirection(fixture, 'rtl');
});
});
describe('linear vertical stepper', () => {
let fixture: ComponentFixture<LinearMatVerticalStepperApp>;
let testComponent: LinearMatVerticalStepperApp;
let stepperComponent: MatVerticalStepper;
beforeEach(() => {
fixture = TestBed.createComponent(LinearMatVerticalStepperApp);
fixture.detectChanges();
testComponent = fixture.componentInstance;
stepperComponent = fixture.debugElement
.query(By.css('mat-vertical-stepper')).componentInstance;
});
it('should have true linear attribute', () => {
expect(stepperComponent.linear).toBe(true);
});
it('should not move to next step if current step is invalid', () => {
expect(testComponent.oneGroup.get('oneCtrl')!.value).toBe('');
expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false);
expect(testComponent.oneGroup.valid).toBe(false);
expect(testComponent.oneGroup.invalid).toBe(true);
expect(stepperComponent.selectedIndex).toBe(0);
let stepHeaderEl = fixture.debugElement
.queryAll(By.css('.mat-vertical-stepper-header'))[1].nativeElement;
assertLinearStepperValidity(stepHeaderEl, testComponent, fixture);
});
it('should not move to next step if current step is pending', () => {
let stepHeaderEl = fixture.debugElement
.queryAll(By.css('.mat-vertical-stepper-header'))[2].nativeElement;
assertLinearStepperPending(stepHeaderEl, testComponent, fixture);
});
it('should not focus step header upon click if it is not able to be selected', () => {
assertStepHeaderBlurred(fixture);
});
it('should be able to move to next step even when invalid if current step is optional', () => {
assertOptionalStepValidity(testComponent, fixture);
});
});
});
/** Asserts that `selectedIndex` updates correctly when header of another step is clicked. */
function assertSelectionChangeOnHeaderClick(fixture: ComponentFixture<any>,
stepHeaders: DebugElement[]) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
expect(stepperComponent.selectedIndex).toBe(0);
expect(stepperComponent.selected instanceof MatStep).toBe(true);
// select the second step
let stepHeaderEl = stepHeaders[1].nativeElement;
stepHeaderEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(1);
expect(stepperComponent.selected instanceof MatStep).toBe(true);
// select the third step
stepHeaderEl = stepHeaders[2].nativeElement;
stepHeaderEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(2);
expect(stepperComponent.selected instanceof MatStep).toBe(true);
}
/** Asserts that 'aria-expanded' attribute is correct for expanded content of step. */
function assertCorrectAriaExpandedAttribute(fixture: ComponentFixture<any>,
stepContents: DebugElement[]) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
let firstStepContentEl = stepContents[0].nativeElement;
expect(firstStepContentEl.getAttribute('aria-expanded')).toBe('true');
stepperComponent.selectedIndex = 1;
fixture.detectChanges();
expect(firstStepContentEl.getAttribute('aria-expanded')).toBe('false');
let secondStepContentEl = stepContents[1].nativeElement;
expect(secondStepContentEl.getAttribute('aria-expanded')).toBe('true');
}
/** Asserts that step has correct label. */
function assertCorrectStepLabel(fixture: ComponentFixture<any>) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
let selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]');
expect(selectedLabel.textContent).toMatch('Step 1');
stepperComponent.selectedIndex = 2;
fixture.detectChanges();
selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]');
expect(selectedLabel.textContent).toMatch('Step 3');
fixture.componentInstance.inputLabel = 'New Label';
fixture.detectChanges();
selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]');
expect(selectedLabel.textContent).toMatch('New Label');
}
/** Asserts that clicking on MatStepperNext button updates `selectedIndex` correctly. */
function assertNextStepperButtonClick(fixture: ComponentFixture<any>) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
expect(stepperComponent.selectedIndex).toBe(0);
let nextButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperNext))[0].nativeElement;
nextButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(1);
nextButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperNext))[1].nativeElement;
nextButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(2);
nextButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperNext))[2].nativeElement;
nextButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(2);
}
/** Asserts that clicking on MatStepperPrevious button updates `selectedIndex` correctly. */
function assertPreviousStepperButtonClick(fixture: ComponentFixture<any>) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
expect(stepperComponent.selectedIndex).toBe(0);
stepperComponent.selectedIndex = 2;
let previousButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperPrevious))[2].nativeElement;
previousButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(1);
previousButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperPrevious))[1].nativeElement;
previousButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(0);
previousButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperPrevious))[0].nativeElement;
previousButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(0);
}
/** Asserts that step position is correct for animation. */
function assertCorrectStepAnimationDirection(fixture: ComponentFixture<any>, rtl?: 'rtl') {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
expect(stepperComponent._getAnimationDirection(0)).toBe('current');
if (rtl === 'rtl') {
expect(stepperComponent._getAnimationDirection(1)).toBe('previous');
expect(stepperComponent._getAnimationDirection(2)).toBe('previous');
} else {
expect(stepperComponent._getAnimationDirection(1)).toBe('next');
expect(stepperComponent._getAnimationDirection(2)).toBe('next');
}
stepperComponent.selectedIndex = 1;
fixture.detectChanges();
if (rtl === 'rtl') {
expect(stepperComponent._getAnimationDirection(0)).toBe('next');
expect(stepperComponent._getAnimationDirection(2)).toBe('previous');
} else {
expect(stepperComponent._getAnimationDirection(0)).toBe('previous');
expect(stepperComponent._getAnimationDirection(2)).toBe('next');
}
expect(stepperComponent._getAnimationDirection(1)).toBe('current');
stepperComponent.selectedIndex = 2;
fixture.detectChanges();
if (rtl === 'rtl') {
expect(stepperComponent._getAnimationDirection(0)).toBe('next');
expect(stepperComponent._getAnimationDirection(1)).toBe('next');
} else {
expect(stepperComponent._getAnimationDirection(0)).toBe('previous');
expect(stepperComponent._getAnimationDirection(1)).toBe('previous');
}
expect(stepperComponent._getAnimationDirection(2)).toBe('current');
}
/** Asserts that keyboard interaction works correctly. */
function assertCorrectKeyboardInteraction(fixture: ComponentFixture<any>,
stepHeaders: DebugElement[]) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
expect(stepperComponent._focusIndex).toBe(0);
expect(stepperComponent.selectedIndex).toBe(0);
let stepHeaderEl = stepHeaders[0].nativeElement;
dispatchKeyboardEvent(stepHeaderEl, 'keydown', RIGHT_ARROW);
fixture.detectChanges();
expect(stepperComponent._focusIndex)
.toBe(1, 'Expected index of focused step to increase by 1 after RIGHT_ARROW event.');
expect(stepperComponent.selectedIndex)
.toBe(0, 'Expected index of selected step to remain unchanged after RIGHT_ARROW event.');
stepHeaderEl = stepHeaders[1].nativeElement;
dispatchKeyboardEvent(stepHeaderEl, 'keydown', ENTER);
fixture.detectChanges();
expect(stepperComponent._focusIndex)
.toBe(1, 'Expected index of focused step to remain unchanged after ENTER event.');
expect(stepperComponent.selectedIndex)
.toBe(1,
'Expected index of selected step to change to index of focused step after ENTER event.');
stepHeaderEl = stepHeaders[1].nativeElement;
dispatchKeyboardEvent(stepHeaderEl, 'keydown', LEFT_ARROW);
fixture.detectChanges();
expect(stepperComponent._focusIndex)
.toBe(0, 'Expected index of focused step to decrease by 1 after LEFT_ARROW event.');
expect(stepperComponent.selectedIndex)
.toBe(1, 'Expected index of selected step to remain unchanged after LEFT_ARROW event.');
// When the focus is on the last step and right arrow key is pressed, the focus should cycle
// through to the first step.
stepperComponent._focusIndex = 2;
stepHeaderEl = stepHeaders[2].nativeElement;
dispatchKeyboardEvent(stepHeaderEl, 'keydown', RIGHT_ARROW);
fixture.detectChanges();
expect(stepperComponent._focusIndex)
.toBe(0,
'Expected index of focused step to cycle through to index 0 after RIGHT_ARROW event.');
expect(stepperComponent.selectedIndex)
.toBe(1, 'Expected index of selected step to remain unchanged after RIGHT_ARROW event.');
stepHeaderEl = stepHeaders[0].nativeElement;
dispatchKeyboardEvent(stepHeaderEl, 'keydown', SPACE);
fixture.detectChanges();
expect(stepperComponent._focusIndex)
.toBe(0, 'Expected index of focused to remain unchanged after SPACE event.');
expect(stepperComponent.selectedIndex)
.toBe(0,
'Expected index of selected step to change to index of focused step after SPACE event.');
}
/** Asserts that step selection change using stepper buttons does not focus step header. */
function assertStepHeaderFocusNotCalled(fixture: ComponentFixture<any>) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
let stepHeaderEl = fixture.debugElement.queryAll(By.css('mat-step-header'))[1].nativeElement;
let nextButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperNext))[0].nativeElement;
spyOn(stepHeaderEl, 'focus');
nextButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(1);
expect(stepHeaderEl.focus).not.toHaveBeenCalled();
}
/** Asserts that arrow key direction works correctly in RTL mode. */
function assertArrowKeyInteractionInRtl(fixture: ComponentFixture<any>,
stepHeaders: DebugElement[]) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
expect(stepperComponent._focusIndex).toBe(0);
let stepHeaderEl = stepHeaders[0].nativeElement;
dispatchKeyboardEvent(stepHeaderEl, 'keydown', LEFT_ARROW);
fixture.detectChanges();
expect(stepperComponent._focusIndex).toBe(1);
stepHeaderEl = stepHeaders[1].nativeElement;
dispatchKeyboardEvent(stepHeaderEl, 'keydown', RIGHT_ARROW);
fixture.detectChanges();
expect(stepperComponent._focusIndex).toBe(0);
}
/**
* Asserts that linear stepper does not allow step selection change if current step is not valid.
*/
function assertLinearStepperValidity(stepHeaderEl: HTMLElement,
testComponent:
LinearMatHorizontalStepperApp |
LinearMatVerticalStepperApp,
fixture: ComponentFixture<any>) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
stepHeaderEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(0);
let nextButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperNext))[0].nativeElement;
nextButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(0);
testComponent.oneGroup.get('oneCtrl')!.setValue('answer');
stepHeaderEl.click();
fixture.detectChanges();
expect(testComponent.oneGroup.valid).toBe(true);
expect(stepperComponent.selectedIndex).toBe(1);
}
/** Asserts that linear stepper does not allow step selection change if current step is pending. */
function assertLinearStepperPending(stepHeaderEl: HTMLElement,
testComponent:
LinearMatHorizontalStepperApp |
LinearMatVerticalStepperApp,
fixture: ComponentFixture<any>) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
let nextButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperNext))[1].nativeElement;
testComponent.oneGroup.get('oneCtrl')!.setValue('input');
testComponent.twoGroup.get('twoCtrl')!.setValue('input');
stepperComponent.selectedIndex = 1;
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(1);
// Step status = PENDING
// Assert that linear stepper does not allow step selection change
expect(testComponent.twoGroup.pending).toBe(true);
stepHeaderEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(1);
nextButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(1);
// Trigger asynchronous validation
testComponent.validationTrigger.next();
// Asynchronous validation completed:
// Step status = VALID
expect(testComponent.twoGroup.pending).toBe(false);
expect(testComponent.twoGroup.valid).toBe(true);
stepHeaderEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(2);
stepperComponent.selectedIndex = 1;
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(1);
nextButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(2);
}
/** Asserts that step header focus is blurred if the step cannot be selected upon header click. */
function assertStepHeaderBlurred(fixture: ComponentFixture<any>) {
let stepHeaderEl = fixture.debugElement
.queryAll(By.css('mat-step-header'))[1].nativeElement;
spyOn(stepHeaderEl, 'blur');
stepHeaderEl.click();
fixture.detectChanges();
expect(stepHeaderEl.blur).toHaveBeenCalled();
}
/** Asserts that it is only possible to go back to a previous step if the step is editable. */
function assertEditableStepChange(fixture: ComponentFixture<any>) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
stepperComponent.selectedIndex = 1;
stepperComponent._steps.toArray()[0].editable = false;
let previousButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperPrevious))[1].nativeElement;
previousButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(1);
stepperComponent._steps.toArray()[0].editable = true;
previousButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(0);
}
/**
* Asserts that it is possible to skip an optional step in linear stepper if there is no input
* or the input is valid.
*/
function assertOptionalStepValidity(testComponent:
LinearMatHorizontalStepperApp | LinearMatVerticalStepperApp,
fixture: ComponentFixture<any>) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
testComponent.oneGroup.get('oneCtrl')!.setValue('input');
testComponent.twoGroup.get('twoCtrl')!.setValue('input');
testComponent.validationTrigger.next();
stepperComponent.selectedIndex = 2;
fixture.detectChanges();
expect(stepperComponent.selectedIndex).toBe(2);
expect(testComponent.threeGroup.get('threeCtrl')!.valid).toBe(true);
let nextButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperNext))[2].nativeElement;
nextButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent.selectedIndex)
.toBe(3, 'Expected selectedIndex to change when optional step input is empty.');
stepperComponent.selectedIndex = 2;
testComponent.threeGroup.get('threeCtrl')!.setValue('input');
nextButtonNativeEl.click();
fixture.detectChanges();
expect(testComponent.threeGroup.get('threeCtrl')!.valid).toBe(false);
expect(stepperComponent.selectedIndex)
.toBe(2, 'Expected selectedIndex to remain unchanged when optional step input is invalid.');
testComponent.threeGroup.get('threeCtrl')!.setValue('valid');
nextButtonNativeEl.click();
fixture.detectChanges();
expect(testComponent.threeGroup.get('threeCtrl')!.valid).toBe(true);
expect(stepperComponent.selectedIndex)
.toBe(3, 'Expected selectedIndex to change when optional step input is valid.');
}
/** Asserts that step header set the correct icon depending on the state of step. */
function assertCorrectStepIcon(fixture: ComponentFixture<any>,
isEditable: boolean,
icon: String) {
let stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
let nextButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MatStepperNext))[0].nativeElement;
expect(stepperComponent._getIndicatorType(0)).toBe('number');
stepperComponent._steps.toArray()[0].editable = isEditable;
nextButtonNativeEl.click();
fixture.detectChanges();
expect(stepperComponent._getIndicatorType(0)).toBe(icon);
}
function asyncValidator(minLength: number, validationTrigger: Observable<any>): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
return validationTrigger.pipe(
map(() => {
const success = control.value && control.value.length >= minLength;
return success ? null : { 'asyncValidation': {}};
}),
take(1)
);
};
}
@Component({
template: `
<mat-horizontal-stepper>
<mat-step>
<ng-template matStepLabel>Step 1</ng-template>
Content 1
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step>
<ng-template matStepLabel>Step 2</ng-template>
Content 2
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step [label]="inputLabel" optional>
Content 3
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
</mat-horizontal-stepper>
`
})
class SimpleMatHorizontalStepperApp {
inputLabel = 'Step 3';
}
@Component({
template: `
<mat-horizontal-stepper linear>
<mat-step [stepControl]="oneGroup">
<form [formGroup]="oneGroup">
<ng-template matStepLabel>Step one</ng-template>
<input formControlName="oneCtrl" required>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="twoGroup">
<form [formGroup]="twoGroup">
<ng-template matStepLabel>Step two</ng-template>
<input formControlName="twoCtrl" required>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="threeGroup" optional>
<form [formGroup]="threeGroup">
<ng-template matStepLabel>Step two</ng-template>
<input formControlName="threeCtrl">
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step>
Done
</mat-step>
</mat-horizontal-stepper>
`
})
class LinearMatHorizontalStepperApp {
oneGroup: FormGroup;
twoGroup: FormGroup;
threeGroup: FormGroup;
validationTrigger: Subject<any> = new Subject();
ngOnInit() {
this.oneGroup = new FormGroup({
oneCtrl: new FormControl('', Validators.required)
});
this.twoGroup = new FormGroup({
twoCtrl: new FormControl('', Validators.required, asyncValidator(3, this.validationTrigger))
});
this.threeGroup = new FormGroup({
threeCtrl: new FormControl('', Validators.pattern(VALID_REGEX))
});
}
}
@Component({
template: `
<mat-vertical-stepper>
<mat-step>
<ng-template matStepLabel>Step 1</ng-template>
Content 1
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step>
<ng-template matStepLabel>Step 2</ng-template>
Content 2
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step [label]="inputLabel">
Content 3
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
</mat-vertical-stepper>
`
})
class SimpleMatVerticalStepperApp {
inputLabel = 'Step 3';
}
@Component({
template: `
<mat-vertical-stepper linear>
<mat-step [stepControl]="oneGroup">
<form [formGroup]="oneGroup">
<ng-template matStepLabel>Step one</ng-template>
<input formControlName="oneCtrl" required>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="twoGroup">
<form [formGroup]="twoGroup">
<ng-template matStepLabel>Step two</ng-template>
<input formControlName="twoCtrl" required>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="threeGroup" optional>
<form [formGroup]="threeGroup">
<ng-template matStepLabel>Step two</ng-template>
<input formControlName="threeCtrl">
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step>
Done
</mat-step>
</mat-vertical-stepper>
`
})
class LinearMatVerticalStepperApp {
oneGroup: FormGroup;
twoGroup: FormGroup;
threeGroup: FormGroup;
validationTrigger: Subject<any> = new Subject();
ngOnInit() {
this.oneGroup = new FormGroup({
oneCtrl: new FormControl('', Validators.required)
});
this.twoGroup = new FormGroup({
twoCtrl: new FormControl('', Validators.required, asyncValidator(3, this.validationTrigger))
});
this.threeGroup = new FormGroup({
threeCtrl: new FormControl('', Validators.pattern(VALID_REGEX))
});
}
}
@Component({
template: `
<mat-horizontal-stepper [linear]="true" [selectedIndex]="index">
<mat-step label="One"></mat-step>
<mat-step label="Two"></mat-step>
<mat-step label="Three"></mat-step>
</mat-horizontal-stepper>
`
})
class SimplePreselectedMatHorizontalStepperApp {
index = 0;
}