mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ACS-10263] sr personal files mark all as read button incorrectly grouped as notification list (#11331)
* [ACS-10263] Exclude mark all as read button from menu items * [ACS-10263] Keyboard navigation for notifications list * [ACS-10263] Excluded load more button from screen reader list for notifications * [ACS-10263] Excluded non list related elements from screen reading as list for columns visibility * [ACS-10263] Unit tests * [ACS-10263] Added readonly and private for constructor parameters * [ACS-10263] Marked fields as readonly
This commit is contained in:
+9
-11
@@ -13,7 +13,6 @@
|
|||||||
<button
|
<button
|
||||||
data-automation-id="adf-columns-selector-close-button"
|
data-automation-id="adf-columns-selector-close-button"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
role="menuitem"
|
|
||||||
[attr.aria-label]="'CLOSE' | translate"
|
[attr.aria-label]="'CLOSE' | translate"
|
||||||
(click)="closeMenu()"
|
(click)="closeMenu()"
|
||||||
>
|
>
|
||||||
@@ -34,21 +33,21 @@
|
|||||||
class="adf-columns-selector-search-input"
|
class="adf-columns-selector-search-input"
|
||||||
data-automation-id="adf-columns-selector-search-input"
|
data-automation-id="adf-columns-selector-search-input"
|
||||||
type="text"
|
type="text"
|
||||||
role="menuitem"
|
|
||||||
[placeholder]='"ADF-DATATABLE.COLUMNS_SELECTOR.SEARCH" | translate'>
|
[placeholder]='"ADF-DATATABLE.COLUMNS_SELECTOR.SEARCH" | translate'>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="adf-columns-selector-list-container">
|
<mat-selection-list
|
||||||
|
class="adf-columns-selector-list-container"
|
||||||
|
(selectionChange)="changeColumnVisibility($event)"
|
||||||
|
[attr.aria-label]='"ADF-DATATABLE.COLUMNS_SELECTOR.COLUMNS_VISIBILITY" | translate'>
|
||||||
<div *ngFor="let column of (columnItems | columnsSearchFilter: searchQuery)" class="adf-columns-selector-list-item">
|
<div *ngFor="let column of (columnItems | columnsSearchFilter: searchQuery)" class="adf-columns-selector-list-item">
|
||||||
<mat-checkbox
|
<mat-list-option
|
||||||
color="primary"
|
color="primary"
|
||||||
class="adf-columns-selector-column-checkbox"
|
class="adf-columns-selector-column-checkbox"
|
||||||
[attr.data-automation-id]="'adf-columns-selector-column-checkbox-' + column.title"
|
[attr.data-automation-id]="'adf-columns-selector-column-checkbox-' + column.title"
|
||||||
[checked]="!column.isHidden"
|
[selected]="!column.isHidden"
|
||||||
role="menuitem"
|
|
||||||
[disabled]="isCheckboxDisabled(column)"
|
[disabled]="isCheckboxDisabled(column)"
|
||||||
(keydown.enter)="changeColumnVisibility(column)"
|
[value]="column.id">
|
||||||
(change)="changeColumnVisibility(column)">
|
|
||||||
<div class="adf-columns-selector-list-content">
|
<div class="adf-columns-selector-list-content">
|
||||||
{{column.title | translate}}
|
{{column.title | translate}}
|
||||||
</div>
|
</div>
|
||||||
@@ -63,16 +62,15 @@
|
|||||||
{{column.subtitle | translate}}
|
{{column.subtitle | translate}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</mat-checkbox>
|
</mat-list-option>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</mat-selection-list>
|
||||||
|
|
||||||
<mat-divider class="adf-columns-selector-divider" />
|
<mat-divider class="adf-columns-selector-divider" />
|
||||||
|
|
||||||
<div class="adf-columns-selector-footer">
|
<div class="adf-columns-selector-footer">
|
||||||
<button
|
<button
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
role="menuitem"
|
|
||||||
data-automation-id="adf-columns-selector-apply-button"
|
data-automation-id="adf-columns-selector-apply-button"
|
||||||
color="primary"
|
color="primary"
|
||||||
(click)="apply()">
|
(click)="apply()">
|
||||||
|
|||||||
+17
-4
@@ -1,3 +1,5 @@
|
|||||||
|
@use '../../../styles/mat-selectors' as ms;
|
||||||
|
|
||||||
$adf-columns-selector-space: 12px;
|
$adf-columns-selector-space: 12px;
|
||||||
|
|
||||||
@mixin adf-columns-selector-side-padding {
|
@mixin adf-columns-selector-side-padding {
|
||||||
@@ -8,6 +10,7 @@ $adf-columns-selector-space: 12px;
|
|||||||
padding: $adf-columns-selector-space 0;
|
padding: $adf-columns-selector-space 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adf-datatable-column-selector {
|
||||||
.adf-columns-selector {
|
.adf-columns-selector {
|
||||||
@include adf-columns-selector-top-bottom-padding;
|
@include adf-columns-selector-top-bottom-padding;
|
||||||
|
|
||||||
@@ -28,13 +31,21 @@ $adf-columns-selector-space: 12px;
|
|||||||
&-list-container {
|
&-list-container {
|
||||||
max-height: 350px;
|
max-height: 350px;
|
||||||
overflow: hidden auto;
|
overflow: hidden auto;
|
||||||
|
margin-left: -16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-list-item {
|
.adf-columns-selector-list-item {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
|
||||||
&:hover {
|
#{ms.$mat-list-item} {
|
||||||
background-color: var(--adf-theme-background-hover-color);
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
padding-left: 0;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
#{ms.$mat-list-item-end} {
|
||||||
|
margin-right: -5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +55,7 @@ $adf-columns-selector-space: 12px;
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
display: block;
|
display: block;
|
||||||
width: 210px;
|
width: 210px;
|
||||||
|
color: var(--theme-secondary-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-subtitle {
|
&-subtitle {
|
||||||
@@ -83,7 +95,7 @@ $adf-columns-selector-space: 12px;
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-search-input {
|
&-search-input {
|
||||||
@@ -106,3 +118,4 @@ $adf-columns-selector-space: 12px;
|
|||||||
font-size: var(--theme-adf-icon-1-font-size);
|
font-size: var(--theme-adf-icon-1-font-size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+31
-40
@@ -114,14 +114,14 @@ describe('ColumnsSelectorComponent', () => {
|
|||||||
menuOpenedTrigger.next();
|
menuOpenedTrigger.next();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const checkboxes = await testingUtils.getAllMatCheckboxes();
|
const options = await testingUtils.getAllMatListOptions();
|
||||||
const inputColumnsWithTitle = inputColumns.filter((column) => !!column.title);
|
const inputColumnsWithTitle = inputColumns.filter((column) => !!column.title);
|
||||||
expect(checkboxes.length).toBe(inputColumnsWithTitle.length);
|
expect(options.length).toBe(inputColumnsWithTitle.length);
|
||||||
|
|
||||||
for await (const checkbox of checkboxes) {
|
for (const option of options) {
|
||||||
const checkboxLabel = await checkbox.getLabelText();
|
const optionLabel = await option.getFullText();
|
||||||
|
|
||||||
const inputColumn = inputColumnsWithTitle.find((inputColumnWithTitle) => inputColumnWithTitle.title === checkboxLabel);
|
const inputColumn = inputColumnsWithTitle.find((inputColumnWithTitle) => inputColumnWithTitle.title === optionLabel);
|
||||||
expect(inputColumn).toBeTruthy('Should have all columns with title');
|
expect(inputColumn).toBeTruthy('Should have all columns with title');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -135,37 +135,37 @@ describe('ColumnsSelectorComponent', () => {
|
|||||||
tick(400);
|
tick(400);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const columnCheckboxes = await testingUtils.getAllMatCheckboxes();
|
const columnOptions = await testingUtils.getAllMatListOptions();
|
||||||
|
|
||||||
expect(columnCheckboxes.length).toBe(1);
|
expect(columnOptions.length).toBe(1);
|
||||||
expect(await columnCheckboxes[0].getLabelText()).toBe(inputColumns[0].title);
|
expect(await columnOptions[0].getFullText()).toBe(inputColumns[0].title);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should change column visibility', async () => {
|
it('should change column visibility', async () => {
|
||||||
menuOpenedTrigger.next();
|
menuOpenedTrigger.next();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const firstColumnCheckbox = await testingUtils.getMatCheckbox();
|
const firstColumnOption = await testingUtils.getMatListOption();
|
||||||
const checkBoxName = await firstColumnCheckbox.getLabelText();
|
const optionName = await firstColumnOption.getFullText();
|
||||||
|
|
||||||
const toggledColumnItem = component.columnItems.find((item) => item.title === checkBoxName);
|
const toggledColumnItem = component.columnItems.find((item) => item.title === optionName);
|
||||||
expect(toggledColumnItem?.isHidden).toBe(undefined);
|
expect(toggledColumnItem?.isHidden).toBeUndefined();
|
||||||
|
|
||||||
await firstColumnCheckbox.toggle();
|
await firstColumnOption.toggle();
|
||||||
expect(toggledColumnItem?.isHidden).toBeTrue();
|
expect(toggledColumnItem?.isHidden).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('checkboxes', () => {
|
describe('list options', () => {
|
||||||
it('should have set proper default state', async () => {
|
it('should have set proper default state', async () => {
|
||||||
menuOpenedTrigger.next();
|
menuOpenedTrigger.next();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const checkboxes = await testingUtils.getAllMatCheckboxes();
|
const options = await testingUtils.getAllMatListOptions();
|
||||||
|
|
||||||
expect(await checkboxes[0].isChecked()).toBe(true);
|
expect(await options[0].isSelected()).toBeTrue();
|
||||||
expect(await checkboxes[1].isChecked()).toBe(true);
|
expect(await options[1].isSelected()).toBeTrue();
|
||||||
expect(await checkboxes[2].isChecked()).toBe(true);
|
expect(await options[2].isSelected()).toBeTrue();
|
||||||
expect(await checkboxes[3].isChecked()).toBe(false);
|
expect(await options[3].isSelected()).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be disabled when visible columns limit is reached', async () => {
|
it('should be disabled when visible columns limit is reached', async () => {
|
||||||
@@ -173,12 +173,12 @@ describe('ColumnsSelectorComponent', () => {
|
|||||||
menuOpenedTrigger.next();
|
menuOpenedTrigger.next();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const checkboxes = await testingUtils.getAllMatCheckboxes();
|
const options = await testingUtils.getAllMatListOptions();
|
||||||
|
|
||||||
expect(await checkboxes[0].isDisabled()).toBe(false);
|
expect(await options[0].isDisabled()).toBeFalse();
|
||||||
expect(await checkboxes[1].isDisabled()).toBe(false);
|
expect(await options[1].isDisabled()).toBeFalse();
|
||||||
expect(await checkboxes[2].isDisabled()).toBe(false);
|
expect(await options[2].isDisabled()).toBeFalse();
|
||||||
expect(await checkboxes[3].isDisabled()).toBe(true);
|
expect(await options[3].isDisabled()).toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -202,12 +202,9 @@ describe('ColumnsSelectorComponent', () => {
|
|||||||
menuOpenedTrigger.next();
|
menuOpenedTrigger.next();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const checkboxes = await testingUtils.getAllMatCheckboxes();
|
const options = await testingUtils.getAllMatListOptions();
|
||||||
const labeTextOne = await checkboxes[0].getLabelText();
|
expect(await options[0].getFullText()).toBe(shownDataColumn.title);
|
||||||
const labeTextTwo = await checkboxes[1].getLabelText();
|
expect(await options[1].getFullText()).toBe(hiddenDataColumn.title);
|
||||||
|
|
||||||
expect(labeTextOne).toBe(shownDataColumn.title!);
|
|
||||||
expect(labeTextTwo).toBe(hiddenDataColumn.title!);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should NOT show hidden columns at the end of the list if sorting is disabled', async () => {
|
it('should NOT show hidden columns at the end of the list if sorting is disabled', async () => {
|
||||||
@@ -216,12 +213,9 @@ describe('ColumnsSelectorComponent', () => {
|
|||||||
menuOpenedTrigger.next();
|
menuOpenedTrigger.next();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const checkboxes = await testingUtils.getAllMatCheckboxes();
|
const options = await testingUtils.getAllMatListOptions();
|
||||||
const labeTextOne = await checkboxes[0].getLabelText();
|
expect(await options[0].getFullText()).toBe(hiddenDataColumn.title);
|
||||||
const labeTextTwo = await checkboxes[1].getLabelText();
|
expect(await options[1].getFullText()).toBe(shownDataColumn.title);
|
||||||
|
|
||||||
expect(labeTextOne).toBe(hiddenDataColumn.title!);
|
|
||||||
expect(labeTextTwo).toBe(shownDataColumn.title!);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show subtitle', async () => {
|
it('should show subtitle', async () => {
|
||||||
@@ -238,10 +232,7 @@ describe('ColumnsSelectorComponent', () => {
|
|||||||
component.columnsSorting = false;
|
component.columnsSorting = false;
|
||||||
menuOpenedTrigger.next();
|
menuOpenedTrigger.next();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
expect(await (await testingUtils.getMatListOption()).getFullText()).toBe(`${column.title} ${column.subtitle}`);
|
||||||
const checkboxes = await testingUtils.getAllMatCheckboxes();
|
|
||||||
const labeTextOne = await checkboxes[0].getLabelText();
|
|
||||||
expect(labeTextOne).toBe(`${column.title} ${column.subtitle}`);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+6
-3
@@ -28,6 +28,7 @@ import { MatDividerModule } from '@angular/material/divider';
|
|||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { ColumnsSearchFilterPipe } from './columns-search-filter.pipe';
|
import { ColumnsSearchFilterPipe } from './columns-search-filter.pipe';
|
||||||
|
import { MatListOption, MatSelectionList, MatSelectionListChange } from '@angular/material/list';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-datatable-column-selector',
|
selector: 'adf-datatable-column-selector',
|
||||||
@@ -39,7 +40,9 @@ import { ColumnsSearchFilterPipe } from './columns-search-filter.pipe';
|
|||||||
MatDividerModule,
|
MatDividerModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
ColumnsSearchFilterPipe
|
ColumnsSearchFilterPipe,
|
||||||
|
MatSelectionList,
|
||||||
|
MatListOption
|
||||||
],
|
],
|
||||||
templateUrl: './columns-selector.component.html',
|
templateUrl: './columns-selector.component.html',
|
||||||
styleUrls: ['./columns-selector.component.scss'],
|
styleUrls: ['./columns-selector.component.scss'],
|
||||||
@@ -85,8 +88,8 @@ export class ColumnsSelectorComponent implements OnInit {
|
|||||||
this.mainMenuTrigger.closeMenu();
|
this.mainMenuTrigger.closeMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
changeColumnVisibility(dataColumn: DataColumn): void {
|
changeColumnVisibility(event: MatSelectionListChange): void {
|
||||||
const selectedColumn = this.columnItems.find((column) => column.id === dataColumn.id);
|
const selectedColumn = this.columnItems.find((column) => column.id === event.options[0].value);
|
||||||
selectedColumn.isHidden = !selectedColumn.isHidden;
|
selectedColumn.isHidden = !selectedColumn.isHidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -426,7 +426,8 @@
|
|||||||
"COLUMNS_SELECTOR": {
|
"COLUMNS_SELECTOR": {
|
||||||
"COLUMNS": "Columns",
|
"COLUMNS": "Columns",
|
||||||
"SEARCH": "Search",
|
"SEARCH": "Search",
|
||||||
"APPLY": "Apply"
|
"APPLY": "Apply",
|
||||||
|
"COLUMNS_VISIBILITY": "Select columns to display"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"USER_PROFILE": {
|
"USER_PROFILE": {
|
||||||
|
|||||||
@@ -19,14 +19,16 @@
|
|||||||
[yPosition]="menuPositionY"
|
[yPosition]="menuPositionY"
|
||||||
id="adf-notification-history-menu"
|
id="adf-notification-history-menu"
|
||||||
class="adf-notification-history-menu adf-notification-history-menu-panel">
|
class="adf-notification-history-menu adf-notification-history-menu-panel">
|
||||||
<div class="adf-notification-history-list-header">
|
<div class="adf-notification-history-list-header" aria-hidden="true">
|
||||||
<span class="adf-notification-history-menu-title">{{ 'NOTIFICATIONS.TITLE' | translate }}</span>
|
<span class="adf-notification-history-menu-title">{{ 'NOTIFICATIONS.TITLE' | translate }}</span>
|
||||||
<button mat-menu-item
|
<button mat-icon-button
|
||||||
*ngIf="notifications.length"
|
*ngIf="notifications.length"
|
||||||
id="adf-notification-history-mark-as-read"
|
data-automation-id="adf-notification-history-mark-as-read"
|
||||||
class="adf-notification-history-mark-as-read"
|
class="adf-notification-history-mark-as-read"
|
||||||
title="{{ 'NOTIFICATIONS.MARK_AS_READ' | translate }}"
|
title="{{ 'NOTIFICATIONS.MARK_AS_READ' | translate }}"
|
||||||
(click)="markAsRead()">
|
(click)="markAsRead()"
|
||||||
|
#markAsReadButton
|
||||||
|
(keydown)="manageFocus($event)">
|
||||||
<mat-icon class="adf-notification-history-mark-as-read-icon">done_all</mat-icon>
|
<mat-icon class="adf-notification-history-mark-as-read-icon">done_all</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,7 +40,8 @@
|
|||||||
<button mat-menu-item
|
<button mat-menu-item
|
||||||
*ngFor="let notification of paginatedNotifications"
|
*ngFor="let notification of paginatedNotifications"
|
||||||
(click)="onNotificationClick(notification, $event)"
|
(click)="onNotificationClick(notification, $event)"
|
||||||
class="adf-notification-history-menu-item">
|
class="adf-notification-history-menu-item"
|
||||||
|
(keydown)="manageFocus($event)">
|
||||||
<div class="adf-notification-history-menu-item-content">
|
<div class="adf-notification-history-menu-item-content">
|
||||||
<div *ngIf="notification.initiator; else no_avatar"
|
<div *ngIf="notification.initiator; else no_avatar"
|
||||||
[outerHTML]="notification.initiator | usernameInitials : 'adf-notification-initiator-pic'"></div>
|
[outerHTML]="notification.initiator | usernameInitials : 'adf-notification-initiator-pic'"></div>
|
||||||
@@ -67,8 +70,16 @@
|
|||||||
|
|
||||||
<mat-divider/>
|
<mat-divider/>
|
||||||
|
|
||||||
<div class="adf-notification-history-load-more" *ngIf="hasMoreNotifications()">
|
<div
|
||||||
<button mat-menu-item (click)="loadMore($event)">
|
class="adf-notification-history-load-more"
|
||||||
|
*ngIf="hasMoreNotifications()"
|
||||||
|
aria-hidden="true">
|
||||||
|
<button
|
||||||
|
data-automation-id="adf-notification-history-load-more"
|
||||||
|
mat-button
|
||||||
|
(click)="loadMore($event)"
|
||||||
|
#loadMoreButton
|
||||||
|
(keydown)="manageFocus($event)">
|
||||||
{{ 'NOTIFICATIONS.LOAD_MORE' | translate }}
|
{{ 'NOTIFICATIONS.LOAD_MORE' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing';
|
||||||
import { NotificationHistoryComponent } from './notification-history.component';
|
import { NotificationHistoryComponent } from './notification-history.component';
|
||||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||||
import { NotificationService } from '../services/notification.service';
|
import { NotificationService } from '../services/notification.service';
|
||||||
@@ -24,6 +24,7 @@ import { NOTIFICATION_TYPE, NotificationModel } from '../models/notification.mod
|
|||||||
import { UnitTestingUtils } from '../../testing/unit-testing-utils';
|
import { UnitTestingUtils } from '../../testing/unit-testing-utils';
|
||||||
import { provideCoreAuthTesting } from '../../testing/noop-auth.module';
|
import { provideCoreAuthTesting } from '../../testing/noop-auth.module';
|
||||||
import { MatIconTestingModule } from '@angular/material/icon/testing';
|
import { MatIconTestingModule } from '@angular/material/icon/testing';
|
||||||
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
|
|
||||||
describe('Notification History Component', () => {
|
describe('Notification History Component', () => {
|
||||||
let fixture: ComponentFixture<NotificationHistoryComponent>;
|
let fixture: ComponentFixture<NotificationHistoryComponent>;
|
||||||
@@ -46,7 +47,7 @@ describe('Notification History Component', () => {
|
|||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(NotificationHistoryComponent);
|
fixture = TestBed.createComponent(NotificationHistoryComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
testingUtils = new UnitTestingUtils(fixture.debugElement);
|
testingUtils = new UnitTestingUtils(fixture.debugElement, TestbedHarnessEnvironment.loader(fixture));
|
||||||
|
|
||||||
storage = TestBed.inject(StorageService);
|
storage = TestBed.inject(StorageService);
|
||||||
notificationService = TestBed.inject(NotificationService);
|
notificationService = TestBed.inject(NotificationService);
|
||||||
@@ -63,6 +64,15 @@ describe('Notification History Component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('ui ', () => {
|
describe('ui ', () => {
|
||||||
|
const getMarkAllAsReadButton = (): HTMLButtonElement =>
|
||||||
|
overlayContainerElement.querySelector<HTMLButtonElement>('[data-automation-id="adf-notification-history-mark-as-read"]');
|
||||||
|
|
||||||
|
const getLoadMoreButton = (): HTMLButtonElement =>
|
||||||
|
overlayContainerElement.querySelector<HTMLButtonElement>('[data-automation-id="adf-notification-history-load-more"]');
|
||||||
|
|
||||||
|
const getNotificationElements = (): NodeListOf<HTMLButtonElement> =>
|
||||||
|
overlayContainerElement.querySelectorAll<HTMLButtonElement>('.adf-notification-history-menu-item');
|
||||||
|
|
||||||
it('should empty message be present when there are no notifications in the history', (done) => {
|
it('should empty message be present when there are no notifications in the history', (done) => {
|
||||||
openNotification();
|
openNotification();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -80,8 +90,7 @@ describe('Notification History Component', () => {
|
|||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(component.notifications.length).toBe(1);
|
expect(component.notifications.length).toBe(1);
|
||||||
const markAllAsRead = overlayContainerElement.querySelector<HTMLButtonElement>('#adf-notification-history-mark-as-read');
|
getMarkAllAsReadButton().click();
|
||||||
markAllAsRead.click();
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(storage.getItem(NotificationHistoryComponent.NOTIFICATION_STORAGE)).toBeNull();
|
expect(storage.getItem(NotificationHistoryComponent.NOTIFICATION_STORAGE)).toBeNull();
|
||||||
expect(component.notifications.length).toBe(0);
|
expect(component.notifications.length).toBe(0);
|
||||||
@@ -115,8 +124,7 @@ describe('Notification History Component', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const notification = overlayContainerElement.querySelector<HTMLButtonElement>('.adf-notification-history-menu-item');
|
getNotificationElements()[0].click();
|
||||||
notification.click();
|
|
||||||
expect(callBackSpy).toHaveBeenCalled();
|
expect(callBackSpy).toHaveBeenCalled();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -133,9 +141,8 @@ describe('Notification History Component', () => {
|
|||||||
openNotification();
|
openNotification();
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const loadMoreButton = overlayContainerElement.querySelector<HTMLButtonElement>('.adf-notification-history-load-more');
|
|
||||||
expect(component.paginatedNotifications.length).toBe(5);
|
expect(component.paginatedNotifications.length).toBe(5);
|
||||||
expect(loadMoreButton).toBeDefined();
|
expect(getLoadMoreButton()).toBeDefined();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -155,8 +162,7 @@ describe('Notification History Component', () => {
|
|||||||
openNotification();
|
openNotification();
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const notification = overlayContainerElement.querySelector<HTMLButtonElement>('.adf-notification-history-menu-item');
|
expect(getNotificationElements()[0]).toBeDefined();
|
||||||
expect(notification).toBeDefined();
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}, 10000);
|
}, 10000);
|
||||||
@@ -173,10 +179,140 @@ describe('Notification History Component', () => {
|
|||||||
openNotification();
|
openNotification();
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const notifications = overlayContainerElement.querySelectorAll('.adf-notification-history-menu-item');
|
expect(getNotificationElements().length).toBe(6);
|
||||||
expect(notifications.length).toBe(6);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}, 45000);
|
}, 45000);
|
||||||
|
|
||||||
|
describe('focus change', () => {
|
||||||
|
let markAllAsReadButton: HTMLButtonElement;
|
||||||
|
let loadMoreButton: HTMLButtonElement;
|
||||||
|
let notifications: NodeListOf<HTMLButtonElement>;
|
||||||
|
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
spyOn(storage, 'getItem').and.returnValue(
|
||||||
|
JSON.stringify([
|
||||||
|
{
|
||||||
|
messages: ['My new message 1']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
messages: ['My new message 2']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
messages: ['My new message 3']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
messages: ['My new message 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
messages: ['My new message 5']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
messages: ['My new message 6']
|
||||||
|
}
|
||||||
|
] as NotificationModel[])
|
||||||
|
);
|
||||||
|
openNotification();
|
||||||
|
tick();
|
||||||
|
markAllAsReadButton = getMarkAllAsReadButton();
|
||||||
|
loadMoreButton = getLoadMoreButton();
|
||||||
|
notifications = getNotificationElements();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should focus mark all as read button when menu has been opened', () => {
|
||||||
|
expect(document.activeElement).toBe(markAllAsReadButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should focus load more button when pressing arrow up when mark all as read button is focused', () => {
|
||||||
|
markAllAsReadButton.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowUp'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(document.activeElement).toBe(loadMoreButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should focus first notification when pressing arrow down when mark all as read button is focused', () => {
|
||||||
|
markAllAsReadButton.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowDown'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(document.activeElement).toBe(notifications[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should focus mark all as read button when pressing arrow up when first notification is focused', () => {
|
||||||
|
markAllAsReadButton.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowDown'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
notifications[0].dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowUp'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(document.activeElement).toBe(markAllAsReadButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should focus second notification when pressing arrow down when first notification is focused', () => {
|
||||||
|
markAllAsReadButton.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowDown'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
notifications[0].dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowDown'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(document.activeElement).toBe(notifications[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should focus mark all as read button when pressing arrow down when load more button is focused', () => {
|
||||||
|
markAllAsReadButton.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowUp'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
loadMoreButton.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowDown'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(document.activeElement).toBe(markAllAsReadButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should last paged notification when pressing arrow up when load more button is focused', () => {
|
||||||
|
markAllAsReadButton.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowUp'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
loadMoreButton.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowUp'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(document.activeElement).toBe(notifications[4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should focus correct notification after loading more', fakeAsync(() => {
|
||||||
|
markAllAsReadButton.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', {
|
||||||
|
key: 'ArrowUp'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
loadMoreButton.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
expect(document.activeElement).toBe(getNotificationElements()[5]);
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,13 +15,25 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AfterViewInit, ChangeDetectorRef, Component, DestroyRef, inject, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
DestroyRef,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
QueryList,
|
||||||
|
ViewChild,
|
||||||
|
ViewChildren,
|
||||||
|
ViewEncapsulation
|
||||||
|
} from '@angular/core';
|
||||||
import { NotificationService } from '../services/notification.service';
|
import { NotificationService } from '../services/notification.service';
|
||||||
import { NOTIFICATION_TYPE, NotificationModel } from '../models/notification.model';
|
import { NOTIFICATION_TYPE, NotificationModel } from '../models/notification.model';
|
||||||
import { MatMenuModule, MatMenuTrigger, MenuPositionX, MenuPositionY } from '@angular/material/menu';
|
import { MatMenuItem, MatMenuModule, MatMenuTrigger, MenuPositionX, MenuPositionY } from '@angular/material/menu';
|
||||||
import { StorageService } from '../../common/services/storage.service';
|
import { StorageService } from '../../common/services/storage.service';
|
||||||
import { PaginationModel } from '../../models/pagination.model';
|
import { PaginationModel } from '../../models/pagination.model';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButton, MatButtonModule, MatIconButton } from '@angular/material/button';
|
||||||
import { TranslatePipe } from '@ngx-translate/core';
|
import { TranslatePipe } from '@ngx-translate/core';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatBadgeModule } from '@angular/material/badge';
|
import { MatBadgeModule } from '@angular/material/badge';
|
||||||
@@ -30,6 +42,7 @@ import { NgForOf, NgIf } from '@angular/common';
|
|||||||
import { InitialUsernamePipe, TimeAgoPipe } from '../../pipes';
|
import { InitialUsernamePipe, TimeAgoPipe } from '../../pipes';
|
||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { FocusKeyManager } from '@angular/cdk/a11y';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-notification-history',
|
selector: 'adf-notification-history',
|
||||||
@@ -73,9 +86,24 @@ export class NotificationHistoryComponent implements OnInit, AfterViewInit {
|
|||||||
paginatedNotifications: NotificationModel[] = [];
|
paginatedNotifications: NotificationModel[] = [];
|
||||||
pagination: PaginationModel;
|
pagination: PaginationModel;
|
||||||
|
|
||||||
|
@ViewChild('markAsReadButton')
|
||||||
|
private readonly markAsReadButton: MatIconButton;
|
||||||
|
|
||||||
|
@ViewChild('loadMoreButton')
|
||||||
|
private readonly loadMoreButton: MatButton;
|
||||||
|
|
||||||
|
@ViewChildren(MatMenuItem)
|
||||||
|
private readonly menuItems: QueryList<MatMenuItem>;
|
||||||
|
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
constructor(private notificationService: NotificationService, public storageService: StorageService, public cd: ChangeDetectorRef) {}
|
private focusKeyManager: FocusKeyManager<MatIconButton | MatMenuItem | MatButton>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly notificationService: NotificationService,
|
||||||
|
private readonly storageService: StorageService,
|
||||||
|
private readonly cd: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.notifications = JSON.parse(this.storageService.getItem(NotificationHistoryComponent.NOTIFICATION_STORAGE)) || [];
|
this.notifications = JSON.parse(this.storageService.getItem(NotificationHistoryComponent.NOTIFICATION_STORAGE)) || [];
|
||||||
@@ -108,6 +136,7 @@ export class NotificationHistoryComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
onMenuOpened() {
|
onMenuOpened() {
|
||||||
this.createPagination();
|
this.createPagination();
|
||||||
|
setTimeout(() => this.initializeFocusManager(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
markAsRead() {
|
markAsRead() {
|
||||||
@@ -132,6 +161,7 @@ export class NotificationHistoryComponent implements OnInit, AfterViewInit {
|
|||||||
this.pagination.skipCount = this.pagination.maxItems + this.pagination.skipCount;
|
this.pagination.skipCount = this.pagination.maxItems + this.pagination.skipCount;
|
||||||
this.pagination.hasMoreItems = this.notifications.length > this.pagination.skipCount;
|
this.pagination.hasMoreItems = this.notifications.length > this.pagination.skipCount;
|
||||||
this.paginatedNotifications = this.notifications.slice(0, this.pagination.skipCount);
|
this.paginatedNotifications = this.notifications.slice(0, this.pagination.skipCount);
|
||||||
|
setTimeout(() => this.initializeFocusManager(this.focusKeyManager.activeItemIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMoreNotifications(): boolean {
|
hasMoreNotifications(): boolean {
|
||||||
@@ -145,4 +175,23 @@ export class NotificationHistoryComponent implements OnInit, AfterViewInit {
|
|||||||
this.trigger.closeMenu();
|
this.trigger.closeMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
manageFocus(event: KeyboardEvent): void {
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
this.focusKeyManager.setPreviousItemActive();
|
||||||
|
event.stopPropagation();
|
||||||
|
} else if (event.key === 'ArrowDown') {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.focusKeyManager.setNextItemActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeFocusManager(activeIndex: number): void {
|
||||||
|
this.focusKeyManager = new FocusKeyManager([
|
||||||
|
this.markAsReadButton,
|
||||||
|
...this.menuItems,
|
||||||
|
...(this.hasMoreNotifications() ? [this.loadMoreButton] : [])
|
||||||
|
]).withWrap();
|
||||||
|
this.focusKeyManager.setActiveItem(activeIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ $mat-calendar-table-header: '.mat-calendar-table-header';
|
|||||||
$mat-calendar-body-disabled: '.mat-calendar-body-disabled';
|
$mat-calendar-body-disabled: '.mat-calendar-body-disabled';
|
||||||
$mat-toolbar: '.mat-toolbar';
|
$mat-toolbar: '.mat-toolbar';
|
||||||
$mat-list-item-unscoped-content: '.mat-mdc-list-item-unscoped-content';
|
$mat-list-item-unscoped-content: '.mat-mdc-list-item-unscoped-content';
|
||||||
|
$mat-list-item: '.mat-mdc-list-item';
|
||||||
|
$mat-list-item-end: '.mdc-list-item__end';
|
||||||
$mat-text-field-no-label: '.mdc-text-field--no-label';
|
$mat-text-field-no-label: '.mdc-text-field--no-label';
|
||||||
$mat-form-field-infix: '.mat-mdc-form-field-infix';
|
$mat-form-field-infix: '.mat-mdc-form-field-infix';
|
||||||
$mat-form-field-error-wrapper: '.mat-mdc-form-field-error-wrapper';
|
$mat-form-field-error-wrapper: '.mat-mdc-form-field-error-wrapper';
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { MatTabGroupHarness, MatTabHarness } from '@angular/material/tabs/testin
|
|||||||
import { MatToolbarHarness } from '@angular/material/toolbar/testing';
|
import { MatToolbarHarness } from '@angular/material/toolbar/testing';
|
||||||
import { MatSnackBarHarness } from '@angular/material/snack-bar/testing';
|
import { MatSnackBarHarness } from '@angular/material/snack-bar/testing';
|
||||||
import { MatProgressBarHarness } from '@angular/material/progress-bar/testing';
|
import { MatProgressBarHarness } from '@angular/material/progress-bar/testing';
|
||||||
|
import { MatListOptionHarness } from '@angular/material/list/testing';
|
||||||
|
|
||||||
export class UnitTestingUtils {
|
export class UnitTestingUtils {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -434,4 +435,14 @@ export class UnitTestingUtils {
|
|||||||
const progress = await this.loader.getHarness(MatProgressBarHarness);
|
const progress = await this.loader.getHarness(MatProgressBarHarness);
|
||||||
return progress.host();
|
return progress.host();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** MatListOption related methods */
|
||||||
|
|
||||||
|
async getMatListOption(): Promise<MatListOptionHarness> {
|
||||||
|
return this.loader.getHarness(MatListOptionHarness);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllMatListOptions(): Promise<MatListOptionHarness[]> {
|
||||||
|
return this.loader.getAllHarnesses(MatListOptionHarness);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user