Skip to content

Commit

Permalink
fix(stepper): not picking up indirect descendant elements (#17529)
Browse files Browse the repository at this point in the history
Fixes the stepper not picking up indirect descendant steps, icon overrides and step headers.
  • Loading branch information
crisbeto authored and mmalerba committed Oct 30, 2019
1 parent ee863b2 commit 5f0f2dc
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 4 deletions.
4 changes: 2 additions & 2 deletions src/cdk/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
* @deprecated use `steps` instead
* @breaking-change 9.0.0 remove this property
*/
@ContentChildren(CdkStep) _steps: QueryList<CdkStep>;
@ContentChildren(CdkStep, {descendants: true}) _steps: QueryList<CdkStep>;

/**
* We need to store the steps in an Iterable due to strict template type checking with *ngFor and
Expand All @@ -273,7 +273,7 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
* @deprecated Type to be changed to `QueryList<CdkStepHeader>`.
* @breaking-change 8.0.0
*/
@ContentChildren(CdkStepHeader) _stepHeader: QueryList<FocusableOption>;
@ContentChildren(CdkStepHeader, {descendants: true}) _stepHeader: QueryList<FocusableOption>;

/** Whether the validity of previous steps should be checked or not. */
@Input()
Expand Down
108 changes: 108 additions & 0 deletions src/material/stepper/stepper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,79 @@ describe('MatStepper', () => {
expect(stepper._getIndicatorType(1)).toBe(STEP_STATE.EDIT);
});
});

describe('indirect descendants', () => {
it('should be able to change steps when steps are indirect descendants', () => {
const fixture = createComponent(StepperWithIndirectDescendantSteps);
fixture.detectChanges();

const stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
const 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);
});

it('should allow for the `edit` icon to be overridden', () => {
const fixture = createComponent(IndirectDescendantIconOverridesStepper);
fixture.detectChanges();

const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
const stepperComponent: MatStepper = stepperDebugElement.componentInstance;

stepperComponent.steps.toArray()[0].editable = true;
stepperComponent.next();
fixture.detectChanges();

const header = stepperDebugElement.nativeElement.querySelector('mat-step-header');

expect(header.textContent).toContain('Custom edit');
});

it('should allow for the `done` icon to be overridden', () => {
const fixture = createComponent(IndirectDescendantIconOverridesStepper);
fixture.detectChanges();

const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
const stepperComponent: MatStepper = stepperDebugElement.componentInstance;

stepperComponent.steps.toArray()[0].editable = false;
stepperComponent.next();
fixture.detectChanges();

const header = stepperDebugElement.nativeElement.querySelector('mat-step-header');

expect(header.textContent).toContain('Custom done');
});

it('should allow for the `number` icon to be overridden with context', () => {
const fixture = createComponent(IndirectDescendantIconOverridesStepper);
fixture.detectChanges();

const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
const headers = stepperDebugElement.nativeElement.querySelectorAll('mat-step-header');

expect(headers[2].textContent).toContain('III');
});

});
});

/** Asserts that keyboard interaction works correctly. */
Expand Down Expand Up @@ -1510,6 +1583,26 @@ class IconOverridesStepper {
}
}

@Component({
template: `
<mat-horizontal-stepper>
<ng-container [ngSwitch]="true">
<ng-template matStepperIcon="edit">Custom edit</ng-template>
<ng-template matStepperIcon="done">Custom done</ng-template>
<ng-template matStepperIcon="number" let-index="index">
{{getRomanNumeral(index + 1)}}
</ng-template>
</ng-container>
<mat-step>Content 1</mat-step>
<mat-step>Content 2</mat-step>
<mat-step>Content 3</mat-step>
</mat-horizontal-stepper>
`
})
class IndirectDescendantIconOverridesStepper extends IconOverridesStepper {
}

@Component({
template: `
<mat-horizontal-stepper linear>
Expand All @@ -1536,3 +1629,18 @@ class StepperWithAriaInputs {
ariaLabel: string;
ariaLabelledby: string;
}


@Component({
template: `
<mat-vertical-stepper>
<ng-container [ngSwitch]="true">
<mat-step label="Step 1">Content 1</mat-step>
<mat-step label="Step 2">Content 2</mat-step>
<mat-step label="Step 3">Content 3</mat-step>
</ng-container>
</mat-vertical-stepper>
`
})
class StepperWithIndirectDescendantSteps {
}
4 changes: 2 additions & 2 deletions src/material/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
@ViewChildren(MatStepHeader) _stepHeader: QueryList<MatStepHeader>;

/** Steps that the stepper holds. */
@ContentChildren(MatStep) _steps: QueryList<MatStep>;
@ContentChildren(MatStep, {descendants: true}) _steps: QueryList<MatStep>;

/** Custom icon overrides passed in by the consumer. */
@ContentChildren(MatStepperIcon) _icons: QueryList<MatStepperIcon>;
@ContentChildren(MatStepperIcon, {descendants: true}) _icons: QueryList<MatStepperIcon>;

/** Event emitted when the current step is done transitioning in. */
@Output() readonly animationDone: EventEmitter<void> = new EventEmitter<void>();
Expand Down

0 comments on commit 5f0f2dc

Please sign in to comment.