Skip to content

Commit dd66ecc

Browse files
authored
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
1 parent 3b14351 commit dd66ecc

File tree

9 files changed

+197
-341
lines changed

9 files changed

+197
-341
lines changed

lib/process-services-cloud/src/lib/i18n/en.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
"MAXIMUM_LENGTH": "Length exceeded, {{characters}} characters max.",
5151
"SPACE_VALIDATOR": "Cannot begin or end with a space."
5252
}
53-
}
53+
},
54+
"LOADING": "Loading"
5455
},
5556
"ADF_CLOUD_TASK_LIST": {
5657
"START_TASK": {
@@ -97,7 +98,8 @@
9798
"SUBTITLE": "Create a new task that you want to easily find later",
9899
"NONE": "No task lists found"
99100
}
100-
}
101+
},
102+
"LOADING": "Loading"
101103
},
102104
"ADF_CLOUD_SERVICE_TASK_LIST": {
103105
"PROPERTIES": {
Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,40 @@
1-
<mat-action-list class="adf-process-filters" *ngIf="filters$ | async as filterList; else loading">
2-
<button
3-
*ngFor="let filter of filterList"
4-
mat-list-item
5-
(click)="onFilterClick(filter)"
6-
[attr.aria-label]="filter.name | translate"
7-
[id]="filter.id"
8-
[attr.data-automation-id]="filter.key + '_filter'"
9-
[class.adf-active]="currentFilter === filter"
10-
>
11-
<div class="adf-process-filters__entry">
12-
<div>
13-
<mat-icon data-automation-id="adf-filter-icon" *ngIf="showIcons" [adf-icon]="filter.icon" aria-hidden="true" />
14-
<span
15-
data-automation-id="adf-filter-label"
16-
class="adf-filter-action-button__label">
17-
{{ filter.name | translate }}
1+
@if (filters$ | async; as filterList) {
2+
<mat-nav-list>
3+
@for (filter of filterList; track filter.id) {
4+
<a mat-list-item
5+
[activated]="filter.id === currentRouteFilterId()"
6+
[routerLink]="[PROCESSES_ROUTE]"
7+
[queryParams]="{ filterId: filter.id }"
8+
[attr.data-automation-id]="filter.key + '_filter'"
9+
class="adf-process-filters__entry"
10+
[id]="filter.id"
11+
[title]="filter.name ?? '' | translate"
12+
[attr.aria-label]="filter.name ?? '' | translate"
13+
(click)="onFilterClick(filter)">
14+
<span matListItemTitle>
15+
{{ filter.name ?? '' | translate }}
1816
</span>
19-
</div>
20-
<span
21-
*ngIf="counters[filter.key]"
22-
[attr.data-automation-id]="filter.key + '_filter-counter'"
23-
class="adf-process-filters__entry-counter"
24-
[class.adf-active]="isFilterUpdated(filter.key)"
25-
>
26-
{{ counters[filter.key] }}
27-
</span>
28-
</div>
29-
</button>
30-
</mat-action-list>
31-
<ng-template #loading>
32-
<ng-container>
33-
<div class="adf-app-list-spinner">
34-
<mat-spinner />
35-
</div>
36-
</ng-container>
37-
</ng-template>
17+
@if (showIcons) {
18+
<mat-icon matListItemIcon
19+
data-automation-id="adf-filter-icon"
20+
[adf-icon]="filter.icon ?? ''"
21+
aria-hidden="true" />
22+
}
23+
@if (counters[filter.key ?? '']) {
24+
<span matListItemMeta
25+
[attr.data-automation-id]="filter.key + '_filter-counter'"
26+
class="adf-process-filters__entry-counter"
27+
[class.adf-active]="isFilterUpdated(filter.key ?? '')"
28+
>
29+
{{ counters[filter.key ?? ''] }}
30+
</span>
31+
}
32+
</a>
33+
}
34+
</mat-nav-list>
35+
} @else {
36+
<div class="adf-app-list-spinner">
37+
<mat-spinner [attr.aria-label]="'ADF_CLOUD_PROCESS_LIST.LOADING' | translate" aria-live="polite" />
38+
</div>
39+
}
40+
Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,18 @@
1-
.adf-process-filters {
2-
margin-right: calc(-1 * var(--adf-theme-spacing));
3-
4-
&__entry {
5-
font-size: var(--theme-body-1-font-size);
6-
color: var(--adf-theme-foreground-text-color-054);
7-
display: flex;
8-
align-items: center;
9-
justify-content: space-between;
10-
flex: 1;
11-
height: 100%;
12-
gap: var(--adf-theme-spacing);
13-
14-
&:hover {
15-
color: var(--theme-primary-color);
16-
}
1+
@use '@angular/material' as mat;
172

18-
&-counter {
19-
padding: 0 5px;
20-
border-radius: 15px;
3+
.adf-process-filters {
4+
&__entry-counter {
5+
padding: 0 5px;
6+
border-radius: var(--mat-sys-corner-large, 15px);
217

22-
&.adf-active {
23-
background-color: var(--theme-accent-color);
24-
color: var(--theme-accent-color-default-contrast);
25-
font-size: smaller;
26-
}
27-
}
28-
}
8+
&.adf-active {
9+
background-color: var(--mat-sys-secondary, var(--theme-accent-color));
2910

30-
.adf-active {
31-
.adf-process-filters__entry {
32-
color: var(--theme-primary-color);
11+
@include mat.list-overrides(
12+
(
13+
list-item-trailing-supporting-text-color: var(--mat-sys-on-secondary, var(--theme-accent-color-default-contrast))
14+
)
15+
);
3316
}
3417
}
3518
}

lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.spec.ts

Lines changed: 37 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { SimpleChange } from '@angular/core';
18+
import { Component, SimpleChange } from '@angular/core';
1919
import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testing';
2020
import { first, of, throwError } from 'rxjs';
2121
import { ProcessFilterCloudService } from '../../services/process-filter-cloud.service';
@@ -30,6 +30,11 @@ import { ApolloTestingModule } from 'apollo-angular/testing';
3030
import { HarnessLoader } from '@angular/cdk/testing';
3131
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
3232
import { MatIconHarness } from '@angular/material/icon/testing';
33+
import { ActivatedRoute, provideRouter, Router } from '@angular/router';
34+
import { RouterTestingHarness } from '@angular/router/testing';
35+
36+
@Component({ selector: 'adf-cloud-dummy', template: '' })
37+
class DummyComponent {}
3338

3439
const ProcessFilterCloudServiceMock = {
3540
getProcessFilters: () => of(mockProcessFilters),
@@ -44,8 +49,9 @@ describe('ProcessFiltersCloudComponent', () => {
4449
let getProcessFiltersSpy: jasmine.Spy;
4550
let getProcessNotificationSubscriptionSpy: jasmine.Spy;
4651
let loader: HarnessLoader;
52+
let router: Router;
4753

48-
const configureTestingModule = (searchApiMethod: 'GET' | 'POST') => {
54+
const configureTestingModule = async (searchApiMethod: 'GET' | 'POST') => {
4955
TestBed.configureTestingModule({
5056
imports: [ProcessFiltersCloudComponent, ApolloTestingModule],
5157
providers: [
@@ -58,7 +64,21 @@ describe('ProcessFiltersCloudComponent', () => {
5864
getProcessListCount: () => of(10)
5965
}
6066
},
61-
{ provide: ProcessFilterCloudService, useValue: ProcessFilterCloudServiceMock }
67+
{ provide: ProcessFilterCloudService, useValue: ProcessFilterCloudServiceMock },
68+
provideRouter([{ path: 'process-list-cloud', component: DummyComponent }]),
69+
{
70+
provide: ActivatedRoute,
71+
useValue: {
72+
queryParamMap: of({
73+
get: (param: string) => {
74+
if (param === 'filterId') {
75+
return 'fake-process-filter-id';
76+
}
77+
return null;
78+
}
79+
})
80+
}
81+
}
6282
]
6383
});
6484
fixture = TestBed.createComponent(ProcessFiltersCloudComponent);
@@ -67,6 +87,9 @@ describe('ProcessFiltersCloudComponent', () => {
6787
component.searchApiMethod = searchApiMethod;
6888

6989
processFilterService = TestBed.inject(ProcessFilterCloudService);
90+
TestBed.inject(ActivatedRoute);
91+
router = TestBed.inject(Router);
92+
await RouterTestingHarness.create();
7093
getProcessFiltersSpy = spyOn(processFilterService, 'getProcessFilters').and.returnValue(of(mockProcessFilters));
7194
getProcessNotificationSubscriptionSpy = spyOn(processFilterService, 'getProcessNotificationSubscription').and.returnValue(of([]));
7295
};
@@ -76,8 +99,8 @@ describe('ProcessFiltersCloudComponent', () => {
7699
});
77100

78101
describe('searchApiMethod set to GET', () => {
79-
beforeEach(() => {
80-
configureTestingModule('GET');
102+
beforeEach(async () => {
103+
await configureTestingModule('GET');
81104
});
82105

83106
it('should attach specific icon for each filter if hasIcon is true', async () => {
@@ -182,20 +205,6 @@ describe('ProcessFiltersCloudComponent', () => {
182205

183206
describe('Highlight Selected Filter', () => {
184207
const allProcessesFilterKey = mockProcessFilters[0].key;
185-
const runningProcessesFilterKey = mockProcessFilters[1].key;
186-
const completedProcessesFilterKey = mockProcessFilters[2].key;
187-
188-
const getActiveFilterElement = (filterKey: string): Element => {
189-
const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
190-
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
191-
};
192-
193-
const clickOnFilter = async (filterKey: string) => {
194-
const button = fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
195-
button.click();
196-
fixture.detectChanges();
197-
await fixture.whenStable();
198-
};
199208

200209
it('should apply active CSS class on filter click', async () => {
201210
component.enableNotifications = true;
@@ -205,36 +214,20 @@ describe('ProcessFiltersCloudComponent', () => {
205214
fixture.detectChanges();
206215
await fixture.whenStable();
207216

208-
await clickOnFilter(allProcessesFilterKey);
209-
fixture.detectChanges();
210-
await fixture.whenStable();
211-
212-
expect(getActiveFilterElement(allProcessesFilterKey)).toBeDefined();
213-
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
214-
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
215-
216-
await clickOnFilter(runningProcessesFilterKey);
217-
fixture.detectChanges();
218-
await fixture.whenStable();
217+
const link = fixture.debugElement.query(By.css(`[data-automation-id="${allProcessesFilterKey}_filter"]`)).nativeElement;
218+
expect(link.getAttribute('href')).toBe('/process-list-cloud?filterId=10');
219219

220-
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
221-
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeDefined();
222-
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
223-
224-
await clickOnFilter(completedProcessesFilterKey);
220+
link.click();
225221
fixture.detectChanges();
226222
await fixture.whenStable();
227-
228-
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
229-
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
230-
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
223+
expect(router.url).toBe('/process-list-cloud?filterId=10');
231224
});
232225
});
233226
});
234227

235228
describe('searchApiMethod set to POST', () => {
236-
beforeEach(() => {
237-
configureTestingModule('POST');
229+
beforeEach(async () => {
230+
await configureTestingModule('POST');
238231
});
239232

240233
it('should attach specific icon for each filter if hasIcon is true', async () => {
@@ -336,62 +329,11 @@ describe('ProcessFiltersCloudComponent', () => {
336329
expect(component.currentFilter).toEqual(mockProcessFilters[0]);
337330
expect(filterClickedSpy).toHaveBeenCalledWith(mockProcessFilters[0]);
338331
});
339-
340-
describe('Highlight Selected Filter', () => {
341-
const allProcessesFilterKey = mockProcessFilters[0].key;
342-
const runningProcessesFilterKey = mockProcessFilters[1].key;
343-
const completedProcessesFilterKey = mockProcessFilters[2].key;
344-
345-
const getActiveFilterElement = (filterKey: string): Element => {
346-
const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
347-
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
348-
};
349-
350-
const clickOnFilter = async (filterKey: string) => {
351-
const button = fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
352-
button.click();
353-
fixture.detectChanges();
354-
await fixture.whenStable();
355-
};
356-
357-
it('should apply active CSS class on filter click', async () => {
358-
component.enableNotifications = true;
359-
component.appName = 'mock-app-name';
360-
const appNameChange = new SimpleChange(null, 'mock-app-name', true);
361-
component.ngOnChanges({ appName: appNameChange });
362-
fixture.detectChanges();
363-
await fixture.whenStable();
364-
365-
await clickOnFilter(allProcessesFilterKey);
366-
fixture.detectChanges();
367-
await fixture.whenStable();
368-
369-
expect(getActiveFilterElement(allProcessesFilterKey)).toBeDefined();
370-
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
371-
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
372-
373-
await clickOnFilter(runningProcessesFilterKey);
374-
fixture.detectChanges();
375-
await fixture.whenStable();
376-
377-
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
378-
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeDefined();
379-
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
380-
381-
await clickOnFilter(completedProcessesFilterKey);
382-
fixture.detectChanges();
383-
await fixture.whenStable();
384-
385-
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
386-
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
387-
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
388-
});
389-
});
390332
});
391333

392334
describe('API agnostic', () => {
393-
beforeEach(() => {
394-
configureTestingModule('GET');
335+
beforeEach(async () => {
336+
await configureTestingModule('GET');
395337
});
396338

397339
it('should emit an error with a bad response', () => {
@@ -549,43 +491,7 @@ describe('ProcessFiltersCloudComponent', () => {
549491
});
550492

551493
describe('Highlight Selected Filter', () => {
552-
const allProcessesFilterKey = mockProcessFilters[0].key;
553-
const runningProcessesFilterKey = mockProcessFilters[1].key;
554-
const completedProcessesFilterKey = mockProcessFilters[2].key;
555-
556-
const getActiveFilterElement = (filterKey: string): Element => {
557-
const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
558-
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
559-
};
560-
561-
it('Should apply active CSS class when filterParam input changed', async () => {
562-
fixture.detectChanges();
563-
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: allProcessesFilterKey }, true) });
564-
fixture.detectChanges();
565-
await fixture.whenStable();
566-
567-
expect(getActiveFilterElement(allProcessesFilterKey)).toBeDefined();
568-
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
569-
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
570-
571-
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: runningProcessesFilterKey }, true) });
572-
fixture.detectChanges();
573-
await fixture.whenStable();
574-
575-
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
576-
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeDefined();
577-
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
578-
579-
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: completedProcessesFilterKey }, true) });
580-
fixture.detectChanges();
581-
await fixture.whenStable();
582-
583-
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
584-
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
585-
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
586-
});
587-
588-
it('should made sbscription', () => {
494+
it('should make subscription', () => {
589495
component.enableNotifications = true;
590496
component.appName = 'mock-app-name';
591497
const appNameChange = new SimpleChange(null, 'mock-app-name', true);

0 commit comments

Comments
 (0)