Skip to content

Commit 015df50

Browse files
authored
fix(autocomplete): close dropdown when list is empty, #3977 - 9.1.x (#7962)
1 parent c1365bb commit 015df50

File tree

2 files changed

+115
-65
lines changed

2 files changed

+115
-65
lines changed

projects/igniteui-angular/src/lib/directives/autocomplete/autocomplete.directive.spec.ts

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -130,24 +130,58 @@ describe('IgxAutocomplete', () => {
130130
tick();
131131
fixture.detectChanges();
132132
expect(dropDown.collapsed).toBeTruthy();
133+
}));
134+
it('Should close the dropdown when no items match the filter', fakeAsync(() => {
135+
expect((autocomplete as any).collapsed).toEqual(true);
136+
spyOn(autocomplete, 'close').and.callThrough();
137+
spyOn(autocomplete, 'open').and.callThrough();
138+
spyOn(autocomplete.target, 'open').and.callThrough();
139+
expect(autocomplete.close).not.toHaveBeenCalled();
140+
UIInteractions.setInputElementValue(input, 'a', fixture);
141+
tick();
142+
expect(autocomplete.open).toHaveBeenCalledTimes(1);
143+
expect(autocomplete.target.open).toHaveBeenCalledTimes(1);
144+
expect(autocomplete.target.collapsed).toEqual(false);
145+
146+
UIInteractions.setInputElementValue(input, 'ax', fixture);
147+
tick();
148+
expect(autocomplete.close).toHaveBeenCalledTimes(1);
149+
expect(autocomplete.open).toHaveBeenCalledTimes(2);
150+
expect(autocomplete.target.open).toHaveBeenCalledTimes(1);
151+
expect(autocomplete.target.collapsed).toEqual(true);
152+
153+
154+
// Should not try to reopen if no items
155+
UIInteractions.setInputElementValue(input, 'axx', fixture);
156+
tick();
157+
expect(autocomplete.close).toHaveBeenCalledTimes(1);
158+
expect(autocomplete.open).toHaveBeenCalledTimes(3);
159+
expect(autocomplete.target.open).toHaveBeenCalledTimes(1);
160+
expect(autocomplete.target.collapsed).toEqual(true);
161+
162+
133163
}));
134164
it('Should close the dropdown when disabled dynamically', fakeAsync(() => {
135165
spyOn(autocomplete.target, 'open').and.callThrough();
136166
spyOn(autocomplete.target, 'close').and.callThrough();
137167

138168
UIInteractions.setInputElementValue(input, 's', fixture);
169+
tick();
139170
fixture.detectChanges();
171+
tick();
140172
expect(dropDown.collapsed).toBeFalsy();
141173
expect(autocomplete.target.open).toHaveBeenCalledTimes(1);
142174

143175
autocomplete.disabled = true;
144176
autocomplete.close();
145-
tick();
146177
fixture.detectChanges();
178+
tick();
147179
expect(dropDown.collapsed).toBeTruthy();
148180
expect(autocomplete.target.close).toHaveBeenCalledTimes(1);
149181
UIInteractions.setInputElementValue(input, 's', fixture);
182+
tick();
150183
fixture.detectChanges();
184+
tick();
151185
expect(dropDown.collapsed).toBeTruthy();
152186
expect(autocomplete.target.open).toHaveBeenCalledTimes(1);
153187
}));
@@ -180,11 +214,12 @@ describe('IgxAutocomplete', () => {
180214
let filteredTowns = fixture.componentInstance.filterTowns(startsWith);
181215
UIInteractions.setInputElementValue(input, startsWith, fixture);
182216
fixture.detectChanges();
217+
tick();
183218
expect(dropDown.collapsed).toBeFalsy();
184219

185220
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
186-
tick();
187221
fixture.detectChanges();
222+
tick();
188223
expect(dropDown.collapsed).toBeTruthy();
189224
expect(fixture.componentInstance.townSelected).toBe(filteredTowns[0]);
190225
expect(input.value).toBe(filteredTowns[0]);
@@ -193,6 +228,7 @@ describe('IgxAutocomplete', () => {
193228
filteredTowns = fixture.componentInstance.filterTowns(startsWith);
194229
UIInteractions.setInputElementValue(input, startsWith, fixture);
195230
fixture.detectChanges();
231+
tick();
196232
expect(dropDown.collapsed).toBeFalsy();
197233

198234
UIInteractions.triggerKeyDownEvtUponElem('space', input.nativeElement, true);
@@ -207,31 +243,31 @@ describe('IgxAutocomplete', () => {
207243
const filteredTowns = fixture.componentInstance.filterTowns(startsWith);
208244

209245
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
210-
tick();
211246
fixture.detectChanges();
247+
tick();
212248
expect(dropDown.collapsed).toBeTruthy();
213249
expect(input.value).toBe('');
214250

215251
UIInteractions.setInputElementValue(input, startsWith, fixture);
216-
fixture.detectChanges();
217-
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
218252
tick();
253+
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
219254
fixture.detectChanges();
255+
tick();
220256
expect(dropDown.collapsed).toBeTruthy();
221257
expect(input.value).toBe(filteredTowns[0]);
222258

223259
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
224-
tick();
225260
fixture.detectChanges();
261+
tick();
226262
expect(dropDown.collapsed).toBeTruthy();
227263
expect(input.value).toBe(filteredTowns[0]);
228264

229265
startsWith = '';
230266
UIInteractions.setInputElementValue(input, startsWith, fixture);
231-
fixture.detectChanges();
232-
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
233267
tick();
268+
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
234269
fixture.detectChanges();
270+
tick();
235271
expect(dropDown.collapsed).toBeTruthy();
236272
expect(input.value).toBe(fixture.componentInstance.towns[0]);
237273
}));
@@ -240,31 +276,31 @@ describe('IgxAutocomplete', () => {
240276
const filteredTowns = fixture.componentInstance.filterTowns(startsWith);
241277

242278
UIInteractions.triggerKeyDownEvtUponElem('space', input.nativeElement, true);
243-
tick();
244279
fixture.detectChanges();
280+
tick();
245281
expect(dropDown.collapsed).toBeTruthy();
246282
expect(input.value).toBe('');
247283

248284
UIInteractions.setInputElementValue(input, startsWith, fixture);
249-
fixture.detectChanges();
250-
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
251285
tick();
286+
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
252287
fixture.detectChanges();
288+
tick();
253289
expect(dropDown.collapsed).toBeTruthy();
254290
expect(input.value).toBe(filteredTowns[0]);
255291

256292
UIInteractions.triggerKeyDownEvtUponElem('space', input.nativeElement, true);
257-
tick();
258293
fixture.detectChanges();
294+
tick();
259295
expect(dropDown.collapsed).toBeTruthy();
260296
expect(input.value).toBe(filteredTowns[0]);
261297

262298
startsWith = '';
263299
UIInteractions.setInputElementValue(input, startsWith, fixture);
264-
fixture.detectChanges();
265-
UIInteractions.triggerKeyDownEvtUponElem('space', input.nativeElement, true);
266300
tick();
301+
UIInteractions.triggerKeyDownEvtUponElem('space', input.nativeElement, true);
267302
fixture.detectChanges();
303+
tick();
268304
expect(dropDown.collapsed).toBeFalsy();
269305
expect(input.value).toBe(startsWith);
270306
}));
@@ -340,16 +376,15 @@ describe('IgxAutocomplete', () => {
340376
const filteredTowns = fixture.componentInstance.filterTowns(startsWith);
341377
UIInteractions.setInputElementValue(input, startsWith, fixture);
342378
tick();
343-
fixture.detectChanges();
344379
expect(dropDown.collapsed).toBeFalsy();
345380

346381
const targetElement = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_DROP_DOWN_ITEM))[0];
347382
targetElement.nativeElement.tabIndex = 0;
348383
targetElement.nativeElement.focus();
349384
targetElement.nativeElement.click();
350385
targetElement.nativeElement.tabIndex = -1;
351-
tick();
352386
fixture.detectChanges();
387+
tick();
353388
expect(dropDown.collapsed).toBeTruthy();
354389
expect(fixture.componentInstance.townSelected).toBe(filteredTowns[0]);
355390
expect(input.value).toBe(filteredTowns[0]);
@@ -491,39 +526,39 @@ describe('IgxAutocomplete', () => {
491526
const dropdownListScrollElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWNLIST_SCROLL));
492527

493528
UIInteractions.setInputElementValue(input, startsWith, fixture);
494-
fixture.detectChanges();
529+
tick();
495530
expect(dropdownListScrollElement.children.length).toEqual(0);
496531
expect(input.nativeElement.value).toEqual(startsWith);
497532

498533
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
499-
tick();
500534
fixture.detectChanges();
535+
tick();
501536
expect(input.nativeElement.value).toEqual(startsWith);
502537

503538
startsWith = 'd';
504539
const filteredTowns = fixture.componentInstance.filterTowns(startsWith);
505540
UIInteractions.setInputElementValue(input, startsWith, fixture);
541+
tick();
506542
fixture.detectChanges();
507543
expect(dropdownListScrollElement.children.length).toEqual(filteredTowns.length);
508544
expect(input.nativeElement.value).toEqual(startsWith);
509545

510546
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
511-
tick();
512547
fixture.detectChanges();
548+
tick();
513549
expect(dropDown.collapsed).toBeTruthy();
514550
expect(fixture.componentInstance.townSelected).toBe(filteredTowns[0]);
515551
expect(input.value).toBe(filteredTowns[0]);
516552

517553
startsWith = 'q';
518554
UIInteractions.setInputElementValue(input, startsWith, fixture);
519-
fixture.detectChanges();
520555
tick();
521556
expect(dropdownListScrollElement.children.length).toEqual(0);
522557
expect(input.nativeElement.value).toEqual(startsWith);
523558

524559
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
525-
tick();
526560
fixture.detectChanges();
561+
tick();
527562
expect(input.nativeElement.value).toEqual(startsWith);
528563
expect(fixture.componentInstance.townSelected).toBe(startsWith);
529564
}));
@@ -565,29 +600,28 @@ describe('IgxAutocomplete', () => {
565600
let filteredTowns = fixture.componentInstance.filterTowns(startsWith);
566601
spyOn(autocomplete.onItemSelected, 'emit').and.callThrough();
567602
UIInteractions.setInputElementValue(input, startsWith, fixture);
568-
fixture.detectChanges();
603+
tick();
569604

570605
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
571-
tick();
572606
fixture.detectChanges();
607+
tick();
573608
expect(fixture.componentInstance.townSelected).toBe(filteredTowns[0]);
574609
expect(autocomplete.onItemSelected.emit).toHaveBeenCalledTimes(1);
575610

576611
startsWith = 't';
577612
filteredTowns = fixture.componentInstance.filterTowns(startsWith);
578613
UIInteractions.setInputElementValue(input, startsWith, fixture);
579-
fixture.detectChanges();
614+
tick();
580615

581616
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
582-
tick();
583617
fixture.detectChanges();
618+
tick();
584619
expect(fixture.componentInstance.townSelected).toBe(filteredTowns[0]);
585620
expect(autocomplete.onItemSelected.emit).toHaveBeenCalledTimes(2);
586621
expect(autocomplete.onItemSelected.emit).toHaveBeenCalledWith({ value: 'Stara Zagora', cancel: false });
587622

588623
fixture.componentInstance.onItemSelected = (args) => { args.cancel = true; };
589624
UIInteractions.setInputElementValue(input, 's', fixture);
590-
fixture.detectChanges();
591625
tick();
592626
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
593627
expect(fixture.componentInstance.townSelected).toBe('s');
@@ -624,44 +658,56 @@ describe('IgxAutocomplete', () => {
624658
spyOn(autocomplete, 'close').and.callThrough();
625659
spyOn(autocomplete.target, 'close').and.callThrough();
626660
spyOn(autocomplete.target, 'open').and.callThrough();
661+
spyOn(autocomplete.target.onOpening, 'emit').and.callThrough();
627662

628663
UIInteractions.setInputElementValue(input, startsWith, fixture);
664+
tick();
629665
fixture.detectChanges();
666+
tick();
630667
expect(autocomplete.onInput).toHaveBeenCalledTimes(1);
631668

632669
startsWith = 'ga';
633670
UIInteractions.setInputElementValue(input, startsWith, fixture);
671+
tick();
634672
fixture.detectChanges();
673+
tick();
635674
expect(autocomplete.onInput).toHaveBeenCalledTimes(2);
636-
// Keeps dropdown opened
675+
expect(autocomplete.target.open).toHaveBeenCalledTimes(1);
676+
// onOpening is emitted once, so no impact on UX
677+
expect(autocomplete.target.onOpening.emit).toHaveBeenCalledTimes(1);
678+
// keeps dropdown opened
637679
expect(autocomplete.close).toHaveBeenCalledTimes(0);
638680
expect(autocomplete.target.close).toHaveBeenCalledTimes(0);
639681

640682
UIInteractions.triggerKeyDownEvtUponElem('enter', input.nativeElement, true);
641-
tick();
642683
fixture.detectChanges();
684+
tick();
643685
expect(autocomplete.handleKeyDown).toHaveBeenCalledTimes(1);
644686
expect(autocomplete.onInput).toHaveBeenCalledTimes(2);
645687
expect(autocomplete.close).toHaveBeenCalledTimes(1);
646688
expect(autocomplete.target.close).toHaveBeenCalledTimes(2);
647-
689+
expect(autocomplete.target.collapsed).toBeTruthy();
648690
// IgxDropDownItemNavigationDirective handleKeyDown is not called when dropdown is closed
649691
spyOn(IgxDropDownItemNavigationDirective.prototype, 'handleKeyDown').and.callThrough();
650692
UIInteractions.triggerKeyDownEvtUponElem('ArrowDown', input.nativeElement, true);
651693
fixture.detectChanges();
694+
tick();
652695
expect(autocomplete.handleKeyDown).toHaveBeenCalledTimes(2);
653696
expect(IgxDropDownItemNavigationDirective.prototype.handleKeyDown).toHaveBeenCalledTimes(0);
654697

655698
startsWith = 'w';
656699
UIInteractions.setInputElementValue(input, startsWith, fixture);
700+
tick();
657701
fixture.detectChanges();
658702
tick();
659703
expect(autocomplete.onInput).toHaveBeenCalledTimes(3);
704+
// initially calls open 2 times. This has no effect on UX, as dropdown.onOpening is not emitted
705+
expect(autocomplete.target.onOpening.emit).toHaveBeenCalledTimes(2);
660706
expect(autocomplete.target.open).toHaveBeenCalledTimes(2);
661707
}));
662-
it('Should navigate through dropdown items with arrow up/down keys', () => {
708+
it('Should navigate through dropdown items with arrow up/down keys', fakeAsync(() => {
663709
UIInteractions.setInputElementValue(input, 'a', fixture);
664-
fixture.detectChanges();
710+
tick();
665711
expect(dropDown.items[0].focused).toBeTruthy();
666712

667713
UIInteractions.triggerKeyDownEvtUponElem('ArrowDown', input.nativeElement, true);
@@ -678,7 +724,7 @@ describe('IgxAutocomplete', () => {
678724
fixture.detectChanges();
679725
expect(dropDown.items[0].focused).toBeTruthy();
680726
expect(dropDown.items[dropDown.items.length - 1].focused).toBeFalsy();
681-
});
727+
}));
682728
it('Should not overwrite browser functionality for Home/End keys', () => {
683729
UIInteractions.setInputElementValue(input, 'r', fixture);
684730
fixture.detectChanges();
@@ -722,7 +768,7 @@ describe('IgxAutocomplete', () => {
722768
expect(input.nativeElement.attributes['aria-expanded'].value).toEqual('false');
723769
expect(input.nativeElement.attributes['aria-activedescendant']).toBeUndefined();
724770
UIInteractions.setInputElementValue(input, 's', fixture);
725-
fixture.detectChanges();
771+
tick();
726772
expect(input.nativeElement.attributes['aria-expanded'].value).toEqual('true');
727773
expect(input.nativeElement.attributes['aria-activedescendant'].value).toEqual(dropDown.focusedItem.id);
728774
autocomplete.close();
@@ -802,16 +848,15 @@ describe('IgxAutocomplete', () => {
802848
const dropdownListScrollElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWNLIST_SCROLL));
803849
UIInteractions.setInputElementValue(plainInput, startsWith, fixture);
804850
tick();
805-
fixture.detectChanges();
806851
expect(dropDown.collapsed).toBeFalsy();
807852
expect(dropdownListScrollElement.children.length).toEqual(filteredTowns.length);
808853
expect(dropDown.children.first.focused).toBeTruthy();
809854
expect(dropDown.items[0].focused).toBeTruthy();
810855
expect(dropDown.items[0].value).toBe(filteredTowns[0]);
811856

812857
UIInteractions.triggerKeyDownEvtUponElem('enter', plainInput.nativeElement, true);
813-
tick();
814858
fixture.detectChanges();
859+
tick();
815860
expect(dropDown.collapsed).toBeTruthy();
816861
expect(dropdownListScrollElement.children.length).toEqual(0);
817862
expect(plainInput.nativeElement.value).toBe(filteredTowns[0]);
@@ -829,7 +874,6 @@ describe('IgxAutocomplete', () => {
829874
const filteredTowns = fixture.componentInstance.filterTowns(startsWith);
830875
const dropdownListScrollElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWNLIST_SCROLL));
831876
UIInteractions.setInputElementValue(textarea, startsWith, fixture);
832-
fixture.detectChanges();
833877
tick();
834878
expect(dropDown.collapsed).toBeFalsy();
835879
expect(dropdownListScrollElement.children.length).toEqual(filteredTowns.length);
@@ -838,22 +882,22 @@ describe('IgxAutocomplete', () => {
838882
expect(dropDown.items[0].value).toBe(filteredTowns[0]);
839883

840884
UIInteractions.triggerKeyDownEvtUponElem('enter', textarea.nativeElement, true);
841-
tick();
842885
fixture.detectChanges();
886+
tick();
843887
expect(dropDown.collapsed).toBeTruthy();
844888
expect(dropdownListScrollElement.children.length).toEqual(0);
845889
expect(textarea.nativeElement.value).toBe(filteredTowns[0]);
846890
}));
847891
it('Should be instantiated properly on ReactiveForm', fakeAsync(() => {
848892
fixture = TestBed.createComponent(AutocompleteFormComponent);
849893
fixture.detectChanges();
894+
tick();
850895
autocomplete = fixture.componentInstance.autocomplete;
851896
input = fixture.componentInstance.input;
852897
group = fixture.componentInstance.group;
853898
dropDown = fixture.componentInstance.dropDown;
854899
input.nativeElement.click();
855900
UIInteractions.clickAndSendInputElementValue(input, 's', fixture);
856-
fixture.detectChanges();
857901
tick();
858902
expect(dropDown.collapsed).toBeFalsy();
859903
expect(dropDown.children.first.focused).toBeTruthy();

0 commit comments

Comments
 (0)