mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ACA-2619][ACA-2616] a11y fixes for Share Link dialog (#5454)
* chore: proper disabling of fields for a11y * component fixes * update tests * update tests * fix aria labels * aria-label fixes * update layout * update e2e tests Co-authored-by: Denys Vuika <denys.vuika@gmail.com> Co-authored-by: Cilibiu Bogdan <bogdan.cilibiu@ness.com>
This commit is contained in:
@@ -7,19 +7,32 @@
|
||||
<p class="adf-share-link__info">{{ 'SHARE.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<div class="adf-share-link--row">
|
||||
<label for="mat-input-1" class="adf-share-link__label">{{ 'SHARE.TITLE' | translate }}</label>
|
||||
<div class="adf-share-link__label">{{ 'SHARE.TITLE' | translate }}</div>
|
||||
|
||||
<mat-slide-toggle color="primary" data-automation-id="adf-share-toggle" [checked]="isFileShared"
|
||||
[disabled]="!canUpdate || isDisabled" (change)="onSlideShareChange($event)">
|
||||
<mat-slide-toggle
|
||||
color="primary"
|
||||
data-automation-id="adf-share-toggle"
|
||||
aria-label="{{ 'SHARE.TITLE' | translate }}"
|
||||
[checked]="isFileShared"
|
||||
[disabled]="!canUpdate || isDisabled"
|
||||
(change)="onSlideShareChange($event)">
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<form [formGroup]="form">
|
||||
<mat-form-field class="adf-full-width adf-float-label" floatLabel='always'>
|
||||
<input #sharedLinkInput data-automation-id="adf-share-link" class="adf-share-link__input" matInput
|
||||
cdkFocusInitial placeholder="{{ 'SHARE.PUBLIC-LINK' | translate }}" formControlName="sharedUrl"
|
||||
<input
|
||||
#sharedLinkInput
|
||||
data-automation-id="adf-share-link"
|
||||
class="adf-share-link__input"
|
||||
matInput
|
||||
cdkFocusInitial
|
||||
placeholder="{{ 'SHARE.PUBLIC-LINK' | translate }}"
|
||||
formControlName="sharedUrl"
|
||||
readonly="readonly">
|
||||
<mat-icon class="adf-input-action" matSuffix
|
||||
<mat-icon
|
||||
class="adf-input-action"
|
||||
matSuffix
|
||||
[clipboard-notification]="'SHARE.CLIPBOARD-MESSAGE' | translate" [adf-clipboard]
|
||||
[target]="sharedLinkInput">
|
||||
link
|
||||
@@ -27,16 +40,31 @@
|
||||
</mat-form-field>
|
||||
|
||||
<div class="adf-share-link--row">
|
||||
<label for="mat-input-2" class="adf-share-link__label">{{ 'SHARE.EXPIRES' | translate }}</label>
|
||||
<mat-slide-toggle [disabled]="!canUpdate" #slideToggleExpirationDate color="primary"
|
||||
data-automation-id="adf-expire-toggle" [checked]="form.controls['time'].value"
|
||||
(change)="onToggleExpirationDate($event)">
|
||||
</mat-slide-toggle>
|
||||
<div class="adf-share-link__label">{{ 'SHARE.EXPIRES' | translate }}</div>
|
||||
<mat-slide-toggle
|
||||
#slideToggleExpirationDate
|
||||
[disabled]="!canUpdate"
|
||||
color="primary"
|
||||
data-automation-id="adf-expire-toggle"
|
||||
aria-label="{{ 'SHARE.EXPIRES' | translate }}"
|
||||
[checked]="time.value"
|
||||
(change)="onToggleExpirationDate($event)">
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<mat-form-field class="adf-full-width adf-float-label" floatLabel='always'>
|
||||
<mat-datetimepicker-toggle #matDatetimepickerToggle="matDatetimepickerToggle" [for]="datetimePicker" matSuffix></mat-datetimepicker-toggle>
|
||||
<mat-datetimepicker #datetimePicker (closed)="onDatetimepickerClosed()" [type]="type" timeInterval="1"></mat-datetimepicker>
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-datetimepicker-toggle
|
||||
#matDatetimepickerToggle="matDatetimepickerToggle"
|
||||
[disabled]="time.disabled"
|
||||
[for]="datetimePicker"
|
||||
matSuffix>
|
||||
</mat-datetimepicker-toggle>
|
||||
<mat-datetimepicker
|
||||
#datetimePicker
|
||||
(closed)="onDatetimepickerClosed()"
|
||||
[type]="type"
|
||||
timeInterval="1">
|
||||
</mat-datetimepicker>
|
||||
<input class="adf-share-link__input"
|
||||
#dateTimePickerInput
|
||||
matInput
|
||||
|
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TestBed, fakeAsync, async, ComponentFixture } from '@angular/core/testing';
|
||||
import { TestBed, fakeAsync, ComponentFixture } from '@angular/core/testing';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material';
|
||||
import { of, empty } from 'rxjs';
|
||||
import {
|
||||
@@ -27,7 +27,9 @@ import {
|
||||
RenditionsService,
|
||||
AppConfigService,
|
||||
CoreModule,
|
||||
AppConfigServiceMock
|
||||
AppConfigServiceMock,
|
||||
AlfrescoApiService,
|
||||
AlfrescoApiServiceMock
|
||||
} from '@alfresco/adf-core';
|
||||
import { ContentNodeShareModule } from './content-node-share.module';
|
||||
import { ShareDialogComponent } from './content-node-share.dialog';
|
||||
@@ -53,6 +55,7 @@ describe('ShareDialogComponent', () => {
|
||||
ContentNodeShareModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock },
|
||||
{ provide: AppConfigService, useClass: AppConfigServiceMock },
|
||||
{ provide: NotificationService, useValue: notificationServiceMock },
|
||||
{ provide: MatDialogRef, useValue: { close: () => {}} },
|
||||
@@ -77,6 +80,12 @@ describe('ShareDialogComponent', () => {
|
||||
properties: {}
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(nodesApiService, 'updateNode').and.returnValue(of({}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
@@ -130,8 +139,10 @@ describe('ShareDialogComponent', () => {
|
||||
expect(fixture.nativeElement.querySelector('.mat-slide-toggle').classList).toContain('mat-checked');
|
||||
});
|
||||
|
||||
it(`should not toggle share action when file has 'sharedId' property`, async(() => {
|
||||
spyOn(sharedLinksApiService, 'createSharedLinks');
|
||||
it(`should not toggle share action when file has 'sharedId' property`, async () => {
|
||||
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of({
|
||||
entry: { id: 'sharedId', sharedId: 'sharedId' }
|
||||
}));
|
||||
spyOn(renditionService, 'generateRenditionForNode').and.returnValue(empty());
|
||||
|
||||
node.entry.properties['qshare:sharedId'] = 'sharedId';
|
||||
@@ -143,19 +154,18 @@ describe('ShareDialogComponent', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sharedLinksApiService.createSharedLinks).not.toHaveBeenCalled();
|
||||
expect(fixture.nativeElement.querySelector('input[formcontrolname="sharedUrl"]').value).toBe('some-url/sharedId');
|
||||
expect(fixture.nativeElement.querySelector('.mat-slide-toggle').classList).toContain('mat-checked');
|
||||
|
||||
});
|
||||
}));
|
||||
expect(sharedLinksApiService.createSharedLinks).not.toHaveBeenCalled();
|
||||
expect(fixture.nativeElement.querySelector('input[formcontrolname="sharedUrl"]').value).toBe('some-url/sharedId');
|
||||
expect(fixture.nativeElement.querySelector('.mat-slide-toggle').classList).toContain('mat-checked');
|
||||
});
|
||||
|
||||
it('should open a confirmation dialog when unshare button is triggered', () => {
|
||||
spyOn(matDialog, 'open').and.returnValue({ beforeClose: () => of(false) });
|
||||
spyOn(sharedLinksApiService, 'deleteSharedLink').and.callThrough();
|
||||
|
||||
node.entry.properties['qshare:sharedId'] = 'sharedId';
|
||||
|
||||
component.data = {
|
||||
@@ -175,7 +185,7 @@ describe('ShareDialogComponent', () => {
|
||||
|
||||
it('should unshare file when confirmation dialog returns true', fakeAsync(() => {
|
||||
spyOn(matDialog, 'open').and.returnValue({ beforeClose: () => of(true) });
|
||||
spyOn(sharedLinksApiService, 'deleteSharedLink').and.callThrough();
|
||||
spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(of({}));
|
||||
node.entry.properties['qshare:sharedId'] = 'sharedId';
|
||||
|
||||
component.data = {
|
||||
@@ -230,7 +240,6 @@ describe('ShareDialogComponent', () => {
|
||||
});
|
||||
|
||||
it('should reset expiration date when toggle is unchecked', () => {
|
||||
spyOn(nodesApiService, 'updateNode').and.returnValue(of({}));
|
||||
node.entry.properties['qshare:sharedId'] = 'sharedId';
|
||||
node.entry.properties['qshare:sharedId'] = '2017-04-15T18:31:37+00:00';
|
||||
node.entry.allowableOperations = ['update'];
|
||||
@@ -282,7 +291,6 @@ describe('ShareDialogComponent', () => {
|
||||
const date = moment();
|
||||
node.entry.allowableOperations = ['update'];
|
||||
node.entry.properties['qshare:sharedId'] = 'sharedId';
|
||||
spyOn(nodesApiService, 'updateNode').and.returnValue(of({}));
|
||||
fixture.componentInstance.form.controls['time'].setValue(null);
|
||||
|
||||
component.data = {
|
||||
@@ -308,7 +316,6 @@ describe('ShareDialogComponent', () => {
|
||||
|
||||
describe('datetimepicker type', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(nodesApiService, 'updateNode').and.returnValue(of({}));
|
||||
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of({}));
|
||||
node.entry.allowableOperations = ['update'];
|
||||
component.data = {
|
||||
@@ -326,7 +333,7 @@ describe('ShareDialogComponent', () => {
|
||||
fixture.nativeElement.querySelector('mat-slide-toggle[data-automation-id="adf-expire-toggle"] label')
|
||||
.dispatchEvent(new MouseEvent('click'));
|
||||
|
||||
fixture.componentInstance.form.controls['time'].setValue(date);
|
||||
fixture.componentInstance.time.setValue(date);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(nodesApiService.updateNode).toHaveBeenCalledWith('nodeId', {
|
||||
@@ -337,13 +344,13 @@ describe('ShareDialogComponent', () => {
|
||||
it('it should update node with input date and time when type is `datetime`', () => {
|
||||
const dateTimePickerType = 'datetime';
|
||||
const date = moment('2525-01-01 13:00:00');
|
||||
spyOn(appConfigService, 'get').and.callFake(() => dateTimePickerType);
|
||||
spyOn(appConfigService, 'get').and.returnValue(dateTimePickerType);
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.nativeElement.querySelector('mat-slide-toggle[data-automation-id="adf-expire-toggle"] label')
|
||||
.dispatchEvent(new MouseEvent('click'));
|
||||
|
||||
fixture.componentInstance.form.controls['time'].setValue(date);
|
||||
fixture.componentInstance.time.setValue(date);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(nodesApiService.updateNode).toHaveBeenCalledWith('nodeId', {
|
||||
|
@@ -24,13 +24,12 @@ import {
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef, MatDialog, MatSlideToggleChange } from '@angular/material';
|
||||
import { FormGroup, FormControl } from '@angular/forms';
|
||||
import { Observable, throwError, Subject } from 'rxjs';
|
||||
import { FormGroup, FormControl, AbstractControl } from '@angular/forms';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import {
|
||||
skip,
|
||||
distinctUntilChanged,
|
||||
mergeMap,
|
||||
catchError,
|
||||
takeUntil
|
||||
} from 'rxjs/operators';
|
||||
import {
|
||||
@@ -62,13 +61,10 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
|
||||
isDisabled: boolean = false;
|
||||
form: FormGroup = new FormGroup({
|
||||
sharedUrl: new FormControl(''),
|
||||
time: new FormControl({ value: '', disabled: false })
|
||||
time: new FormControl({ value: '', disabled: true })
|
||||
});
|
||||
type = 'datetime';
|
||||
|
||||
@ViewChild('matDatetimepickerToggle')
|
||||
matDatetimepickerToggle;
|
||||
|
||||
@ViewChild('slideToggleExpirationDate')
|
||||
slideToggleExpirationDate;
|
||||
|
||||
@@ -92,10 +88,10 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
|
||||
this.type = this.appConfigService.get<string>('sharedLinkDateTimePickerType', 'datetime');
|
||||
|
||||
if (!this.canUpdate) {
|
||||
this.form.controls['time'].disable();
|
||||
this.time.disable();
|
||||
}
|
||||
|
||||
this.form.controls.time.valueChanges
|
||||
this.time.valueChanges
|
||||
.pipe(
|
||||
skip(1),
|
||||
distinctUntilChanged(),
|
||||
@@ -103,9 +99,6 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
|
||||
(updates) => this.updateNode(updates),
|
||||
(formUpdates) => formUpdates
|
||||
),
|
||||
catchError((error) => {
|
||||
return throwError(error);
|
||||
}),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe(updates => this.updateEntryExpiryDate(updates));
|
||||
@@ -120,12 +113,15 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
this.sharedId = properties['qshare:sharedId'];
|
||||
this.isFileShared = true;
|
||||
|
||||
this.updateForm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get time(): AbstractControl {
|
||||
return this.form.controls['time'];
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
@@ -155,17 +151,16 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
onToggleExpirationDate(slideToggle: MatSlideToggleChange) {
|
||||
if (slideToggle.checked) {
|
||||
this.matDatetimepickerToggle.datetimepicker.open();
|
||||
this.time.enable();
|
||||
} else {
|
||||
this.matDatetimepickerToggle.datetimepicker.close();
|
||||
this.form.controls.time.setValue(null);
|
||||
this.time.disable();
|
||||
}
|
||||
}
|
||||
|
||||
onDatetimepickerClosed() {
|
||||
this.dateTimePickerInput.nativeElement.blur();
|
||||
|
||||
if (!this.form.controls.time.value) {
|
||||
if (!this.time.value) {
|
||||
this.slideToggleExpirationDate.checked = false;
|
||||
}
|
||||
}
|
||||
@@ -275,6 +270,12 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
|
||||
sharedUrl: `${this.baseShareUrl}${this.sharedId}`,
|
||||
time: expiryDate ? moment(expiryDate).local() : null
|
||||
});
|
||||
|
||||
if (expiryDate) {
|
||||
this.time.enable();
|
||||
} else {
|
||||
this.time.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private updateNode(date: moment.Moment): Observable<Node> {
|
||||
|
Reference in New Issue
Block a user