mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-04-23 22:30:37 +00:00
[ACS-10302] Add aria-live to selected aspects counter (#11593)
* [ACS-10302] Add aria-live to selected aspects counter * [ACS-10302] Add the live announcer for amount of selected items in breadcrumb * [ACS-10302] CR fixes
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class="adf-aspect-list-dialog-information">
|
||||
<p id="aspect-list-dialog-over-table-message">{{overTableMessage | translate}}</p>
|
||||
<p id="aspect-list-dialog-counter">{{counter}}
|
||||
<p id="aspect-list-dialog-counter" aria-live="polite">{{counter}}
|
||||
{{'ADF-ASPECT-LIST.DIALOG.SELECTED' | translate}}</p>
|
||||
</div>
|
||||
<mat-dialog-content class="adf-aspect-dialog-content">
|
||||
|
||||
@@ -27,6 +27,8 @@ import { NodesApiService } from '../common/services/nodes-api.service';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { AspectListComponent } from './aspect-list.component';
|
||||
import { provideApiTesting } from '../testing/providers';
|
||||
import { UnitTestingUtils } from '@alfresco/adf-core';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
const aspectListMock: AspectEntry[] = [
|
||||
{
|
||||
@@ -99,11 +101,19 @@ describe('AspectListDialogComponent', () => {
|
||||
let aspectListService: AspectListService;
|
||||
let nodeService: NodesApiService;
|
||||
let data: AspectListDialogComponentData;
|
||||
let testingUtils: UnitTestingUtils;
|
||||
const event = new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
keyCode: 27
|
||||
} as KeyboardEventInit);
|
||||
|
||||
const getResetButton = (): DebugElement => testingUtils.getByCSS('#aspect-list-dialog-actions-reset');
|
||||
const getClearButton = (): DebugElement => testingUtils.getByCSS('#aspect-list-dialog-actions-clear');
|
||||
const getCancelButton = (): DebugElement => testingUtils.getByCSS('#aspect-list-dialog-actions-cancel');
|
||||
const getApplyButton = (): DebugElement => testingUtils.getByCSS('#aspect-list-dialog-actions-apply');
|
||||
const getAspectCounter = (): string => testingUtils.getInnerTextByCSS('#aspect-list-dialog-counter');
|
||||
const getAspectCheckbox = (index: number): HTMLInputElement => testingUtils.getByCSS(`#aspect-list-${index}-check-input`).nativeElement;
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
title: 'Title',
|
||||
@@ -128,6 +138,7 @@ describe('AspectListDialogComponent', () => {
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(AspectListDialogComponent);
|
||||
testingUtils = new UnitTestingUtils(fixture.debugElement);
|
||||
});
|
||||
|
||||
describe('Without passing node id', () => {
|
||||
@@ -144,91 +155,81 @@ describe('AspectListDialogComponent', () => {
|
||||
});
|
||||
|
||||
it('should show 4 actions : CLEAR, RESET, CANCEL and APPLY', () => {
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-reset')).not.toBeNull();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-reset')).toBeDefined();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-clear')).not.toBeNull();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-clear')).toBeDefined();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-cancel')).not.toBeNull();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-cancel')).toBeDefined();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-apply')).not.toBeNull();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-apply')).toBeDefined();
|
||||
expect(getResetButton()).toBeDefined();
|
||||
expect(getClearButton()).toBeDefined();
|
||||
expect(getCancelButton()).toBeDefined();
|
||||
expect(getApplyButton()).toBeDefined();
|
||||
});
|
||||
|
||||
it('should show basic information for the dialog', () => {
|
||||
const dialogTitle = fixture.nativeElement.querySelector('[data-automation-id="aspect-list-dialog-title"] .adf-aspect-list-dialog-title');
|
||||
expect(dialogTitle).not.toBeNull();
|
||||
expect(dialogTitle.innerText).toBe(data.title);
|
||||
const dialogTitleText = testingUtils.getInnerTextByCSS('[data-automation-id="aspect-list-dialog-title"] .adf-aspect-list-dialog-title');
|
||||
expect(dialogTitleText).toBe(data.title);
|
||||
|
||||
const dialogDescription = fixture.nativeElement.querySelector(
|
||||
const dialogDescription = testingUtils.getInnerTextByCSS(
|
||||
'[data-automation-id="aspect-list-dialog-title"] .adf-aspect-list-dialog-description'
|
||||
);
|
||||
expect(dialogDescription).not.toBeNull();
|
||||
expect(dialogDescription.innerText).toBe(data.description);
|
||||
expect(dialogDescription).toBe(data.description);
|
||||
|
||||
const overTableMessage = fixture.nativeElement.querySelector('#aspect-list-dialog-over-table-message');
|
||||
expect(overTableMessage).not.toBeNull();
|
||||
expect(overTableMessage.innerText).toBe(data.overTableMessage);
|
||||
|
||||
const selectionCounter = fixture.nativeElement.querySelector('#aspect-list-dialog-counter');
|
||||
expect(selectionCounter).not.toBeNull();
|
||||
expect(selectionCounter.innerText).toBe('0 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
expect(testingUtils.getInnerTextByCSS('#aspect-list-dialog-over-table-message')).toBe(data.overTableMessage);
|
||||
expect(getAspectCounter()).toBe('0 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
});
|
||||
|
||||
it('should update the counter when an option is selcted and unselected', async () => {
|
||||
const firstAspectCheckbox: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
it('should update the counter when an option is selected and unselected', async () => {
|
||||
const firstAspectCheckbox = getAspectCheckbox(0);
|
||||
expect(firstAspectCheckbox).toBeDefined();
|
||||
expect(firstAspectCheckbox).not.toBeNull();
|
||||
let selectionCounter = fixture.nativeElement.querySelector('#aspect-list-dialog-counter');
|
||||
expect(selectionCounter).not.toBeNull();
|
||||
expect(selectionCounter.innerText).toBe('0 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
firstAspectCheckbox.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
selectionCounter = fixture.nativeElement.querySelector('#aspect-list-dialog-counter');
|
||||
expect(selectionCounter).not.toBeNull();
|
||||
expect(selectionCounter.innerText).toBe('1 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
expect(getAspectCounter()).toBe('0 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
|
||||
firstAspectCheckbox.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
selectionCounter = fixture.nativeElement.querySelector('#aspect-list-dialog-counter');
|
||||
expect(selectionCounter).not.toBeNull();
|
||||
expect(selectionCounter.innerText).toBe('0 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
expect(getAspectCounter()).toBe('1 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
|
||||
firstAspectCheckbox.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getAspectCounter()).toBe('0 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
});
|
||||
|
||||
it('should reset to the node values when Reset button is clicked', async () => {
|
||||
let firstAspectCheckbox: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
let firstAspectCheckbox = getAspectCheckbox(0);
|
||||
expect(firstAspectCheckbox).toBeDefined();
|
||||
expect(firstAspectCheckbox).not.toBeNull();
|
||||
|
||||
firstAspectCheckbox.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const resetButton: HTMLButtonElement = fixture.nativeElement.querySelector('#aspect-list-dialog-actions-reset');
|
||||
|
||||
const resetButton: HTMLButtonElement = getResetButton().nativeElement;
|
||||
expect(resetButton).toBeDefined();
|
||||
expect(firstAspectCheckbox.checked).toBeTruthy();
|
||||
|
||||
resetButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
firstAspectCheckbox = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
|
||||
firstAspectCheckbox = getAspectCheckbox(0);
|
||||
expect(firstAspectCheckbox.checked).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should clear all the value when Clear button is clicked', async () => {
|
||||
let firstAspectCheckbox: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
let firstAspectCheckbox = getAspectCheckbox(0);
|
||||
expect(firstAspectCheckbox).toBeDefined();
|
||||
expect(firstAspectCheckbox).not.toBeNull();
|
||||
|
||||
firstAspectCheckbox.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const clearButton: HTMLButtonElement = fixture.nativeElement.querySelector('#aspect-list-dialog-actions-clear');
|
||||
|
||||
const clearButton: HTMLButtonElement = getClearButton().nativeElement;
|
||||
expect(clearButton).toBeDefined();
|
||||
expect(firstAspectCheckbox.checked).toBeTruthy();
|
||||
|
||||
clearButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
firstAspectCheckbox = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
|
||||
firstAspectCheckbox = getAspectCheckbox(0);
|
||||
expect(firstAspectCheckbox.checked).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -238,7 +239,7 @@ describe('AspectListDialogComponent', () => {
|
||||
() => {},
|
||||
() => done()
|
||||
);
|
||||
const cancelButton: HTMLButtonElement = fixture.nativeElement.querySelector('#aspect-list-dialog-actions-cancel');
|
||||
const cancelButton: HTMLButtonElement = getCancelButton().nativeElement;
|
||||
expect(cancelButton).toBeDefined();
|
||||
cancelButton.click();
|
||||
fixture.detectChanges();
|
||||
@@ -272,17 +273,14 @@ describe('AspectListDialogComponent', () => {
|
||||
await fixture.whenRenderingDone();
|
||||
const firstAspectCheckbox: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
expect(firstAspectCheckbox).toBeDefined();
|
||||
expect(firstAspectCheckbox).not.toBeNull();
|
||||
expect(firstAspectCheckbox.checked).toBeTruthy();
|
||||
|
||||
const notCheckedAspect: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-1-check-input');
|
||||
expect(notCheckedAspect).toBeDefined();
|
||||
expect(notCheckedAspect).not.toBeNull();
|
||||
expect(notCheckedAspect.checked).toBeFalsy();
|
||||
|
||||
const customAspectCheckbox: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-2-check-input');
|
||||
expect(customAspectCheckbox).toBeDefined();
|
||||
expect(customAspectCheckbox).not.toBeNull();
|
||||
expect(customAspectCheckbox.checked).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -298,14 +296,16 @@ describe('AspectListDialogComponent', () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const applyButton = fixture.nativeElement.querySelector('#aspect-list-dialog-actions-apply');
|
||||
|
||||
fixture.nativeElement.querySelector('#aspect-list-dialog-actions-clear').click();
|
||||
getClearButton().nativeElement.click();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(applyButton.disabled).toBe(false);
|
||||
expect(getApplyButton().nativeElement.disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('should announce the amount of selected aspects', () => {
|
||||
expect(testingUtils.getByCSS('#aspect-list-dialog-counter').nativeElement.getAttribute('aria-live')).toBe('polite');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ import { DocumentListComponent, DocumentListService } from '../document-list';
|
||||
import { BreadcrumbComponent } from './breadcrumb.component';
|
||||
import { of } from 'rxjs';
|
||||
import { NoopAuthModule } from '@alfresco/adf-core';
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { LiveAnnouncer } from '@angular/cdk/a11y';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
describe('Breadcrumb', () => {
|
||||
let component: BreadcrumbComponent;
|
||||
@@ -31,6 +34,8 @@ describe('Breadcrumb', () => {
|
||||
isCustomSourceService: false
|
||||
});
|
||||
let documentListComponent: DocumentListComponent;
|
||||
let liveAnnouncer: LiveAnnouncer;
|
||||
let translateService: TranslateService;
|
||||
|
||||
const getBreadcrumbActionText = (): string => fixture.debugElement.nativeElement.querySelector('.adf-breadcrumb-item-current').textContent.trim();
|
||||
|
||||
@@ -43,6 +48,8 @@ describe('Breadcrumb', () => {
|
||||
component = fixture.componentInstance;
|
||||
documentListComponent = TestBed.createComponent<DocumentListComponent>(DocumentListComponent).componentInstance;
|
||||
documentListService = TestBed.inject(DocumentListService);
|
||||
liveAnnouncer = TestBed.inject(LiveAnnouncer);
|
||||
translateService = TestBed.inject(TranslateService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -59,7 +66,7 @@ describe('Breadcrumb', () => {
|
||||
it('should root be present as default node if the path is null', () => {
|
||||
component.root = 'default';
|
||||
component.folderNode = fakeNodeWithCreatePermission;
|
||||
component.ngOnChanges();
|
||||
component.ngOnChanges({});
|
||||
|
||||
expect(component.route[0].name).toBe('default');
|
||||
});
|
||||
@@ -313,7 +320,7 @@ describe('Breadcrumb', () => {
|
||||
return transformNode;
|
||||
};
|
||||
component.folderNode = node;
|
||||
component.ngOnChanges();
|
||||
component.ngOnChanges({});
|
||||
expect(component.route.length).toBe(4);
|
||||
expect(component.route[3].id).toBe('test-id');
|
||||
expect(component.route[3].name).toBe('test-name');
|
||||
@@ -330,7 +337,7 @@ describe('Breadcrumb', () => {
|
||||
path: { elements: [{ id: 'element-1-id', name: 'element-1-name' }] }
|
||||
} as Node;
|
||||
|
||||
component.ngOnChanges();
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getBreadcrumbActionText()).toEqual('test-name');
|
||||
@@ -340,4 +347,15 @@ describe('Breadcrumb', () => {
|
||||
|
||||
expect(getBreadcrumbActionText()).toEqual('BREADCRUMB.HEADER.SELECTED');
|
||||
});
|
||||
|
||||
it('should announce number of selected items when selectedRowItemsCount changes', () => {
|
||||
const change = new SimpleChange(null, 10, true);
|
||||
spyOn(liveAnnouncer, 'announce');
|
||||
spyOn(translateService, 'instant').and.callThrough();
|
||||
|
||||
component.ngOnChanges({ selectedRowItemsCount: change });
|
||||
|
||||
expect(translateService.instant).toHaveBeenCalledWith('BREADCRUMB.HEADER.SELECTED', { count: 10 });
|
||||
expect(liveAnnouncer.announce).toHaveBeenCalledWith('BREADCRUMB.HEADER.SELECTED');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,14 +15,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, DestroyRef, EventEmitter, inject, Input, OnChanges, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { MatSelect, MatSelectModule } from '@angular/material/select';
|
||||
import { Node, PathElement } from '@alfresco/js-api';
|
||||
import { DocumentListComponent } from '../document-list/components/document-list.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { IconModule } from '@alfresco/adf-core';
|
||||
import { LiveAnnouncer } from '@angular/cdk/a11y';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-breadcrumb',
|
||||
@@ -85,6 +98,8 @@ export class BreadcrumbComponent implements OnInit, OnChanges {
|
||||
route: PathElement[] = [];
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly liveAnnouncer = inject(LiveAnnouncer);
|
||||
private readonly translationService = inject(TranslateService);
|
||||
|
||||
get hasRoot(): boolean {
|
||||
return !!this.root;
|
||||
@@ -109,8 +124,13 @@ export class BreadcrumbComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.recalculateNodes();
|
||||
|
||||
if (changes['selectedRowItemsCount'] && changes['selectedRowItemsCount'].currentValue > 0) {
|
||||
const msg = this.translationService.instant('BREADCRUMB.HEADER.SELECTED', { count: changes['selectedRowItemsCount'].currentValue });
|
||||
this.liveAnnouncer.announce(msg);
|
||||
}
|
||||
}
|
||||
|
||||
protected recalculateNodes(): void {
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('DropdownBreadcrumb', () => {
|
||||
|
||||
const triggerComponentChange = (fakeNodeData) => {
|
||||
component.folderNode = fakeNodeData;
|
||||
component.ngOnChanges();
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user