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
text/typescript
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