mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-42804 Cleaning up tasks and process filter components (#11693)
* AAE-42804 Cleaning up tasks and process filter components * Added loading aria label and code improvements * AAE-42804 Fixing query param
This commit is contained in:
@@ -50,7 +50,8 @@
|
||||
"MAXIMUM_LENGTH": "Length exceeded, {{characters}} characters max.",
|
||||
"SPACE_VALIDATOR": "Cannot begin or end with a space."
|
||||
}
|
||||
}
|
||||
},
|
||||
"LOADING": "Loading"
|
||||
},
|
||||
"ADF_CLOUD_TASK_LIST": {
|
||||
"START_TASK": {
|
||||
@@ -97,7 +98,8 @@
|
||||
"SUBTITLE": "Create a new task that you want to easily find later",
|
||||
"NONE": "No task lists found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LOADING": "Loading"
|
||||
},
|
||||
"ADF_CLOUD_SERVICE_TASK_LIST": {
|
||||
"PROPERTIES": {
|
||||
|
||||
+39
-36
@@ -1,37 +1,40 @@
|
||||
<mat-action-list class="adf-process-filters" *ngIf="filters$ | async as filterList; else loading">
|
||||
<button
|
||||
*ngFor="let filter of filterList"
|
||||
mat-list-item
|
||||
(click)="onFilterClick(filter)"
|
||||
[attr.aria-label]="filter.name | translate"
|
||||
[id]="filter.id"
|
||||
[attr.data-automation-id]="filter.key + '_filter'"
|
||||
[class.adf-active]="currentFilter === filter"
|
||||
>
|
||||
<div class="adf-process-filters__entry">
|
||||
<div>
|
||||
<mat-icon data-automation-id="adf-filter-icon" *ngIf="showIcons" [adf-icon]="filter.icon" aria-hidden="true" />
|
||||
<span
|
||||
data-automation-id="adf-filter-label"
|
||||
class="adf-filter-action-button__label">
|
||||
{{ filter.name | translate }}
|
||||
@if (filters$ | async; as filterList) {
|
||||
<mat-nav-list>
|
||||
@for (filter of filterList; track filter.id) {
|
||||
<a mat-list-item
|
||||
[activated]="filter.id === currentRouteFilterId()"
|
||||
[routerLink]="[PROCESSES_ROUTE]"
|
||||
[queryParams]="{ filterId: filter.id }"
|
||||
[attr.data-automation-id]="filter.key + '_filter'"
|
||||
class="adf-process-filters__entry"
|
||||
[id]="filter.id"
|
||||
[title]="filter.name ?? '' | translate"
|
||||
[attr.aria-label]="filter.name ?? '' | translate"
|
||||
(click)="onFilterClick(filter)">
|
||||
<span matListItemTitle>
|
||||
{{ filter.name ?? '' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
*ngIf="counters[filter.key]"
|
||||
[attr.data-automation-id]="filter.key + '_filter-counter'"
|
||||
class="adf-process-filters__entry-counter"
|
||||
[class.adf-active]="isFilterUpdated(filter.key)"
|
||||
>
|
||||
{{ counters[filter.key] }}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</mat-action-list>
|
||||
<ng-template #loading>
|
||||
<ng-container>
|
||||
<div class="adf-app-list-spinner">
|
||||
<mat-spinner />
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
@if (showIcons) {
|
||||
<mat-icon matListItemIcon
|
||||
data-automation-id="adf-filter-icon"
|
||||
[adf-icon]="filter.icon ?? ''"
|
||||
aria-hidden="true" />
|
||||
}
|
||||
@if (counters[filter.key ?? '']) {
|
||||
<span matListItemMeta
|
||||
[attr.data-automation-id]="filter.key + '_filter-counter'"
|
||||
class="adf-process-filters__entry-counter"
|
||||
[class.adf-active]="isFilterUpdated(filter.key ?? '')"
|
||||
>
|
||||
{{ counters[filter.key ?? ''] }}
|
||||
</span>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
</mat-nav-list>
|
||||
} @else {
|
||||
<div class="adf-app-list-spinner">
|
||||
<mat-spinner [attr.aria-label]="'ADF_CLOUD_PROCESS_LIST.LOADING' | translate" aria-live="polite" />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
+12
-29
@@ -1,35 +1,18 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
.adf-process-filters {
|
||||
margin-right: calc(-1 * var(--adf-theme-spacing));
|
||||
&__entry-counter {
|
||||
padding: 0 5px;
|
||||
border-radius: var(--mat-sys-corner-large, 15px);
|
||||
|
||||
&__entry {
|
||||
font-size: var(--theme-body-1-font-size);
|
||||
color: var(--adf-theme-foreground-text-color-054);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
gap: var(--adf-theme-spacing);
|
||||
&.adf-active {
|
||||
background-color: var(--mat-sys-secondary, var(--theme-accent-color));
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-primary-color);
|
||||
}
|
||||
|
||||
&-counter {
|
||||
padding: 0 5px;
|
||||
border-radius: 15px;
|
||||
|
||||
&.adf-active {
|
||||
background-color: var(--theme-accent-color);
|
||||
color: var(--theme-accent-color-default-contrast);
|
||||
font-size: smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adf-active {
|
||||
.adf-process-filters__entry {
|
||||
color: var(--theme-primary-color);
|
||||
@include mat.list-overrides(
|
||||
(
|
||||
list-item-trailing-supporting-text-color: var(--mat-sys-on-secondary, var(--theme-accent-color-default-contrast))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+38
-132
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { Component, SimpleChange } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testing';
|
||||
import { first, of, throwError } from 'rxjs';
|
||||
import { ProcessFilterCloudService } from '../../services/process-filter-cloud.service';
|
||||
@@ -30,6 +30,11 @@ import { ApolloTestingModule } from 'apollo-angular/testing';
|
||||
import { HarnessLoader } from '@angular/cdk/testing';
|
||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||
import { MatIconHarness } from '@angular/material/icon/testing';
|
||||
import { ActivatedRoute, provideRouter, Router } from '@angular/router';
|
||||
import { RouterTestingHarness } from '@angular/router/testing';
|
||||
|
||||
@Component({ selector: 'adf-cloud-dummy', template: '' })
|
||||
class DummyComponent {}
|
||||
|
||||
const ProcessFilterCloudServiceMock = {
|
||||
getProcessFilters: () => of(mockProcessFilters),
|
||||
@@ -44,8 +49,9 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
let getProcessFiltersSpy: jasmine.Spy;
|
||||
let getProcessNotificationSubscriptionSpy: jasmine.Spy;
|
||||
let loader: HarnessLoader;
|
||||
let router: Router;
|
||||
|
||||
const configureTestingModule = (searchApiMethod: 'GET' | 'POST') => {
|
||||
const configureTestingModule = async (searchApiMethod: 'GET' | 'POST') => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ProcessFiltersCloudComponent, ApolloTestingModule],
|
||||
providers: [
|
||||
@@ -58,7 +64,21 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
getProcessListCount: () => of(10)
|
||||
}
|
||||
},
|
||||
{ provide: ProcessFilterCloudService, useValue: ProcessFilterCloudServiceMock }
|
||||
{ provide: ProcessFilterCloudService, useValue: ProcessFilterCloudServiceMock },
|
||||
provideRouter([{ path: 'process-list-cloud', component: DummyComponent }]),
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
queryParamMap: of({
|
||||
get: (param: string) => {
|
||||
if (param === 'filterId') {
|
||||
return 'fake-process-filter-id';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(ProcessFiltersCloudComponent);
|
||||
@@ -67,6 +87,9 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
component.searchApiMethod = searchApiMethod;
|
||||
|
||||
processFilterService = TestBed.inject(ProcessFilterCloudService);
|
||||
TestBed.inject(ActivatedRoute);
|
||||
router = TestBed.inject(Router);
|
||||
await RouterTestingHarness.create();
|
||||
getProcessFiltersSpy = spyOn(processFilterService, 'getProcessFilters').and.returnValue(of(mockProcessFilters));
|
||||
getProcessNotificationSubscriptionSpy = spyOn(processFilterService, 'getProcessNotificationSubscription').and.returnValue(of([]));
|
||||
};
|
||||
@@ -76,8 +99,8 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
});
|
||||
|
||||
describe('searchApiMethod set to GET', () => {
|
||||
beforeEach(() => {
|
||||
configureTestingModule('GET');
|
||||
beforeEach(async () => {
|
||||
await configureTestingModule('GET');
|
||||
});
|
||||
|
||||
it('should attach specific icon for each filter if hasIcon is true', async () => {
|
||||
@@ -182,20 +205,6 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
|
||||
describe('Highlight Selected Filter', () => {
|
||||
const allProcessesFilterKey = mockProcessFilters[0].key;
|
||||
const runningProcessesFilterKey = mockProcessFilters[1].key;
|
||||
const completedProcessesFilterKey = mockProcessFilters[2].key;
|
||||
|
||||
const getActiveFilterElement = (filterKey: string): Element => {
|
||||
const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
|
||||
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
|
||||
};
|
||||
|
||||
const clickOnFilter = async (filterKey: string) => {
|
||||
const button = fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
|
||||
button.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
};
|
||||
|
||||
it('should apply active CSS class on filter click', async () => {
|
||||
component.enableNotifications = true;
|
||||
@@ -205,36 +214,20 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
await clickOnFilter(allProcessesFilterKey);
|
||||
const link = fixture.debugElement.query(By.css(`[data-automation-id="${allProcessesFilterKey}_filter"]`)).nativeElement;
|
||||
expect(link.getAttribute('href')).toBe('/process-list-cloud?filterId=10');
|
||||
|
||||
link.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(allProcessesFilterKey)).toBeDefined();
|
||||
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
|
||||
|
||||
await clickOnFilter(runningProcessesFilterKey);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeDefined();
|
||||
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
|
||||
|
||||
await clickOnFilter(completedProcessesFilterKey);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
|
||||
expect(router.url).toBe('/process-list-cloud?filterId=10');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchApiMethod set to POST', () => {
|
||||
beforeEach(() => {
|
||||
configureTestingModule('POST');
|
||||
beforeEach(async () => {
|
||||
await configureTestingModule('POST');
|
||||
});
|
||||
|
||||
it('should attach specific icon for each filter if hasIcon is true', async () => {
|
||||
@@ -336,62 +329,11 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
expect(component.currentFilter).toEqual(mockProcessFilters[0]);
|
||||
expect(filterClickedSpy).toHaveBeenCalledWith(mockProcessFilters[0]);
|
||||
});
|
||||
|
||||
describe('Highlight Selected Filter', () => {
|
||||
const allProcessesFilterKey = mockProcessFilters[0].key;
|
||||
const runningProcessesFilterKey = mockProcessFilters[1].key;
|
||||
const completedProcessesFilterKey = mockProcessFilters[2].key;
|
||||
|
||||
const getActiveFilterElement = (filterKey: string): Element => {
|
||||
const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
|
||||
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
|
||||
};
|
||||
|
||||
const clickOnFilter = async (filterKey: string) => {
|
||||
const button = fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
|
||||
button.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
};
|
||||
|
||||
it('should apply active CSS class on filter click', async () => {
|
||||
component.enableNotifications = true;
|
||||
component.appName = 'mock-app-name';
|
||||
const appNameChange = new SimpleChange(null, 'mock-app-name', true);
|
||||
component.ngOnChanges({ appName: appNameChange });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
await clickOnFilter(allProcessesFilterKey);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(allProcessesFilterKey)).toBeDefined();
|
||||
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
|
||||
|
||||
await clickOnFilter(runningProcessesFilterKey);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeDefined();
|
||||
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
|
||||
|
||||
await clickOnFilter(completedProcessesFilterKey);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('API agnostic', () => {
|
||||
beforeEach(() => {
|
||||
configureTestingModule('GET');
|
||||
beforeEach(async () => {
|
||||
await configureTestingModule('GET');
|
||||
});
|
||||
|
||||
it('should emit an error with a bad response', () => {
|
||||
@@ -549,43 +491,7 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
});
|
||||
|
||||
describe('Highlight Selected Filter', () => {
|
||||
const allProcessesFilterKey = mockProcessFilters[0].key;
|
||||
const runningProcessesFilterKey = mockProcessFilters[1].key;
|
||||
const completedProcessesFilterKey = mockProcessFilters[2].key;
|
||||
|
||||
const getActiveFilterElement = (filterKey: string): Element => {
|
||||
const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
|
||||
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
|
||||
};
|
||||
|
||||
it('Should apply active CSS class when filterParam input changed', async () => {
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: allProcessesFilterKey }, true) });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(allProcessesFilterKey)).toBeDefined();
|
||||
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
|
||||
|
||||
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: runningProcessesFilterKey }, true) });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeDefined();
|
||||
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
|
||||
|
||||
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: completedProcessesFilterKey }, true) });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
|
||||
});
|
||||
|
||||
it('should made sbscription', () => {
|
||||
it('should make subscription', () => {
|
||||
component.enableNotifications = true;
|
||||
component.appName = 'mock-app-name';
|
||||
const appNameChange = new SimpleChange(null, 'mock-app-name', true);
|
||||
|
||||
+10
-7
@@ -15,29 +15,30 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, DestroyRef, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, DestroyRef, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ProcessFilterCloudService } from '../../services/process-filter-cloud.service';
|
||||
import { ProcessFilterCloudModel } from '../../models/process-filter-cloud.model';
|
||||
import { AppConfigService, IconModule, TranslationService } from '@alfresco/adf-core';
|
||||
import { FilterParamsModel } from '../../../../task/task-filters/models/filter-cloud.model';
|
||||
import { debounceTime, tap } from 'rxjs/operators';
|
||||
import { debounceTime, map, tap } from 'rxjs/operators';
|
||||
import { ProcessListCloudService } from '../../../process-list/services/process-list-cloud.service';
|
||||
import { ProcessFilterCloudAdapter } from '../../../process-list/models/process-cloud-query-request.model';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-cloud-process-filters',
|
||||
imports: [TranslatePipe, IconModule, NgIf, MatProgressSpinnerModule, NgForOf, MatListModule, AsyncPipe],
|
||||
imports: [TranslatePipe, IconModule, MatProgressSpinnerModule, MatListModule, AsyncPipe, RouterLink],
|
||||
templateUrl: './process-filters-cloud.component.html',
|
||||
styleUrls: ['./process-filters-cloud.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
styleUrls: ['./process-filters-cloud.component.scss']
|
||||
})
|
||||
export class ProcessFiltersCloudComponent implements OnInit, OnChanges {
|
||||
protected readonly PROCESSES_ROUTE = '/process-list-cloud';
|
||||
/** (required) The application name */
|
||||
@Input()
|
||||
appName: string = '';
|
||||
@@ -87,6 +88,8 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges {
|
||||
private readonly translationService = inject(TranslationService);
|
||||
private readonly appConfigService = inject(AppConfigService);
|
||||
private readonly processListCloudService = inject(ProcessListCloudService);
|
||||
private readonly activatedRoute = inject(ActivatedRoute);
|
||||
protected readonly currentRouteFilterId = toSignal(this.activatedRoute.queryParamMap.pipe(map((params) => params.get('filterId'))));
|
||||
|
||||
ngOnInit() {
|
||||
this.enableNotifications = this.appConfigService.get('notifications', true);
|
||||
|
||||
+38
-34
@@ -1,35 +1,39 @@
|
||||
<mat-action-list class="adf-task-filters" *ngIf="filters$ | async as filterList; else loading">
|
||||
<button
|
||||
*ngFor="let filter of filterList"
|
||||
mat-list-item
|
||||
(click)="onFilterClick(filter)"
|
||||
[attr.aria-label]="filter.name | translate"
|
||||
[id]="filter.id"
|
||||
[attr.data-automation-id]="filter.key + '_filter'"
|
||||
[class.adf-active]="currentFilter === filter"
|
||||
>
|
||||
<div class="adf-task-filters__entry">
|
||||
<div class="adf-task-filters__entry-label">
|
||||
<mat-icon data-automation-id="adf-filter-icon" *ngIf="showIcons" [adf-icon]="filter.icon" aria-hidden="true" />
|
||||
<span data-automation-id="adf-filter-label">
|
||||
{{ filter.name | translate }}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
*ngIf="counters[filter.key]"
|
||||
[attr.data-automation-id]="filter.key + '_filter-counter'"
|
||||
class="adf-task-filters__entry-counter"
|
||||
[class.adf-active]="wasFilterUpdated(filter.key)"
|
||||
>
|
||||
{{ counters[filter.key] }}
|
||||
@if (filters$ | async; as filterList) {
|
||||
<mat-nav-list>
|
||||
@for (filter of filterList; track filter.id) {
|
||||
<a mat-list-item
|
||||
[activated]="filter.id === currentRouteFilterId()"
|
||||
[routerLink]="[TASKS_ROUTE]"
|
||||
[queryParams]="{ filter: filter.id }"
|
||||
[attr.data-automation-id]="filter.key + '_filter'"
|
||||
class="adf-task-filters__entry"
|
||||
[id]="filter.id"
|
||||
[title]="filter.name | translate"
|
||||
[attr.aria-label]="filter.name | translate"
|
||||
(click)="onFilterClick(filter)">
|
||||
<span matListItemTitle>
|
||||
{{ filter.name | translate }}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</mat-action-list>
|
||||
<ng-template #loading>
|
||||
<ng-container>
|
||||
<div class="adf-app-list-spinner">
|
||||
<mat-spinner />
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
@if (showIcons) {
|
||||
<mat-icon matListItemIcon
|
||||
data-automation-id="adf-filter-icon"
|
||||
[adf-icon]="filter.icon"
|
||||
aria-hidden="true" />
|
||||
}
|
||||
@if (counters[filter.key]) {
|
||||
<span matListItemMeta
|
||||
[attr.data-automation-id]="filter.key + '_filter-counter'"
|
||||
class="adf-task-filters__entry-counter"
|
||||
[class.adf-active]="wasFilterUpdated(filter.key)"
|
||||
>
|
||||
{{ counters[filter.key] }}
|
||||
</span>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
</mat-nav-list>
|
||||
} @else {
|
||||
<div class="adf-app-list-spinner">
|
||||
<mat-spinner [attr.aria-label]="'ADF_CLOUD_TASK_LIST.LOADING' | translate" aria-live="polite" />
|
||||
</div>
|
||||
}
|
||||
|
||||
+10
-31
@@ -1,39 +1,18 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
.adf-task-filters {
|
||||
margin-right: calc(-1 * var(--adf-theme-spacing));
|
||||
|
||||
&__entry {
|
||||
font-size: var(--theme-body-1-font-size);
|
||||
color: var(--adf-theme-foreground-text-color-054);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
&__entry-label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: var(--adf-theme-spacing);
|
||||
}
|
||||
|
||||
&__entry-counter {
|
||||
padding: 0 5px;
|
||||
border-radius: 15px;
|
||||
border-radius: var(--mat-sys-corner-large, 15px);
|
||||
|
||||
&.adf-active {
|
||||
background-color: var(--theme-accent-color);
|
||||
color: var(--theme-accent-color-default-contrast);
|
||||
font-size: smaller;
|
||||
background-color: var(--mat-sys-secondary, var(--theme-accent-color));
|
||||
|
||||
@include mat.list-overrides(
|
||||
(
|
||||
list-item-trailing-supporting-text-color: var(--mat-sys-on-secondary, var(--theme-accent-color-default-contrast))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.adf-active .adf-task-filters__entry-label {
|
||||
color: var(--theme-primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
+36
-64
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { AppConfigService, NoopAuthModule } from '@alfresco/adf-core';
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { Component, SimpleChange } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, fakeAsync, flush } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { first, of, throwError } from 'rxjs';
|
||||
@@ -27,12 +27,17 @@ import { TaskFilterCloudService } from '../../services/task-filter-cloud.service
|
||||
import { TaskFiltersCloudComponent } from './task-filters-cloud.component';
|
||||
import { TaskListCloudService } from '../../../task-list/services/task-list-cloud.service';
|
||||
import { HarnessLoader } from '@angular/cdk/testing';
|
||||
import { MatActionListItemHarness } from '@angular/material/list/testing';
|
||||
import { MatNavListItemHarness } from '@angular/material/list/testing';
|
||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||
import { TaskFilterCloudAdapter } from '../../../../models/filter-cloud-model';
|
||||
import { ApolloTestingModule } from 'apollo-angular/testing';
|
||||
import { TaskFilterCloudModel } from '../../models/filter-cloud.model';
|
||||
import { MatIconHarness } from '@angular/material/icon/testing';
|
||||
import { ActivatedRoute, provideRouter, Router } from '@angular/router';
|
||||
import { RouterTestingHarness } from '@angular/router/testing';
|
||||
|
||||
@Component({ selector: 'adf-cloud-dummy', template: '' })
|
||||
class DummyComponent {}
|
||||
|
||||
describe('TaskFiltersCloudComponent', () => {
|
||||
let loader: HarnessLoader;
|
||||
@@ -45,11 +50,28 @@ describe('TaskFiltersCloudComponent', () => {
|
||||
let getTaskFilterCounterSpy: jasmine.Spy;
|
||||
let getTaskListFiltersSpy: jasmine.Spy;
|
||||
let getTaskListCountSpy: jasmine.Spy;
|
||||
let router: Router;
|
||||
|
||||
const configureTestingModule = (searchApiMethod: 'GET' | 'POST') => {
|
||||
const configureTestingModule = async (searchApiMethod: 'GET' | 'POST') => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [NoopAuthModule, TaskFiltersCloudComponent, ApolloTestingModule],
|
||||
providers: [{ provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }]
|
||||
providers: [
|
||||
{ provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService },
|
||||
provideRouter([{ path: 'task-list-cloud', component: DummyComponent }]),
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
queryParamMap: of({
|
||||
get: (param: string) => {
|
||||
if (param === 'filter') {
|
||||
return 'fake-task-filter-id';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
taskFilterService = TestBed.inject(TaskFilterCloudService);
|
||||
taskListService = TestBed.inject(TaskListCloudService);
|
||||
@@ -65,6 +87,9 @@ describe('TaskFiltersCloudComponent', () => {
|
||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||
|
||||
component.searchApiMethod = searchApiMethod;
|
||||
TestBed.inject(ActivatedRoute);
|
||||
router = TestBed.inject(Router);
|
||||
await RouterTestingHarness.create();
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
@@ -72,8 +97,8 @@ describe('TaskFiltersCloudComponent', () => {
|
||||
});
|
||||
|
||||
describe('searchApiMethod set to GET', () => {
|
||||
beforeEach(() => {
|
||||
configureTestingModule('GET');
|
||||
beforeEach(async () => {
|
||||
await configureTestingModule('GET');
|
||||
});
|
||||
|
||||
it('should attach specific icon for each filter if hasIcon is true', async () => {
|
||||
@@ -295,7 +320,7 @@ describe('TaskFiltersCloudComponent', () => {
|
||||
const spy = spyOn(component.filterClicked, 'emit');
|
||||
|
||||
const filterButton = await loader.getHarness(
|
||||
MatActionListItemHarness.with({ selector: `[data-automation-id="${fakeGlobalFilter[0].key}_filter"]` })
|
||||
MatNavListItemHarness.with({ selector: `[data-automation-id="${fakeGlobalFilter[0].key}_filter"]` })
|
||||
);
|
||||
await filterButton.click();
|
||||
|
||||
@@ -353,7 +378,7 @@ describe('TaskFiltersCloudComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const filterButton = await loader.getHarness(
|
||||
MatActionListItemHarness.with({ selector: `[data-automation-id="${fakeGlobalFilter[0].key}_filter"]` })
|
||||
MatNavListItemHarness.with({ selector: `[data-automation-id="${fakeGlobalFilter[0].key}_filter"]` })
|
||||
);
|
||||
await filterButton.click();
|
||||
|
||||
@@ -577,19 +602,6 @@ describe('TaskFiltersCloudComponent', () => {
|
||||
|
||||
describe('Highlight Selected Filter', () => {
|
||||
const assignedTasksFilterKey = defaultTaskFiltersMock[1].key;
|
||||
const queuedTasksFilterKey = defaultTaskFiltersMock[0].key;
|
||||
const completedTasksFilterKey = defaultTaskFiltersMock[2].key;
|
||||
|
||||
const getActiveFilterElement = (filterKey: string): Element => {
|
||||
const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
|
||||
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
|
||||
};
|
||||
|
||||
const clickOnFilter = async (filterKey: string) => {
|
||||
fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`).click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
};
|
||||
|
||||
it('Should highlight task filter on filter click', async () => {
|
||||
getTaskListFiltersSpy.and.returnValue(of(defaultTaskFiltersMock));
|
||||
@@ -599,52 +611,12 @@ describe('TaskFiltersCloudComponent', () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
await clickOnFilter(assignedTasksFilterKey);
|
||||
|
||||
expect(getActiveFilterElement(assignedTasksFilterKey)).toBeDefined();
|
||||
expect(getActiveFilterElement(queuedTasksFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(completedTasksFilterKey)).toBeNull();
|
||||
|
||||
await clickOnFilter(queuedTasksFilterKey);
|
||||
|
||||
expect(getActiveFilterElement(assignedTasksFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(queuedTasksFilterKey)).toBeDefined();
|
||||
expect(getActiveFilterElement(completedTasksFilterKey)).toBeNull();
|
||||
|
||||
await clickOnFilter(completedTasksFilterKey);
|
||||
|
||||
expect(getActiveFilterElement(assignedTasksFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(queuedTasksFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(completedTasksFilterKey)).toBeDefined();
|
||||
});
|
||||
|
||||
it('Should highlight task filter when filterParam input changed', async () => {
|
||||
getTaskListFiltersSpy.and.returnValue(of(defaultTaskFiltersMock));
|
||||
fixture.detectChanges();
|
||||
|
||||
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: assignedTasksFilterKey }, true) });
|
||||
const filterLink = fixture.debugElement.query(By.css(`[data-automation-id="${assignedTasksFilterKey}_filter"]`));
|
||||
filterLink.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(assignedTasksFilterKey)).toBeDefined();
|
||||
expect(getActiveFilterElement(queuedTasksFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(completedTasksFilterKey)).toBeNull();
|
||||
|
||||
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: queuedTasksFilterKey }, true) });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(assignedTasksFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(queuedTasksFilterKey)).toBeDefined();
|
||||
expect(getActiveFilterElement(completedTasksFilterKey)).toBeNull();
|
||||
|
||||
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: completedTasksFilterKey }, true) });
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getActiveFilterElement(assignedTasksFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(queuedTasksFilterKey)).toBeNull();
|
||||
expect(getActiveFilterElement(completedTasksFilterKey)).toBeDefined();
|
||||
expect(router.url).toBe('/task-list-cloud?filter=2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+11
-7
@@ -15,31 +15,33 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TaskFilterCloudService } from '../../services/task-filter-cloud.service';
|
||||
import { FilterParamsModel, TaskFilterCloudModel } from '../../models/filter-cloud.model';
|
||||
import { AppConfigService, IconModule, TranslationService } from '@alfresco/adf-core';
|
||||
import { debounceTime, tap } from 'rxjs/operators';
|
||||
import { debounceTime, map, tap } from 'rxjs/operators';
|
||||
import { BaseTaskFiltersCloudComponent } from '../base-task-filters-cloud.component';
|
||||
import { TaskDetailsCloudModel } from '../../../models/task-details-cloud.model';
|
||||
import { TaskCloudEngineEvent } from '../../../../models/engine-event-cloud.model';
|
||||
import { TaskListCloudService } from '../../../task-list/services/task-list-cloud.service';
|
||||
import { TaskFilterCloudAdapter } from '../../../../models/filter-cloud-model';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-cloud-task-filters',
|
||||
imports: [CommonModule, MatProgressSpinnerModule, TranslatePipe, IconModule, MatListModule],
|
||||
imports: [AsyncPipe, MatProgressSpinnerModule, TranslatePipe, MatListModule, RouterLink, IconModule],
|
||||
templateUrl: './task-filters-cloud.component.html',
|
||||
styleUrls: ['./task-filters-cloud.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
styleUrls: ['./task-filters-cloud.component.scss']
|
||||
})
|
||||
export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent implements OnInit, OnChanges {
|
||||
protected readonly TASKS_ROUTE = '/task-list-cloud';
|
||||
|
||||
/** (optional) From Activiti 8.7.0 forward, use the 'POST' method to get the task count. */
|
||||
@Input()
|
||||
searchApiMethod: 'GET' | 'POST' = 'GET';
|
||||
@@ -70,6 +72,8 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
|
||||
private readonly taskListCloudService = inject(TaskListCloudService);
|
||||
private readonly translationService = inject(TranslationService);
|
||||
private readonly appConfigService = inject(AppConfigService);
|
||||
private readonly activatedRoute = inject(ActivatedRoute);
|
||||
readonly currentRouteFilterId = toSignal(this.activatedRoute.queryParamMap.pipe(map((params) => params.get('filter'))));
|
||||
|
||||
ngOnInit() {
|
||||
this.enableNotifications = this.appConfigService.get('notifications', true);
|
||||
|
||||
Reference in New Issue
Block a user