[ADF-5543] Enable lint accessibility and resolve found issues (#9421)

This commit is contained in:
tomson
2024-04-17 09:36:40 +02:00
committed by GitHub
parent eaad09b06d
commit 74ef7eed1a
61 changed files with 1162 additions and 1054 deletions

View File

@@ -26,7 +26,6 @@ import { AspectListDialogComponentData } from './aspect-list-dialog-data.interfa
encapsulation: ViewEncapsulation.None
})
export class AspectListDialogComponent implements OnInit {
title: string;
description: string;
currentNodeId: string;
@@ -35,8 +34,7 @@ export class AspectListDialogComponent implements OnInit {
currentAspectSelection: string[] = [];
constructor(private dialog: MatDialogRef<AspectListDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: AspectListDialogComponentData) {
constructor(private dialog: MatDialogRef<AspectListDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: AspectListDialogComponentData) {
this.title = data.title;
this.description = data.description;
this.overTableMessage = data.overTableMessage;
@@ -47,7 +45,7 @@ export class AspectListDialogComponent implements OnInit {
this.dialog.backdropClick().subscribe(() => {
this.close();
});
this.dialog.keydownEvents().subscribe(event => {
this.dialog.keydownEvents().subscribe((event) => {
// Esc
if (event.keyCode === 27) {
event.preventDefault();

View File

@@ -16,7 +16,7 @@
*/
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { NodesApiService } from '../common/services/nodes-api.service';
import { NodesApiService } from '../common/services/nodes-api.service';
import { Observable, Subject, zip } from 'rxjs';
import { concatMap, map, takeUntil, tap } from 'rxjs/operators';
import { AspectListService } from './services/aspect-list.service';
@@ -28,9 +28,7 @@ import { AspectEntry } from '@alfresco/js-api';
styleUrls: ['./aspect-list.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class AspectListComponent implements OnInit, OnDestroy {
/** Node Id of the node that we want to update */
@Input()
nodeId: string = '';
@@ -56,8 +54,7 @@ export class AspectListComponent implements OnInit, OnDestroy {
private onDestroy$ = new Subject<boolean>();
constructor(private aspectListService: AspectListService, private nodeApiService: NodesApiService) {
}
constructor(private aspectListService: AspectListService, private nodeApiService: NodesApiService) {}
ngOnDestroy(): void {
this.onDestroy$.next(true);
@@ -68,26 +65,28 @@ export class AspectListComponent implements OnInit, OnDestroy {
let aspects$: Observable<AspectEntry[]>;
if (this.nodeId) {
const node$ = this.nodeApiService.getNode(this.nodeId);
const customAspect$ = this.aspectListService.getCustomAspects(this.aspectListService.getVisibleAspects())
.pipe(map(
(customAspects) => customAspects.flatMap((customAspect) => customAspect.entry.id)
));
const customAspect$ = this.aspectListService
.getCustomAspects(this.aspectListService.getVisibleAspects())
.pipe(map((customAspects) => customAspects.flatMap((customAspect) => customAspect.entry.id)));
aspects$ = zip(node$, customAspect$).pipe(
tap(([node, customAspects]) => {
this.nodeAspects = node.aspectNames.filter((aspect) => this.aspectListService.getVisibleAspects().includes(aspect) || customAspects.includes(aspect));
this.nodeAspectStatus = [ ...this.nodeAspects ];
this.notDisplayedAspects = node.aspectNames.filter((aspect) => !this.aspectListService.getVisibleAspects().includes(aspect) && !customAspects.includes(aspect));
this.nodeAspects = node.aspectNames.filter(
(aspect) => this.aspectListService.getVisibleAspects().includes(aspect) || customAspects.includes(aspect)
);
this.nodeAspectStatus = [...this.nodeAspects];
this.notDisplayedAspects = node.aspectNames.filter(
(aspect) => !this.aspectListService.getVisibleAspects().includes(aspect) && !customAspects.includes(aspect)
);
this.valueChanged.emit([...this.nodeAspects, ...this.notDisplayedAspects]);
this.updateCounter.emit(this.nodeAspects.length);
}),
concatMap(() => this.aspectListService.getAspects()),
takeUntil(this.onDestroy$));
takeUntil(this.onDestroy$)
);
} else {
aspects$ = this.aspectListService.getAspects()
.pipe(takeUntil(this.onDestroy$));
aspects$ = this.aspectListService.getAspects().pipe(takeUntil(this.onDestroy$));
}
this.aspects$ = aspects$.pipe(map((aspects) =>
aspects.filter((aspect) => !this.excludedAspects.includes(aspect.entry.id))));
this.aspects$ = aspects$.pipe(map((aspects) => aspects.filter((aspect) => !this.excludedAspects.includes(aspect.entry.id))));
}
onCheckBoxClick(event: Event) {
@@ -133,7 +132,7 @@ export class AspectListComponent implements OnInit, OnDestroy {
private updateEqualityOfAspectList() {
if (this.nodeAspectStatus.length !== this.nodeAspects.length) {
this.hasEqualAspect = false;
this.hasEqualAspect = false;
} else {
this.hasEqualAspect = this.nodeAspects.every((aspect) => this.nodeAspectStatus.includes(aspect));
}

View File

@@ -27,6 +27,7 @@
*ngIf="hasPreviousNodes()"
class="adf-breadcrumb-dropdown-path"
tabindex="-1"
role="button"
>
<mat-option
*ngFor="let node of previousNodes"

View File

@@ -20,6 +20,7 @@
*ngIf="hasPreviousNodes()"
class="adf-dropdown-breadcrumb-path-select"
tabindex="-1"
role="button"
data-automation-id="dropdown-breadcrumb-path"
aria-labelledby="dropdown-breadcrumb-button">

View File

@@ -34,6 +34,9 @@
<ng-container *ngIf="isCRUDMode && (!existingCategoriesLoading || existingCategories)">
<span class="adf-create-category-label"
(click)="addCategory()"
tabindex="0"
role="button"
(keyup.enter)="addCategory()"
[hidden]="categoryNameControl.invalid || typing">
{{ 'CATEGORIES_MANAGEMENT.GENERIC_CREATE' | translate : { name: categoryNameControl.value } }}
</span>

View File

@@ -2,6 +2,8 @@
id="userinfo_container"
[class.adf-userinfo-name-right]="showOnRight"
(keyup)="onKeyPress($event)"
tabindex="0"
role="button"
class="adf-userinfo-container"
*ngIf="canShow"
>

View File

@@ -86,9 +86,7 @@ import { ContentAuthLoaderService } from './auth-loader/content-auth-loader.serv
SecurityControlsServiceModule,
CategoriesModule
],
providers: [
provideTranslations('adf-content-services', 'assets/adf-content-services')
],
providers: [provideTranslations('adf-content-services', 'assets/adf-content-services')],
exports: [
ContentPipeModule,
TagModule,

View File

@@ -38,6 +38,8 @@ import { takeUntil } from 'rxjs/operators';
class="adf-datatable-cell-value"
title="{{ displayTooltip$ | async }}"
(click)="onClick()"
tabindex="0"
(keyup.enter)="onClick()"
>
{{ displayText$ | async }}
</span>

View File

@@ -37,6 +37,8 @@ import { takeUntil } from 'rxjs/operators';
class="adf-datatable-cell-value"
title="{{ node | adfNodeNameTooltip }}"
(click)="onClick()"
tabindex="0"
(keyup.enter)="onClick()"
>
{{ displayText$ | async }}
</span>

View File

@@ -119,7 +119,13 @@ export const errorJson = {
>
<ng-template let-data>
<ul id="autocomplete-search-result-list">
<li *ngFor="let item of data?.list?.entries; let idx = index" (click)="elementClicked()">
<li
*ngFor="let item of data?.list?.entries; let idx = index"
(click)="elementClicked()"
tabindex="0"
role="button"
(keyup.enter)="elementClicked()"
>
<div id="result_option_{{ idx }}">
<span>{{ item?.entry.name }}</span>
</div>

View File

@@ -23,8 +23,14 @@ import { OverlayModule } from '@angular/cdk/overlay';
@Component({
template: `
<div [adf-pop-over]="popOver" [autofocusedElementSelector]="'#test'" [target]="target" #target tabindex="0" [panelClass]="'adf-popover-test'">
</div>
<div
[adf-pop-over]="popOver"
[autofocusedElementSelector]="'#test'"
[target]="target"
#target
tabindex="0"
[panelClass]="'adf-popover-test'"
></div>
<ng-template #popOver>
<div id="test" tabindex="0"></div>
</ng-template>
@@ -38,10 +44,7 @@ describe('PopOverDirective', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [OverlayModule],
declarations: [
PopOverDirective,
PopOverTestComponent
]
declarations: [PopOverDirective, PopOverTestComponent]
});
fixture = TestBed.createComponent(PopOverTestComponent);
});
@@ -56,9 +59,11 @@ describe('PopOverDirective', () => {
it('should focus element indicated by autofocusedElementSelector on pop over trigger enter keyup', () => {
const popOverTrigger = fixture.debugElement.query(By.directive(PopOverDirective)).nativeElement;
fixture.detectChanges();
popOverTrigger.dispatchEvent(new KeyboardEvent('keyup', {
key: 'Enter'
}));
popOverTrigger.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'Enter'
})
);
expect(fixture.debugElement.query(By.css('#test')).nativeElement).toBe(document.activeElement);
});
@@ -66,27 +71,33 @@ describe('PopOverDirective', () => {
const popOverTrigger = fixture.debugElement.query(By.directive(PopOverDirective)).nativeElement;
fixture.detectChanges();
popOverTrigger.click();
document.dispatchEvent(new KeyboardEvent('keyup', {
key: 'Escape'
}));
document.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'Escape'
})
);
expect(popOverTrigger).toBe(document.activeElement);
});
it('should not focus pop over trigger on document esc keyup if pop over is not open', () => {
const popOverTrigger = fixture.debugElement.query(By.directive(PopOverDirective)).nativeElement;
fixture.detectChanges();
document.dispatchEvent(new KeyboardEvent('keyup', {
key: 'Escape'
}));
document.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'Escape'
})
);
expect(popOverTrigger).not.toEqual(document.activeElement);
});
it('should open pop over on enter key press if pop over is not open', () => {
const popOverTrigger = fixture.debugElement.query(By.directive(PopOverDirective)).nativeElement;
fixture.detectChanges();
popOverTrigger.dispatchEvent(new KeyboardEvent('keyup', {
key: 'Enter'
}));
popOverTrigger.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'Enter'
})
);
fixture.detectChanges();
const popOverPanel = document.querySelector('.adf-popover-test');
expect(popOverPanel).toBeDefined();
@@ -97,9 +108,11 @@ describe('PopOverDirective', () => {
fixture.detectChanges();
popOverTrigger.click();
fixture.detectChanges();
popOverTrigger.dispatchEvent(new KeyboardEvent('keyup', {
key: 'Enter'
}));
popOverTrigger.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'Enter'
})
);
fixture.detectChanges();
const popOverPanel = document.querySelector('.adf-popover-test');
expect(popOverPanel).toBeNull();

View File

@@ -22,7 +22,7 @@ import { SearchFacetFiltersService } from '../services/search-facet-filters.serv
import { SearchQueryBuilderService } from '../services/search-query-builder.service';
@Component({
template: `<button adf-reset-search></button>`
template: `<button adf-reset-search>Reset</button>`
})
class TestComponent {}

View File

@@ -5,11 +5,20 @@
<mat-icon class="adf-search-field-icon">search</mat-icon>
</button>
<mat-form-field class="adf-facet-search-field" floatLabel="never">
<input matInput placeholder="{{ 'SEARCH.FILTER.ACTIONS.SEARCH' | translate }}"
[attr.data-automation-id]="'facet-result-filter-'+field.label" [(ngModel)]="field.buckets.filterText">
<button *ngIf="field.buckets.filterText" mat-button matSuffix mat-icon-button
[attr.title]="'SEARCH.FILTER.BUTTONS.CLEAR' | translate"
(click)="field.buckets.filterText = ''">
<input
matInput
placeholder="{{ 'SEARCH.FILTER.ACTIONS.SEARCH' | translate }}"
[attr.data-automation-id]="'facet-result-filter-' + field.label"
[(ngModel)]="field.buckets.filterText"
/>
<button
*ngIf="field.buckets.filterText"
mat-button
matSuffix
mat-icon-button
[attr.title]="'SEARCH.FILTER.BUTTONS.CLEAR' | translate"
(click)="field.buckets.filterText = ''"
>
<mat-icon role="button" [attr.aria-label]="'SEARCH.FILTER.BUTTONS.CLEAR' | translate">clear</mat-icon>
</button>
</mat-form-field>
@@ -17,14 +26,19 @@
</div>
<div class="adf-checklist">
<mat-checkbox *ngFor="let bucket of field.buckets" [checked]="bucket.checked"
[attr.data-automation-id]="'checkbox-'+field.label+'-'+(bucket.display || bucket.label)"
<mat-checkbox
*ngFor="let bucket of field.buckets"
[checked]="bucket.checked"
[attr.data-automation-id]="'checkbox-' + field.label + '-' + (bucket.display || bucket.label)"
(change)="onToggleBucket($event, field, bucket)"
class="adf-search-filter-facet-checkbox">
<div matTooltip="{{ bucket.display || bucket.label | translate }} {{ getBucketCountDisplay(bucket) }}"
class="adf-search-filter-facet-checkbox"
>
<div
matTooltip="{{ bucket.display || bucket.label | translate }} {{ getBucketCountDisplay(bucket) }}"
matTooltipPosition="right"
class="adf-facet-label"
[class.adf-search-filter-facet-checkbox-checked]='bucket.checked'>
[class.adf-search-filter-facet-checkbox-checked]="bucket.checked"
>
{{ bucket.display || bucket.label | translate }} {{ getBucketCountDisplay(bucket) }}
</div>
</mat-checkbox>
@@ -37,16 +51,28 @@
</div>
<div class="adf-facet-buttons" *ngIf="!field.buckets.fitsPage">
<button mat-icon-button *ngIf="canResetSelectedBuckets(field)"
title="{{ 'SEARCH.FILTER.ACTIONS.CLEAR-ALL' | translate }}" (click)="resetSelectedBuckets(field)">
<button
mat-icon-button
*ngIf="canResetSelectedBuckets(field)"
title="{{ 'SEARCH.FILTER.ACTIONS.CLEAR-ALL' | translate }}"
(click)="resetSelectedBuckets(field)"
>
<mat-icon>clear</mat-icon>
</button>
<button mat-icon-button *ngIf="field.buckets.canShowLessItems" (click)="field.buckets.showLessItems()"
title="{{ 'SEARCH.FILTER.ACTIONS.SHOW-LESS' | translate }}">
<button
mat-icon-button
*ngIf="field.buckets.canShowLessItems"
(click)="field.buckets.showLessItems()"
title="{{ 'SEARCH.FILTER.ACTIONS.SHOW-LESS' | translate }}"
>
<mat-icon>keyboard_arrow_up</mat-icon>
</button>
<button mat-icon-button *ngIf="field.buckets.canShowMoreItems" (click)="field.buckets.showMoreItems()"
title="{{ 'SEARCH.FILTER.ACTIONS.SHOW-MORE' | translate }}">
<button
mat-icon-button
*ngIf="field.buckets.canShowMoreItems"
(click)="field.buckets.showMoreItems()"
title="{{ 'SEARCH.FILTER.ACTIONS.SHOW-MORE' | translate }}"
>
<mat-icon>keyboard_arrow_down</mat-icon>
</button>
</div>

View File

@@ -3,7 +3,8 @@
class="adf-search-filter-chip-tabbed"
[class.adf-search-toggle-chip]="displayValue || menuTrigger.menuOpen"
[disabled]="!isPopulated"
[tabIndex]="0"
tabIndex="0"
role="button"
[matMenuTriggerFor]="menu"
(menuOpened)="onMenuOpen()"
(keydown.enter)="onEnterKeydown()"

View File

@@ -3,7 +3,8 @@
class="adf-search-filter-chip adf-search-filter-facet-chip"
[class.adf-search-toggle-chip]="(facetField.displayValue$ | async) || menuTrigger.menuOpen"
[disabled]="!isPopulated()"
[tabIndex]="0"
tabIndex="0"
role="button"
[matMenuTriggerFor]="menu"
(menuOpened)="onMenuOpen()"
(keydown.enter)="onEnterKeydown()"

View File

@@ -2,7 +2,8 @@
[disableRipple]="true"
class="adf-search-filter-chip"
[class.adf-search-toggle-chip]="(widget.getDisplayValue() | async) || menuTrigger.menuOpen"
[tabIndex]="0"
tabIndex="0"
role="button"
[matMenuTriggerFor]="menu"
(menuOpened)="onMenuOpen()"
(keydown.enter)="onEnterKeydown()"

View File

@@ -1,52 +1,58 @@
<div *ngIf="!!category"
class="adf-filter">
<button mat-icon-button
[matMenuTriggerFor]="filter"
data-automation-id="filter-menu-button"
#menuTrigger="matMenuTrigger"
(click)="$event.stopPropagation()"
(menuOpened)="onMenuOpen()"
(keyup.enter)="$event.stopPropagation()"
class="adf-filter-button"
[attr.aria-label]="getTooltipTranslation(col?.title)"
[matTooltip]="getTooltipTranslation(col?.title)">
<adf-icon value="filter_list"
[ngClass]="{ 'adf-icon-active': isActive() || menuTrigger.menuOpen }"
class="adf-filter-icon"
matBadge="filter"
matBadgeColor="warn"
[matBadgeHidden]="!isActive()">
<div *ngIf="!!category" class="adf-filter">
<button
mat-icon-button
[matMenuTriggerFor]="filter"
data-automation-id="filter-menu-button"
#menuTrigger="matMenuTrigger"
(click)="$event.stopPropagation()"
(menuOpened)="onMenuOpen()"
(keyup.enter)="$event.stopPropagation()"
class="adf-filter-button"
[attr.aria-label]="getTooltipTranslation(col?.title)"
[matTooltip]="getTooltipTranslation(col?.title)"
>
<adf-icon
value="filter_list"
[ngClass]="{ 'adf-icon-active': isActive() || menuTrigger.menuOpen }"
class="adf-filter-icon"
matBadge="filter"
matBadgeColor="warn"
[matBadgeHidden]="!isActive()"
>
</adf-icon>
</button>
<mat-menu #filter="matMenu"
class="adf-filter-menu adf-search-filter-menu"
(closed)="onClosed()">
<div #filterContainer
role="menuitem"
(keydown.tab)="$event.stopPropagation();">
<div (click)="$event.stopPropagation()"
class="adf-filter-container">
<mat-menu #filter="matMenu" class="adf-filter-menu adf-search-filter-menu" (closed)="onClosed()">
<div #filterContainer role="menuitem" tabindex="0" (keydown.tab)="$event.stopPropagation()">
<div (click)="$event.stopPropagation()" role="button" tabindex="0" (keyup.enter)="$event.stopPropagation()" class="adf-filter-container">
<div class="adf-filter-title">{{ category?.name | translate }}</div>
<adf-search-widget-container (keypress)="onKeyPressed($event, menuTrigger)"
[id]="category?.id"
[selector]="category?.component?.selector"
[settings]="category?.component?.settings"
[value]="initialValue">
<adf-search-widget-container
(keypress)="onKeyPressed($event, menuTrigger)"
[id]="category?.id"
[selector]="category?.component?.selector"
[settings]="category?.component?.settings"
[value]="initialValue"
>
</adf-search-widget-container>
</div>
<mat-dialog-actions class="adf-filter-actions">
<button mat-button
id="clear-filter-button"
[attr.aria-label]="'SEARCH.SEARCH_HEADER.CLEAR' | translate"
(click)="onClearButtonClick($event)">{{ 'SEARCH.SEARCH_HEADER.CLEAR' | translate | uppercase }}
<button
mat-button
id="clear-filter-button"
[attr.aria-label]="'SEARCH.SEARCH_HEADER.CLEAR' | translate"
(click)="onClearButtonClick($event)"
>
{{ 'SEARCH.SEARCH_HEADER.CLEAR' | translate | uppercase }}
</button>
<button mat-button
color="primary"
id="apply-filter-button"
class="adf-filter-apply-button"
[attr.aria-label]="'SEARCH.SEARCH_HEADER.APPLY' | translate"
(click)="onApply()">{{ 'SEARCH.SEARCH_HEADER.APPLY' | translate | uppercase }}
<button
mat-button
color="primary"
id="apply-filter-button"
class="adf-filter-apply-button"
[attr.aria-label]="'SEARCH.SEARCH_HEADER.APPLY' | translate"
(click)="onApply()"
>
{{ 'SEARCH.SEARCH_HEADER.APPLY' | translate | uppercase }}
</button>
</mat-dialog-actions>
</div>

View File

@@ -25,10 +25,10 @@ import { SearchFilterContainerComponent } from './search-filter-container.compon
import { SearchCategory } from '../../models/search-category.interface';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatInputHarness } from '@angular/material/input/testing';
import { MatMenuHarness } from '@angular/material/menu/testing';
import { MatButtonHarness } from '@angular/material/button/testing';
import { MatBadgeHarness } from '@angular/material/badge/testing';
import { MatInputHarness } from '@angular/material/input/testing';
const mockCategory: SearchCategory = {
id: 'queryName',
@@ -176,7 +176,9 @@ describe('SearchFilterContainerComponent', () => {
expect(component.focusTrap._element).toBe(component.filterContainer.nativeElement);
});
it('should focus the input element when the menu is opened', async () => {
// TODO: very flaky test, need to be refactored
// eslint-disable-next-line ban/ban
xit('should focus the input element when the menu is opened', async () => {
const menu = await loader.getHarness(MatMenuHarness);
await menu.open();

View File

@@ -25,12 +25,8 @@
</button>
<mat-menu #menu="matMenu" class="adf-search-form-menu">
<button *ngFor="let form of forms"
mat-menu-item
tabindex="0"
[attr.aria-label]="form.name | translate"
(click)="onSelectionChange(form)">
{{form.name | translate}}
<button *ngFor="let form of forms" mat-menu-item tabindex="0" [attr.aria-label]="form.name | translate" (click)="onSelectionChange(form)">
{{ form.name | translate }}
</button>
</mat-menu>
</ng-container>

View File

@@ -0,0 +1,19 @@
<div id="adf-like-container" class="adf-like-container">
<div class="adf-like">
<span
id="adf-like-{{ nodeId }}"
[ngClass]="{ 'adf-like-select': isLike, 'adf-like-grey': !isLike }"
(click)="likeClick()"
tabindex="0"
role="button"
(keyup.enter)="likeClick()"
>
<mat-icon>thumb_up</mat-icon>
</span>
</div>
<div class="adf-like-counter-container">
<div id="adf-like-counter" class="adf-like-counter">{{ likesCounter }}</div>
<div class="adf-left" *ngIf="likesCounter === 1">Like</div>
<div class="adf-left" *ngIf="likesCounter !== 1">Likes</div>
</div>
</div>

View File

@@ -1,7 +1,14 @@
<mat-list>
<mat-list-item *ngFor="let currentEntry of tagsEntries; let idx = index">
<div class="adf-tag-actions-container" id="tag_delete_{{currentEntry.entry.tag}}" (click)="removeTag(currentEntry.entry.id)">
<div class="adf-tag-actions-delete-text" id="tag_name_{{currentEntry.entry.tag}}">{{currentEntry.entry.tag}}</div>
<div
class="adf-tag-actions-container"
id="tag_delete_{{ currentEntry.entry.tag }}"
tabindex="0"
role="button"
(keyup.enter)="removeTag(currentEntry.entry.id)"
(click)="removeTag(currentEntry.entry.id)"
>
<div class="adf-tag-actions-delete-text" id="tag_name_{{ currentEntry.entry.tag }}">{{ currentEntry.entry.tag }}</div>
<mat-icon class="adf-tag-actions-delete-icon">delete</mat-icon>
</div>
</mat-list-item>
@@ -12,23 +19,19 @@
<mat-form-field class="adf-full-width">
<input
id="new-tag-text"
matInput placeholder="{{'TAG.LABEL.NEWTAG' | translate }}"
matInput
placeholder="{{ 'TAG.LABEL.NEWTAG' | translate }}"
type="text"
(keypress)="cleanErrorMsg()"
[(ngModel)]="newTagName"
/>
<mat-hint data-automation-id="errorMessage" *ngIf="error" [ngStyle]="{'color': 'red'}" align="start">{{errorMsg}}</mat-hint>
<mat-hint data-automation-id="errorMessage" *ngIf="error" [ngStyle]="{ color: 'red' }" align="start">{{ errorMsg }} </mat-hint>
</mat-form-field>
</td>
<td>
<button
id="add-tag"
class="adf-full-width"
color="primary"
(click)="addTag()"
[disabled]="disableAddTag"
mat-raised-button
>{{'TAG.BUTTON.ADD' | translate }}</button>
<button id="add-tag" class="adf-full-width" color="primary" (click)="addTag()" [disabled]="disableAddTag" mat-raised-button>
{{ 'TAG.BUTTON.ADD' | translate }}
</button>
</td>
</tr>
</table>

View File

@@ -1,25 +1,21 @@
<div class="adf-tags-creation">
<div *ngIf="tagNameControlVisible" class="adf-tag-name-field">
<input #tagNameInput
class="adf-tag-search-field"
matInput
autocomplete="off"
[formControl]="tagNameControl"
(keyup.enter)="addTag()"
adf-auto-focus
placeholder="{{'TAG.TAGS_CREATOR.TAG_SEARCH_PLACEHOLDER' | translate}}"
<input
#tagNameInput
class="adf-tag-search-field"
matInput
autocomplete="off"
[formControl]="tagNameControl"
(keyup.enter)="addTag()"
adf-auto-focus
placeholder="{{ 'TAG.TAGS_CREATOR.TAG_SEARCH_PLACEHOLDER' | translate }}"
/>
<mat-error *ngIf="tagNameControl.invalid && tagNameControl.touched">{{ tagNameErrorMessageKey | translate }}</mat-error>
<mat-error *ngIf="tagNameControl.invalid && tagNameControl.touched">{{ tagNameErrorMessageKey | translate }} </mat-error>
</div>
<p
class="adf-no-tags-message"
*ngIf="showEmptyTagMessage">
<p class="adf-no-tags-message" *ngIf="showEmptyTagMessage">
{{ 'TAG.TAGS_CREATOR.NO_TAGS_CREATED' | translate }}
</p>
<div
class="adf-tags-list"
[class.adf-tags-list-fixed]="!tagNameControlVisible"
#tagsList>
<div class="adf-tags-list" [class.adf-tags-list-fixed]="!tagNameControlVisible" #tagsList>
<span *ngFor="let tag of tags" class="adf-tag adf-label-with-icon-button">
{{ tag }}
<button
@@ -28,32 +24,31 @@
(click)="removeTag(tag)"
[attr.title]="'TAG.TAGS_CREATOR.TOOLTIPS.DELETE_TAG' | translate"
[disabled]="disabledTagsRemoving"
class="adf-remove-tag">
class="adf-remove-tag"
>
<mat-icon>close</mat-icon>
</button>
</span>
</div>
</div>
<div
class="adf-existing-tags-panel"
*ngIf="existingTagsPanelVisible">
<span *ngIf="!spinnerVisible || existingTags"
<div class="adf-existing-tags-panel" *ngIf="existingTagsPanelVisible">
<span
*ngIf="!spinnerVisible || existingTags"
class="adf-create-tag-label"
(click)="addTag()"
[hidden]="tagNameControl.invalid || typing">
role="button"
tabindex="0"
(keyup.enter)="addTag()"
[hidden]="tagNameControl.invalid || typing"
>
{{ 'TAG.TAGS_CREATOR.CREATE_TAG' | translate : { tag: tagNameControl.value } }}
</span>
<p *ngIf="!spinnerVisible && existingTags" class="adf-existing-tags-label">
{{ (isOnlyCreateMode() ? 'TAG.TAGS_CREATOR.EXISTING_TAGS' : 'TAG.TAGS_CREATOR.EXISTING_TAGS_SELECTION') | translate }}
</p>
<div class="adf-tags-list">
<mat-list
*ngIf="!spinnerVisible && existingTags"
[disabled]="isOnlyCreateMode()">
<mat-list-item
*ngFor="let tagRow of existingTags"
class="adf-tag"
(click)='addExistingTagToTagsToAssign(tagRow)'>
<mat-list *ngIf="!spinnerVisible && existingTags" [disabled]="isOnlyCreateMode()">
<mat-list-item *ngFor="let tagRow of existingTags" class="adf-tag" (click)="addExistingTagToTagsToAssign(tagRow)">
{{ tagRow.entry.tag }}
</mat-list-item>
<p *ngIf="!existingTags?.length">{{ 'TAG.TAGS_CREATOR.NO_EXISTING_TAGS' | translate }}</p>
@@ -62,7 +57,8 @@
*ngIf="spinnerVisible"
class="adf-tags-creator-spinner"
[diameter]="50"
[attr.aria-label]="'TAG.TAGS_CREATOR.TAGS_LOADING' | translate">
[attr.aria-label]="'TAG.TAGS_CREATOR.TAGS_LOADING' | translate"
>
</mat-spinner>
</div>
</div>

View File

@@ -113,7 +113,7 @@ describe('TagsCreatorComponent', () => {
*/
function getRemoveTagButtons(): HTMLButtonElement[] {
const elements = fixture.debugElement.queryAll(By.css(`[data-automation-id="remove-tag-button"]`));
return elements.map(el => el.nativeElement);
return elements.map((el) => el.nativeElement);
}
/**
@@ -123,7 +123,7 @@ describe('TagsCreatorComponent', () => {
*/
function getAddedTags(): string[] {
const tagElements = fixture.debugElement.queryAll(By.css(`.adf-tags-creation .adf-tag`));
return tagElements.map(el => el.nativeElement.firstChild.nodeValue.trim());
return tagElements.map((el) => el.nativeElement.firstChild.nodeValue.trim());
}
/**
@@ -175,7 +175,7 @@ describe('TagsCreatorComponent', () => {
describe('Created tags list', () => {
it('should display no tags created message after initialization', () => {
const message = fixture.debugElement.query(By.css('.adf-no-tags-message')).nativeElement.textContent.trim();
const message = fixture.debugElement.query(By.css('.adf-no-tags-message')).nativeElement.textContent.trim();
expect(message).toBe('TAG.TAGS_CREATOR.NO_TAGS_CREATED');
});
@@ -227,12 +227,14 @@ describe('TagsCreatorComponent', () => {
it('should not duplicate already existing tag', fakeAsync(() => {
const tag = 'Tag';
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
spyOn(tagService, 'findTagByName').and.returnValue(
of({
entry: {
tag,
id: 'tag-1'
}
})
);
addTagToAddedList(tag, true);
expect(getAddedTags().length).toBe(0);
@@ -322,7 +324,7 @@ describe('TagsCreatorComponent', () => {
*/
function getFirstError(): string {
const error = fixture.debugElement.query(By.directive(MatError));
return error?.nativeElement.textContent;
return error?.nativeElement.textContent.trim();
}
it('should show error for only spaces', fakeAsync(() => {
@@ -389,12 +391,14 @@ describe('TagsCreatorComponent', () => {
it('should show error when duplicated already existing tag', fakeAsync(() => {
const tag = 'Some tag';
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
spyOn(tagService, 'findTagByName').and.returnValue(
of({
entry: {
tag,
id: 'tag-1'
}
})
);
typeTag(tag);
const error = getFirstError();
@@ -408,12 +412,14 @@ describe('TagsCreatorComponent', () => {
addTagToAddedList(tag1, true, 0);
tick();
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag: tag2,
id: 'tag-1'
}
}));
spyOn(tagService, 'findTagByName').and.returnValue(
of({
entry: {
tag: tag2,
id: 'tag-1'
}
})
);
typeTag(tag2);
component.removeTag(tag1);
tick();
@@ -505,12 +511,14 @@ describe('TagsCreatorComponent', () => {
it('should not be visible when trying to duplicate already existing tag', fakeAsync(() => {
const tag = 'Tag';
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
spyOn(tagService, 'findTagByName').and.returnValue(
of({
entry: {
tag,
id: 'tag-1'
}
})
);
typeTag(tag);
expect(getCreateTagLabel().hasAttribute('hidden')).toBeTruthy();
}));
@@ -531,7 +539,7 @@ describe('TagsCreatorComponent', () => {
*/
function getExistingTags(): string[] {
const tagElements = fixture.debugElement.queryAll(By.css(`.adf-existing-tags-panel .adf-tag`));
return tagElements.map(el => el.nativeElement.textContent.trim());
return tagElements.map((el) => el.nativeElement.textContent.trim());
}
it('should call findTagByName on tagService using name set in input', fakeAsync(() => {
@@ -560,8 +568,7 @@ describe('TagsCreatorComponent', () => {
const name = 'Tag';
typeTag(name);
expect(tagService.searchTags).toHaveBeenCalledWith(name, { orderBy: 'tag', direction: 'asc' },
false, 0, 15 );
expect(tagService.searchTags).toHaveBeenCalledWith(name, { orderBy: 'tag', direction: 'asc' }, false, 0, 15);
}));
it('should display loaded existing tags', fakeAsync(() => {
@@ -571,10 +578,7 @@ describe('TagsCreatorComponent', () => {
spyOn(tagService, 'searchTags').and.returnValue(
of({
list: {
entries: [
{ entry: { tag: tag1 } as any },
{ entry: { tag: tag2 } as any }
],
entries: [{ entry: { tag: tag1 } as any }, { entry: { tag: tag2 } as any }],
pagination: {}
}
})
@@ -596,10 +600,7 @@ describe('TagsCreatorComponent', () => {
spyOn(tagService, 'searchTags').and.returnValue(
of({
list: {
entries: [
{ entry: { tag: tag1 } as any },
{ entry: { tag: tag2 } as any }
],
entries: [{ entry: { tag: tag1 } as any }, { entry: { tag: tag2 } as any }],
pagination: {}
}
})
@@ -622,12 +623,14 @@ describe('TagsCreatorComponent', () => {
it('should display exact tag', fakeAsync(() => {
const tag = 'Tag';
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
spyOn(tagService, 'findTagByName').and.returnValue(
of({
entry: {
tag,
id: 'tag-1'
}
})
);
typeTag(tag);
@@ -638,12 +641,14 @@ describe('TagsCreatorComponent', () => {
it('should not display exact tag if that tag was passed through tags input', fakeAsync(() => {
const tag = 'Tag';
component.tags = [tag];
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
spyOn(tagService, 'findTagByName').and.returnValue(
of({
entry: {
tag,
id: 'tag-1'
}
})
);
typeTag(tag);
@@ -665,19 +670,18 @@ describe('TagsCreatorComponent', () => {
const tag1 = 'Tag 1';
const tag2 = 'Tag 2';
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
spyOn(tagService, 'findTagByName').and.returnValue(
of({
entry: {
tag,
id: 'tag-1'
}
})
);
spyOn(tagService, 'searchTags').and.returnValue(
of({
list: {
entries: [
{ entry: { tag: tag1 } as any },
{ entry: { tag: tag2 } as any }
],
entries: [{ entry: { tag: tag1 } as any }, { entry: { tag: tag2 } as any }],
pagination: {}
}
})
@@ -705,10 +709,7 @@ describe('TagsCreatorComponent', () => {
spyOn(tagService, 'searchTags').and.returnValue(
of({
list: {
entries: [
selectedTag,
{ entry: { tag: leftTag } as any }
],
entries: [selectedTag, { entry: { tag: leftTag } as any }],
pagination: {}
}
})

View File

@@ -1,26 +1,50 @@
<mat-tree class="adf-tree-view-main" [dataSource]="dataSource"
[treeControl]="treeControl" *ngIf="nodeId; else missingNodeId">
<mat-tree-node class="adf-tree-view adf-tree-view-node"
*matTreeNodeDef="let treeNode" id="{{treeNode.name + '-tree-node'}}"
matTreeNodePadding [matTreeNodePaddingIndent]="15">
{{treeNode.name}}
<mat-tree class="adf-tree-view-main" [dataSource]="dataSource" [treeControl]="treeControl" *ngIf="nodeId; else missingNodeId">
<mat-tree-node
class="adf-tree-view adf-tree-view-node"
*matTreeNodeDef="let treeNode"
id="{{ treeNode.name + '-tree-node' }}"
matTreeNodePadding
[matTreeNodePaddingIndent]="15"
>
{{ treeNode.name }}
</mat-tree-node>
<mat-tree-node class="adf-tree-view adf-tree-view-node"
id="{{treeNode.name + '-tree-child-node'}}" *matTreeNodeDef="let treeNode; when: hasChild"
matTreeNodePadding [matTreeNodePaddingIndent]="15">
<button matTreeNodeToggle id="{{'button-'+treeNode.name}}" (click)="onNodeClicked(treeNode.node)"
mat-icon-button [attr.aria-label]="'ADF-TREE-VIEW.ACCESSIBILITY.ARIA_LABEL' | translate: {
name: treeNode.name
}">
<mat-tree-node
class="adf-tree-view adf-tree-view-node"
id="{{ treeNode.name + '-tree-child-node' }}"
*matTreeNodeDef="let treeNode; when: hasChild"
matTreeNodePadding
[matTreeNodePaddingIndent]="15"
>
<button
matTreeNodeToggle
id="{{ 'button-' + treeNode.name }}"
(click)="onNodeClicked(treeNode.node)"
mat-icon-button
[attr.aria-label]="
'ADF-TREE-VIEW.ACCESSIBILITY.ARIA_LABEL'
| translate
: {
name: treeNode.name
}
"
>
<mat-icon class="adf-tree-view-icon">
{{treeControl.isExpanded(treeNode) ? 'folder_open' : 'folder'}}
{{ treeControl.isExpanded(treeNode) ? 'folder_open' : 'folder' }}
</mat-icon>
</button>
<span class="adf-tree-view-label" matTreeNodeToggle (click)="onNodeClicked(treeNode.node)">{{treeNode.name}}</span>
<span
class="adf-tree-view-label"
matTreeNodeToggle
role="button"
(click)="onNodeClicked(treeNode.node)"
tabindex="0"
(keyup.enter)="onNodeClicked(treeNode.node)"
>{{ treeNode.name }}</span
>
</mat-tree-node>
</mat-tree>
<ng-template #missingNodeId>
<div id="adf-tree-view-missing-node">
{{'ADF-TREE-VIEW.MISSING-ID' | translate}}
{{ 'ADF-TREE-VIEW.MISSING-ID' | translate }}
</div>
</ng-template>

View File

@@ -35,6 +35,9 @@
<div class="adf-tree-cell">
<span
class="adf-tree-cell-value"
tabindex="0"
role="button"
(keyup.enter)="loadMoreSubnodes(node)"
(click)="loadMoreSubnodes(node)">
{{ 'ADF-TREE.LOAD-MORE-BUTTON' | translate: { name: loadMoreSuffix } }}
</span>
@@ -50,8 +53,8 @@
(contextmenu)="contextMenuSource = node">
<div class="adf-tree-expand-collapse-container">
<button *ngIf="node.hasChildren"
class="adf-tree-expand-collapse-button"
mat-icon-button>
class="adf-tree-expand-collapse-button"
mat-icon-button>
<mat-progress-spinner
color="primary"
mode="indeterminate"
@@ -68,12 +71,12 @@
</div>
<ng-container *ngIf="selectableNodes">
<mat-checkbox *ngIf="node.hasChildren; else noChildrenNodeCheckbox"
color="primary"
[id]="node.id"
[checked]="descendantsAllSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="onNodeSelected(node)"
data-automation-id="has-children-node-checkbox">
color="primary"
[id]="node.id"
[checked]="descendantsAllSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="onNodeSelected(node)"
data-automation-id="has-children-node-checkbox">
</mat-checkbox>
<ng-template #noChildrenNodeCheckbox>
<mat-checkbox
@@ -89,21 +92,24 @@
<span
class="adf-tree-cell-value"
[class.adf-tree-clickable-cell-value]="node.hasChildren"
tabindex="0"
role="button"
(keyup.enter)="expandCollapseNode(node)"
(click)="expandCollapseNode(node)">
{{ node.nodeName }}
</span>
</div>
<div class="adf-tree-actions">
<button mat-icon-button
[matMenuTriggerFor]="menu"
[attr.id]="'action_menu_right_' + node.id">
[matMenuTriggerFor]="menu"
[attr.id]="'action_menu_right_' + node.id">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<ng-template
[ngTemplateOutlet]="nodeActionsMenuTemplate"
[ngTemplateOutletContext]="{ node: node }">
</ng-template>
</ng-template>
</mat-menu>
</div>
</mat-tree-node>

View File

@@ -25,7 +25,6 @@
</button>
<span
tabindex="0"
class="adf-upload-dialog__title"
*ngIf="!uploadList.isUploadCancelled()">
{{ 'FILE_UPLOAD.MESSAGES.UPLOAD_PROGRESS'
@@ -37,7 +36,6 @@
</span>
<span
tabindex="0"
class="adf-upload-dialog__title"
*ngIf="uploadList.isUploadCancelled()">
{{ 'FILE_UPLOAD.MESSAGES.UPLOAD_CANCELED' | translate }}
@@ -45,7 +43,6 @@
</header>
<section class="adf-upload-dialog__info"
tabindex="0"
*ngIf="totalErrors">
{{
(totalErrors > 1
@@ -76,7 +73,7 @@
aria-describedby="confirmationDescription"
class="adf-upload-dialog__confirmation"
[class.adf-upload-dialog--hide]="!isConfirmation">
<p role="heading" id="confirmationTitle" class="adf-upload-dialog__confirmation--title">
<p role="heading" aria-level="2" id="confirmationTitle" class="adf-upload-dialog__confirmation--title">
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TITLE' | translate }}
</p>
<p id="confirmationDescription" class="adf-upload-dialog__confirmation--text">

View File

@@ -6,7 +6,6 @@
<adf-icon *ngIf="mimeType !== 'default'" value="adf:{{ mimeType }}"></adf-icon>
<span
tabindex="0"
class="adf-file-uploading-row__name"
title="{{ file.name }}">
{{ file.name }}
@@ -60,7 +59,6 @@
*ngIf="isUploadVersionComplete()"
class="adf-file-uploading-row__file-version"
[attr.aria-label]="'ADF_FILE_UPLOAD.STATUS.FILE_DONE_STATUS' | translate"
role="status"
>
<mat-icon
mat-list-icon
@@ -93,7 +91,6 @@
</button>
<div
tabindex="0"
role="status"
*ngIf="isUploadError()"
class="adf-file-uploading-row__block adf-file-uploading-row__status--error">
@@ -105,7 +102,6 @@
</div>
<div
tabindex="0"
[attr.aria-label]="'ADF_FILE_UPLOAD.STATUS.FILE_CANCELED_STATUS' | translate"
role="status"
*ngIf="showCancelledStatus()"