From 261cdaebed2242435cc57bdfffa9a1b450fbb16f Mon Sep 17 00:00:00 2001
From: Mykyta Maliarchuk <84377976+nikita-web-ua@users.noreply.github.com>
Date: Mon, 17 Feb 2025 14:05:02 +0100
Subject: [PATCH] [ACS-9266] Make notification and user menu accessible with
keyboard (#4394)
* [ACS-9266] Make notification and user menu accessible with keyboard
* [ACS-9266] cr fixes
* [ACS-9266] cr fix
* [link-adf:dev-mmaliarchuk/ACS-9266-Notification-and-user-menu-are-not-accessible-with-keyboard][ci:force]
* empty commit
---
.../common/logout/logout.component.ts | 7 ++-
.../common/user-info/user-info.component.html | 4 +-
.../common/user-info/user-info.component.ts | 7 ++-
.../user-menu/user-menu.component.spec.ts | 61 +++++++++++++++++++
.../sidenav/user-menu/user-menu.component.ts | 25 ++++++--
.../toolbar-menu-item.component.spec.ts | 19 +++++-
.../toolbar-menu-item.component.ts | 13 +++-
7 files changed, 122 insertions(+), 14 deletions(-)
create mode 100644 projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.spec.ts
diff --git a/projects/aca-content/src/lib/components/common/logout/logout.component.ts b/projects/aca-content/src/lib/components/common/logout/logout.component.ts
index 1f7026af4..e68b38daa 100644
--- a/projects/aca-content/src/lib/components/common/logout/logout.component.ts
+++ b/projects/aca-content/src/lib/components/common/logout/logout.component.ts
@@ -22,12 +22,12 @@
* from Hyland Software. If not, see .
*/
-import { Component, ViewEncapsulation } from '@angular/core';
+import { Component, ViewChild, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store';
import { SetSelectedNodesAction } from '@alfresco/aca-shared/store';
import { TranslateModule } from '@ngx-translate/core';
import { MatIconModule } from '@angular/material/icon';
-import { MatMenuModule } from '@angular/material/menu';
+import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
import { LogoutDirective } from '@alfresco/adf-core';
@Component({
@@ -45,6 +45,9 @@ import { LogoutDirective } from '@alfresco/adf-core';
export class LogoutComponent {
constructor(private store: Store) {}
+ @ViewChild(MatMenuItem)
+ menuItem: MatMenuItem;
+
onLogoutEvent() {
this.store.dispatch(new SetSelectedNodesAction([]));
}
diff --git a/projects/aca-content/src/lib/components/common/user-info/user-info.component.html b/projects/aca-content/src/lib/components/common/user-info/user-info.component.html
index 37bf7c773..74959936d 100644
--- a/projects/aca-content/src/lib/components/common/user-info/user-info.component.html
+++ b/projects/aca-content/src/lib/components/common/user-info/user-info.component.html
@@ -1,4 +1,4 @@
-
-
+
diff --git a/projects/aca-content/src/lib/components/common/user-info/user-info.component.ts b/projects/aca-content/src/lib/components/common/user-info/user-info.component.ts
index cda6961ff..35d98ac7b 100644
--- a/projects/aca-content/src/lib/components/common/user-info/user-info.component.ts
+++ b/projects/aca-content/src/lib/components/common/user-info/user-info.component.ts
@@ -22,10 +22,10 @@
* from Hyland Software. If not, see .
*/
-import { Component, ViewEncapsulation, inject } from '@angular/core';
+import { Component, ViewEncapsulation, inject, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
-import { MatMenuModule } from '@angular/material/menu';
+import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
import { TranslateModule } from '@ngx-translate/core';
import { UserProfileService } from '@alfresco/aca-shared';
@@ -40,5 +40,8 @@ import { UserProfileService } from '@alfresco/aca-shared';
export class UserInfoComponent {
private userProfileService = inject(UserProfileService);
+ @ViewChild(MatMenuItem)
+ menuItem: MatMenuItem;
+
user$ = this.userProfileService.userProfile$;
}
diff --git a/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.spec.ts b/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.spec.ts
new file mode 100644
index 000000000..bf8937a4c
--- /dev/null
+++ b/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.spec.ts
@@ -0,0 +1,61 @@
+/*!
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Alfresco Example Content Application
+ *
+ * This file is part of the Alfresco Example Content Application.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * The Alfresco Example Content Application is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Alfresco Example Content Application is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * from Hyland Software. If not, see .
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MatMenuTrigger } from '@angular/material/menu';
+import { CoreTestingModule, UnitTestingUtils } from '@alfresco/adf-core';
+import { UserMenuComponent } from './user-menu.component';
+import { AppExtensionService } from '@alfresco/aca-shared';
+
+describe('UserMenuComponent', () => {
+ let component: UserMenuComponent;
+ let fixture: ComponentFixture;
+ let testingUtils: UnitTestingUtils;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [CoreTestingModule, UserMenuComponent],
+ providers: [{ provide: AppExtensionService, useValue: { runActionById() {} } }]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(UserMenuComponent);
+ testingUtils = new UnitTestingUtils(fixture.debugElement);
+
+ component = fixture.componentInstance;
+ component.data = { items: [{ id: 'id-1' }, { id: 'id-2' }] };
+ fixture.detectChanges();
+ });
+
+ it('should merge nested menu items to MatMenu in ngAfterViewInit', () => {
+ spyOn(component.menu, 'ngAfterContentInit').and.callThrough();
+ testingUtils.getByDirective(MatMenuTrigger).nativeElement.click();
+ fixture.detectChanges();
+ component.ngAfterViewInit();
+
+ expect(component.menu._allItems.length).toBe(2);
+ expect(component.menu.ngAfterContentInit).toHaveBeenCalled();
+ });
+});
diff --git a/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.ts b/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.ts
index e21c5db2a..441bec5c0 100644
--- a/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.ts
+++ b/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.ts
@@ -22,24 +22,24 @@
* from Hyland Software. If not, see .
*/
-import { Component, inject, Input, OnInit, ViewEncapsulation } from '@angular/core';
-import { ContentActionRef } from '@alfresco/adf-extensions';
+import { AfterViewInit, Component, inject, Input, OnInit, QueryList, ViewChild, ViewChildren, ViewEncapsulation } from '@angular/core';
+import { ContentActionRef, DynamicExtensionComponent } from '@alfresco/adf-extensions';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { TranslateModule } from '@ngx-translate/core';
-import { MatMenuModule } from '@angular/material/menu';
+import { MatMenu, MatMenuItem, MatMenuModule } from '@angular/material/menu';
import { ToolbarMenuItemComponent, UserProfileService } from '@alfresco/aca-shared';
@Component({
standalone: true,
- imports: [CommonModule, TranslateModule, MatButtonModule, MatMenuModule, ToolbarMenuItemComponent],
+ imports: [CommonModule, TranslateModule, MatButtonModule, MatMenuModule, ToolbarMenuItemComponent, DynamicExtensionComponent],
selector: 'aca-user-menu',
templateUrl: './user-menu.component.html',
styleUrls: ['./user-menu.component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-user-menu' }
})
-export class UserMenuComponent implements OnInit {
+export class UserMenuComponent implements OnInit, AfterViewInit {
private userProfileService = inject(UserProfileService);
user$ = this.userProfileService.userProfile$;
@@ -50,9 +50,24 @@ export class UserMenuComponent implements OnInit {
@Input()
data: { items: any[] };
+ @ViewChild(MatMenu)
+ menu: MatMenu;
+
+ @ViewChildren(ToolbarMenuItemComponent)
+ toolbarMenuItems: QueryList;
+
ngOnInit() {
if (this.data?.items) {
this.data.items.sort((a, b) => a.order - b.order);
}
}
+
+ ngAfterViewInit() {
+ const menuItems = this.toolbarMenuItems.map((toolbarMenuItem) => toolbarMenuItem.menuItem).filter((menuItem) => menuItem !== undefined);
+
+ const menuItemsQueryList = new QueryList();
+ menuItemsQueryList.reset(menuItems);
+ this.menu._allItems = menuItemsQueryList;
+ this.menu.ngAfterContentInit();
+ }
}
diff --git a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.spec.ts b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.spec.ts
index 8a35d5a7e..402f9da14 100644
--- a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.spec.ts
+++ b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.spec.ts
@@ -27,11 +27,14 @@ import { ToolbarMenuItemComponent } from './toolbar-menu-item.component';
import { AppExtensionService } from '../../../services/app.extension.service';
import { Store } from '@ngrx/store';
import { of } from 'rxjs';
-import { ContentActionRef, ContentActionType } from '@alfresco/adf-extensions';
+import { ContentActionRef, ContentActionType, DynamicExtensionComponent } from '@alfresco/adf-extensions';
import { LibTestingModule } from '@alfresco/aca-shared';
+import { MatMenuItem } from '@angular/material/menu';
+import { UnitTestingUtils } from '@alfresco/adf-core';
describe('ToolbarMenuItemComponent', () => {
let fixture: ComponentFixture;
+ let testingUtils: UnitTestingUtils;
let component: ToolbarMenuItemComponent;
let appExtensionService: AppExtensionService;
@@ -51,6 +54,7 @@ describe('ToolbarMenuItemComponent', () => {
});
fixture = TestBed.createComponent(ToolbarMenuItemComponent);
+ testingUtils = new UnitTestingUtils(fixture.debugElement);
component = fixture.componentInstance;
appExtensionService = TestBed.inject(AppExtensionService);
});
@@ -110,4 +114,17 @@ describe('ToolbarMenuItemComponent', () => {
expect(component.trackByActionId(0, contentActionRef)).toBe('action1');
});
+
+ it('should assign menuItem from dynamic component', () => {
+ component.actionRef = {
+ id: 'action1',
+ type: ContentActionType.custom
+ };
+ fixture.detectChanges();
+
+ const innerElement = testingUtils.getByDirective(DynamicExtensionComponent);
+ innerElement.componentInstance.menuItem = new MatMenuItem(null, null, null, null, null);
+ component.ngAfterViewInit();
+ expect(component.menuItem).toBeInstanceOf(MatMenuItem);
+ });
});
diff --git a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.ts b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.ts
index 69053e263..d6193c455 100644
--- a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.ts
+++ b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.ts
@@ -22,7 +22,7 @@
* from Hyland Software. If not, see .
*/
-import { Component, Input, ViewChild, ViewEncapsulation } from '@angular/core';
+import { AfterViewInit, Component, Input, ViewChild, ViewEncapsulation } from '@angular/core';
import { ContentActionRef, DynamicExtensionComponent } from '@alfresco/adf-extensions';
import { AppExtensionService } from '../../../services/app.extension.service';
import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
@@ -40,7 +40,7 @@ import { IconComponent } from '@alfresco/adf-core';
encapsulation: ViewEncapsulation.None,
host: { class: 'app-toolbar-menu-item' }
})
-export class ToolbarMenuItemComponent {
+export class ToolbarMenuItemComponent implements AfterViewInit {
@Input()
actionRef: ContentActionRef;
@Input()
@@ -49,6 +49,9 @@ export class ToolbarMenuItemComponent {
@ViewChild(MatMenuItem)
menuItem: MatMenuItem;
+ @ViewChild(DynamicExtensionComponent)
+ dynamicComponent: DynamicExtensionComponent;
+
constructor(private extensions: AppExtensionService) {}
runAction() {
@@ -64,6 +67,12 @@ export class ToolbarMenuItemComponent {
}
}
+ ngAfterViewInit() {
+ if (this.dynamicComponent?.menuItem) {
+ this.menuItem = this.dynamicComponent.menuItem;
+ }
+ }
+
private hasClickAction(actionRef: ContentActionRef): boolean {
return !!actionRef?.actions?.click;
}