[ACS-12041] Add debounceTime in PeopleWidgetComponent to limit API calls on every keystroke (#12027)

This commit is contained in:
Michal Kinas
2026-07-02 17:46:42 +02:00
committed by GitHub
parent d8b36606e2
commit 5050c3d974
2 changed files with 34 additions and 15 deletions
@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { FormFieldTypes, FormFieldModel, FormModel } from '@alfresco/adf-core'; import { FormFieldTypes, FormFieldModel, FormModel } from '@alfresco/adf-core';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
@@ -250,6 +250,22 @@ describe('PeopleWidgetComponent', () => {
expect(widget.searchTerm.value).toBe(''); expect(widget.searchTerm.value).toBe('');
}); });
it('should call the users API only once when the user types several characters quickly', fakeAsync(() => {
const getWorkflowUsersSpy = spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of([]));
widget.searchTerm.setValue('T');
tick(100);
widget.searchTerm.setValue('Te');
tick(100);
widget.searchTerm.setValue('Tes');
tick(100);
widget.searchTerm.setValue('Test');
tick(300);
expect(getWorkflowUsersSpy).toHaveBeenCalledTimes(1);
expect(getWorkflowUsersSpy).toHaveBeenCalledWith(undefined, 'Test', widget.groupId);
}));
it('should remove user from selectedUsers if user exists', () => { it('should remove user from selectedUsers if user exists', () => {
const users: LightUserRepresentation[] = [ const users: LightUserRepresentation[] = [
{ id: 1, firstName: 'John', lastName: 'Doe' }, { id: 1, firstName: 'John', lastName: 'Doe' },
@@ -351,21 +367,21 @@ describe('PeopleWidgetComponent', () => {
expect(element.querySelector('#people-widget-content')).not.toBeNull(); expect(element.querySelector('#people-widget-content')).not.toBeNull();
}); });
it('should show an error message if the user is invalid', async () => { it('should show an error message if the user is invalid', fakeAsync(() => {
const peopleHTMLElement = element.querySelector<HTMLInputElement>('input'); const peopleHTMLElement = element.querySelector<HTMLInputElement>('input');
peopleHTMLElement.focus(); peopleHTMLElement.focus();
peopleHTMLElement.value = 'K'; peopleHTMLElement.value = 'K';
peopleHTMLElement.dispatchEvent(new Event('keyup')); peopleHTMLElement.dispatchEvent(new Event('keyup'));
peopleHTMLElement.dispatchEvent(new Event('input')); peopleHTMLElement.dispatchEvent(new Event('input'));
tick(300);
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('.adf-error-text')).not.toBeNull(); expect(element.querySelector('.adf-error-text')).not.toBeNull();
expect(element.querySelector('.adf-error-text').textContent).toContain('FORM.FIELD.VALIDATOR.INVALID_VALUE'); expect(element.querySelector('.adf-error-text').textContent).toContain('FORM.FIELD.VALIDATOR.INVALID_VALUE');
}); }));
it('should show the people if the typed result match', async () => { it('should show the people if the typed result match', fakeAsync(() => {
const peopleHTMLElement = element.querySelector<HTMLInputElement>('input'); const peopleHTMLElement = element.querySelector<HTMLInputElement>('input');
peopleHTMLElement.focus(); peopleHTMLElement.focus();
peopleHTMLElement.value = 'T'; peopleHTMLElement.value = 'T';
@@ -373,13 +389,14 @@ describe('PeopleWidgetComponent', () => {
peopleHTMLElement.dispatchEvent(new Event('input')); peopleHTMLElement.dispatchEvent(new Event('input'));
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); tick(300);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-0'))).not.toBeNull(); expect(fixture.debugElement.query(By.css('#adf-people-widget-user-0'))).not.toBeNull();
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-1'))).not.toBeNull(); expect(fixture.debugElement.query(By.css('#adf-people-widget-user-1'))).not.toBeNull();
}); }));
it('should hide result list if input is empty', async () => { it('should hide result list if input is empty', fakeAsync(() => {
const peopleHTMLElement = element.querySelector<HTMLInputElement>('input'); const peopleHTMLElement = element.querySelector<HTMLInputElement>('input');
peopleHTMLElement.focus(); peopleHTMLElement.focus();
peopleHTMLElement.value = ''; peopleHTMLElement.value = '';
@@ -388,14 +405,14 @@ describe('PeopleWidgetComponent', () => {
peopleHTMLElement.dispatchEvent(new Event('input')); peopleHTMLElement.dispatchEvent(new Event('input'));
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); tick(300);
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-0'))).toBeNull(); expect(fixture.debugElement.query(By.css('#adf-people-widget-user-0'))).toBeNull();
}); }));
it('should display two options if we tap one letter', async () => { it('should display two options if we tap one letter', fakeAsync(() => {
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); tick(300);
const peopleHTMLElement = element.querySelector<HTMLInputElement>('input'); const peopleHTMLElement = element.querySelector<HTMLInputElement>('input');
peopleHTMLElement.focus(); peopleHTMLElement.focus();
@@ -404,11 +421,12 @@ describe('PeopleWidgetComponent', () => {
peopleHTMLElement.dispatchEvent(new Event('input')); peopleHTMLElement.dispatchEvent(new Event('input'));
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); tick(300);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-0'))).not.toBeNull(); expect(fixture.debugElement.query(By.css('#adf-people-widget-user-0'))).not.toBeNull();
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-1'))).not.toBeNull(); expect(fixture.debugElement.query(By.css('#adf-people-widget-user-1'))).not.toBeNull();
}); }));
it('should emit peopleSelected if option is valid', async () => { it('should emit peopleSelected if option is valid', async () => {
const selectEmitSpy = spyOn(widget.peopleSelected, 'emit'); const selectEmitSpy = spyOn(widget.peopleSelected, 'emit');
@@ -23,7 +23,7 @@ import { ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import { MatChipsModule } from '@angular/material/chips'; import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators'; import { catchError, debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators';
import { PeopleProcessService } from '../../../services/people-process.service'; import { PeopleProcessService } from '../../../services/people-process.service';
import { LightUserRepresentation } from '@alfresco/js-api'; import { LightUserRepresentation } from '@alfresco/js-api';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
@@ -77,6 +77,7 @@ export class PeopleWidgetComponent extends WidgetComponent implements OnInit {
searchTerms$ = this.searchTerm.valueChanges; searchTerms$ = this.searchTerm.valueChanges;
users$: Observable<LightUserRepresentation[]> = this.searchTerms$.pipe( users$: Observable<LightUserRepresentation[]> = this.searchTerms$.pipe(
debounceTime(300),
distinctUntilChanged(), distinctUntilChanged(),
switchMap((searchTerm) => { switchMap((searchTerm) => {
if (!searchTerm) { if (!searchTerm) {