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
text/typescript
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();