UNPKG

igniteui-angular-sovn

Version:

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

979 lines (917 loc) 186 kB
import { AsyncPipe } from '@angular/common'; import { AfterViewInit, ChangeDetectorRef, Component, DebugElement, Injectable, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { FormsModule, NgControl, NgForm, NgModel, ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { BehaviorSubject, Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { DisplayDensity } from '../core/density'; import { IgxSelectionAPIService } from '../core/selection'; import { IBaseCancelableBrowserEventArgs } from '../core/utils'; import { SortingDirection } from '../data-operations/sorting-strategy'; import { IForOfState } from '../directives/for-of/for_of.directive'; import { IgxInputState } from '../directives/input/input.directive'; import { IgxIconService } from '../icon/public_api'; import { IgxLabelDirective } from '../input-group/public_api'; import { AbsoluteScrollStrategy, ConnectedPositioningStrategy } from '../services/public_api'; import { configureTestSuite } from '../test-utils/configure-suite'; import { UIInteractions, wait } from '../test-utils/ui-interactions.spec'; import { IgxComboAddItemComponent } from './combo-add-item.component'; import { IgxComboDropDownComponent } from './combo-dropdown.component'; import { IgxComboItemComponent } from './combo-item.component'; import { IComboFilteringOptions, IgxComboState } from './combo.common'; import { IComboItemAdditionEvent, IComboSearchInputEventArgs, IComboSelectionChangingEventArgs, IgxComboComponent } from './combo.component'; import { IgxComboFooterDirective, IgxComboHeaderDirective, IgxComboItemDirective } from './combo.directives'; import { IgxComboFilteringPipe } from './combo.pipes'; import { IgxDropDownItemBaseDirective } from '../drop-down/drop-down-item.base'; const CSS_CLASS_COMBO = 'igx-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_SELECTED = 'igx-drop-down__item--selected'; const CSS_CLASS_FOCUSED = 'igx-drop-down__item--focused'; const CSS_CLASS_HEADERITEM = 'igx-drop-down__header'; const CSS_CLASS_SCROLLBAR_VERTICAL = 'igx-vhelper--vertical'; const CSS_CLASS_INPUTGROUP = 'igx-input-group'; const CSS_CLASS_COMBO_INPUTGROUP = 'igx-input-group__input'; const CSS_CLASS_INPUTGROUP_WRAPPER = 'igx-input-group__wrapper'; const CSS_CLASS_INPUTGROUP_REQUIRED = 'igx-input-group--required'; const CSS_CLASS_INPUTGROUP_LABEL = 'igx-input-group__label'; const CSS_CLASS_SEARCHINPUT = 'input[name=\'searchInput\']'; 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 CSS_CLASS_EMPTY = 'igx-combo__empty'; const CSS_CLASS_ITEM_CHECKBOX = 'igx-combo__checkbox'; const CSS_CLASS_ITME_CHECKBOX_CHECKED = 'igx-checkbox--checked'; const defaultDropdownItemHeight = 40; const defaultDropdownItemMaxHeight = 400; describe('igxCombo', () => { let fixture: ComponentFixture<any>; let combo: IgxComboComponent; let input: DebugElement; 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); it('should correctly implement interface methods - ControlValueAccessor ', () => { combo = new IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, null, null, mockInjector); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); expect(mockInjector.get).toHaveBeenCalledWith(NgControl, null); combo.registerOnChange(mockNgControl.registerOnChangeCb); combo.registerOnTouched(mockNgControl.registerOnTouchedCb); // writeValue expect(combo.value).toBe(''); mockSelection.get.and.returnValue(new Set(['test'])); spyOnProperty(combo, 'isRemote').and.returnValue(false); combo.writeValue(['test']); expect(mockNgControl.registerOnChangeCb).not.toHaveBeenCalled(); expect(mockSelection.select_items).toHaveBeenCalledWith(combo.id, ['test'], true); expect(combo.value).toBe('test'); // setDisabledState combo.setDisabledState(true); expect(combo.disabled).toBe(true); combo.setDisabledState(false); expect(combo.disabled).toBe(false); // OnChange callback mockSelection.add_items.and.returnValue(new Set(['simpleValue'])); combo.select(['simpleValue']); expect(mockSelection.add_items).toHaveBeenCalledWith(combo.id, ['simpleValue'], undefined); expect(mockSelection.select_items).toHaveBeenCalledWith(combo.id, ['simpleValue'], true); expect(mockNgControl.registerOnChangeCb).toHaveBeenCalledWith(['simpleValue']); // OnTouched callback spyOnProperty(combo, 'collapsed').and.returnValue(true); spyOnProperty(combo, 'valid', 'set'); combo.onBlur(); expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1); }); it('should properly call dropdown methods on toggle', () => { combo = new IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, 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 not focus search input when property autoFocusSearch=false`, () => { combo = new IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, null, null, mockInjector); const dropdownContainer = { nativeElement: { focus: () => { } } }; combo['dropdownContainer'] = dropdownContainer; spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); spyOn(combo, 'focusSearchInput'); combo.autoFocusSearch = false; combo.handleOpened(); expect(combo.focusSearchInput).toHaveBeenCalledTimes(0); combo.autoFocusSearch = true; combo.handleOpened(); expect(combo.focusSearchInput).toHaveBeenCalledTimes(1); combo.autoFocusSearch = false; combo.handleOpened(); expect(combo.focusSearchInput).toHaveBeenCalledTimes(1); }); it('should call dropdown toggle with correct overlaySettings', () => { combo = new IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, 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 IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, 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 properly call "writeValue" method', () => { combo = new IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, null, null, mockInjector); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; mockSelection.select_items.calls.reset(); spyOnProperty(combo, 'isRemote').and.returnValue(false); combo.writeValue(['EXAMPLE']); expect(mockSelection.select_items).toHaveBeenCalledTimes(1); // Calling "select_items" through the writeValue accessor should clear the previous values; // Select items is called with the invalid value and it is written in selection, though no item is selected // Controlling the selection is up to the user expect(mockSelection.select_items).toHaveBeenCalledWith(combo.id, ['EXAMPLE'], true); combo.writeValue(combo.data[0]); // When value key is specified, the item's value key is stored in the selection expect(mockSelection.select_items).toHaveBeenCalledWith(combo.id, [], true); }); it('should select items through setSelctedItem method', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, 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 selectedItems = [combo.data[0].country]; combo.setSelectedItem('UK', true); expect(combo.selection).toEqual(selectedItems); combo.setSelectedItem('Germany', true); selectedItems.push(combo.data[2].country); expect(combo.selection).toEqual(selectedItems); selectedItems.pop(); combo.setSelectedItem('Germany', false); expect(combo.selection).toEqual(selectedItems); selectedItems.pop(); combo.setSelectedItem('UK', false); expect(combo.selection).toEqual(selectedItems); combo.valueKey = null; selectedItems.push(combo.data[5]); combo.setSelectedItem(combo.data[5], true); expect(combo.selection).toEqual(selectedItems); selectedItems.push(combo.data[1]); combo.setSelectedItem(combo.data[1], true); expect(combo.selection).toEqual(selectedItems); selectedItems.pop(); combo.setSelectedItem(combo.data[1], false); expect(combo.selection).toEqual(selectedItems); }); it('should set selectedItems correctly on selectItems method call', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, 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); combo.select([], false); expect(combo.selection).toEqual([]); combo.select([], true); expect(combo.selection).toEqual([]); const selectedItems = combo.data.slice(0, 3); combo.select(combo.data.slice(0, 3), true); expect(combo.selection).toEqual(selectedItems); combo.select([], false); expect(combo.selection).toEqual(selectedItems); selectedItems.push(combo.data[3]); combo.select([combo.data[3]], false); expect(combo.selection).toEqual(combo.data.slice(0, 4)); combo.select([], true); expect(combo.selection).toEqual([]); }); it('should emit owner on `opening` and `closing`', () => { combo = new IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, 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.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 IgxComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, 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'); let oldSelection = []; let newSelection = [combo.data[1], combo.data[5], combo.data[6]]; combo.select(newSelection); expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(1); expect(combo.selectionChanging.emit).toHaveBeenCalledWith({ oldSelection, newSelection, added: newSelection, removed: [], event: undefined, owner: combo, displayText: `${newSelection.join(', ')}`, cancel: false }); let newItem = combo.data[3]; combo.select([newItem]); oldSelection = [...newSelection]; newSelection.push(newItem); expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(2); expect(combo.selectionChanging.emit).toHaveBeenCalledWith({ oldSelection, newSelection, removed: [], added: [combo.data[3]], event: undefined, owner: combo, displayText: `${newSelection.join(', ')}`, cancel: false }); oldSelection = [...newSelection]; newSelection = [combo.data[0]]; combo.select(newSelection, true); expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(3); expect(combo.selectionChanging.emit).toHaveBeenCalledWith({ oldSelection, newSelection, removed: oldSelection, added: newSelection, event: undefined, owner: combo, displayText: `${newSelection.join(', ')}`, cancel: false }); oldSelection = [...newSelection]; newSelection = []; newItem = combo.data[0]; combo.deselect([newItem]); expect(combo.selection.length).toEqual(0); expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(4); expect(combo.selectionChanging.emit).toHaveBeenCalledWith({ oldSelection, newSelection, removed: [combo.data[0]], added: [], event: undefined, owner: combo, displayText: `${newSelection.join(', ')}`, cancel: false }); }); it('should properly emit added and removed values in change event on single value selection', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, 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: IComboSelectionChangingEventArgs = { newSelection: [combo.data[0][combo.valueKey]], oldSelection: [], added: [combo.data[0][combo.valueKey]], removed: [], event: undefined, owner: combo, displayText: `${combo.data[0][combo.displayKey]}`, cancel: false }; combo.select([combo.data[0][combo.valueKey]]); expect(selectionSpy).toHaveBeenCalledWith(expectedResults); Object.assign(expectedResults, { newSelection: [], oldSelection: [combo.data[0][combo.valueKey]], added: [], displayText: '', removed: [combo.data[0][combo.valueKey]] }); combo.deselect([combo.data[0][combo.valueKey]]); expect(selectionSpy).toHaveBeenCalledWith(expectedResults); }); it('should properly emit added and removed values in change event on multiple values selection', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = complexData; combo.valueKey = 'country'; combo.displayKey = 'city'; combo.dropdown = dropdown; spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length); let oldSelection = []; let newSelection = [combo.data[0], combo.data[1], combo.data[2]]; const selectionSpy = spyOn(combo.selectionChanging, 'emit'); const expectedResults: IComboSelectionChangingEventArgs = { newSelection: newSelection.map(e => e[combo.valueKey]), oldSelection, added: newSelection.map(e => e[combo.valueKey]), removed: [], event: undefined, owner: combo, displayText: `${newSelection.map(entry => entry[combo.displayKey]).join(', ')}`, cancel: false }; combo.select(newSelection.map(e => e[combo.valueKey])); expect(selectionSpy).toHaveBeenCalledWith(expectedResults); oldSelection = [...newSelection].map(e => e[combo.valueKey]); newSelection = [combo.data[1], combo.data[2]]; combo.deselect([combo.data[0][combo.valueKey]]); Object.assign(expectedResults, { newSelection: newSelection.map(e => e[combo.valueKey]), oldSelection, added: [], displayText: newSelection.map(e => e[combo.displayKey]).join(', '), removed: [combo.data[0][combo.valueKey]] }); oldSelection = [...newSelection].map(e => e[combo.valueKey]); newSelection = [combo.data[4], combo.data[5], combo.data[6]]; expect(selectionSpy).toHaveBeenCalledWith(expectedResults); Object.assign(expectedResults, { newSelection: newSelection.map(e => e[combo.valueKey]), oldSelection, added: newSelection.map(e => e[combo.valueKey]), displayText: newSelection.map(e => e[combo.displayKey]).join(', '), removed: oldSelection }); combo.select(newSelection.map(e => e[combo.valueKey]), true); expect(selectionSpy).toHaveBeenCalledWith(expectedResults); }); it('should handle select/deselect ALL items', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, 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, 'selectAllItems'); spyOn(combo, 'deselectAllItems'); combo.handleSelectAll({ checked: true }); expect(combo.selectAllItems).toHaveBeenCalledTimes(1); expect(combo.deselectAllItems).toHaveBeenCalledTimes(0); combo.handleSelectAll({ checked: false }); expect(combo.selectAllItems).toHaveBeenCalledTimes(1); expect(combo.deselectAllItems).toHaveBeenCalledTimes(1); }); it('should emit onSelectonChange event on select/deselect ALL items method call', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, 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'); combo.selectAllItems(true); expect(combo.selection).toEqual(data); expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(1); expect(combo.selectionChanging.emit).toHaveBeenCalledWith({ oldSelection: [], newSelection: data, added: data, removed: [], owner: combo, event: undefined, displayText: `${combo.data.join(', ')}`, cancel: false }); combo.deselectAllItems(true); expect(combo.selection).toEqual([]); expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(2); expect(combo.selectionChanging.emit).toHaveBeenCalledWith({ oldSelection: data, newSelection: [], added: [], removed: data, owner: combo, event: undefined, displayText: '', cancel: false }); }); it('should properly handle selection manipulation through selectionChanging emit', () => { const selectionService = new IgxSelectionAPIService(); combo = new IgxComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, 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 = []); // No items are initially selected expect(combo.selection).toEqual([]); // Select the first 5 items combo.select(combo.data.splice(0, 5)); // selectionChanging fires and overrides the selection to be []; expect(combo.selection).toEqual([]); }); it('should not throw error when setting data to null', () => { combo = new IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, 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 IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, 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 IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; combo.comboInput = { value: '', } as any; combo.filteringOptions.filterable = true; const matchSpy = spyOn<any>(combo, 'checkMatch').and.callThrough(); spyOn(combo.searchInputUpdate, 'emit'); 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.filteringOptions.filterable = false; combo.handleInputChange(); expect(matchSpy).toHaveBeenCalledTimes(4); expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(2); }); it('should be able to cancel searchInputUpdate', () => { combo = new IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, null, null, mockInjector); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.filteringOptions.filterable = true; combo.searchInputUpdate.subscribe((e) => { e.cancel = true; }); const matchSpy = spyOn<any>(combo, 'checkMatch').and.callThrough(); spyOn(combo.searchInputUpdate, 'emit').and.callThrough(); 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 IgxComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService, mockIconService, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['open', 'close', 'toggle']); const spyObj = jasmine.createSpyObj('event', ['stopPropagation', 'preventDefault']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); 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 IgxComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); const spyObj = jasmine.createSpyObj('event', ['stopPropagation']); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; combo.disabled = true; spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length); const item = combo.data.slice(0, 1); combo.select(item, true); combo.handleClearItems(spyObj); expect(combo.value).toEqual(item[0]); }); it('should allow canceling and overwriting of item addition', fakeAsync(() => { const selectionService = new IgxSelectionAPIService(); combo = new IgxComboComponent(elementRef, mockCdr, selectionService, mockComboService, mockIconService, null, null, mockInjector); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); const mockVirtDir = jasmine.createSpyObj('virtDir', ['scrollTo']); const mockInput = jasmine.createSpyObj('mockInput', [], { nativeElement: jasmine.createSpyObj('mockElement', ['focus']) }); spyOn(combo.addition, 'emit').and.callThrough(); spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); const subParams: { cancel: boolean; newValue: string; modify: boolean } = { cancel: false, modify: false, newValue: 'mockValue' }; const sub = combo.addition.subscribe((e) => { if (subParams.cancel) { e.cancel = true; } if (subParams.modify) { e.addedItem = subParams.newValue; } }); combo.ngOnInit(); combo.data = ['Item 1', 'Item 2', 'Item 3']; combo.dropdown = dropdown; combo.searchInput = mockInput; (combo as any).virtDir = mockVirtDir; let mockAddParams: IComboItemAdditionEvent = { cancel: false, owner: combo, addedItem: 'Item 99', newCollection: ['Item 1', 'Item 2', 'Item 3', 'Item 99'], oldCollection: ['Item 1', 'Item 2', 'Item 3'] }; // handle addition combo.searchValue = 'Item 99'; combo.addItemToCollection(); tick(); expect(combo.data.length).toEqual(4); expect(combo.addition.emit).toHaveBeenCalledWith(mockAddParams); expect(combo.addition.emit).toHaveBeenCalledTimes(1); expect(mockVirtDir.scrollTo).toHaveBeenCalledTimes(1); expect(combo.searchInput.nativeElement.focus).toHaveBeenCalledTimes(1); expect(combo.data[combo.data.length - 1]).toBe('Item 99'); expect(selectionService.get(combo.id).size).toBe(1); expect([...selectionService.get(combo.id)][0]).toBe('Item 99'); // cancel subParams.cancel = true; mockAddParams = { cancel: true, owner: combo, addedItem: 'Item 99', newCollection: ['Item 1', 'Item 2', 'Item 3', 'Item 99', 'Item 99'], oldCollection: ['Item 1', 'Item 2', 'Item 3', 'Item 99'] }; combo.searchValue = 'Item 99'; combo.addItemToCollection(); tick(); expect(combo.addition.emit).toHaveBeenCalledWith(mockAddParams); expect(combo.addition.emit).toHaveBeenCalledTimes(2); expect(mockVirtDir.scrollTo).toHaveBeenCalledTimes(1); expect(combo.searchInput.nativeElement.focus).toHaveBeenCalledTimes(1); expect(combo.data.length).toEqual(4); expect(combo.data[combo.data.length - 1]).toBe('Item 99'); expect(selectionService.get(combo.id).size).toBe(1); expect([...selectionService.get(combo.id)][0]).toBe('Item 99'); // overwrite subParams.modify = true; subParams.cancel = false; mockAddParams = { cancel: false, owner: combo, addedItem: 'mockValue', newCollection: ['Item 1', 'Item 2', 'Item 3', 'Item 99', 'Item 99'], oldCollection: ['Item 1', 'Item 2', 'Item 3', 'Item 99'] }; combo.searchValue = 'Item 99'; combo.addItemToCollection(); tick(); expect(combo.addition.emit).toHaveBeenCalledWith(mockAddParams); expect(combo.addition.emit).toHaveBeenCalledTimes(3); expect(mockVirtDir.scrollTo).toHaveBeenCalledTimes(2); expect(combo.searchInput.nativeElement.focus).toHaveBeenCalledTimes(2); expect(combo.data.length).toEqual(5); expect(combo.data[combo.data.length - 1]).toBe(subParams.newValue); expect(selectionService.get(combo.id).size).toBe(2); expect([...selectionService.get(combo.id)][1]).toBe(subParams.newValue); sub.unsubscribe(); })); }); describe('Combo feature tests: ', () => { configureTestSuite(() => { return TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, IgxComboSampleComponent, IgxComboInContainerTestComponent, IgxComboRemoteDataComponent, ComboModelBindingComponent, IgxComboBindingDataAfterInitComponent, IgxComboFormComponent, IgxComboInTemplatedFormComponent ] }); }); describe('Initialization and rendering tests: ', () => { beforeEach(() => { fixture = TestBed.createComponent(IgxComboSampleComponent); 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.searchInput).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.searchPlaceholder).toEqual('Enter a Search Term'); expect(combo.filteringOptions.filterable).toEqual(true); 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(CSS_CLASS_COMBO); 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-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 searchInput = fixture.debugElement.query(By.css(CSS_CLASS_SEARCHINPUT)); expect(searchInput.nativeElement.getAttribute('role')).toEqual('searchbox'); expect(searchInput.nativeElement.getAttribute('aria-label')).toEqual('search'); expect(searchInput.nativeElement.getAttribute('aria-autocomplete')).toEqual('list'); const list = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`)); expect(list.nativeElement.getAttribute('aria-multiselectable')).toEqual('true'); expect(list.nativeElement.getAttribute('aria-activedescendant')).toEqual(''); 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'); expect(combo.searchPlaceholder).toEqual('Enter a Search Term'); expect(combo.searchInput.nativeElement.placeholder).toEqual('Enter a Search Term'); combo.searchPlaceholder = 'Filter'; fixture.detectChanges(); expect(combo.searchPlaceholder).toEqual('Filter'); expect(combo.searchInput.nativeElement.placeholder).toEqual('Filter'); 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 grouped items properly', (done) => { let dropdownContainer; let dropdownItems; let scrollIndex = 0; const headers: Array<string> = Array.from(new Set(combo.data.map(item => item.region))); combo.toggle(); fixture.detectChanges(); const checkGroupedItemsClass = () => { fixture.detectChanges(); dropdownContainer = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTAINER}`)).nativeElement; dropdownItems = dropdownContainer.children; Array.from(dropdownItems).forEach((item) => { const itemElement = item as HTMLElement; const itemText = itemElement.innerText.toString();