From 577dd546a471dfd3335f0e8eff86ded9eeef7164 Mon Sep 17 00:00:00 2001 From: AleksanderSklorz <115619721+AleksanderSklorz@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:34:26 +0200 Subject: [PATCH] [ACS-11350] keyboard navigation user incorrect focus order after collapsing the left navigation menu (#11835) * [ACS-11350] Fixed focus after collapsing navigation menu * [ACS-11350] Unit tests * [ACS-11350] Addressed copilot comments * [ACS-11350] Addressed copilot comment * [ACS-11350] Addressed copilot comment --- .../layout-container.component.html | 2 +- .../layout-container.component.spec.ts | 49 +++++++++++++------ .../layout-container.component.ts | 1 + .../src/lib/testing/unit-testing-utils.ts | 7 +++ 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/lib/core/src/lib/layout/components/layout-container/layout-container.component.html b/lib/core/src/lib/layout/components/layout-container/layout-container.component.html index 3cd717aa52..d158e519e6 100644 --- a/lib/core/src/lib/layout/components/layout-container/layout-container.component.html +++ b/lib/core/src/lib/layout/components/layout-container/layout-container.component.html @@ -4,7 +4,7 @@ [position]="position" [disableClose]="!isMobileScreenSize" [@sidenavAnimation]="sidenavAnimationState" - [opened]="!isMobileScreenSize || !hideSidenav" + [opened]="!isMobileScreenSize && !hideSidenav" [mode]="isMobileScreenSize ? 'over' : 'side'"> diff --git a/lib/core/src/lib/layout/components/layout-container/layout-container.component.spec.ts b/lib/core/src/lib/layout/components/layout-container/layout-container.component.spec.ts index f929505e8a..a97ecf8e28 100644 --- a/lib/core/src/lib/layout/components/layout-container/layout-container.component.spec.ts +++ b/lib/core/src/lib/layout/components/layout-container/layout-container.component.spec.ts @@ -17,11 +17,16 @@ import { LayoutContainerComponent } from './layout-container.component'; import { SimpleChange } from '@angular/core'; -import { MatSidenav } from '@angular/material/sidenav'; import { Direction } from '@angular/cdk/bidi'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UnitTestingUtils } from '../../../testing/unit-testing-utils'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatSidenavHarness } from '@angular/material/sidenav/testing'; describe('LayoutContainerComponent', () => { + let fixture: ComponentFixture; let layoutContainerComponent: LayoutContainerComponent; + let unitTestingUtils: UnitTestingUtils; const setupComponent = (expandedSidenav: boolean, position: 'start' | 'end', direction: Direction) => { layoutContainerComponent.expandedSidenav = expandedSidenav; @@ -45,7 +50,12 @@ describe('LayoutContainerComponent', () => { }; beforeEach(() => { - layoutContainerComponent = new LayoutContainerComponent(); + TestBed.configureTestingModule({ + imports: [LayoutContainerComponent] + }); + fixture = TestBed.createComponent(LayoutContainerComponent); + layoutContainerComponent = fixture.componentInstance; + unitTestingUtils = new UnitTestingUtils(fixture.debugElement, TestbedHarnessEnvironment.loader(fixture)); layoutContainerComponent.sidenavMin = 70; layoutContainerComponent.sidenavMax = 200; layoutContainerComponent.mediaQueryList = { @@ -53,11 +63,6 @@ describe('LayoutContainerComponent', () => { addListener: jasmine.createSpy('addListener').and.callFake((callback) => window.addEventListener('resize', callback)), removeListener: jasmine.createSpy('removeListener').and.callFake((callback) => window.removeEventListener('resize', callback)) }; - layoutContainerComponent.sidenav = { - open: jasmine.createSpy('open'), - close: jasmine.createSpy('close'), - toggle: jasmine.createSpy('toggle') - } as unknown as MatSidenav; }); describe('OnInit', () => { @@ -164,28 +169,40 @@ describe('LayoutContainerComponent', () => { }); describe('toggleMenu()', () => { - it('should switch to sidenav to compact state', () => { + let sidenav: MatSidenavHarness; + + beforeEach(async () => { + sidenav = await unitTestingUtils.getMatSidenav(); + }); + + it('should switch to sidenav to compact state', async () => { layoutContainerComponent.expandedSidenav = true; layoutContainerComponent.ngOnInit(); layoutContainerComponent.toggleMenu(); + fixture.detectChanges(); expect(layoutContainerComponent.sidenavAnimationState).toEqual({ value: 'compact', params: { width: layoutContainerComponent.sidenavMin } }); + expect(await sidenav.isOpen()).toBeFalse(); }); - it('should switch to sidenav to expanded state', () => { + it('should switch to sidenav to expanded state', async () => { layoutContainerComponent.expandedSidenav = false; layoutContainerComponent.ngOnInit(); layoutContainerComponent.toggleMenu(); + fixture.detectChanges(); expect(layoutContainerComponent.sidenavAnimationState).toEqual({ value: 'expanded', params: { width: layoutContainerComponent.sidenavMax } }); + expect(await sidenav.isOpen()).toBeTrue(); }); }); describe('Media query change', () => { + let sidenav: MatSidenavHarness; + const expandedState = { value: 'expanded', params: { width: 200 } @@ -196,14 +213,18 @@ describe('LayoutContainerComponent', () => { }; const testMediaQueryChange = (matches: boolean, expectedSidenavState: any, expectedContentState: any) => { - layoutContainerComponent.ngOnInit(); + fixture.detectChanges(); layoutContainerComponent.mediaQueryList.matches = matches; window.dispatchEvent(new Event('resize')); expect(layoutContainerComponent.sidenavAnimationState).toEqual(expectedSidenavState); expect(layoutContainerComponent.contentAnimationState).toEqual(expectedContentState); }; - it('should close sidenav on mobile and open on desktop', () => { + beforeEach(async () => { + sidenav = await unitTestingUtils.getMatSidenav(); + }); + + it('should close sidenav on mobile and open on desktop', async () => { testMediaQueryChange(true, expandedState, expandedContentState); layoutContainerComponent.mediaQueryList.matches = false; window.dispatchEvent(new Event('resize')); @@ -212,10 +233,10 @@ describe('LayoutContainerComponent', () => { value: 'compact', params: { 'margin-left': layoutContainerComponent.sidenavMax } }); - expect(layoutContainerComponent.sidenav.open).toHaveBeenCalled(); + expect(await sidenav.isOpen()).toBeTrue(); }); - it('should keep sidenav compact when resized back to desktop and hideSidenav is true', () => { + it('should keep sidenav compact when resized back to desktop and hideSidenav is true', async () => { layoutContainerComponent.hideSidenav = true; testMediaQueryChange(true, expandedState, expandedContentState); layoutContainerComponent.mediaQueryList.matches = false; @@ -225,7 +246,7 @@ describe('LayoutContainerComponent', () => { value: 'expanded', params: { 'margin-left': layoutContainerComponent.sidenavMin } }); - expect(layoutContainerComponent.sidenav.open).not.toHaveBeenCalled(); + expect(await sidenav.isOpen()).toBeFalse(); }); }); }); diff --git a/lib/core/src/lib/layout/components/layout-container/layout-container.component.ts b/lib/core/src/lib/layout/components/layout-container/layout-container.component.ts index 568ace4ee3..d35a7a8220 100644 --- a/lib/core/src/lib/layout/components/layout-container/layout-container.component.ts +++ b/lib/core/src/lib/layout/components/layout-container/layout-container.component.ts @@ -110,6 +110,7 @@ export class LayoutContainerComponent implements OnInit, OnDestroy, OnChanges { } else { this.sidenavAnimationState = this.toggledSidenavAnimation; this.contentAnimationState = this.toggledContentAnimation; + this.hideSidenav = this.sidenavAnimationState === this.SIDENAV_STATES.COMPACT; } } diff --git a/lib/core/src/lib/testing/unit-testing-utils.ts b/lib/core/src/lib/testing/unit-testing-utils.ts index 78a37b2af0..df6417157e 100644 --- a/lib/core/src/lib/testing/unit-testing-utils.ts +++ b/lib/core/src/lib/testing/unit-testing-utils.ts @@ -36,6 +36,7 @@ import { MatListOptionHarness } from '@angular/material/list/testing'; import { MatCellHarness } from '@angular/material/table/testing'; import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing'; import { MatMenuHarness } from '@angular/material/menu/testing'; +import { MatSidenavHarness } from '@angular/material/sidenav/testing'; export class UnitTestingUtils { constructor( @@ -496,4 +497,10 @@ export class UnitTestingUtils { async getMatMenuByCSS(selector: string): Promise { return this.loader.getHarness(MatMenuHarness.with({ selector })); } + + /** MatSidenav related methods */ + + async getMatSidenav(): Promise { + return this.loader.getHarness(MatSidenavHarness); + } }