UNPKG

igniteui-angular-sovn

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

991 lines (895 loc) 72.2 kB
import { Component, ViewChild, OnInit, ElementRef, ViewChildren, QueryList } from '@angular/core'; import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { IgxToggleActionDirective, IgxToggleDirective } from '../directives/toggle/toggle.directive'; import { IgxDropDownItemComponent } from './drop-down-item.component'; import { IgxDropDownComponent, IgxDropDownItemNavigationDirective } from './public_api'; import { ISelectionEventArgs } from './drop-down.common'; import { IgxTabContentComponent, IgxTabHeaderComponent, IgxTabItemComponent, IgxTabsComponent } from '../tabs/tabs/public_api'; import { UIInteractions, wait } from '../test-utils/ui-interactions.spec'; import { CancelableEventArgs, IBaseCancelableBrowserEventArgs } from '../core/utils'; import { configureTestSuite } from '../test-utils/configure-suite'; import { take } from 'rxjs/operators'; import { IgxDropDownGroupComponent } from './drop-down-group.component'; import { IgxForOfDirective } from '../directives/for-of/for_of.directive'; import { IgxDropDownItemBaseDirective } from './drop-down-item.base'; import { DisplayDensity } from '../core/density'; import { IgxSelectionAPIService } from '../core/selection'; import { IgxButtonDirective } from '../directives/button/button.directive'; import { NgFor } from '@angular/common'; const CSS_CLASS_DROP_DOWN_BASE = 'igx-drop-down'; const CSS_CLASS_LIST = 'igx-drop-down__list'; const CSS_CLASS_SCROLL = 'igx-drop-down__list-scroll'; const CSS_CLASS_ITEM = 'igx-drop-down__item'; const CSS_CLASS_INNER_SPAN = 'igx-drop-down__inner'; const CSS_CLASS_GROUP_ITEM = 'igx-drop-down__group'; const CSS_CLASS_ITEM_COSY = 'igx-drop-down__item--cosy'; const CSS_CLASS_ITEM_COMPACT = 'igx-drop-down__item--compact'; const CSS_CLASS_FOCUSED = 'igx-drop-down__item--focused'; const CSS_CLASS_SELECTED = 'igx-drop-down__item--selected'; const CSS_CLASS_DISABLED = 'igx-drop-down__item--disabled'; const CSS_CLASS_HEADER = 'igx-drop-down__header'; const CSS_CLASS_HEADER_COSY = 'igx-drop-down__header--cosy'; const CSS_CLASS_HEADER_COMPACT = 'igx-drop-down__header--compact'; const CSS_CLASS_TABS = '.igx-tabs__header-item'; describe('IgxDropDown ', () => { let fixture; let dropdown: IgxDropDownComponent; describe('Unit tests', () => { const data = [ { value: 'Item0', index: 0 } as IgxDropDownItemComponent, { value: 'Item1', index: 1 } as IgxDropDownItemComponent, { value: 'Item2', index: 2 } as IgxDropDownItemComponent, { value: 'Item3', index: 3 } as IgxDropDownItemComponent, { value: 'Item4', index: 4 } as IgxDropDownItemComponent, { value: 'Item5', index: 5 } as IgxDropDownItemComponent]; const mockSelection: { [key: string]: jasmine.Spy; } = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'add_items', 'select_items']); const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['markForCheck', 'detectChanges']); mockSelection.get.and.returnValue(new Set([])); const mockForOf = jasmine.createSpyObj('IgxForOfDirective', ['totalItemCount']); it('should notify when selection has changed', () => { const selectionService = new IgxSelectionAPIService(); dropdown = new IgxDropDownComponent({ nativeElement: null }, mockCdr, selectionService, null); dropdown.ngOnInit(); (dropdown as any).virtDir = mockForOf; spyOnProperty(dropdown, 'items', 'get').and.returnValue(data); spyOn(dropdown.selectionChanging, 'emit').and.callThrough(); dropdown.selectItem(data[0]); expect(dropdown.selectedItem).toEqual(data[0]); expect(dropdown.selectionChanging.emit).toHaveBeenCalledTimes(1); dropdown.selectItem(data[4]); expect(dropdown.selectedItem).toEqual(data[4]); expect(dropdown.selectionChanging.emit).toHaveBeenCalledTimes(2); }); it('should fire selectionChanging with correct args', () => { const selectionService = new IgxSelectionAPIService(); dropdown = new IgxDropDownComponent({ nativeElement: null }, mockCdr, selectionService, null); dropdown.ngOnInit(); (dropdown as any).virtDir = mockForOf; spyOnProperty(dropdown, 'items', 'get').and.returnValue(data); spyOn(dropdown.selectionChanging, 'emit').and.callThrough(); const selectionArgs: ISelectionEventArgs = { newSelection: dropdown.items[1], oldSelection: null, cancel: false, owner: dropdown }; dropdown.selectItem(data[1]); expect(dropdown.selectionChanging.emit).toHaveBeenCalledWith(selectionArgs); const newSelectionArgs: ISelectionEventArgs = { newSelection: dropdown.items[4], oldSelection: dropdown.items[1], cancel: false, owner: dropdown }; dropdown.selectItem(data[4]); expect(dropdown.selectionChanging.emit).toHaveBeenCalledWith(newSelectionArgs); }); it('should notify when selection is cleared', () => { const selectionService = new IgxSelectionAPIService(); dropdown = new IgxDropDownComponent({ nativeElement: null }, mockCdr, selectionService, null); dropdown.ngOnInit(); (dropdown as any).virtDir = mockForOf; spyOnProperty(dropdown, 'items', 'get').and.returnValue(data); spyOn(dropdown.selectionChanging, 'emit').and.callThrough(); spyOn(dropdown.closed, 'emit').and.callThrough(); dropdown.selectItem(data[1]); const selected = dropdown.selectedItem; expect(dropdown.selectedItem).toEqual(data[1]); expect(dropdown.selectionChanging.emit).toHaveBeenCalledTimes(1); let args: ISelectionEventArgs = { oldSelection: null, newSelection: data[1], cancel: false, owner: dropdown }; expect(dropdown.selectionChanging.emit).toHaveBeenCalledWith(args); dropdown.clearSelection(); expect(dropdown.selectedItem).toBeNull(); expect(dropdown.selectionChanging.emit).toHaveBeenCalledTimes(2); args = { oldSelection: selected, newSelection: null, cancel: false, owner: dropdown }; expect(dropdown.selectionChanging.emit).toHaveBeenCalledWith(args); }); it('setSelectedItem should return selected item', () => { const selectionService = new IgxSelectionAPIService(); dropdown = new IgxDropDownComponent({ nativeElement: null }, mockCdr, selectionService, null); dropdown.ngOnInit(); (dropdown as any).virtDir = mockForOf; (dropdown as any).virtDir.igxForOf = data; spyOnProperty(dropdown, 'items', 'get').and.returnValue(data); expect(dropdown.selectedItem).toBeNull(); dropdown.setSelectedItem(3); const selectedItem = dropdown.selectedItem; expect(selectedItem).toBeTruthy(); expect(selectedItem.index).toEqual(3); }); it('setSelectedItem should return null when selection is cleared', () => { const selectionService = new IgxSelectionAPIService(); dropdown = new IgxDropDownComponent({ nativeElement: null }, mockCdr, selectionService, null); dropdown.ngOnInit(); (dropdown as any).virtDir = mockForOf; (dropdown as any).virtDir.igxForOf = data; spyOnProperty(dropdown, 'items', 'get').and.returnValue(data); dropdown.setSelectedItem(3); expect(dropdown.selectedItem).toBeTruthy(); expect(dropdown.selectedItem.index).toEqual(3); dropdown.clearSelection(); expect(dropdown.selectedItem).toBeNull(); }); it('toggle should call open method when dropdown is collapsed', () => { const selectionService = new IgxSelectionAPIService(); dropdown = new IgxDropDownComponent({ nativeElement: null }, mockCdr, selectionService, null); dropdown.ngOnInit(); (dropdown as any).virtDir = mockForOf; spyOnProperty(dropdown, 'items', 'get').and.returnValue(data); spyOnProperty(dropdown, 'collapsed', 'get').and.returnValue(true); spyOn(dropdown, 'open'); dropdown.toggle(); expect(dropdown.open).toHaveBeenCalledTimes(1); }); it('toggle should call close method when dropdown is opened', () => { const selectionService = new IgxSelectionAPIService(); dropdown = new IgxDropDownComponent({ nativeElement: null }, mockCdr, selectionService, null); dropdown.ngOnInit(); (dropdown as any).virtDir = mockForOf; const mockToggle = jasmine.createSpyObj('IgxToggleDirective', ['open']); mockToggle.isClosing = false; (dropdown as any).toggleDirective = mockToggle; spyOnProperty(dropdown, 'items', 'get').and.returnValue(data); spyOnProperty(dropdown, 'collapsed', 'get').and.returnValue(false); spyOn(dropdown, 'close'); dropdown.toggle(); expect(dropdown.close).toHaveBeenCalledTimes(1); }); }); describe('User interaction tests', () => { describe('Selection & key navigation', () => { configureTestSuite(); beforeAll(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, IgxDropDownTestComponent ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(IgxDropDownTestComponent); fixture.detectChanges(); dropdown = fixture.componentInstance.dropdown; }); it('should toggle drop down on open/close methods call', fakeAsync(() => { spyOn(dropdown, 'onToggleOpening'); spyOn(dropdown, 'onToggleOpened'); spyOn(dropdown, 'onToggleClosing'); spyOn(dropdown, 'onToggleClosed'); expect(dropdown.collapsed).toBeTruthy(); dropdown.open(); tick(); fixture.detectChanges(); expect(dropdown.collapsed).toBeFalsy(); expect(dropdown.onToggleOpening).toHaveBeenCalledTimes(1); expect(dropdown.onToggleOpened).toHaveBeenCalledTimes(1); dropdown.close(); tick(); fixture.detectChanges(); expect(dropdown.collapsed).toBeTruthy(); expect(dropdown.onToggleClosing).toHaveBeenCalledTimes(1); expect(dropdown.onToggleClosed).toHaveBeenCalledTimes(1); })); it('#3810 - should not emit events when calling open on opened dropdown', fakeAsync(() => { spyOn(dropdown.opening, 'emit').and.callThrough(); spyOn(dropdown.opened, 'emit').and.callThrough(); dropdown.open(); tick(); fixture.detectChanges(); expect(dropdown.opening.emit).toHaveBeenCalledTimes(1); expect(dropdown.opened.emit).toHaveBeenCalledTimes(1); dropdown.open(); tick(); fixture.detectChanges(); expect(dropdown.opening.emit).toHaveBeenCalledTimes(1); expect(dropdown.opened.emit).toHaveBeenCalledTimes(1); })); it('#2798 - should allow canceling of open/close through opening/closing events', fakeAsync(() => { const toggle: IgxToggleDirective = (dropdown as any).toggleDirective; const onOpeningSpy = spyOn(dropdown.opening, 'emit').and.callThrough(); const onOpenedSpy = spyOn(dropdown.opened, 'emit').and.callThrough(); spyOn(dropdown.closing, 'emit').and.callThrough(); spyOn(dropdown.closed, 'emit').and.callThrough(); dropdown.closing.pipe(take(1)).subscribe((e: CancelableEventArgs) => e.cancel = true); dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.opening.emit).toHaveBeenCalledTimes(1); expect(dropdown.opened.emit).toHaveBeenCalledTimes(1); dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.closing.emit).toHaveBeenCalledTimes(1); expect(dropdown.closed.emit).toHaveBeenCalledTimes(0); toggle.close(); fixture.detectChanges(); onOpeningSpy.calls.reset(); onOpenedSpy.calls.reset(); dropdown.opening.pipe(take(1)).subscribe((e: CancelableEventArgs) => e.cancel = true); dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.opening.emit).toHaveBeenCalledTimes(1); expect(dropdown.opened.emit).toHaveBeenCalledTimes(0); })); it('should select item by SPACE/ENTER keys', fakeAsync(() => { dropdown.toggle(); tick(); fixture.detectChanges(); let focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(0); expect(dropdown.collapsed).toEqual(false); let dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`)); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', dropdownElement); tick(); fixture.detectChanges(); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem).toBeDefined(); expect(focusedItem.componentInstance.itemIndex).toEqual(1); expect(dropdown.selectedItem).toBeFalsy(); UIInteractions.triggerEventHandlerKeyDown('Space', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.selectedItem).toEqual(dropdown.items[1]); expect(dropdown.collapsed).toEqual(true); dropdown.toggle(); tick(); fixture.detectChanges(); dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`)); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', dropdownElement); tick(); fixture.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('Enter', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.collapsed).toEqual(true); expect(dropdown.selectedItem).toEqual(dropdown.items[2]); dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.collapsed).toEqual(false); })); it('should close the dropdown and not change selection by pressing ESC key', fakeAsync(() => { spyOn(dropdown.selectionChanging, 'emit').and.callThrough(); spyOn(dropdown.opening, 'emit').and.callThrough(); spyOn(dropdown.opened, 'emit').and.callThrough(); spyOn(dropdown.closing, 'emit').and.callThrough(); spyOn(dropdown.closed, 'emit').and.callThrough(); dropdown.toggle(); tick(); fixture.detectChanges(); let focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem).toBeDefined(); const dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`)); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', dropdownElement); fixture.detectChanges(); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem).toBeDefined(); expect(focusedItem.componentInstance.itemIndex).toEqual(1); UIInteractions.triggerEventHandlerKeyDown('Escape', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.collapsed).toEqual(true); expect(dropdown.opening.emit).toHaveBeenCalledTimes(1); expect(dropdown.opened.emit).toHaveBeenCalledTimes(1); expect(dropdown.selectionChanging.emit).toHaveBeenCalledTimes(0); expect(dropdown.closing.emit).toHaveBeenCalledTimes(1); expect(dropdown.closed.emit).toHaveBeenCalledTimes(1); })); it('should navigate through items using Up/Down/Home/End keys', fakeAsync(() => { dropdown.toggle(); tick(); fixture.detectChanges(); const dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`)); dropdownElement.triggerEventHandler('keydown', UIInteractions.getKeyboardEvent('keydown', 'ArrowDown')); tick(); fixture.detectChanges(); let focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(1); UIInteractions.triggerEventHandlerKeyDown('End', dropdownElement); tick(); fixture.detectChanges(); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(14); UIInteractions.triggerEventHandlerKeyDown('ArrowUp', dropdownElement); tick(); fixture.detectChanges(); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(13); UIInteractions.triggerEventHandlerKeyDown('Home', dropdownElement); tick(); fixture.detectChanges(); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(0); })); it('should not change selection when setting it to non-existing item', fakeAsync(() => { dropdown.toggle(); tick(); fixture.detectChanges(); dropdown.setSelectedItem(0); fixture.detectChanges(); let selectedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_SELECTED}`)); expect(selectedItem.componentInstance.itemIndex).toEqual(0); dropdown.setSelectedItem(-4); fixture.detectChanges(); expect(dropdown.items[0].selected).toBeTruthy(); selectedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_SELECTED}`)); expect(selectedItem.componentInstance.itemIndex).toEqual(0); dropdown.setSelectedItem(24); fixture.detectChanges(); expect(dropdown.items[0].selected).toBeTruthy(); selectedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_SELECTED}`)); expect(selectedItem.componentInstance.itemIndex).toEqual(0); dropdown.setSelectedItem(5); fixture.detectChanges(); expect(dropdown.items[5].selected).toBeTruthy(); selectedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_SELECTED}`)); expect(selectedItem.componentInstance.itemIndex).toEqual(5); // Verify selecting the already selected element is not affecting selection dropdown.setSelectedItem(5); fixture.detectChanges(); expect(dropdown.items[5].selected).toBeTruthy(); selectedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_SELECTED}`)); expect(selectedItem.componentInstance.itemIndex).toEqual(5); })); it('should focus the first enabled item by pressing HOME key', fakeAsync(() => { dropdown.items[0].disabled = true; dropdown.items[1].isHeader = true; dropdown.items[3].disabled = true; dropdown.items[4].isHeader = true; dropdown.items[7].disabled = true; dropdown.items[10].selected = true; fixture.detectChanges(); dropdown.toggle(); tick(); fixture.detectChanges(); const selectedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_SELECTED}`)); expect(selectedItem.componentInstance.itemIndex).toEqual(10); const dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`)); UIInteractions.triggerEventHandlerKeyDown('Home', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.items[1].focused).toBeTruthy(); const focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(1); })); it('should set isSelected via igxDropDownIteComponent', fakeAsync(() => { dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.selectedItem).toBeNull(); const items = dropdown.items as IgxDropDownItemComponent[]; items[2].selected = true; tick(); fixture.detectChanges(); expect(items[2].selected).toBeTruthy(); expect(dropdown.selectedItem.itemIndex).toEqual(2); items[1].selected = true; tick(); fixture.detectChanges(); expect(items[2].selected).toBeFalsy(); expect(items[1].selected).toBeTruthy(); expect(dropdown.selectedItem.itemIndex).toEqual(1); dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.selectedItem.itemIndex).toEqual(1); })); it('should not set isSelected via igxDropDownItemBase on header items', fakeAsync(() => { dropdown.items[0].disabled = true; dropdown.items[1].isHeader = true; dropdown.items[3].disabled = true; dropdown.items[4].isHeader = true; dropdown.items[7].disabled = true; dropdown.items[10].isHeader = true; fixture.detectChanges(); dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.selectedItem).toBeNull(); const items = dropdown.items as IgxDropDownItemComponent[]; const headerItems = dropdown.headers as IgxDropDownItemComponent[]; // Try to select header item headerItems[0].selected = true; tick(); fixture.detectChanges(); expect(headerItems[0].selected).toBeFalsy(); expect(dropdown.selectedItem).toBeNull(); // Try to select disabled item items[2].selected = true; tick(); fixture.detectChanges(); expect(items[2].selected).toBeTruthy(); expect(dropdown.selectedItem.itemIndex).toEqual(2); // Try to select header item headerItems[1].selected = true; expect(headerItems[1].selected).toBeFalsy(); expect(dropdown.selectedItem.itemIndex).toEqual(2); dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.selectedItem.itemIndex).toEqual(2); })); it('should return the proper eventArgs if selection has been cancelled', fakeAsync(() => { spyOn(dropdown.selectionChanging, 'emit').and.callThrough(); dropdown.toggle(); tick(); fixture.detectChanges(); let selectedItem = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_ITEM}`))[3]; selectedItem.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); fixture.detectChanges(); const selectionArgs: ISelectionEventArgs = { oldSelection: null, newSelection: dropdown.items[3], cancel: false, owner: dropdown }; expect(dropdown.selectionChanging.emit).toHaveBeenCalledWith(selectionArgs); dropdown.selectionChanging.pipe(take(1)).subscribe((e: CancelableEventArgs) => e.cancel = true); selectedItem = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_ITEM}`))[1]; selectedItem.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); fixture.detectChanges(); const canceledSelectionArgs: ISelectionEventArgs = { oldSelection: dropdown.items[3], newSelection: dropdown.items[1], cancel: true, owner: dropdown }; expect(dropdown.selectionChanging.emit).toHaveBeenCalledWith(canceledSelectionArgs); })); it('should provide correct event argument when closing through keyboard', fakeAsync(() => { spyOn(dropdown.closing, 'emit').and.callThrough(); const dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`)); dropdown.toggle(); tick(); fixture.detectChanges(); let focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem).toBeDefined(); let eventArgs: IBaseCancelableBrowserEventArgs; dropdown.closing.pipe(take(1)).subscribe((args: IBaseCancelableBrowserEventArgs) => { eventArgs = args; }); UIInteractions.triggerEventHandlerKeyDown('escape', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.closing.emit).toHaveBeenCalledTimes(1); expect(eventArgs.event).toBeDefined(); expect((eventArgs.event as KeyboardEvent).type).toEqual('keydown'); expect((eventArgs.event as KeyboardEvent).key).toEqual('escape'); dropdown.toggle(); tick(); fixture.detectChanges(); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem).toBeDefined(); dropdown.closing.pipe(take(1)).subscribe((args: IBaseCancelableBrowserEventArgs) => { eventArgs = args; }); UIInteractions.triggerEventHandlerKeyDown('enter', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.closing.emit).toHaveBeenCalledTimes(2); expect(eventArgs.event).toBeDefined(); expect((eventArgs.event as KeyboardEvent).type).toEqual('keydown'); expect((eventArgs.event as KeyboardEvent).key).toEqual('enter'); })); it('should be able to change selection when manipulating ISelectionEventArgs', fakeAsync(() => { expect(dropdown.selectedItem).toEqual(null); dropdown.toggle(); tick(); fixture.detectChanges(); // Overwrite selection args let expectedSelected = dropdown.items[4]; const calledSelected = dropdown.items[1]; const subscription = dropdown.selectionChanging.subscribe((e: ISelectionEventArgs) => { expect(e.newSelection).toEqual(calledSelected); e.newSelection = expectedSelected; }); dropdown.selectItem(calledSelected); tick(); expect(dropdown.selectedItem).toEqual(expectedSelected); // Clear selection expectedSelected = null; dropdown.selectItem(calledSelected); tick(); expect(dropdown.selectedItem).toEqual(expectedSelected); // Set header - error dropdown.toggle(); tick(); fixture.detectChanges(); expectedSelected = dropdown.items[4]; dropdown.items[4].isHeader = true; spyOn(dropdown, 'selectItem').and.callThrough(); expect(() => { dropdown.selectItem(calledSelected); }).toThrow(); // Set non-IgxDropDownItemBaseDirective expectedSelected = 7 as any; expect(() => { dropdown.selectItem(calledSelected); }).toThrow(); subscription.unsubscribe(); })); it('should not take focus when allowItemsFocus is set to false', fakeAsync(() => { dropdown.toggle(); tick(); fixture.detectChanges(); const focusedItem = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_ITEM}`))[0].nativeElement; expect(document.activeElement).toEqual(focusedItem); dropdown.toggle(); tick(); fixture.detectChanges(); dropdown.allowItemsFocus = false; tick(); fixture.detectChanges(); const button = fixture.debugElement.query(By.css('button')).nativeElement; button.focus(); button.click(); tick(); fixture.detectChanges(); expect(document.activeElement).toEqual(button); })); it('should not be able to select disabled and header items', fakeAsync(() => { dropdown.items[2].isHeader = true; dropdown.items[4].disabled = true; fixture.detectChanges(); dropdown.toggle(); tick(); fixture.detectChanges(); const currentItem = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DISABLED}`))[0]; const headerItem = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_HEADER}`))[0]; expect(currentItem.componentInstance.itemIndex).toEqual(4); expect(headerItem.componentInstance).toEqual(dropdown.headers[0]); currentItem.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); fixture.detectChanges(); expect(dropdown.selectedItem).toBeNull(); headerItem.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); fixture.detectChanges(); expect(dropdown.selectedItem).toBeNull(); // clicking on header item should not close the drop down expect(dropdown.collapsed).toEqual(false); })); it('should be possible to enable/disable items at runtime', fakeAsync(() => { dropdown.items[3].disabled = true; dropdown.items[7].disabled = true; dropdown.items[11].disabled = true; fixture.detectChanges(); dropdown.toggle(); tick(); fixture.detectChanges(); let disabledItems = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DISABLED}`)); expect(disabledItems.length).toEqual(3); expect(dropdown.items[4].disabled).toBeFalsy(); dropdown.items[4].disabled = true; fixture.detectChanges(); disabledItems = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DISABLED}`)); expect(disabledItems.length).toEqual(4); expect(dropdown.items[4].disabled).toBeTruthy(); })); it('should focus the last enabled item by pressing END key', fakeAsync(() => { dropdown.items[0].disabled = true; dropdown.items[1].isHeader = true; dropdown.items[3].disabled = true; dropdown.items[4].isHeader = true; dropdown.items[7].disabled = true; dropdown.items[10].selected = true; dropdown.items[12].disabled = true; fixture.detectChanges(); dropdown.toggle(); tick(); fixture.detectChanges(); const selectedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_SELECTED}`)); expect(selectedItem.componentInstance.itemIndex).toEqual(10); const dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`)); UIInteractions.triggerEventHandlerKeyDown('End', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.items[11].focused).toBeTruthy(); let focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(11); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.items[11].focused).toBeTruthy(); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(11); })); it('should skip disabled/header items on key navigation', fakeAsync(() => { dropdown.items[0].disabled = true; dropdown.items[1].isHeader = true; dropdown.items[3].disabled = true; dropdown.items[8].isHeader = true; dropdown.items[9].disabled = true; dropdown.items[10].selected = true; dropdown.items[12].disabled = true; fixture.detectChanges(); dropdown.toggle(); tick(); fixture.detectChanges(); const dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`)); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.items[11].focused).toBeTruthy(); let focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(11); UIInteractions.triggerEventHandlerKeyDown('ArrowUp', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.items[10].focused).toBeTruthy(); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(10); UIInteractions.triggerEventHandlerKeyDown('ArrowUp', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.items[8].focused).toBeTruthy(); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(8); UIInteractions.triggerEventHandlerKeyDown('ArrowUp', dropdownElement); tick(); fixture.detectChanges(); expect(dropdown.items[7].focused).toBeTruthy(); focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)); expect(focusedItem.componentInstance.itemIndex).toEqual(7); })); it('should select disabled items via code behind', fakeAsync(() => { dropdown.items[0].disabled = true; dropdown.items[1].isHeader = true; fixture.detectChanges(); dropdown.setSelectedItem(0); fixture.detectChanges(); dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.items[0].selected).toBeTruthy(); })); it('should not move the focus when clicking a disabled item', fakeAsync(() => { dropdown.items[0].disabled = true; dropdown.items[1].isHeader = true; dropdown.items[3].disabled = true; dropdown.items[4].isHeader = true; dropdown.items[7].disabled = true; dropdown.items[10].selected = true; fixture.detectChanges(); dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.items[10].focused).toEqual(true); const dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`)); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', dropdownElement); fixture.detectChanges(); expect(dropdown.items[11].focused).toEqual(true); const firstItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_ITEM}`)); firstItem.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); fixture.detectChanges(); expect(dropdown.items[11].focused).toEqual(true); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', dropdownElement); fixture.detectChanges(); expect(dropdown.items[12].focused).toEqual(true); })); }); describe('Other', () => { configureTestSuite(); beforeAll(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, DoubleIgxDropDownComponent, InputWithDropDownDirectiveComponent ] }).compileComponents(); })); it('should call preventDefault on a mousedown event when allowItemsFocus is disabled', () => { fixture = TestBed.createComponent(InputWithDropDownDirectiveComponent); fixture.detectChanges(); dropdown = fixture.componentInstance.dropdown; dropdown.allowItemsFocus = false; fixture.detectChanges(); dropdown.open(); fixture.detectChanges(); const itemToClick = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_ITEM}`))[0]; const event = new Event('mousedown', { }); spyOn(event, 'preventDefault'); itemToClick.triggerEventHandler('mousedown', event); fixture.detectChanges(); expect(event.preventDefault).toHaveBeenCalled(); }); it('should properly handle OnEnterKeyDown when the dropdown is not visible', fakeAsync(() => { fixture = TestBed.createComponent(InputWithDropDownDirectiveComponent); fixture.detectChanges(); dropdown = fixture.componentInstance.dropdown; const input = fixture.debugElement.query(By.css('input')); spyOn(dropdown, 'selectItem').and.callThrough(); expect(dropdown).toBeDefined(); expect(dropdown.focusedItem).toEqual(null); expect(dropdown.selectedItem).toEqual(null); expect(dropdown.selectItem).toHaveBeenCalledTimes(0); expect(dropdown.collapsed).toEqual(true); input.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); tick(); fixture.detectChanges(); expect(dropdown.collapsed).toEqual(true); expect(dropdown.selectItem).toHaveBeenCalledTimes(0); expect(dropdown.focusedItem).toEqual(null); UIInteractions.triggerEventHandlerKeyDown('Enter', input); tick(); fixture.detectChanges(); // does not attempt to select item on keydown if DD is closed; expect(dropdown.selectItem).toHaveBeenCalledTimes(0); expect(dropdown.selectedItem).toEqual(null); expect(dropdown.collapsed).toEqual(true); dropdown.toggle(); tick(); fixture.detectChanges(); expect(dropdown.collapsed).toEqual(false); expect(dropdown.focusedItem).toEqual(dropdown.items[0]); const dropdownItem = dropdown.items[0]; input.triggerEventHandler('keydown', UIInteractions.getKeyboardEvent('keydown', 'Enter')); tick(); fixture.detectChanges(); expect(dropdown.selectItem).toHaveBeenCalledTimes(1); expect(dropdown.selectItem).toHaveBeenCalledWith(dropdownItem, UIInteractions.getKeyboardEvent('keydown', 'Enter')); expect(dropdown.selectedItem).toEqual(dropdownItem); expect(dropdown.collapsed).toEqual(true); })); it('should keep selection per instance', () => { fixture = TestBed.createComponent(DoubleIgxDropDownComponent); fixture.detectChanges(); const dropdown1 = fixture.componentInstance.dropdown1; const dropdown2 = fixture.componentInstance.dropdown2; dropdown1.setSelectedItem(1); expect(dropdown1.selectedItem).toEqual(dropdown1.items[1]); expect(dropdown2.selectedItem).toEqual(null); dropdown2.setSelectedItem(3); expect(dropdown1.selectedItem).toEqual(dropdown1.items[1]); expect(dropdown2.selectedItem).toEqual(dropdown2.items[3]); dropdown1.setSelectedItem(5); expect(dropdown1.selectedItem).toEqual(dropdown1.items[5]); expect(dropdown2.selectedItem).toEqual(dropdown2.items[3]); }); }); }); describe('Virtualisation tests', () => { let scroll: IgxForOfDirective<any>; let items; configureTestSuite(); beforeAll(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, VirtualizedDropDownComponent ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(VirtualizedDropDownComponent); fixture.detectChanges(); dropdown = fixture.componentInstance.dropdown; scroll = fixture.componentInstance.virtualScroll; items = fixture.componentInstance.dropdownItems; }); it('should properly scroll when virtualized', async () => { dropdown.toggle(); fixture.detectChanges(); await wait(50); let firstItemElement = fixture.componentInstance.dropdownItems.first.element.nativeElement; let lastItemElement = fixture.componentInstance.dropdownItems.last.element.nativeElement; expect(lastItemElement.textContent.trim()).toEqual('Item 11'); expect(firstItemElement.textContent.trim()).toEqual('Item 1'); scroll.getScroll().scrollTop = scroll.getScroll().scrollHeight; fixture.detectChanges(); await wait(50); firstItemElement = fixture.componentInstance.dropdownItems.first.element.nativeElement; lastItemElement = fixture.componentInstance.dropdownItems.last.element.nativeElement; expect(firstItemElement.textContent.trim()).toEqual('Item 1990'); expect(lastItemElement.textContent.trim()).toEqual('Item 2000'); }); xit('Should properly handle keyboard navigation when virtualized', async () => { pending('does not have time to focus last item on navigateLast()'); // dropdown.toggle(); // fixture.detectChanges(); // dropdown.navigateFirst(); // expect(scroll.state.startIndex).toEqual(0); // expect(items.first.focused).toEqual(true); // dropdown.navigateLast(); // await wait(200); // fixture.detectChanges(); // expect(scroll.state.startIndex).toEqual(2000 - scroll.state.chunkSize); // expect(items.last.focused).toEqual(true); // const toggleBtn = fixture.debugElement.query(By.css('button')); // UIInteractions.triggerEventHandlerKeyDown('ArrowUp', toggleBtn); // await wait(30); // fixture.detectChanges(); // expect(scroll.state.startIndex).toEqual(2000 - scroll.state.chunkSize); // expect(items.toArray()[items.toArray().length - 2].focused).toEqual(true); }); it('should persist selection on scrolling', async () => { dropdown.toggle(); expect(dropdown.selectedItem).toBe(null); dropdown.selectItem(dropdown.items[5]); fixture.detectChanges(); expect(dropdown.selectedItem.value).toEqual({ name: fixture.componentInstance.items[5].name, id: 5 }); expect(dropdown.items[5].selected).toBeTruthy(); scroll.scrollTo(412); await wait(50); fixture.detectChanges(); expect(items.toArray()[5].selected).toBeFalsy(); expect(document.getElementsByClassName(CSS_CLASS_SELECTED).length).toEqual(0); scroll.scrollTo(0); await wait(50); fixture.detectChanges(); expect(items.toArray()[5].selected).toBeTruthy(); expect(document.getElementsByClassName(CSS_CLASS_SELECTED).length).toEqual(1); }); it('should properly select items both inside and outside of the virtual view', async () => { dropdown.toggle(); expect(dropdown.selectedItem).toBe(null); let selectedItem = { value: fixture.componentInstance.items[5], index: 5 } as IgxDropDownItemBaseDirective; dropdown.selectItem(selectedItem); fixture.detectChanges(); expect(dropdown.selectedItem as any).toEqual(selectedItem); expect(items.toArray()[5].selected).toEqual(true); selectedItem = { value: fixture.componentInstance.items[412], index: 412 } as IgxDropDownItemBaseDirective; dropdown.selectItem(selectedItem); fixture.detectChanges(); expect(dropdown.selectedItem as any).toEqual(selectedItem); expect(items.toArray()[5].selected).toEqual(false); scroll.scrollTo(412); await wait(50); fixture.detectChanges(); const selectedEntry = items.find(e => e.value === selectedItem.value && e.index === selectedItem.index); expect(selectedEntry).toBeTruthy(); expect(selectedEntry.selected).toBeTruthy(); }); it('should scroll selected item into view when virtualized', async () => { dropdown.toggle(); expect(dropdown.selectedItem).toBe(null); const virtualScroll = fixture.componentInstance.virtualScroll; const selectedItem = { value: fixture.componentInstance.items[1000], index: 1000 } as IgxDropDownItemBaseDirective; dropdown.selectItem(selectedItem); fixture.detectChanges(); dropdown.toggle(); await wait(50); dropdown.toggle(); await wait(50); const itemsInView = virtualScroll.igxForContainerSize / virtualScroll.igxForItemSize; const expectedScroll = virtualScroll.getScrollForIndex(selectedItem.index) - (itemsInView / 2 - 1) * virtualScroll.igxForItemSize; const acceptableDelta = virtualScroll.igxForItemSize; const scrollTop = virtualScroll.getScroll().scrollTop; expect(expectedScroll - acceptableDelta < scrollTop && expectedScroll + accep