UNPKG

igniteui-angular-sovn

Version:

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

1,001 lines (921 loc) 108 kB
import { AsyncPipe } from '@angular/common'; import { AfterViewInit, ChangeDetectorRef, Component, DebugElement, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { FormsModule, NgForm, ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { IgxComboDropDownComponent } from '../combo/combo-dropdown.component'; import { IgxComboState } from '../combo/combo.common'; import { RemoteDataService } from '../combo/combo.component.spec'; import { IComboSelectionChangingEventArgs, IgxComboFooterDirective, IgxComboHeaderDirective, IgxComboItemDirective, IgxComboToggleIconDirective } from '../combo/public_api'; import { DisplayDensity } from '../core/density'; import { IgxSelectionAPIService } from '../core/selection'; import { IBaseCancelableBrowserEventArgs, PlatformUtil } from '../core/utils'; import { IgxIconComponent } from '../icon/icon.component'; import { IgxIconService } from '../icon/icon.service'; import { IgxInputState, IgxLabelDirective } from '../input-group/public_api'; import { AbsoluteScrollStrategy, AutoPositionStrategy, ConnectedPositioningStrategy } from '../services/public_api'; import { configureTestSuite } from '../test-utils/configure-suite'; import { UIInteractions, wait } from '../test-utils/ui-interactions.spec'; import { IgxSimpleComboComponent, ISimpleComboSelectionChangingEventArgs } from './public_api'; const CSS_CLASS_COMBO = 'igx-combo'; const SIMPLE_COMBO_ELEMENT = 'igx-simple-combo'; const CSS_CLASS_COMBO_DROPDOWN = 'igx-combo__drop-down'; const CSS_CLASS_DROPDOWN = 'igx-drop-down'; const CSS_CLASS_DROPDOWNLIST = 'igx-drop-down__list'; const CSS_CLASS_DROPDOWNLIST_SCROLL = 'igx-drop-down__list-scroll'; const CSS_CLASS_CONTENT = 'igx-combo__content'; const CSS_CLASS_CONTAINER = 'igx-display-container'; const CSS_CLASS_DROPDOWNLISTITEM = 'igx-drop-down__item'; const CSS_CLASS_TOGGLEBUTTON = 'igx-combo__toggle-button'; const CSS_CLASS_CLEARBUTTON = 'igx-combo__clear-button'; const CSS_CLASS_ADDBUTTON = 'igx-combo__add-item'; const CSS_CLASS_FOCUSED = 'igx-drop-down__item--focused'; const CSS_CLASS_INPUTGROUP = 'igx-input-group'; const CSS_CLASS_COMBO_INPUTGROUP = 'igx-input-group__input'; const CSS_CLASS_INPUTGROUP_REQUIRED = 'igx-input-group--required'; const CSS_CLASS_HEADER = 'header-class'; const CSS_CLASS_FOOTER = 'footer-class'; const CSS_CLASS_ITEM = 'igx-drop-down__item'; const CSS_CLASS_ITEM_COSY = 'igx-drop-down__item--cosy'; const CSS_CLASS_ITEM_COMPACT = 'igx-drop-down__item--compact'; const CSS_CLASS_HEADER_ITEM = '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_INPUT_COSY = 'igx-input-group--cosy'; const CSS_CLASS_INPUT_COMPACT = 'igx-input-group--compact'; const CSS_CLASS_INPUT_COMFORTABLE = 'igx-input-group--comfortable'; const CSS_CLASS_INPUT_GROUP_REQUIRED = 'igx-input-group--required'; const CSS_CLASS_INPUT_GROUP_INVALID = 'igx-input-group--invalid'; const defaultDropdownItemHeight = 40; const defaultDropdownItemMaxHeight = 400; describe('IgxSimpleCombo', () => { let fixture: ComponentFixture<any>; let combo: IgxSimpleComboComponent; let input: DebugElement; configureTestSuite(); describe('Unit tests: ', () => { const data = ['Item1', 'Item2', 'Item3', 'Item4', 'Item5', 'Item6', 'Item7']; const complexData = [ { country: 'UK', city: 'London' }, { country: 'France', city: 'Paris' }, { country: 'Germany', city: 'Berlin' }, { country: 'Bulgaria', city: 'Sofia' }, { country: 'Austria', city: 'Vienna' }, { country: 'Spain', city: 'Madrid' }, { country: 'Italy', city: 'Rome' } ]; const elementRef = { nativeElement: null }; const mockSelection: { [key: string]: jasmine.Spy; } = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'add_items', 'select_items']); const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['markForCheck', 'detectChanges']); const mockComboService = jasmine.createSpyObj('IgxComboAPIService', ['register']); const mockNgControl = jasmine.createSpyObj('NgControl', ['registerOnChangeCb', 'registerOnTouchedCb']); const mockInjector = jasmine.createSpyObj('Injector', { get: mockNgControl }); mockSelection.get.and.returnValue(new Set([])); const mockIconService = new IgxIconService(null, null, null, null); const platformUtil = new PlatformUtil('browser'); it('should properly call dropdown methods on toggle', () => { combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, platformUtil, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['open', 'close', 'toggle']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.dropdown = dropdown; dropdown.collapsed = true; combo.open(); dropdown.collapsed = false; expect(combo.dropdown.open).toHaveBeenCalledTimes(1); expect(combo.collapsed).toBe(false); combo.close(); dropdown.collapsed = true; expect(combo.dropdown.close).toHaveBeenCalledTimes(1); expect(combo.collapsed).toBe(true); combo.toggle(); dropdown.collapsed = false; expect(combo.dropdown.toggle).toHaveBeenCalledTimes(1); expect(combo.collapsed).toBe(false); }); it('should call dropdown toggle with correct overlaySettings', () => { combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, platformUtil, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['toggle']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.dropdown = dropdown; const defaultSettings = (combo as any)._overlaySettings; combo.toggle(); expect(combo.dropdown.toggle).toHaveBeenCalledWith(defaultSettings || {}); const newSettings = { positionStrategy: new ConnectedPositioningStrategy(), scrollStrategy: new AbsoluteScrollStrategy() }; combo.overlaySettings = newSettings; const expectedSettings = Object.assign({}, defaultSettings, newSettings); combo.toggle(); expect(combo.dropdown.toggle).toHaveBeenCalledWith(expectedSettings); }); it('should properly get/set displayKey', () => { combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, platformUtil, null, null, mockInjector); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.valueKey = 'field'; expect(combo.displayKey).toEqual(combo.valueKey); combo.displayKey = 'region'; expect(combo.displayKey).toEqual('region'); expect(combo.displayKey === combo.valueKey).toBeFalsy(); }); it('should select items through select method', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, platformUtil, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.comboInput = comboInput; combo.data = complexData; combo.valueKey = 'country'; // with valueKey combo.dropdown = dropdown; spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length); const selectedItems = [combo.data[0].country]; combo.select('UK'); expect(combo.selection).toEqual(selectedItems); combo.select('Germany'); selectedItems.push(combo.data[2].country); selectedItems.shift(); expect(combo.selection).toEqual(selectedItems); selectedItems.shift(); combo.valueKey = null; // without valueKey selectedItems.push(combo.data[5]); combo.select(combo.data[5]); expect(combo.selection).toEqual(selectedItems); selectedItems.shift(); selectedItems.push(combo.data[1]); combo.select(combo.data[1]); expect(combo.selection).toEqual(selectedItems); }); it('should emit owner on `opening` and `closing`', () => { combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, platformUtil, null, null, mockInjector); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); spyOn(combo.opening, 'emit').and.callThrough(); spyOn(combo.closing, 'emit').and.callThrough(); const mockObj = {}; const mockEvent = new Event('mock'); const inputEvent: IBaseCancelableBrowserEventArgs = { cancel: false, owner: mockObj, event: mockEvent }; combo.comboInput = { nativeElement: { focus: () => { } } } as any; (combo as any).textSelection = { selected: false, trigger: () => { } }; combo.handleOpening(inputEvent); const expectedCall: IBaseCancelableBrowserEventArgs = { owner: combo, event: inputEvent.event, cancel: inputEvent.cancel }; expect(combo.opening.emit).toHaveBeenCalledWith(expectedCall); combo.handleClosing(inputEvent); expect(combo.closing.emit).toHaveBeenCalledWith(expectedCall); let sub = combo.opening.subscribe((e: IBaseCancelableBrowserEventArgs) => { e.cancel = true; }); combo.handleOpening(inputEvent); expect(inputEvent.cancel).toEqual(true); sub.unsubscribe(); inputEvent.cancel = false; sub = combo.closing.subscribe((e: IBaseCancelableBrowserEventArgs) => { e.cancel = true; }); combo.handleClosing(inputEvent); expect(inputEvent.cancel).toEqual(true); sub.unsubscribe(); }); it('should fire selectionChanging event on item selection', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, platformUtil, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']); comboInput.value = 'test'; combo.comboInput = comboInput; spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length); spyOn(combo.selectionChanging, 'emit'); let oldSelection = undefined; let newSelection = [combo.data[1]]; combo.select(combo.data[1]); expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(1); expect(combo.selectionChanging.emit).toHaveBeenCalledWith({ oldSelection, newSelection: newSelection[0], owner: combo, displayText: newSelection[0].trim(), cancel: false }); oldSelection = [...newSelection]; newSelection = [combo.data[0]]; combo.select(combo.data[0]); expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(2); expect(combo.selectionChanging.emit).toHaveBeenCalledWith({ oldSelection: oldSelection[0], newSelection: newSelection[0], owner: combo, displayText: newSelection[0].trim(), cancel: false }); }); it('should properly emit added and removed values in change event on single value selection', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, platformUtil, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = complexData; combo.valueKey = 'country'; combo.dropdown = dropdown; spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length); const selectionSpy = spyOn(combo.selectionChanging, 'emit'); const expectedResults: ISimpleComboSelectionChangingEventArgs = { newSelection: combo.data[0][combo.valueKey], oldSelection: undefined, owner: combo, displayText: `${combo.data[0][combo.displayKey]}`, cancel: false }; const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']); comboInput.value = 'test'; combo.comboInput = comboInput; combo.select(combo.data[0][combo.valueKey]); expect(selectionSpy).toHaveBeenCalledWith(expectedResults); Object.assign(expectedResults, { newSelection: undefined, oldSelection: combo.data[0][combo.valueKey], displayText: '' }); combo.deselect(); expect(selectionSpy).toHaveBeenCalledWith(expectedResults); }); it('should properly handle selection manipulation through selectionChanging emit', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, platformUtil, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length); spyOn(combo.selectionChanging, 'emit').and.callFake((event: IComboSelectionChangingEventArgs) => event.newSelection = []); const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']); combo.comboInput = comboInput; // No items are initially selected expect(combo.selection).toEqual([]); // Select the first item combo.select(combo.data[0]); // selectionChanging fires and overrides the selection to be []; expect(combo.selection).toEqual([]); }); it('should not throw error when setting data to null', () => { combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, platformUtil, null, null, mockInjector); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); let errorMessage = ''; try { combo.data = null; } catch (ex) { errorMessage = ex.message; } expect(errorMessage).toBe(''); expect(combo.data).not.toBeUndefined(); expect(combo.data).not.toBeNull(); expect(combo.data.length).toBe(0); }); it('should not throw error when setting data to undefined', () => { combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, platformUtil, null, null, mockInjector); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); let errorMessage = ''; try { combo.data = undefined; } catch (ex) { errorMessage = ex.message; } expect(errorMessage).toBe(''); expect(combo.data).not.toBeUndefined(); expect(combo.data).not.toBeNull(); expect(combo.data.length).toBe(0); }); it('should properly handleInputChange', () => { combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, platformUtil, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem', 'navigateFirst']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; const matchSpy = spyOn<any>(combo, 'checkMatch').and.callThrough(); spyOn(combo.searchInputUpdate, 'emit'); const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']); comboInput.value = 'test'; combo.comboInput = comboInput; combo.handleInputChange(); expect(matchSpy).toHaveBeenCalledTimes(1); expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(0); const args = { searchText: 'Fake', owner: combo, cancel: false }; combo.handleInputChange('Fake'); expect(matchSpy).toHaveBeenCalledTimes(2); expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(1); expect(combo.searchInputUpdate.emit).toHaveBeenCalledWith(args); args.searchText = ''; combo.handleInputChange(''); expect(matchSpy).toHaveBeenCalledTimes(3); expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(2); expect(combo.searchInputUpdate.emit).toHaveBeenCalledWith(args); combo.handleInputChange(); expect(matchSpy).toHaveBeenCalledTimes(4); expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(2); }); it('should be able to cancel searchInputUpdate', () => { combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, platformUtil, null, null, mockInjector); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.searchInputUpdate.subscribe((e) => { e.cancel = true; }); const matchSpy = spyOn<any>(combo, 'checkMatch').and.callThrough(); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem', 'collapsed', 'open', 'navigateFirst']); combo.dropdown = dropdown; spyOn(combo.searchInputUpdate, 'emit').and.callThrough(); const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value', 'focused']); comboInput.value = 'test'; combo.comboInput = comboInput; combo.handleInputChange('Item1'); expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(1); expect(matchSpy).toHaveBeenCalledTimes(1); }); it('should not open on click if combo is disabled', () => { combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, platformUtil, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['open', 'close', 'toggle']); const spyObj = jasmine.createSpyObj('event', ['stopPropagation', 'preventDefault']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']); comboInput.value = 'test'; combo.comboInput = comboInput; combo.ngOnInit(); combo.dropdown = dropdown; dropdown.collapsed = true; combo.disabled = true; combo.onClick(spyObj); expect(combo.dropdown.collapsed).toBeTruthy(); }); it('should not clear value when combo is disabled', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, platformUtil, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem', 'focusedItem']); const spyObj = jasmine.createSpyObj('event', ['stopPropagation']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; combo.disabled = true; const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value', 'focus']); comboInput.value = 'test'; combo.comboInput = comboInput; spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length); const item = combo.data.slice(0, 1); combo.select(item); combo.handleClear(spyObj); expect(combo.value).toEqual(item[0]); }); }); describe('Initialization and rendering tests: ', () => { beforeAll(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, ReactiveFormsModule, FormsModule, IgxSimpleComboSampleComponent, IgxSimpleComboEmptyComponent ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(IgxSimpleComboSampleComponent); fixture.detectChanges(); combo = fixture.componentInstance.combo; input = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_INPUTGROUP}`)); }); it('should initialize the combo component properly', () => { const toggleButton = fixture.debugElement.query(By.css('.' + CSS_CLASS_TOGGLEBUTTON)); expect(fixture.componentInstance).toBeDefined(); expect(combo).toBeDefined(); expect(combo.collapsed).toBeDefined(); expect(combo.collapsed).toBeTruthy(); expect(input).toBeDefined(); expect(toggleButton).toBeDefined(); expect(combo.placeholder).toBeDefined(); }); it('should initialize input properties properly', () => { expect(combo.data).toBeDefined(); expect(combo.valueKey).toEqual('field'); expect(combo.displayKey).toEqual('field'); expect(combo.groupKey).toEqual('region'); expect(combo.width).toEqual('400px'); expect(combo.itemsMaxHeight).toEqual(320); expect(combo.itemHeight).toEqual(32); expect(combo.placeholder).toEqual('Location'); expect(combo.allowCustomValues).toEqual(false); expect(combo.cssClass).toEqual(CSS_CLASS_COMBO); expect(combo.type).toEqual('box'); }); it('should apply all appropriate classes on combo initialization', () => { const comboWrapper = fixture.nativeElement.querySelector(SIMPLE_COMBO_ELEMENT); expect(comboWrapper).not.toBeNull(); expect(comboWrapper.classList.contains(CSS_CLASS_COMBO)).toBeTruthy(); expect(comboWrapper.childElementCount).toEqual(2); const dropDownElement = comboWrapper.children[1]; expect(dropDownElement.classList.contains(CSS_CLASS_COMBO_DROPDOWN)).toBeTruthy(); expect(dropDownElement.classList.contains(CSS_CLASS_DROPDOWN)).toBeTruthy(); expect(dropDownElement.childElementCount).toEqual(1); const dropDownList = dropDownElement.children[0]; const dropDownScrollList = dropDownElement.children[0].children[0]; expect(dropDownList.classList.contains(CSS_CLASS_DROPDOWNLIST)).toBeTruthy(); expect(dropDownList.classList.contains('igx-toggle--hidden')).toBeTruthy(); expect(dropDownScrollList.childElementCount).toEqual(0); }); it('should render aria attributes properly', fakeAsync(() => { expect(input.nativeElement.getAttribute('role')).toEqual('combobox'); expect(input.nativeElement.getAttribute('aria-haspopup')).toEqual('listbox'); expect(input.nativeElement.getAttribute('aria-readonly')).toMatch('false'); expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('false'); expect(input.nativeElement.getAttribute('aria-controls')).toEqual(combo.dropdown.listId); expect(input.nativeElement.getAttribute('aria-labelledby')).toEqual(combo.placeholder); const dropdown = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_DROPDOWN}`)); expect(dropdown.nativeElement.getAttribute('ng-reflect-labelled-by')).toEqual(combo.placeholder); combo.open(); tick(); fixture.detectChanges(); const list = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`)); expect(list.nativeElement.getAttribute('aria-activedescendant')).toEqual(combo.dropdown.focusedItem.id); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', list); tick(); fixture.detectChanges(); expect(list.nativeElement.getAttribute('aria-activedescendant')).toEqual(combo.dropdown.focusedItem.id); })); it('should render aria-expanded attribute properly', fakeAsync(() => { expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('false'); combo.open(); tick(); fixture.detectChanges(); expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('true'); combo.close(); tick(); fixture.detectChanges(); expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('false'); })); it('should render placeholder values for inputs properly', () => { combo.toggle(); fixture.detectChanges(); expect(combo.collapsed).toBeFalsy(); expect(combo.placeholder).toEqual('Location'); expect(combo.comboInput.nativeElement.placeholder).toEqual('Location'); combo.placeholder = 'States'; fixture.detectChanges(); expect(combo.placeholder).toEqual('States'); expect(combo.comboInput.nativeElement.placeholder).toEqual('States'); }); it('should render dropdown list and item height properly', fakeAsync(() => { // NOTE: Minimum itemHeight is 2 rem, per Material Design Guidelines (for mobile only) let itemHeight = defaultDropdownItemHeight; let itemMaxHeight = defaultDropdownItemMaxHeight; combo.displayDensity = DisplayDensity.comfortable; fixture.detectChanges(); combo.toggle(); tick(); fixture.detectChanges(); const dropdownItems = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`)); const dropdownList = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`)); const verifyDropdownItemHeight = () => { expect(combo.itemHeight).toEqual(itemHeight); expect(dropdownItems[0].nativeElement.clientHeight).toEqual(itemHeight); expect(combo.itemsMaxHeight).toEqual(itemMaxHeight); expect(dropdownList.nativeElement.clientHeight).toEqual(itemMaxHeight); }; verifyDropdownItemHeight(); itemHeight = 48; itemMaxHeight = 480; combo.itemHeight = itemHeight; tick(); fixture.detectChanges(); verifyDropdownItemHeight(); itemMaxHeight = 438; combo.itemsMaxHeight = 438; tick(); fixture.detectChanges(); verifyDropdownItemHeight(); itemMaxHeight = 1171; combo.itemsMaxHeight = 1171; tick(); fixture.detectChanges(); verifyDropdownItemHeight(); itemHeight = 83; combo.itemHeight = 83; tick(); fixture.detectChanges(); verifyDropdownItemHeight(); })); it('should render focused items properly', () => { const dropdown = combo.dropdown; combo.toggle(); fixture.detectChanges(); dropdown.navigateItem(2); // Component is virtualized, so this will focus the ACTUAL 3rd item fixture.detectChanges(); const dropdownList = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLIST_SCROLL}`)).nativeElement; const dropdownItems = dropdownList.querySelectorAll(`.${CSS_CLASS_DROPDOWNLISTITEM}`); const focusedItem_1 = dropdownItems[1]; expect(focusedItem_1.classList.contains(CSS_CLASS_FOCUSED)).toBeTruthy(); // Change focus dropdown.navigateItem(6); fixture.detectChanges(); const focusedItem_2 = dropdownItems[5]; expect(focusedItem_2.classList.contains(CSS_CLASS_FOCUSED)).toBeTruthy(); expect(focusedItem_1.classList.contains(CSS_CLASS_FOCUSED)).toBeFalsy(); }); it('should properly initialize templates', () => { expect(combo).toBeDefined(); expect(combo.footerTemplate).toBeDefined(); expect(combo.headerTemplate).toBeDefined(); expect(combo.itemTemplate).toBeDefined(); expect(combo.addItemTemplate).toBeUndefined(); expect(combo.headerItemTemplate).toBeUndefined(); }); it('should properly render header and footer templates', () => { let headerElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_HEADER}`)); let footerElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOOTER}`)); expect(headerElement).toBeNull(); expect(footerElement).toBeNull(); combo.toggle(); fixture.detectChanges(); expect(combo.headerTemplate).toBeDefined(); expect(combo.footerTemplate).toBeDefined(); const dropdownList: HTMLElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLIST_SCROLL}`)).nativeElement; headerElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_HEADER}`)); footerElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOOTER}`)); expect(headerElement).not.toBeNull(); const headerHTMLElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_HEADER}`)).nativeElement; expect(headerHTMLElement.parentNode).toEqual(dropdownList); expect(headerHTMLElement.textContent).toEqual('This is a header'); expect(footerElement).not.toBeNull(); const footerHTMLElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOOTER}`)).nativeElement; expect(footerHTMLElement.parentNode).toEqual(dropdownList); expect(footerHTMLElement.textContent).toEqual('This is a footer'); }); it('should initialize the component with empty data and bindings', () => { fixture = TestBed.createComponent(IgxSimpleComboEmptyComponent); expect(() => { fixture.detectChanges(); }).not.toThrow(); expect(fixture.componentInstance.combo).toBeDefined(); }); }); describe('Binding tests: ', () => { beforeAll(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, ReactiveFormsModule, FormsModule, IgxSimpleComboSampleComponent, IgxComboInContainerTestComponent, IgxComboRemoteDataComponent, ComboModelBindingComponent ] }).compileComponents(); })); it('should bind combo data to array of primitive data', () => { fixture = TestBed.createComponent(IgxComboInContainerTestComponent); fixture.detectChanges(); const data = [...fixture.componentInstance.citiesData]; combo = fixture.componentInstance.combo; const comboData = combo.data; expect(comboData).toEqual(data); }); it('should remove undefined from array of primitive data', () => { fixture = TestBed.createComponent(IgxComboInContainerTestComponent); fixture.detectChanges(); combo = fixture.componentInstance.combo; combo.data = ['New York', 'Sofia', undefined, 'Istanbul','Paris']; expect(combo.data).toEqual(['New York', 'Sofia', 'Istanbul','Paris']); }); it('should bind combo data to array of objects', () => { fixture = TestBed.createComponent(IgxSimpleComboSampleComponent); fixture.detectChanges(); const data = [...fixture.componentInstance.items]; combo = fixture.componentInstance.combo; const comboData = combo.data; expect(comboData).toEqual(data); }); it('should render empty template when combo data source is not set', () => { fixture = TestBed.createComponent(IgxComboInContainerTestComponent); fixture.detectChanges(); combo = fixture.componentInstance.combo; combo.data = []; fixture.detectChanges(); combo.toggle(); fixture.detectChanges(); const dropdownList = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLIST_SCROLL}`)).nativeElement; const dropdownItemsContainer = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`)).nativeElement; const dropDownContainer = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTAINER}`)).nativeElement; const listItems = dropDownContainer.querySelectorAll(`.${CSS_CLASS_DROPDOWNLISTITEM}`); expect(listItems.length).toEqual(0); // Expect no items to be rendered in the virtual container expect(dropdownItemsContainer.children[0].childElementCount).toEqual(0); // Expect the list child (NOT COMBO ITEM) to be a container with "The list is empty"; const dropdownItem = dropdownList.lastElementChild as HTMLElement; expect(dropdownItem.firstElementChild.textContent).toEqual('The list is empty'); }); it('should bind combo data properly when changing data source runtime', () => { const newData = ['Item 1', 'Item 2']; fixture = TestBed.createComponent(IgxComboInContainerTestComponent); fixture.detectChanges(); const data = [...fixture.componentInstance.citiesData]; combo = fixture.componentInstance.combo; expect(combo.data).toEqual(data); combo.data = newData; fixture.detectChanges(); expect(combo.data).toEqual(newData); }); it('should properly bind to object value w/ valueKey', fakeAsync(() => { fixture = TestBed.createComponent(ComboModelBindingComponent); fixture.detectChanges(); tick(); const component = fixture.componentInstance; combo = fixture.componentInstance.combo; combo.valueKey = 'id'; component.selectedItem = 1; fixture.detectChanges(); tick(); expect(combo.selection).toEqual([combo.data[1][combo.valueKey]]); combo.select(combo.data[4][combo.valueKey]); fixture.detectChanges(); expect(component.selectedItem).toEqual(4); })); it('should properly bind to object value w/o valueKey', fakeAsync(() => { fixture = TestBed.createComponent(ComboModelBindingComponent); fixture.detectChanges(); tick(); const component = fixture.componentInstance; combo = fixture.componentInstance.combo; component.selectedItem = component.items[0]; fixture.detectChanges(); tick(); expect(combo.selection).toEqual([combo.data[0]]); combo.select(combo.data[4]); fixture.detectChanges(); expect(component.selectedItem).toEqual(combo.data[4]); })); it('should clear selection w/o valueKey', fakeAsync(() => { fixture = TestBed.createComponent(ComboModelBindingComponent); fixture.detectChanges(); const component = fixture.componentInstance; combo = fixture.componentInstance.combo; component.items = ['One', 'Two', 'Three', 'Four', 'Five']; combo.select('Three'); fixture.detectChanges(); expect(combo.selection).toEqual(['Three']); combo.handleClear(new MouseEvent('click')); fixture.detectChanges(); expect(combo.value).toEqual(''); })); it('should properly bind to values w/o valueKey', fakeAsync(() => { fixture = TestBed.createComponent(ComboModelBindingComponent); fixture.detectChanges(); const component = fixture.componentInstance; combo = fixture.componentInstance.combo; component.items = ['One', 'Two', 'Three', 'Four', 'Five']; component.selectedItem = 'One'; fixture.detectChanges(); tick(); expect(combo.selection).toEqual([component.selectedItem]); combo.select('Three'); fixture.detectChanges(); expect(fixture.componentInstance.selectedItem).toEqual('Three'); })); it('should bind combo data to remote data and clear selection properly', (async () => { fixture = TestBed.createComponent(IgxComboRemoteDataComponent); fixture.detectChanges(); combo = fixture.componentInstance.instance; expect(combo).toBeDefined(); expect(combo.valueKey).toBeDefined(); let selectedItem = combo.data[1]; const spyObj = jasmine.createSpyObj('event', ['stopPropagation']); combo.toggle(); combo.select(combo.data[1][combo.valueKey]); expect(combo.value).toEqual(`${selectedItem[combo.displayKey]}`); expect(combo.selection).toEqual([selectedItem[combo.valueKey]]); // Clear items while they are in view combo.handleClear(spyObj); expect(combo.selection).toEqual([]); expect(combo.value).toBe(''); selectedItem = combo.data[2]; combo.select(combo.data[2][combo.valueKey]); expect(combo.value).toEqual(`${selectedItem[combo.displayKey]}`); // Scroll selected items out of view combo.virtualScrollContainer.scrollTo(40); await wait(); fixture.detectChanges(); combo.handleClear(spyObj); expect(combo.selection).toEqual([]); expect(combo.value).toBe(''); combo.select(combo.data[7][combo.valueKey]); expect(combo.value).toBe(combo.data[7][combo.displayKey]); })); }); describe('Keyboard navigation and interactions', () => { let dropdown: IgxComboDropDownComponent; beforeAll(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, ReactiveFormsModule, FormsModule, IgxSimpleComboSampleComponent, IgxComboInContainerTestComponent, IgxSimpleComboIconTemplatesComponent ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(IgxSimpleComboSampleComponent); fixture.detectChanges(); combo = fixture.componentInstance.combo; input = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_INPUTGROUP}`)); dropdown = combo.dropdown; }); it('should toggle dropdown list with arrow down/up keys', fakeAsync(() => { spyOn(combo, 'open').and.callThrough(); spyOn(combo, 'close').and.callThrough(); combo.onArrowDown(UIInteractions.getKeyboardEvent('keydown', 'ArrowDown')); tick(); fixture.detectChanges(); expect(combo.open).toHaveBeenCalledTimes(1); combo.onArrowDown(UIInteractions.getKeyboardEvent('keydown', 'ArrowDown', true)); tick(); fixture.detectChanges(); expect(combo.collapsed).toEqual(false); expect(combo.open).toHaveBeenCalledTimes(1); combo.handleKeyDown(UIInteractions.getKeyboardEvent('keydown', 'ArrowUp')); tick(); fixture.detectChanges(); expect(combo.close).toHaveBeenCalledTimes(1); combo.handleKeyDown(UIInteractions.getKeyboardEvent('keydown', 'ArrowUp', true)); fixture.detectChanges(); tick(); expect(combo.close).toHaveBeenCalledTimes(2); })); it('should not close the dropdown on ArrowUp key if the active item is the first one in the list', fakeAsync(() => { combo.open(); tick(); fixture.detectChanges(); const list = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`)); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', list); tick(); fixture.detectChanges(); expect(combo.collapsed).toEqual(false); UIInteractions.triggerEventHandlerKeyDown('ArrowUp', list); tick(); fixture.detectChanges(); expect(combo.collapsed).toEqual(false); })); it('should select an item from the dropdown list with the Space key without closing it', () => { combo.open(); fixture.detectChanges(); const dropdownContent = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`)); expect(dropdownContent).not.toBeFalsy(); combo.handleKeyUp(UIInteractions.getKeyboardEvent('keyup', 'ArrowDown')); fixture.detectChanges(); expect(dropdown.focusedItem).toBeTruthy(); expect(dropdown.focusedItem.index).toEqual(1); spyOn(combo.closed, 'emit').and.callThrough(); UIInteractions.triggerEventHandlerKeyDown('Space', dropdownContent); fixture.detectChanges(); expect(combo.closed.emit).not.toHaveBeenCalled(); expect(combo.selection.length).toEqual(1); }); it('should clear the selection on tab/blur if the search text does not match any value', () => { // allowCustomValues does not matter combo.select(combo.data[2][combo.valueKey]); fixture.detectChanges(); expect(combo.selection.length).toBe(1); expect(input.nativeElement.value).toEqual('Massachusetts'); UIInteractions.simulateTyping('L', input, 13, 14); const event = { target: input.nativeElement, key: 'L', stopPropagation: () => { }, stopImmediatePropagation: () => { }, preventDefault: () => { } }; combo.onArrowDown(new KeyboardEvent('keydown', { ...event })); fixture.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('Tab', input); fixture.detectChanges(); expect(input.nativeElement.value.length).toEqual(0); expect(combo.selection.length).toEqual(0); }); it('should not clear selection on tab/blur after filtering and selecting a value', () => { UIInteractions.simulateTyping('con', input); expect(combo.comboInput.value).toEqual('con'); fixture.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('Enter', input.nativeElement); expect(combo.selection.length).toEqual(1); expect(combo.value).toEqual('Wisconsin'); UIInteractions.triggerEventHandlerKeyDown('Tab', input); fixture.detectChanges(); expect(combo.selection.length).toEqual(1); expect(combo.value).toEqual('Wisconsin'); }); it('should display the AddItem button when allowCustomValues is true and there is a partial match', fakeAsync(() => { fixture.componentInstance.allowCustomValues = true; fixture.detectChanges(); combo.open(); fixture.detectChanges(); UIInteractions.setInputElementValue(input.nativeElement, 'Massachuset'); fixture.detectChanges(); expect(combo.isAddButtonVisible()).toBeTruthy(); let addItemButton = fixture.debugElement.query(By.css(`.${CSS_CLASS_ADDBUTTON}`)); expect(addItemButton).not.toBeNull(); // after adding the item, the addItem button should not be displayed (there is a full match) addItemButton.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); fixture.detectChanges(); expect(combo.collapsed).toBeFalsy(); expect(combo.data.findIndex(i => i.field === 'Massachuset')).not.toBe(-1); addItemButton = fixture.debugElement.query(By.css(`.${CSS_CLASS_ADDBUTTON}`)); expect(combo.isAddButtonVisible()).toBeFalsy(); expect(addItemButton).toBeNull(); })); it('should move the focus to the AddItem button with ArrowDown when allowCustomValues is true', fakeAsync(() => { fixture.componentInstance.allowCustomValues = true; fixture.detectChanges(); UIInteractions.setInputElementValue(input.nativeElement, 'MassachusettsL'); fixture.detectChanges(); combo.open(); fixture.detectChanges(); const addItemButton = fixture.debugElement.query(By.css(`.${CSS_CLASS_ADDBUTTON}`)); expect(addItemButton).toBeDefined(); input.nativeElement.focus(); fixture.detectChanges(); combo.onArrowDown(new Event('keydown')); fixture.detectChanges(); expect(document.activeElement).toEqual(addItemButton.nativeElement); })); it('should close when an item is clicked on', () => { spyOn(combo, 'close').and.callThrough(); combo.open(); fixture.detectChanges(); const item1 = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`)); expect(item1).toBeDefined(); item1.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); fixture.detectChanges(); expect(combo.close).toHaveBeenCalledTimes(1); }); it('should retain selection after blurring', () => { combo.open(); fixture.detectChanges(); const item1 = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`)); expect(item1).toBeDefined(); item1.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); fixture.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('Tab', input); fixture.detectChanges(); expect(combo.selection.length).toBe(1); }); it('should scroll to top when opened and there is no selection', () => { combo.deselect(); fixture.detectChanges(); spyOn(combo, 'onClick').and.callThrough(); spyOn((combo as any).virtDir, 'scrollTo').and.callThrough(); const toggleButton = fixture.debugElement.query(By.directive(IgxIc