From 5102017b43a28e2b26da7ab45b0a9f253ed4072f Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Mon, 3 Mar 2025 18:32:24 +0000 Subject: [PATCH 01/23] fix(material/stepper): updates vertical-stepper aria roles Updates Angular Component's vertical stepper to use more generic aria roles since having the default tablist/tab/tabpanel applied to the vertical stepper violates WCAG rules of having tabpanel as a nested child within tablist. The new roles of group and region satisfy aria requirements while maintaining the current html structure of the vertical stepper. Fixes b/361783174 --- src/cdk/stepper/step-header.ts | 3 -- src/material/stepper/step-header.ts | 1 - src/material/stepper/stepper.html | 54 ++++++++++++++++------------- src/material/stepper/stepper.ts | 1 - 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/cdk/stepper/step-header.ts b/src/cdk/stepper/step-header.ts index f0709e3f9878..057b496d2ccc 100644 --- a/src/cdk/stepper/step-header.ts +++ b/src/cdk/stepper/step-header.ts @@ -11,9 +11,6 @@ import {FocusableOption} from '../a11y'; @Directive({ selector: '[cdkStepHeader]', - host: { - 'role': 'tab', - }, }) export class CdkStepHeader implements FocusableOption { _elementRef = inject>(ElementRef); diff --git a/src/material/stepper/step-header.ts b/src/material/stepper/step-header.ts index c6f1815e0b5e..c2f1066cdbf3 100644 --- a/src/material/stepper/step-header.ts +++ b/src/material/stepper/step-header.ts @@ -35,7 +35,6 @@ import {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/privat host: { 'class': 'mat-step-header', '[class]': '"mat-" + (color || "primary")', - 'role': 'tab', }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index a75e48522600..80d230f6f90c 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -11,7 +11,7 @@ @switch (orientation) { @case ('horizontal') {
-
+
@for (step of steps; track step) { - -
-
-
- +
+ @for (step of steps; track step) { +
+ +
+
+
+ +
-
- } + } +
} } @@ -70,15 +72,17 @@ Date: Tue, 4 Mar 2025 23:42:18 +0000 Subject: [PATCH 02/23] refactor(material/stepper): remove shared mat-step-header Updates previous fix to create individual mat-step-header tags to be used within the horizontal and vertical steppers to make it easier to edit/understand. --- src/material/stepper/stepper.html | 83 +++++++++++++++++-------------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index 80d230f6f90c..d486eb2b9ef2 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -13,9 +13,31 @@
@for (step of steps; track step) { - + @if (!$last) {
} @@ -43,9 +65,28 @@
@for (step of steps; track step) {
- +
} } - - - - - From 84729bdf1eef54c022a71f8916a6e8f2e6fa3e64 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Wed, 5 Mar 2025 00:00:43 +0000 Subject: [PATCH 03/23] fix(material/stepper): update vertical-stepper aria attributes Updates previous fix to add aria-expanded attribute to vertical stepper to be more descriptive as to when the associated content is open and the respective step is current. --- src/material/stepper/stepper.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index d486eb2b9ef2..a5d7b0b3ecc0 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -62,7 +62,7 @@ } @case ('vertical') { -
+
@for (step of steps; track step) {
Date: Wed, 5 Mar 2025 00:34:00 +0000 Subject: [PATCH 04/23] test(material/stepper): updates role value for stepper.spec.ts Updates stepper.spec.ts to check for region role for the vertical stepper based on the new role updates. --- src/material/stepper/stepper.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index 287f9dcb2327..f2dc84076856 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -132,28 +132,28 @@ describe('MatStepper', () => { expect(stepperComponent.selected instanceof MatStep).toBe(true); }); - it('should set the "tablist" role on stepper', () => { + it('should set the "region" role on the vertical stepper', () => { const stepperEl = fixture.debugElement.query(By.css('mat-stepper'))!.nativeElement; - expect(stepperEl.getAttribute('role')).toBe('tablist'); + expect(stepperEl.getAttribute('role')).toBe('region'); }); it('should display the correct label', () => { const stepperComponent = fixture.debugElement.query( By.directive(MatStepper), )!.componentInstance; - let selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]'); + let selectedLabel = fixture.nativeElement.querySelector('[aria-expanded="true"]'); expect(selectedLabel.textContent).toMatch('Step 1'); stepperComponent.selectedIndex = 2; fixture.detectChanges(); - selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]'); + selectedLabel = fixture.nativeElement.querySelector('[aria-expanded="true"]'); expect(selectedLabel.textContent).toMatch('Step 3'); fixture.componentInstance.inputLabel.set('New Label'); fixture.detectChanges(); - selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]'); + selectedLabel = fixture.nativeElement.querySelector('[aria-expanded="true"]'); expect(selectedLabel.textContent).toMatch('New Label'); }); From 8178b9668aa5f408513625a313b008887974c535 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Wed, 5 Mar 2025 01:28:31 +0000 Subject: [PATCH 05/23] test(material/stepper): update stepper tests Work on fixing failing tests. --- src/material/stepper/stepper.html | 1 + src/material/stepper/stepper.spec.ts | 4 +++- .../stepper/testing/step-harness-filters.ts | 2 ++ src/material/stepper/testing/step-harness.ts | 16 +++++++++++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index a5d7b0b3ecc0..bf2e7da9c597 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -73,6 +73,7 @@ [tabIndex]="_getFocusIndex() === $index ? 0 : -1" [id]="_getStepLabelId($index)" [attr.aria-expanded]="selectedIndex === $index" + [attr.aria-pressed]="selectedIndex == $index" [attr.aria-controls]="_getStepContentId($index)" [attr.aria-current]="selectedIndex == $index" [attr.aria-label]="step.ariaLabel || null" diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index f2dc84076856..c450f93f560f 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -133,7 +133,9 @@ describe('MatStepper', () => { }); it('should set the "region" role on the vertical stepper', () => { - const stepperEl = fixture.debugElement.query(By.css('mat-stepper'))!.nativeElement; + const stepperEl = fixture.debugElement.query( + By.css('mat-vertical-stepper-wrapper'), + )!.nativeElement; expect(stepperEl.getAttribute('role')).toBe('region'); }); diff --git a/src/material/stepper/testing/step-harness-filters.ts b/src/material/stepper/testing/step-harness-filters.ts index b4e5fc3bf26e..ffdacc038774 100644 --- a/src/material/stepper/testing/step-harness-filters.ts +++ b/src/material/stepper/testing/step-harness-filters.ts @@ -19,6 +19,8 @@ export interface StepHarnessFilters extends BaseHarnessFilters { label?: string | RegExp; /** Only find steps with the given selected state. */ selected?: boolean; + /** Only find steps with the given pressed state. */ + pressed?: boolean; /** Only find completed steps. */ completed?: boolean; /** Only find steps that have errors. */ diff --git a/src/material/stepper/testing/step-harness.ts b/src/material/stepper/testing/step-harness.ts index f3af257d6667..de12558d25bd 100644 --- a/src/material/stepper/testing/step-harness.ts +++ b/src/material/stepper/testing/step-harness.ts @@ -34,6 +34,11 @@ export class MatStepHarness extends ContentContainerComponentHarness { options.selected, async (harness, selected) => (await harness.isSelected()) === selected, ) + .addOption( + 'pressed', + options.pressed, + async (harness, pressed) => (await harness.isPressed()) === pressed, + ) .addOption( 'completed', options.completed, @@ -67,10 +72,19 @@ export class MatStepHarness extends ContentContainerComponentHarness { return (await host.getAttribute('aria-selected')) === 'true'; } + /** Whether the step is selected. */ + async isPressed(): Promise { + const host = await this.host(); + return (await host.getAttribute('aria-pressed')) === 'true'; + } + /** Whether the step has been filled out. */ async isCompleted(): Promise { const state = await this._getIconState(); - return state === 'done' || (state === 'edit' && !(await this.isSelected())); + return ( + state === 'done' || + (state === 'edit' && !((await this.isSelected()) || (await this.isPressed()))) + ); } /** From ee4179f27262457260b78b1fd01c1a7b49aa28fd Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Thu, 6 Mar 2025 00:12:03 +0000 Subject: [PATCH 06/23] test(material/stepper): fixes failing stepper tests Updates stepper.spec.ts to match updated aria-roles for vertical stepper so that it is looking for the appropriate roles/attributes. --- src/material/stepper/stepper.html | 6 +++--- src/material/stepper/stepper.spec.ts | 20 ++++++-------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index bf2e7da9c597..1691cb797774 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -62,9 +62,9 @@ } @case ('vertical') { -
+
@for (step of steps; track step) { -
+
{ expect(stepperComponent.selected instanceof MatStep).toBe(true); }); - it('should set the "region" role on the vertical stepper', () => { - const stepperEl = fixture.debugElement.query( - By.css('mat-vertical-stepper-wrapper'), - )!.nativeElement; - expect(stepperEl.getAttribute('role')).toBe('region'); + it('should set the "group" role on a vertical stepper', () => { + const stepperWrapper = fixture.debugElement.query(By.css('.mat-vertical-stepper-wrapper')); + expect(stepperWrapper).toBeTruthy(); + + const stepperEl = stepperWrapper!.nativeElement; + expect(stepperEl.getAttribute('role')).toBe('group'); }); it('should display the correct label', () => { @@ -383,15 +384,6 @@ describe('MatStepper', () => { animationDoneSubscription.unsubscribe(); }); - it('should set the correct aria-posinset and aria-setsize', () => { - const headers = Array.from( - fixture.nativeElement.querySelectorAll('.mat-step-header'), - ); - - expect(headers.map(header => header.getAttribute('aria-posinset'))).toEqual(['1', '2', '3']); - expect(headers.every(header => header.getAttribute('aria-setsize') === '3')).toBe(true); - }); - it('should adjust the index when removing a step before the current one', () => { const stepperComponent: MatStepper = fixture.debugElement.query( By.css('mat-stepper'), From 0c18fd44953baf37b461537d243c0e9cf31e7529 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Fri, 7 Mar 2025 17:07:10 +0000 Subject: [PATCH 07/23] test(material/stepper): revert updated harness files Reverts previously changed step-harness files. --- .../stepper/testing/step-harness-filters.ts | 2 -- src/material/stepper/testing/step-harness.ts | 16 +--------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/material/stepper/testing/step-harness-filters.ts b/src/material/stepper/testing/step-harness-filters.ts index ffdacc038774..b4e5fc3bf26e 100644 --- a/src/material/stepper/testing/step-harness-filters.ts +++ b/src/material/stepper/testing/step-harness-filters.ts @@ -19,8 +19,6 @@ export interface StepHarnessFilters extends BaseHarnessFilters { label?: string | RegExp; /** Only find steps with the given selected state. */ selected?: boolean; - /** Only find steps with the given pressed state. */ - pressed?: boolean; /** Only find completed steps. */ completed?: boolean; /** Only find steps that have errors. */ diff --git a/src/material/stepper/testing/step-harness.ts b/src/material/stepper/testing/step-harness.ts index de12558d25bd..f3af257d6667 100644 --- a/src/material/stepper/testing/step-harness.ts +++ b/src/material/stepper/testing/step-harness.ts @@ -34,11 +34,6 @@ export class MatStepHarness extends ContentContainerComponentHarness { options.selected, async (harness, selected) => (await harness.isSelected()) === selected, ) - .addOption( - 'pressed', - options.pressed, - async (harness, pressed) => (await harness.isPressed()) === pressed, - ) .addOption( 'completed', options.completed, @@ -72,19 +67,10 @@ export class MatStepHarness extends ContentContainerComponentHarness { return (await host.getAttribute('aria-selected')) === 'true'; } - /** Whether the step is selected. */ - async isPressed(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-pressed')) === 'true'; - } - /** Whether the step has been filled out. */ async isCompleted(): Promise { const state = await this._getIconState(); - return ( - state === 'done' || - (state === 'edit' && !((await this.isSelected()) || (await this.isPressed()))) - ); + return state === 'done' || (state === 'edit' && !(await this.isSelected())); } /** From 4d1a5655a21b20ea29b94bc9f710ce2719440318 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Fri, 7 Mar 2025 17:24:53 +0000 Subject: [PATCH 08/23] refactor(material/stepper): remove aria-pressed from vertical stepper Remove unnecessary aria-pressed from vertical stepper. --- src/material/stepper/stepper.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index 1691cb797774..d2618ed55877 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -73,7 +73,6 @@ [tabIndex]="_getFocusIndex() === $index ? 0 : -1" [id]="_getStepLabelId($index)" [attr.aria-expanded]="selectedIndex === $index" - [attr.aria-pressed]="selectedIndex == $index" [attr.aria-controls]="_getStepContentId($index)" [attr.aria-current]="selectedIndex == $index" [attr.aria-label]="step.ariaLabel || null" From bc8d4752d305c88701a5b78d97a647ed10126434 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Fri, 7 Mar 2025 23:30:37 +0000 Subject: [PATCH 09/23] test(material/stepper): update stepper-harness tests Updates stepper-harness tests to make checks depending on whether the stepper is horizontal or vertical and checking the attributes accordingly. --- src/material/stepper/stepper.html | 2 +- .../stepper/testing/step-harness-filters.ts | 4 +- src/material/stepper/testing/step-harness.ts | 13 +- .../stepper/testing/stepper-harness.spec.ts | 125 ++++++++++++++++-- 4 files changed, 128 insertions(+), 16 deletions(-) diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index d2618ed55877..5674e1e4e6a5 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -76,7 +76,7 @@ [attr.aria-controls]="_getStepContentId($index)" [attr.aria-current]="selectedIndex == $index" [attr.aria-label]="step.ariaLabel || null" - [attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : _getStepLabelId($index)" + [attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : null" [attr.aria-disabled]="_stepIsNavigable($index, step) ? null : true" [index]="$index" [state]="_getIndicatorType($index, step.state)" diff --git a/src/material/stepper/testing/step-harness-filters.ts b/src/material/stepper/testing/step-harness-filters.ts index b4e5fc3bf26e..84a2e55e99ba 100644 --- a/src/material/stepper/testing/step-harness-filters.ts +++ b/src/material/stepper/testing/step-harness-filters.ts @@ -17,8 +17,10 @@ export enum StepperOrientation { export interface StepHarnessFilters extends BaseHarnessFilters { /** Only find instances whose label matches the given value. */ label?: string | RegExp; - /** Only find steps with the given selected state. */ + /** Only find steps with the given selected (if Horizontal stepper) state. */ selected?: boolean; + /** Only find steps with the given expanded (if Vertical stepper) state. */ + expanded?: boolean; /** Only find completed steps. */ completed?: boolean; /** Only find steps that have errors. */ diff --git a/src/material/stepper/testing/step-harness.ts b/src/material/stepper/testing/step-harness.ts index f3af257d6667..79b823598e93 100644 --- a/src/material/stepper/testing/step-harness.ts +++ b/src/material/stepper/testing/step-harness.ts @@ -34,6 +34,11 @@ export class MatStepHarness extends ContentContainerComponentHarness { options.selected, async (harness, selected) => (await harness.isSelected()) === selected, ) + .addOption( + 'expanded', + options.expanded, + async (harness, expanded) => (await harness.isExpanded()) === expanded, + ) .addOption( 'completed', options.completed, @@ -61,12 +66,18 @@ export class MatStepHarness extends ContentContainerComponentHarness { return (await this.host()).getAttribute('aria-labelledby'); } - /** Whether the step is selected. */ + /** Whether the step is selected (Horizontal Stepper). */ async isSelected(): Promise { const host = await this.host(); return (await host.getAttribute('aria-selected')) === 'true'; } + /** Whether the step is expanded (Vertical Stepper). */ + async isExpanded(): Promise { + const host = await this.host(); + return (await host.getAttribute('aria-expanded')) === 'true'; + } + /** Whether the step has been filled out. */ async isCompleted(): Promise { const state = await this._getIconState(); diff --git a/src/material/stepper/testing/stepper-harness.spec.ts b/src/material/stepper/testing/stepper-harness.spec.ts index 0627dcd39abb..68c76b40dfbf 100644 --- a/src/material/stepper/testing/stepper-harness.spec.ts +++ b/src/material/stepper/testing/stepper-harness.spec.ts @@ -67,11 +67,11 @@ describe('MatStepperHarness', () => { expect(await parallel(() => steps.map(step => step.getLabel()))).toEqual(['Two', 'Four']); }); - it('should be able to select a particular step that matches a filter', async () => { + it('should be able to select a particular step that matches a filter on a vertical stepper', async () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#one-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ true, false, false, @@ -80,7 +80,7 @@ describe('MatStepperHarness', () => { await stepper.selectStep({label: 'Three'}); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, false, true, @@ -88,6 +88,25 @@ describe('MatStepperHarness', () => { ]); }); + it('should be able to select a particular step that matches a filter on a horizontal stepper', async () => { + const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); + const steps = await stepper.getSteps(); + + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + true, + false, + false, + ]); + + await stepper.selectStep({label: 'Three'}); + + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + false, + true, + ]); + }); + it('should be able to get the text-based label of a step', async () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#one-stepper'})); const steps = await stepper.getSteps(); @@ -132,11 +151,11 @@ describe('MatStepperHarness', () => { ]); }); - it('should get the selected state of a step', async () => { + it('should get the expanded state of a step in a vertical stepper', async () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#one-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ true, false, false, @@ -144,20 +163,31 @@ describe('MatStepperHarness', () => { ]); }); - it('should be able to select a step', async () => { - const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#one-stepper'})); + it('should get the selected state of a step in a horizontal stepper', async () => { + const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ true, false, false, + ]); + }); + + it('should be able to select a step in a vertical stepper', async () => { + const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#one-stepper'})); + const steps = await stepper.getSteps(); + + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + true, + false, + false, false, ]); await steps[2].select(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, false, true, @@ -165,6 +195,25 @@ describe('MatStepperHarness', () => { ]); }); + it('should be able to select a step in a horizontal stepper', async () => { + const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); + const steps = await stepper.getSteps(); + + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + true, + false, + false, + ]); + + await steps[2].select(); + + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + false, + true, + ]); + }); + it('should get whether a step is optional', async () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); @@ -183,7 +232,7 @@ describe('MatStepperHarness', () => { expect(await previousButton.getText()).toBe('Previous'); }); - it('should go forward when pressing the next button', async () => { + it('should go forward when pressing the next button in a vertical stepper', async () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#one-stepper'})); const steps = await stepper.getSteps(); const secondStep = steps[1]; @@ -191,7 +240,7 @@ describe('MatStepperHarness', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, true, false, @@ -200,7 +249,7 @@ describe('MatStepperHarness', () => { await nextButton.click(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, false, true, @@ -208,7 +257,30 @@ describe('MatStepperHarness', () => { ]); }); - it('should go backward when pressing the previous button', async () => { + it('should go forward when pressing the next button in a horizontal stepper', async () => { + const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); + const steps = await stepper.getSteps(); + const secondStep = steps[1]; + const nextButton = await secondStep.getHarness(MatStepperNextHarness); + + await secondStep.select(); + + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + true, + false, + ]); + + await nextButton.click(); + + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + false, + true, + ]); + }); + + it('should go backward when pressing the previous button of a vertical stepper', async () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#one-stepper'})); const steps = await stepper.getSteps(); const secondStep = steps[1]; @@ -216,7 +288,7 @@ describe('MatStepperHarness', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, true, false, @@ -225,9 +297,32 @@ describe('MatStepperHarness', () => { await previousButton.click(); + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + true, + false, + false, + false, + ]); + }); + + it('should go backward when pressing the previous button of a horizontal stepper', async () => { + const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); + const steps = await stepper.getSteps(); + const secondStep = steps[1]; + const previousButton = await secondStep.getHarness(MatStepperPreviousHarness); + + await secondStep.select(); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, true, false, + ]); + + await previousButton.click(); + + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + true, false, false, ]); @@ -279,12 +374,16 @@ describe('MatStepperHarness', () => { One + Two + + Three + From 42d8ffb45006a1176a34c054fa89d82157f7aa9f Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Tue, 11 Mar 2025 01:03:58 +0000 Subject: [PATCH 10/23] build(material/stepper): update stepper api golden tests Ran command to update api golden checks with new stepper tests. --- goldens/material/stepper/testing/index.api.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/goldens/material/stepper/testing/index.api.md b/goldens/material/stepper/testing/index.api.md index 800b19934610..d9f1d5681930 100644 --- a/goldens/material/stepper/testing/index.api.md +++ b/goldens/material/stepper/testing/index.api.md @@ -20,6 +20,7 @@ export class MatStepHarness extends ContentContainerComponentHarness { hasErrors(): Promise; static hostSelector: string; isCompleted(): Promise; + isExpanded(): Promise; isOptional(): Promise; isSelected(): Promise; select(): Promise; @@ -50,6 +51,7 @@ export class MatStepperPreviousHarness extends StepperButtonHarness { // @public export interface StepHarnessFilters extends BaseHarnessFilters { completed?: boolean; + expanded?: boolean; invalid?: boolean; label?: string | RegExp; selected?: boolean; From 93b6b25f02ce95a4a63e2626bd6dd8134dc15d93 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Fri, 14 Mar 2025 00:27:37 +0000 Subject: [PATCH 11/23] refactor(material/stepper): update code to use ng-container Updates previous fix to use an ng-template instead of using mat-step-header twice. --- src/material/stepper/stepper.html | 85 ++++++++++++++----------------- 1 file changed, 37 insertions(+), 48 deletions(-) diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index 5674e1e4e6a5..44713f073060 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -13,31 +13,9 @@
@for (step of steps; track step) { - + @if (!$last) {
} @@ -65,29 +43,9 @@
@for (step of steps; track step) {
- +
} } + + + + + \ No newline at end of file From cf968c5a9ad27d8d3fd8520e97bdaf8c68caecf3 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Mon, 17 Mar 2025 16:18:40 +0000 Subject: [PATCH 12/23] refactor(material/stepper): updates stepper to use same aria-roles pattern as vertical Updates previous fix so that the aria-roles are cohesive between horizontal/vertical to simplify the patterns. --- src/cdk/stepper/step-header.ts | 3 +++ src/material/stepper/stepper.html | 15 ++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cdk/stepper/step-header.ts b/src/cdk/stepper/step-header.ts index 057b496d2ccc..26ebcbbcb0f7 100644 --- a/src/cdk/stepper/step-header.ts +++ b/src/cdk/stepper/step-header.ts @@ -11,6 +11,9 @@ import {FocusableOption} from '../a11y'; @Directive({ selector: '[cdkStepHeader]', + host: { + 'role': 'button', + }, }) export class CdkStepHeader implements FocusableOption { _elementRef = inject>(ElementRef); diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index 44713f073060..fc33b97d9a87 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -10,8 +10,8 @@ @switch (orientation) { @case ('horizontal') { -
-
+
+
@for (step of steps; track step) { +
@for (step of steps; track step) {
Date: Mon, 17 Mar 2025 16:22:32 +0000 Subject: [PATCH 13/23] docs(material/stepper): updates docs to use new aria roles Updates previous documentation so it uses the correct/updated aria roles of , , and with the attribute. --- src/cdk/stepper/stepper.md | 10 +++++----- src/material/stepper/stepper.md | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cdk/stepper/stepper.md b/src/cdk/stepper/stepper.md index 8eee8df85730..6052dbccb8ac 100644 --- a/src/cdk/stepper/stepper.md +++ b/src/cdk/stepper/stepper.md @@ -59,10 +59,10 @@ resetting it will call `reset` on the underlying form control which clears the v ### Accessibility Apart from the built-in keyboard support, the stepper doesn't apply any treatment. When implementing -your own component, it is recommended that the stepper is treated as a tabbed view for accessibility -purposes by giving it a `role="tablist"`. The header of step that can be clicked to select the step -should be given `role="tab"`, and the content that can be expanded upon selection should be given -`role="tabpanel"`. Furthermore, the step header should have an `aria-selected` attribute that -reflects its selected state. +your own component, it is recommended that the stepper is treated as a grouped view for accessibility +purposes by giving it a `role="group"`. The header of step that can be clicked to select the step +should be given `role="button"`, and the content that can be expanded upon selection should be given +`role="region"`. Furthermore, the step header should have an `aria-expanded` attribute that +reflects its expanded state. You can refer to the [Angular Material stepper](https://github.com/angular/components/tree/main/src/material/stepper) as an example of an accessible implementation. diff --git a/src/material/stepper/stepper.md b/src/material/stepper/stepper.md index b8c9f1602175..593ab14f6780 100644 --- a/src/material/stepper/stepper.md +++ b/src/material/stepper/stepper.md @@ -228,10 +228,10 @@ export class MyApp {} ### Accessibility -The stepper is treated as a tabbed view for accessibility purposes, so it is given -`role="tablist"` by default. The header of step that can be clicked to select the step -is given `role="tab"`, and the content that can be expanded upon selection is given -`role="tabpanel"`. `aria-selected` attribute of step header is automatically set based on +The stepper is treated as a grouped view for general accessibility purposes, so it is given +`role="group"` by default. The header of step that can be clicked to select the step +is given `role="button"`, and the content that can be expanded upon selection is given +`role="region"`. `aria-expanded` attribute of step header is automatically set based on step selection change. The stepper and each step should be given a meaningful label via `aria-label` or `aria-labelledby`. From 5fdc4b2fe8c1dfcf007004335f5978782e398f3e Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Mon, 17 Mar 2025 16:24:08 +0000 Subject: [PATCH 14/23] test(material/stepper): updates tests affected by new stepper aria roles & attributes Updates all tests that are affected by the updated stepper aria roles and attributes. --- .../stepper-harness-example.spec.ts | 4 ++-- src/material/stepper/stepper.spec.ts | 4 ++-- .../stepper/testing/step-harness-filters.ts | 4 +--- src/material/stepper/testing/step-harness.ts | 17 +++-------------- .../stepper/testing/stepper-harness.spec.ts | 18 +++++++++--------- 5 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts b/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts index ac0a4ba48213..6453ef02e081 100644 --- a/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts +++ b/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts @@ -47,7 +47,7 @@ describe('StepperHarnessExample', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, true, false, @@ -55,7 +55,7 @@ describe('StepperHarnessExample', () => { await nextButton.click(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, false, true, diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index f0e7b62268bd..57af46738d3e 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -132,8 +132,8 @@ describe('MatStepper', () => { expect(stepperComponent.selected instanceof MatStep).toBe(true); }); - it('should set the "group" role on a vertical stepper', () => { - const stepperWrapper = fixture.debugElement.query(By.css('.mat-vertical-stepper-wrapper')); + it('should set the "group" role on the stepper', () => { + const stepperWrapper = fixture.debugElement.query(By.css('.mat-stepper-wrapper')); expect(stepperWrapper).toBeTruthy(); const stepperEl = stepperWrapper!.nativeElement; diff --git a/src/material/stepper/testing/step-harness-filters.ts b/src/material/stepper/testing/step-harness-filters.ts index 84a2e55e99ba..10e497087a96 100644 --- a/src/material/stepper/testing/step-harness-filters.ts +++ b/src/material/stepper/testing/step-harness-filters.ts @@ -17,9 +17,7 @@ export enum StepperOrientation { export interface StepHarnessFilters extends BaseHarnessFilters { /** Only find instances whose label matches the given value. */ label?: string | RegExp; - /** Only find steps with the given selected (if Horizontal stepper) state. */ - selected?: boolean; - /** Only find steps with the given expanded (if Vertical stepper) state. */ + /** Only find steps with the given expanded state. */ expanded?: boolean; /** Only find completed steps. */ completed?: boolean; diff --git a/src/material/stepper/testing/step-harness.ts b/src/material/stepper/testing/step-harness.ts index 79b823598e93..9a20ae364736 100644 --- a/src/material/stepper/testing/step-harness.ts +++ b/src/material/stepper/testing/step-harness.ts @@ -29,11 +29,6 @@ export class MatStepHarness extends ContentContainerComponentHarness { .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label), ) - .addOption( - 'selected', - options.selected, - async (harness, selected) => (await harness.isSelected()) === selected, - ) .addOption( 'expanded', options.expanded, @@ -66,13 +61,7 @@ export class MatStepHarness extends ContentContainerComponentHarness { return (await this.host()).getAttribute('aria-labelledby'); } - /** Whether the step is selected (Horizontal Stepper). */ - async isSelected(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-selected')) === 'true'; - } - - /** Whether the step is expanded (Vertical Stepper). */ + /** Whether the step is expanded. */ async isExpanded(): Promise { const host = await this.host(); return (await host.getAttribute('aria-expanded')) === 'true'; @@ -81,7 +70,7 @@ export class MatStepHarness extends ContentContainerComponentHarness { /** Whether the step has been filled out. */ async isCompleted(): Promise { const state = await this._getIconState(); - return state === 'done' || (state === 'edit' && !(await this.isSelected())); + return state === 'done' || (state === 'edit' && !(await this.isExpanded())); } /** @@ -103,7 +92,7 @@ export class MatStepHarness extends ContentContainerComponentHarness { } /** - * Selects the given step by clicking on the label. The step may not be selected + * Selects the given step by clicking on the label. The step may not be selected/expanded * if the stepper doesn't allow it (e.g. if there are validation errors). */ async select(): Promise { diff --git a/src/material/stepper/testing/stepper-harness.spec.ts b/src/material/stepper/testing/stepper-harness.spec.ts index 68c76b40dfbf..2fec401adc7d 100644 --- a/src/material/stepper/testing/stepper-harness.spec.ts +++ b/src/material/stepper/testing/stepper-harness.spec.ts @@ -92,7 +92,7 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ true, false, false, @@ -100,7 +100,7 @@ describe('MatStepperHarness', () => { await stepper.selectStep({label: 'Three'}); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, false, true, @@ -167,7 +167,7 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ true, false, false, @@ -199,7 +199,7 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ true, false, false, @@ -207,7 +207,7 @@ describe('MatStepperHarness', () => { await steps[2].select(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, false, true, @@ -265,7 +265,7 @@ describe('MatStepperHarness', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, true, false, @@ -273,7 +273,7 @@ describe('MatStepperHarness', () => { await nextButton.click(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, false, true, @@ -313,7 +313,7 @@ describe('MatStepperHarness', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ false, true, false, @@ -321,7 +321,7 @@ describe('MatStepperHarness', () => { await previousButton.click(); - expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ true, false, false, From be3fc76a8c41341d26347e9754799e5aa9cc6ced Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Mon, 17 Mar 2025 17:55:25 +0000 Subject: [PATCH 15/23] build(material/stepper): update stepper api goldens Ran command to update API goldens. --- goldens/material/stepper/testing/index.api.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/goldens/material/stepper/testing/index.api.md b/goldens/material/stepper/testing/index.api.md index d9f1d5681930..2eb03a128783 100644 --- a/goldens/material/stepper/testing/index.api.md +++ b/goldens/material/stepper/testing/index.api.md @@ -22,7 +22,6 @@ export class MatStepHarness extends ContentContainerComponentHarness { isCompleted(): Promise; isExpanded(): Promise; isOptional(): Promise; - isSelected(): Promise; select(): Promise; static with(options?: StepHarnessFilters): HarnessPredicate; } @@ -54,7 +53,6 @@ export interface StepHarnessFilters extends BaseHarnessFilters { expanded?: boolean; invalid?: boolean; label?: string | RegExp; - selected?: boolean; } // @public From 9a96219b422b517a247e1d172c510e27d5e95390 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Mon, 17 Mar 2025 23:16:40 +0000 Subject: [PATCH 16/23] refactor(material/stepper): update aria-attributes for horizontal stepper Updates previous changes to only use aria-expanded for vertical stepper and to add aria-pressed and aria-current values depending on the selectedIndex value. --- src/material/stepper/stepper.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index fc33b97d9a87..d2251fa1f731 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -72,13 +72,14 @@ Date: Mon, 17 Mar 2025 23:19:00 +0000 Subject: [PATCH 17/23] test(material/stepper): update tests based on new stepper aria-attributes Updates stepper tests based on the new aria-attributes that were added to the stepper, particularly for the horizontal stepper. --- .../stepper-harness-example.spec.ts | 12 +---- .../stepper/testing/step-harness-filters.ts | 4 +- src/material/stepper/testing/step-harness.ts | 15 +++++- .../stepper/testing/stepper-harness.spec.ts | 54 ++++--------------- 4 files changed, 27 insertions(+), 58 deletions(-) diff --git a/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts b/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts index 6453ef02e081..21332cb9db6b 100644 --- a/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts +++ b/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts @@ -47,18 +47,10 @@ describe('StepperHarnessExample', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - false, - true, - false, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, true, false]); await nextButton.click(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - false, - false, - true, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, false, true]); }); }); diff --git a/src/material/stepper/testing/step-harness-filters.ts b/src/material/stepper/testing/step-harness-filters.ts index 10e497087a96..50951354afe5 100644 --- a/src/material/stepper/testing/step-harness-filters.ts +++ b/src/material/stepper/testing/step-harness-filters.ts @@ -17,7 +17,9 @@ export enum StepperOrientation { export interface StepHarnessFilters extends BaseHarnessFilters { /** Only find instances whose label matches the given value. */ label?: string | RegExp; - /** Only find steps with the given expanded state. */ + /** Only find steps with the given expanded for Horizontal Stepper state. */ + pressed?: boolean; + /** Only find steps with the given expanded for Vertical Stepper state. */ expanded?: boolean; /** Only find completed steps. */ completed?: boolean; diff --git a/src/material/stepper/testing/step-harness.ts b/src/material/stepper/testing/step-harness.ts index 9a20ae364736..5d9cce18b6cf 100644 --- a/src/material/stepper/testing/step-harness.ts +++ b/src/material/stepper/testing/step-harness.ts @@ -29,6 +29,11 @@ export class MatStepHarness extends ContentContainerComponentHarness { .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label), ) + .addOption( + 'pressed', + options.pressed, + async (harness, pressed) => (await harness.isPressed()) === pressed, + ) .addOption( 'expanded', options.expanded, @@ -61,7 +66,13 @@ export class MatStepHarness extends ContentContainerComponentHarness { return (await this.host()).getAttribute('aria-labelledby'); } - /** Whether the step is expanded. */ + /** Whether the step of Horizontal Stepper is pressed. */ + async isPressed(): Promise { + const host = await this.host(); + return (await host.getAttribute('aria-pressed')) === 'true'; + } + + /** Whether the step of Vertical Stepper is expanded. */ async isExpanded(): Promise { const host = await this.host(); return (await host.getAttribute('aria-expanded')) === 'true'; @@ -70,7 +81,7 @@ export class MatStepHarness extends ContentContainerComponentHarness { /** Whether the step has been filled out. */ async isCompleted(): Promise { const state = await this._getIconState(); - return state === 'done' || (state === 'edit' && !(await this.isExpanded())); + return state === 'done' || (state === 'edit' && !(await this.isPressed())); } /** diff --git a/src/material/stepper/testing/stepper-harness.spec.ts b/src/material/stepper/testing/stepper-harness.spec.ts index 2fec401adc7d..e562209bfa62 100644 --- a/src/material/stepper/testing/stepper-harness.spec.ts +++ b/src/material/stepper/testing/stepper-harness.spec.ts @@ -92,19 +92,11 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - true, - false, - false, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([true, false, false]); await stepper.selectStep({label: 'Three'}); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - false, - false, - true, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, false, true]); }); it('should be able to get the text-based label of a step', async () => { @@ -167,11 +159,7 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - true, - false, - false, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([true, false, false]); }); it('should be able to select a step in a vertical stepper', async () => { @@ -199,19 +187,11 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - true, - false, - false, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([true, false, false]); await steps[2].select(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - false, - false, - true, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, false, true]); }); it('should get whether a step is optional', async () => { @@ -265,19 +245,11 @@ describe('MatStepperHarness', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - false, - true, - false, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, true, false]); await nextButton.click(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - false, - false, - true, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, false, true]); }); it('should go backward when pressing the previous button of a vertical stepper', async () => { @@ -313,19 +285,11 @@ describe('MatStepperHarness', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - false, - true, - false, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, true, false]); await previousButton.click(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ - true, - false, - false, - ]); + expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([true, false, false]); }); it('should get whether a step has errors', async () => { From 27226d07ee36e1ddf68bed0d69e14fb7f55c2af1 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Mon, 17 Mar 2025 23:37:13 +0000 Subject: [PATCH 18/23] build(material/stepper): update api goldens Ran command to update api goldens. --- goldens/material/stepper/testing/index.api.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/goldens/material/stepper/testing/index.api.md b/goldens/material/stepper/testing/index.api.md index 2eb03a128783..83e7fc5cfeb5 100644 --- a/goldens/material/stepper/testing/index.api.md +++ b/goldens/material/stepper/testing/index.api.md @@ -22,6 +22,7 @@ export class MatStepHarness extends ContentContainerComponentHarness { isCompleted(): Promise; isExpanded(): Promise; isOptional(): Promise; + isPressed(): Promise; select(): Promise; static with(options?: StepHarnessFilters): HarnessPredicate; } @@ -53,6 +54,7 @@ export interface StepHarnessFilters extends BaseHarnessFilters { expanded?: boolean; invalid?: boolean; label?: string | RegExp; + pressed?: boolean; } // @public From 5d9f3b554d272bda221435d1d98a3ca833381550 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Mon, 24 Mar 2025 20:41:32 +0000 Subject: [PATCH 19/23] test(material/stepper): reset tests to use isSelected Updates previous changes to tests which used isPressed and isExpanded to simplify to isSelected instead. --- .../stepper-harness-example.spec.ts | 12 +++- .../stepper/testing/step-harness-filters.ts | 6 +- src/material/stepper/testing/step-harness.ts | 23 ++---- .../stepper/testing/stepper-harness.spec.ts | 72 ++++++++++++++----- 4 files changed, 72 insertions(+), 41 deletions(-) diff --git a/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts b/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts index 21332cb9db6b..ac0a4ba48213 100644 --- a/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts +++ b/src/components-examples/material/stepper/stepper-harness/stepper-harness-example.spec.ts @@ -47,10 +47,18 @@ describe('StepperHarnessExample', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, true, false]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + true, + false, + ]); await nextButton.click(); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, false, true]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + false, + true, + ]); }); }); diff --git a/src/material/stepper/testing/step-harness-filters.ts b/src/material/stepper/testing/step-harness-filters.ts index 50951354afe5..ead2e99659dd 100644 --- a/src/material/stepper/testing/step-harness-filters.ts +++ b/src/material/stepper/testing/step-harness-filters.ts @@ -17,10 +17,8 @@ export enum StepperOrientation { export interface StepHarnessFilters extends BaseHarnessFilters { /** Only find instances whose label matches the given value. */ label?: string | RegExp; - /** Only find steps with the given expanded for Horizontal Stepper state. */ - pressed?: boolean; - /** Only find steps with the given expanded for Vertical Stepper state. */ - expanded?: boolean; + /** Only find steps with the given pressed/expanded for Stepper state. */ + selected?: boolean; /** Only find completed steps. */ completed?: boolean; /** Only find steps that have errors. */ diff --git a/src/material/stepper/testing/step-harness.ts b/src/material/stepper/testing/step-harness.ts index 5d9cce18b6cf..e74b874ee0ca 100644 --- a/src/material/stepper/testing/step-harness.ts +++ b/src/material/stepper/testing/step-harness.ts @@ -30,14 +30,9 @@ export class MatStepHarness extends ContentContainerComponentHarness { HarnessPredicate.stringMatches(harness.getLabel(), label), ) .addOption( - 'pressed', - options.pressed, - async (harness, pressed) => (await harness.isPressed()) === pressed, - ) - .addOption( - 'expanded', - options.expanded, - async (harness, expanded) => (await harness.isExpanded()) === expanded, + 'selected', + options.selected, + async (harness, selected) => (await harness.isSelected()) === selected, ) .addOption( 'completed', @@ -66,22 +61,16 @@ export class MatStepHarness extends ContentContainerComponentHarness { return (await this.host()).getAttribute('aria-labelledby'); } - /** Whether the step of Horizontal Stepper is pressed. */ - async isPressed(): Promise { + /** Whether the step of Stepper is pressed/expanded. */ + async isSelected(): Promise { const host = await this.host(); return (await host.getAttribute('aria-pressed')) === 'true'; } - /** Whether the step of Vertical Stepper is expanded. */ - async isExpanded(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-expanded')) === 'true'; - } - /** Whether the step has been filled out. */ async isCompleted(): Promise { const state = await this._getIconState(); - return state === 'done' || (state === 'edit' && !(await this.isPressed())); + return state === 'done' || (state === 'edit' && !(await this.isSelected())); } /** diff --git a/src/material/stepper/testing/stepper-harness.spec.ts b/src/material/stepper/testing/stepper-harness.spec.ts index e562209bfa62..2115ebd2c4c0 100644 --- a/src/material/stepper/testing/stepper-harness.spec.ts +++ b/src/material/stepper/testing/stepper-harness.spec.ts @@ -71,7 +71,7 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#one-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ true, false, false, @@ -80,7 +80,7 @@ describe('MatStepperHarness', () => { await stepper.selectStep({label: 'Three'}); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ false, false, true, @@ -92,11 +92,19 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([true, false, false]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + true, + false, + false, + ]); await stepper.selectStep({label: 'Three'}); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, false, true]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + false, + true, + ]); }); it('should be able to get the text-based label of a step', async () => { @@ -147,7 +155,7 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#one-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ true, false, false, @@ -159,14 +167,18 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([true, false, false]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + true, + false, + false, + ]); }); it('should be able to select a step in a vertical stepper', async () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#one-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ true, false, false, @@ -175,7 +187,7 @@ describe('MatStepperHarness', () => { await steps[2].select(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ false, false, true, @@ -187,11 +199,19 @@ describe('MatStepperHarness', () => { const stepper = await loader.getHarness(MatStepperHarness.with({selector: '#two-stepper'})); const steps = await stepper.getSteps(); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([true, false, false]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + true, + false, + false, + ]); await steps[2].select(); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, false, true]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + false, + true, + ]); }); it('should get whether a step is optional', async () => { @@ -220,7 +240,7 @@ describe('MatStepperHarness', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ false, true, false, @@ -229,7 +249,7 @@ describe('MatStepperHarness', () => { await nextButton.click(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ false, false, true, @@ -245,11 +265,19 @@ describe('MatStepperHarness', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, true, false]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + true, + false, + ]); await nextButton.click(); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, false, true]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + false, + true, + ]); }); it('should go backward when pressing the previous button of a vertical stepper', async () => { @@ -260,7 +288,7 @@ describe('MatStepperHarness', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ false, true, false, @@ -269,7 +297,7 @@ describe('MatStepperHarness', () => { await previousButton.click(); - expect(await parallel(() => steps.map(step => step.isExpanded()))).toEqual([ + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ true, false, false, @@ -285,11 +313,19 @@ describe('MatStepperHarness', () => { await secondStep.select(); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([false, true, false]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + false, + true, + false, + ]); await previousButton.click(); - expect(await parallel(() => steps.map(step => step.isPressed()))).toEqual([true, false, false]); + expect(await parallel(() => steps.map(step => step.isSelected()))).toEqual([ + true, + false, + false, + ]); }); it('should get whether a step has errors', async () => { From d89fa45eca17252cb01f9b230f90dd83cd7b1e02 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Fri, 28 Mar 2025 22:50:29 +0000 Subject: [PATCH 20/23] fix(material/stepper): add default role to material/stepper/step-header.ts Updates to add default role of button to material/steppper/step-header.ts --- src/material/stepper/step-header.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/material/stepper/step-header.ts b/src/material/stepper/step-header.ts index c2f1066cdbf3..f2c56ea7d025 100644 --- a/src/material/stepper/step-header.ts +++ b/src/material/stepper/step-header.ts @@ -35,6 +35,7 @@ import {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/privat host: { 'class': 'mat-step-header', '[class]': '"mat-" + (color || "primary")', + 'role': 'button', }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, From 55f47e7c1e0997926cad105c44c8d33b6b08701e Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Tue, 1 Apr 2025 16:33:46 +0000 Subject: [PATCH 21/23] fix(material/stepper): add data-test-index attribute to improve unit testing Updates previous fix to add the data-test-index attribute to the step-header to improve unit testing for all developers working with the stepper. --- src/material/stepper/stepper.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index d2251fa1f731..46c5c6120bed 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -84,6 +84,7 @@ [attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : null" [attr.aria-disabled]="_stepIsNavigable(i, step) ? null : true" [attr.aria-owns]="_getStepContentId(i)" + [attr.data-test-index]="i" [index]="i" [state]="_getIndicatorType(i, step.state)" [label]="step.stepLabel || step.label" From 8270c460240f1c0f1d436bd0c1e19a669f949e8e Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Tue, 1 Apr 2025 17:30:23 +0000 Subject: [PATCH 22/23] refactor(material/stepper): update naming convention for new index Updates previous change to change naming convention for index to indicate that it is from/for Material Components. --- src/material/stepper/stepper.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index 46c5c6120bed..ea234279a9b1 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -84,7 +84,7 @@ [attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : null" [attr.aria-disabled]="_stepIsNavigable(i, step) ? null : true" [attr.aria-owns]="_getStepContentId(i)" - [attr.data-test-index]="i" + [attr.mat-step-index]="i" [index]="i" [state]="_getIndicatorType(i, step.state)" [label]="step.stepLabel || step.label" From 1ce35e17bf576bcfb6c90fb0549978a86909e998 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Tue, 1 Apr 2025 18:45:39 +0000 Subject: [PATCH 23/23] build(material/stepper): update material api goldens Ran command to update material api goldens to fix failures. --- goldens/material/stepper/testing/index.api.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/goldens/material/stepper/testing/index.api.md b/goldens/material/stepper/testing/index.api.md index 83e7fc5cfeb5..800b19934610 100644 --- a/goldens/material/stepper/testing/index.api.md +++ b/goldens/material/stepper/testing/index.api.md @@ -20,9 +20,8 @@ export class MatStepHarness extends ContentContainerComponentHarness { hasErrors(): Promise; static hostSelector: string; isCompleted(): Promise; - isExpanded(): Promise; isOptional(): Promise; - isPressed(): Promise; + isSelected(): Promise; select(): Promise; static with(options?: StepHarnessFilters): HarnessPredicate; } @@ -51,10 +50,9 @@ export class MatStepperPreviousHarness extends StepperButtonHarness { // @public export interface StepHarnessFilters extends BaseHarnessFilters { completed?: boolean; - expanded?: boolean; invalid?: boolean; label?: string | RegExp; - pressed?: boolean; + selected?: boolean; } // @public