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:
Ehsan Rezaei
2026-02-26 16:15:22 +00:00
committed by GitHub
parent 3b14351389
commit dd66ecc72a
9 changed files with 198 additions and 342 deletions
@@ -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": {
@@ -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>
}
@@ -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))
)
);
}
}
}
@@ -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);
@@ -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);
@@ -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>
}
@@ -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);
}
}
@@ -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');
});
});
});
@@ -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);