UNPKG

igniteui-angular-sovn

Version:

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

1,094 lines (920 loc) 138 kB
import { Component, ViewChild, DebugElement, OnInit, ElementRef } from '@angular/core'; import { NgFor, NgIf, NgStyle } from '@angular/common'; import { TestBed, tick, fakeAsync, waitForAsync, discardPeriodicTasks } from '@angular/core/testing'; import { FormsModule, UntypedFormGroup, UntypedFormBuilder, UntypedFormControl, Validators, ReactiveFormsModule, NgForm, NgControl } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { IgxDropDownItemComponent, ISelectionEventArgs } from '../drop-down/public_api'; import { IgxHintDirective, IgxLabelDirective, IgxPrefixDirective, IgxSuffixDirective } from '../input-group/public_api'; import { IgxSelectComponent, IgxSelectFooterDirective, IgxSelectHeaderDirective } from './select.component'; import { IgxSelectItemComponent } from './select-item.component'; import { configureTestSuite } from '../test-utils/configure-suite'; import { HorizontalAlignment, VerticalAlignment, ConnectedPositioningStrategy, AbsoluteScrollStrategy } from '../services/public_api'; import { addScrollDivToElement } from '../services/overlay/overlay.spec'; import { UIInteractions } from '../test-utils/ui-interactions.spec'; import { IgxButtonDirective } from '../directives/button/button.directive'; import { IgxIconComponent } from '../icon/icon.component'; import { IgxInputState } from './../directives/input/input.directive'; import { IgxSelectGroupComponent } from './select-group.component'; import { IgxDropDownItemBaseDirective } from '../drop-down/drop-down-item.base'; const CSS_CLASS_INPUT_GROUP = 'igx-input-group'; const CSS_CLASS_INPUT = 'igx-input-group__input'; const CSS_CLASS_TOGGLE_BUTTON = 'igx-icon'; const CSS_CLASS_DROPDOWN_LIST_SCROLL = 'igx-drop-down__list-scroll'; const CSS_CLASS_DROPDOWN_LIST = 'igx-drop-down__list'; const CSS_CLASS_DROPDOWN_SELECT_HEADER = 'igx-drop-down__select-header'; const CSS_CLASS_DROPDOWN_SELECT_FOOTER = 'igx-drop-down__select-footer'; const CSS_CLASS_DROPDOWN_LIST_ITEM = 'igx-drop-down__item'; const CSS_CLASS_SELECTED_ITEM = 'igx-drop-down__item--selected'; const CSS_CLASS_DISABLED_ITEM = 'igx-drop-down__item--disabled'; const CSS_CLASS_FOCUSED_ITEM = 'igx-drop-down__item--focused'; const CSS_CLASS_INPUT_GROUP_BOX = 'igx-input-group--box'; const CSS_CLASS_INPUT_GROUP_REQUIRED = 'igx-input-group--required'; const CSS_CLASS_INPUT_GROUP_INVALID = 'igx-input-group--invalid'; const CSS_CLASS_INPUT_GROUP_LABEL = 'igx-input-group__label'; const CSS_CLASS_INPUT_GROUP_BORDER = 'igx-input-group--border'; const CSS_CLASS_INPUT_GROUP_COMFORTABLE = 'igx-input-group--comfortable'; const CSS_CLASS_INPUT_GROUP_COSY = 'igx-input-group--cosy'; const CSS_CLASS_INPUT_GROUP_COMPACT = 'igx-input-group--compact'; const arrowDownKeyEvent = new KeyboardEvent('keydown', { key: 'ArrowDown' }); const arrowUpKeyEvent = new KeyboardEvent('keydown', { key: 'ArrowUp' }); const altArrowDownKeyEvent = new KeyboardEvent('keydown', { altKey: true, key: 'ArrowDown' }); const altArrowUpKeyEvent = new KeyboardEvent('keydown', { altKey: true, key: 'ArrowUp' }); const spaceKeyEvent = new KeyboardEvent('keydown', { key: 'Space' }); const escapeKeyEvent = new KeyboardEvent('keydown', { key: 'Escape' }); const enterKeyEvent = new KeyboardEvent('keydown', { key: 'Enter' }); const endKeyEvent = new KeyboardEvent('keydown', { key: 'End' }); const homeKeyEvent = new KeyboardEvent('keydown', { key: 'Home' }); const tabKeyEvent = new KeyboardEvent('keydown', { key: 'Tab' }); const shiftTabKeysEvent = new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true }); describe('igxSelect', () => { let fixture; let select: IgxSelectComponent; let inputElement: DebugElement; let selectList: DebugElement; let selectListWrapper: DebugElement; const verifyFocusedItem = focusedItemIndex => { const focusedItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_FOCUSED_ITEM)); expect(focusedItems.length).toEqual(1); expect(selectList.children[focusedItemIndex].nativeElement.classList.contains(CSS_CLASS_FOCUSED_ITEM)).toBeTruthy(); expect(select.focusedItem).toBe(select.items[focusedItemIndex]); expect(select.items[focusedItemIndex].focused).toBeTruthy(); }; const verifySelectedItem = itemIndex => { expect(select.input.value).toEqual(select.items[itemIndex].value); expect(select.value).toEqual(select.items[itemIndex].value); const selectedItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_SELECTED_ITEM)); expect(selectedItems.length).toEqual(1); expect(selectList.children[itemIndex].nativeElement.classList.contains(CSS_CLASS_SELECTED_ITEM)).toBeTruthy(); expect(select.selectedItem).toBe(select.items[itemIndex] as IgxSelectItemComponent); expect(select.items[itemIndex].selected).toBeTruthy(); }; const verifyOpenCloseEvents = (openEventCounter = 0, closeEventCounter = 0, toggleCallCounter = 0) => { expect(select.opening.emit).toHaveBeenCalledTimes(openEventCounter); expect(select.opened.emit).toHaveBeenCalledTimes(openEventCounter); expect(select.open).toHaveBeenCalledTimes(openEventCounter); expect(select.closing.emit).toHaveBeenCalledTimes(closeEventCounter); expect(select.closed.emit).toHaveBeenCalledTimes(closeEventCounter); expect(select.close).toHaveBeenCalledTimes(closeEventCounter); expect(select.toggle).toHaveBeenCalledTimes(toggleCallCounter); }; configureTestSuite(); beforeAll(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, IgxSelectSimpleComponent, IgxSelectGroupsComponent, IgxSelectMiddleComponent, IgxSelectTopComponent, IgxSelectBottomComponent, IgxSelectAffixComponent, IgxSelectReactiveFormComponent, IgxSelectTemplateFormComponent, IgxSelectHeaderFooterComponent, IgxSelectCDRComponent ] }).compileComponents(); })); beforeEach(() => { UIInteractions.clearOverlay(); }); afterAll(() => { UIInteractions.clearOverlay(); }); describe('General tests: ', () => { beforeEach(() => { fixture = TestBed.createComponent(IgxSelectSimpleComponent); fixture.detectChanges(); select = fixture.componentInstance.select; inputElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT)); selectList = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWN_LIST_SCROLL)); selectListWrapper = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWN_LIST)); }); it('should initialize the select component properly', () => { const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP)); expect(fixture.componentInstance).toBeDefined(); expect(select).toBeDefined(); expect(inputGroup).toBeTruthy(); expect(select.placeholder).toBeDefined(); expect(select.value).toBeNull(); expect(select.disabled).toBeFalsy(); expect(select.overlaySettings).toBeUndefined(); expect(select.collapsed).toBeDefined(); expect(select.collapsed).toBeTruthy(); select.open(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); }); it('should properly accept input properties', () => { expect(select.width).toEqual('300px'); expect(select.height).toEqual('200px'); expect(select.maxHeight).toEqual('256px'); expect(select.disabled).toBeFalsy(); expect(select.placeholder).toEqual('Choose a city'); expect(select.value).toBeNull(); // Default type will be set - currently 'line' expect(select.type).toEqual('line'); expect(select.displayDensity).toEqual('comfortable'); expect(select.overlaySettings).toBeUndefined(); expect(select.items).toBeDefined(); // Reset input values select.width = '500px'; expect(select.width).toEqual('500px'); select.height = '450px'; expect(select.height).toEqual('450px'); select.maxHeight = '300px'; expect(select.maxHeight).toEqual('300px'); select.placeholder = 'Your home town'; expect(select.placeholder).toEqual('Your home town'); select.value = 'Hamburg'; expect(select.value).toEqual('Hamburg'); select.type = 'box'; expect(select.type).toEqual('box'); select.displayDensity = 'compact'; expect(select.displayDensity).toEqual('compact'); select.items[3].disabled = true; expect(select.items[3].disabled).toBeTruthy(); select.items[10].selected = true; expect(select.items[10].selected).toBeTruthy(); select.items[11].value = 'Milano'; expect(select.items[11].value).toEqual('Milano'); const positionSettings = { target: select.inputGroup.element.nativeElement, horizontalDirection: HorizontalAlignment.Right, verticalDirection: VerticalAlignment.Bottom, horizontalStartPoint: HorizontalAlignment.Left, verticalStartPoint: VerticalAlignment.Bottom }; const customOverlaySettings = { modal: true, closeOnOutsideClick: false, positionStrategy: new ConnectedPositioningStrategy( positionSettings ), scrollStrategy: new AbsoluteScrollStrategy() }; select.overlaySettings = customOverlaySettings; expect(select.overlaySettings).toBe(customOverlaySettings); expect(select.collapsed).toBeTruthy(); select.toggle(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); select.disabled = true; expect(select.disabled).toBeTruthy(); }); it('should open dropdown on input click', () => { const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP)); expect(select.collapsed).toBeTruthy(); inputGroup.nativeElement.click(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); }); it('should close dropdown on item click', fakeAsync(() => { const selectedItemEl = selectList.children[2]; expect(select.collapsed).toBeTruthy(); select.toggle(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); selectedItemEl.nativeElement.click(); tick(); fixture.detectChanges(); expect(select.collapsed).toBeTruthy(); })); it('should close dropdown on clicking selected item', fakeAsync(() => { spyOn(select.selectionChanging, 'emit'); select.items[1].selected = true; select.open(); fixture.detectChanges(); const selectedItemEl = selectList.children[1]; expect(select.collapsed).toBeFalsy(); selectedItemEl.nativeElement.click(); tick(); fixture.detectChanges(); expect(select.collapsed).toBeTruthy(); select.open(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); selectedItemEl.nativeElement.click(); tick(); fixture.detectChanges(); expect(select.collapsed).toBeTruthy(); expect(select.selectionChanging.emit).toHaveBeenCalledTimes(1); })); it('should toggle dropdown on toggle button click', fakeAsync(() => { const toggleBtn = fixture.debugElement.query(By.css('.' + CSS_CLASS_TOGGLE_BUTTON)); expect(select.collapsed).toBeTruthy(); toggleBtn.nativeElement.click(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); toggleBtn.nativeElement.click(); tick(); fixture.detectChanges(); expect(select.collapsed).toBeTruthy(); })); it('should toggle dropdown using API methods', fakeAsync(() => { select.items[0].selected = true; expect(select.collapsed).toBeTruthy(); select.open(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); select.close(); tick(); fixture.detectChanges(); expect(select.collapsed).toBeTruthy(); select.toggle(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); select.toggle(); tick(); fixture.detectChanges(); expect(select.collapsed).toBeTruthy(); })); it('should not display dropdown list when no select items', fakeAsync(() => { fixture.componentInstance.items = []; fixture.detectChanges(); const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP)); inputGroup.nativeElement.click(); tick(); fixture.detectChanges(); expect(select.collapsed).toBeTruthy(); expect(selectListWrapper.nativeElement.classList.contains('igx-toggle--hidden')).toBeTruthy(); })); it('should properly emit opening/closing events on input click', fakeAsync(() => { const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP)); expect(select).toBeTruthy(); spyOn(select.opening, 'emit'); spyOn(select.opened, 'emit'); spyOn(select.closing, 'emit'); spyOn(select.closed, 'emit'); spyOn(select, 'toggle').and.callThrough(); spyOn(select, 'open').and.callThrough(); spyOn(select, 'close').and.callThrough(); inputGroup.nativeElement.click(); tick(); fixture.detectChanges(); verifyOpenCloseEvents(1, 0, 1); inputGroup.nativeElement.click(); tick(); fixture.detectChanges(); verifyOpenCloseEvents(1, 1, 2); select.disabled = true; fixture.detectChanges(); inputGroup.nativeElement.click(); tick(); fixture.detectChanges(); // No additional calls, because select is disabled expect(select.closing.emit).toHaveBeenCalledTimes(1); expect(select.closed.emit).toHaveBeenCalledTimes(1); expect(select.opening.emit).toHaveBeenCalledTimes(1); expect(select.opened.emit).toHaveBeenCalledTimes(1); })); it('should properly emit closing events on item click', fakeAsync(() => { const selectedItemEl = selectList.children[2]; select.toggle(); tick(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); spyOn(select.closing, 'emit'); spyOn(select.closed, 'emit'); selectedItemEl.nativeElement.click(); tick(); fixture.detectChanges(); expect(select.closing.emit).toHaveBeenCalledTimes(1); expect(select.closed.emit).toHaveBeenCalledTimes(1); })); it('should properly emit opening/closing events on toggle button click', fakeAsync(() => { const toggleBtn = fixture.debugElement.query(By.css('.' + CSS_CLASS_TOGGLE_BUTTON)); expect(select).toBeTruthy(); spyOn(select.opening, 'emit'); spyOn(select.opened, 'emit'); spyOn(select.closing, 'emit'); spyOn(select.closed, 'emit'); spyOn(select, 'toggle').and.callThrough(); spyOn(select, 'open').and.callThrough(); spyOn(select, 'close').and.callThrough(); toggleBtn.nativeElement.click(); tick(); fixture.detectChanges(); verifyOpenCloseEvents(1, 0, 1); toggleBtn.nativeElement.click(); tick(); fixture.detectChanges(); verifyOpenCloseEvents(1, 1, 2); })); it('should emit closing events on input blur when closeOnOutsideClick: true (default value)', fakeAsync(() => { const dummyInput = fixture.componentInstance.dummyInput.nativeElement; spyOn(select.closing, 'emit'); spyOn(select.closed, 'emit'); expect(select).toBeDefined(); select.toggle(); tick(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); dummyInput.focus(); dummyInput.click(); tick(); fixture.detectChanges(); expect(dummyInput).toEqual(document.activeElement); expect(select.collapsed).toBeTruthy(); expect(select.closing.emit).toHaveBeenCalledTimes(1); expect(select.closed.emit).toHaveBeenCalledTimes(1); })); it('should NOT emit closing events on input blur when closeOnOutsideClick: false', fakeAsync(() => { const dummyInput = fixture.componentInstance.dummyInput.nativeElement; spyOn(select.closing, 'emit'); spyOn(select.closed, 'emit'); const customOverlaySettings = { closeOnOutsideClick: false }; select.overlaySettings = customOverlaySettings; expect(select.overlaySettings).toBe(customOverlaySettings); expect(select.collapsed).toBeTruthy(); fixture.detectChanges(); expect(select).toBeDefined(); select.toggle(); tick(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); dummyInput.focus(); dummyInput.click(); tick(); fixture.detectChanges(); expect(dummyInput).toEqual(document.activeElement); expect(select.collapsed).toBeFalsy(); expect(select.closing.emit).toHaveBeenCalledTimes(0); expect(select.closed.emit).toHaveBeenCalledTimes(0); })); it('should render aria attributes properly', fakeAsync(() => { const dropdownListElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWN_LIST_SCROLL)); const dropdownWrapper = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWN_LIST)); const toggleBtn = fixture.debugElement.query(By.css('.' + CSS_CLASS_TOGGLE_BUTTON)); const labelID = fixture.componentInstance.label1.nativeElement.getAttribute('id'); expect(inputElement.nativeElement.getAttribute('role')).toEqual('combobox'); expect(inputElement.nativeElement.getAttribute('aria-haspopup')).toEqual('listbox'); expect(inputElement.nativeElement.getAttribute('aria-labelledby')).toEqual(labelID); expect(dropdownListElement.nativeElement.getAttribute('aria-labelledby')).toEqual(labelID); expect(inputElement.nativeElement.getAttribute('aria-owns')).toEqual(select.listId); expect(inputElement.nativeElement.getAttribute('aria-expanded')).toEqual('false'); expect(toggleBtn.nativeElement.getAttribute('aria-hidden')).toEqual('true'); expect(dropdownListElement.nativeElement.getAttribute('role')).toEqual('listbox'); expect(dropdownWrapper.nativeElement.getAttribute('aria-hidden')).toEqual('true'); select.toggle(); tick(); fixture.detectChanges(); expect(inputElement.nativeElement.getAttribute('aria-expanded')).toEqual('true'); expect(dropdownWrapper.nativeElement.getAttribute('aria-hidden')).toEqual('false'); select.toggle(); tick(); fixture.detectChanges(); expect(inputElement.nativeElement.getAttribute('aria-expanded')).toEqual('false'); expect(dropdownWrapper.nativeElement.getAttribute('aria-hidden')).toEqual('true'); })); it('should render aria attributes on dropdown items properly', () => { const selectItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_DROPDOWN_LIST_ITEM)); selectItems.forEach(item => { expect(item.nativeElement.getAttribute('role')).toEqual('option'); expect(item.nativeElement.getAttribute('aria-selected')).toEqual('false'); expect(item.nativeElement.getAttribute('aria-disabled')).toEqual('false'); }); const selectedItem = select.items[2]; const disabledItem = select.items[8]; selectedItem.selected = true; disabledItem.disabled = true; fixture.detectChanges(); expect(selectItems[selectedItem.index].nativeElement.getAttribute('aria-selected')).toEqual('true'); expect(selectItems[disabledItem.index].nativeElement.getAttribute('aria-disabled')).toEqual('true'); }); it('should render input type properly', fakeAsync(() => { const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP)); // Default type will be set - currently 'line' expect(select.type).toEqual('line'); expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_BOX)).toBeFalsy(); expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_BORDER)).toBeFalsy(); select.type = 'box'; fixture.detectChanges(); tick(); fixture.detectChanges(); expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_BOX)).toBeTruthy(); select.type = 'border'; fixture.detectChanges(); tick(); expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_BORDER)).toBeTruthy(); })); it('should render display density properly', () => { const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP)); // Default display density is 'comfortable' expect(select.displayDensity).toEqual('comfortable'); expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_COMFORTABLE)).toBeTruthy(); select.displayDensity = 'cosy'; fixture.detectChanges(); expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_COSY)).toBeTruthy(); select.displayDensity = 'compact'; fixture.detectChanges(); expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_COMPACT)).toBeTruthy(); }); it('should close dropdown on blur when closeOnOutsideClick: true (default value)', fakeAsync(() => { const dummyInput = fixture.componentInstance.dummyInput.nativeElement; expect(select.collapsed).toBeTruthy(); select.toggle(); fixture.detectChanges(); expect(select.collapsed).toBeFalsy(); dummyInput.focus(); dummyInput.click(); tick(); fixture.detectChanges(); expect(dummyInput).toEqual(document.activeElement); expect(select.collapsed).toBeTruthy(); })); it('should NOT close dropdown on blur when closeOnOutsideClick: false', fakeAsync(() => { const dummyInput = fixture.componentInstance.dummyInput.nativeElement; const customOverlaySettings = { closeOnOutsideClick: false }; select.overlaySettings = customOverlaySettings; expect(select.overlaySettings).toBe(customOverlaySettings); expect(select.collapsed).toBeTruthy(); fixture.detectChanges(); select.toggle(); expect(select.collapsed).toBeFalsy(); dummyInput.focus(); dummyInput.click(); tick(); fixture.detectChanges(); expect(dummyInput).toEqual(document.activeElement); expect(select.collapsed).toBeFalsy(); })); }); describe('Form tests: ', () => { it('Should properly initialize when used as a reactive form control - with validators', fakeAsync(() => { const fix = TestBed.createComponent(IgxSelectReactiveFormComponent); const inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED)); fix.detectChanges(); const selectComp = fix.componentInstance.select; const selectFormReference = fix.componentInstance.reactiveForm.controls.optionsSelect; expect(selectFormReference).toBeDefined(); expect(selectComp).toBeDefined(); expect(selectComp.selectedItem).toBeUndefined(); expect(selectComp.value).toEqual(''); expect(inputGroupIsRequiredClass).toBeDefined(); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.toggle(); expect(selectComp.collapsed).toEqual(false); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.onBlur(); expect(selectComp.input.valid).toEqual(IgxInputState.INVALID); selectComp.selectItem(selectComp.items[4]); expect(selectComp.value).toEqual('Option 5'); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.onBlur(); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.value = 'Option 1'; expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); })); it('Should properly initialize when used as a reactive form control - without initial validators', fakeAsync(() => { const fix = TestBed.createComponent(IgxSelectReactiveFormComponent); fix.detectChanges(); // 1) check if label's --required class and its asterisk are applied const dom = fix.debugElement; const selectComp = fix.componentInstance.select; const formGroup: UntypedFormGroup = fix.componentInstance.reactiveForm; let inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED)); let inputGroupInvalidClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_INVALID)); // interaction test - expect actual asterisk // The only way to get a pseudo elements like :before OR :after is to use getComputedStyle(element [, pseudoElt]), // as these are not in the actual DOM let asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content; expect(asterisk).toBe('"*"'); expect(inputGroupIsRequiredClass).toBeDefined(); expect(inputGroupIsRequiredClass).not.toBeNull(); // 2) check that input group's --invalid class is NOT applied expect(inputGroupInvalidClass).toBeNull(); // interaction test - markAsTouched + open&close so the --invalid and --required classes are applied fix.debugElement.componentInstance.markAsTouched(); const inputGroup = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP)); inputGroup.nativeElement.click(); const toggleBtn = fix.debugElement.query(By.css('.' + CSS_CLASS_TOGGLE_BUTTON)); toggleBtn.nativeElement.click(); tick(); fix.detectChanges(); expect(selectComp.collapsed).toEqual(true); inputGroupInvalidClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_INVALID)); expect(inputGroupInvalidClass).not.toBeNull(); expect(inputGroupInvalidClass).not.toBeUndefined(); inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED)); expect(inputGroupIsRequiredClass).not.toBeNull(); expect(inputGroupIsRequiredClass).not.toBeUndefined(); // 3) Check if the input group's --invalid and --required classes are removed when validator is dynamically cleared fix.componentInstance.removeValidators(formGroup); fix.detectChanges(); tick(); inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED)); const selectFormReference = fix.componentInstance.reactiveForm.controls.optionsSelect; // interaction test - expect no asterisk asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content; expect(selectFormReference).toBeDefined(); expect(selectComp).toBeDefined(); expect(selectComp.selectedItem).toBeUndefined(); expect(selectComp.value).toEqual(''); expect(inputGroupIsRequiredClass).toBeNull(); expect(asterisk).toBe('none'); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.onBlur(); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.selectItem(selectComp.items[4]); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); document.documentElement.dispatchEvent(new Event('click')); expect(selectComp.collapsed).toEqual(true); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.onBlur(); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); // Re-add all Validators fix.componentInstance.addValidators(formGroup); fix.detectChanges(); inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED)); expect(inputGroupIsRequiredClass).toBeDefined(); expect(inputGroupIsRequiredClass).not.toBeNull(); expect(inputGroupIsRequiredClass).not.toBeUndefined(); // interaction test - expect actual asterisk asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content; expect(asterisk).toBe('"*"'); // 4) Should NOT remove asterisk, when remove validators on igxSelect with required HTML attribute set(edge case) // set required HTML attribute inputGroup.parent.nativeElement.setAttribute('required', ''); // Re-add all Validators fix.componentInstance.addValidators(formGroup); fix.detectChanges(); // update and clear validators fix.componentInstance.removeValidators(formGroup); fix.detectChanges(); tick(); // expect asterisk asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content; expect(asterisk).toBe('"*"'); inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED)); expect(inputGroupIsRequiredClass).toBeDefined(); expect(inputGroupIsRequiredClass).not.toBeNull(); expect(inputGroupIsRequiredClass).not.toBeUndefined(); })); it('should update validity state when programmatically setting errors on reactive form controls', fakeAsync(() => { const fix = TestBed.createComponent(IgxSelectReactiveFormComponent); fix.detectChanges(); const selectComp = fix.componentInstance.select; const formGroup: UntypedFormGroup = fix.componentInstance.reactiveForm; // the form control has validators formGroup.markAllAsTouched(); formGroup.get('optionsSelect').setErrors({ error: true }); fix.detectChanges(); expect(selectComp.input.valid).toBe(IgxInputState.INVALID); expect((selectComp as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_INVALID)).toBe(true); expect((selectComp as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(true); // remove the validators and set errors (fix.componentInstance as IgxSelectReactiveFormComponent).removeValidators(formGroup); formGroup.markAsUntouched(); fix.detectChanges(); formGroup.markAllAsTouched(); formGroup.get('optionsSelect').setErrors({ error: true }); fix.detectChanges(); // no validator, but there is a set error expect(selectComp.input.valid).toBe(IgxInputState.INVALID); expect((selectComp as any).inputGroup.element.nativeElement).toHaveClass(CSS_CLASS_INPUT_GROUP_INVALID); expect((selectComp as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(false); })); it('Should properly initialize when used as a form control - with initial validators', fakeAsync(() => { const fix = TestBed.createComponent(IgxSelectTemplateFormComponent); let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED)); fix.detectChanges(); const selectComp = fix.componentInstance.select; const selectFormReference = fix.componentInstance.ngForm.form; expect(selectFormReference).toBeDefined(); expect(selectComp).toBeDefined(); tick(); fix.detectChanges(); expect(selectComp.selectedItem).toBeUndefined(); expect(selectComp.value).toBeNull(); expect(inputGroupIsRequiredClass).toBeDefined(); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.toggle(); expect(selectComp.collapsed).toEqual(false); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.onBlur(); expect(selectComp.input.valid).toEqual(IgxInputState.INVALID); selectComp.selectItem(selectComp.items[4]); expect(selectComp.value).toEqual('Option 5'); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.onBlur(); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.value = 'Option 1'; expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); fix.componentInstance.isRequired = false; fix.detectChanges(); inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED)); expect(inputGroupIsRequiredClass).toBeNull(); })); it('Should properly initialize when used as a form control - without initial validators', fakeAsync(() => { const fix = TestBed.createComponent(IgxSelectTemplateFormComponent); let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED)); fix.detectChanges(); const selectComp = fix.componentInstance.select; const selectFormReference = fix.componentInstance.ngForm.form; selectFormReference.clearValidators(); expect(selectFormReference).toBeDefined(); expect(selectComp).toBeDefined(); expect(selectComp.selectedItem).toBeUndefined(); expect(selectComp.value).toBeUndefined(); expect(inputGroupIsRequiredClass).toBeNull(); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.onBlur(); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.selectItem(selectComp.items[4]); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); document.documentElement.dispatchEvent(new Event('click')); expect(selectComp.collapsed).toEqual(true); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); selectComp.onBlur(); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); fix.componentInstance.isRequired = true; fix.detectChanges(); inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED)); expect(inputGroupIsRequiredClass).toBeDefined(); })); it('Should have correctly bound focus and blur handlers', () => { const fix = TestBed.createComponent(IgxSelectTemplateFormComponent); fix.detectChanges(); select = fix.componentInstance.select; const input = fix.debugElement.query(By.css(`.${CSS_CLASS_INPUT}`)); spyOn(select, 'onFocus'); spyOn(select, 'onBlur'); input.triggerEventHandler('focus', {}); expect(select.onFocus).toHaveBeenCalled(); expect(select.onFocus).toHaveBeenCalledWith(); input.triggerEventHandler('blur', {}); expect(select.onBlur).toHaveBeenCalled(); expect(select.onFocus).toHaveBeenCalledWith(); }); // Bug #6025 Select does not disable in reactive form it('Should disable when form is disabled', fakeAsync(() => { const fix = TestBed.createComponent(IgxSelectReactiveFormComponent); fix.detectChanges(); const formGroup: UntypedFormGroup = fix.componentInstance.reactiveForm; const selectComp = fix.componentInstance.select; const inputGroup = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP)); inputGroup.nativeElement.click(); tick(); fix.detectChanges(); expect(selectComp.collapsed).toBeFalsy(); selectComp.close(); fix.detectChanges(); formGroup.disable(); tick(); fix.detectChanges(); inputGroup.nativeElement.click(); tick(); fix.detectChanges(); expect(selectComp.collapsed).toBeTruthy(); })); it('should set validity to initial when the form is reset', fakeAsync(() => { const fix = TestBed.createComponent(IgxSelectTemplateFormComponent); fix.detectChanges(); tick(); const selectComp = fix.componentInstance.select; selectComp.onBlur(); expect(selectComp.input.valid).toEqual(IgxInputState.INVALID); fix.componentInstance.ngForm.resetForm(); tick(); expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); })); }); describe('Selection tests: ', () => { describe('Using simple select component', () => { beforeEach(() => { fixture = TestBed.createComponent(IgxSelectSimpleComponent); select = fixture.componentInstance.select; fixture.detectChanges(); inputElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT)); selectList = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWN_LIST_SCROLL)); }); it('should select item with mouse click', fakeAsync(() => { let selectedItemIndex = 5; select.toggle(); tick(); fixture.detectChanges(); selectList.children[selectedItemIndex].nativeElement.click(); tick(); fixture.detectChanges(); verifySelectedItem(selectedItemIndex); selectedItemIndex = 15; select.toggle(); tick(); fixture.detectChanges(); selectList.children[selectedItemIndex].nativeElement.click(); tick(); fixture.detectChanges(); verifySelectedItem(selectedItemIndex); })); it('should select item with API selectItem() method', fakeAsync(() => { let selectedItemIndex = 15; select.selectItem(select.items[selectedItemIndex]); tick(); fixture.detectChanges(); verifySelectedItem(selectedItemIndex); selectedItemIndex = 1; select.selectItem(select.items[selectedItemIndex]); tick(); fixture.detectChanges(); verifySelectedItem(selectedItemIndex); })); it('should select item on setting value property', fakeAsync(() => { let selectedItemIndex = 7; select.value = select.items[selectedItemIndex].value.toString(); fixture.detectChanges(); tick(); verifySelectedItem(selectedItemIndex); selectedItemIndex = 12; select.value = select.items[selectedItemIndex].value.toString(); fixture.detectChanges(); tick(); verifySelectedItem(selectedItemIndex); })); it('should select item on setting item\'s selected property', () => { let selectedItemIndex = 9; select.items[selectedItemIndex].selected = true; fixture.detectChanges(); verifySelectedItem(selectedItemIndex); selectedItemIndex = 14; select.items[selectedItemIndex].selected = true; fixture.detectChanges(); verifySelectedItem(selectedItemIndex); }); it('should select item with ENTER/SPACE keys', fakeAsync(() => { let selectedItemIndex = 2; select.toggle(); tick(); fixture.detectChanges(); inputElement.triggerEventHandler('keydown', arrowDownKeyEvent); inputElement.triggerEventHandler('keydown', arrowDownKeyEvent); inputElement.triggerEventHandler('keydown', spaceKeyEvent); tick(); fixture.detectChanges(); verifySelectedItem(selectedItemIndex); selectedItemIndex = 4; select.toggle(); tick(); fixture.detectChanges(); inputElement.triggerEventHandler('keydown', arrowDownKeyEvent); inputElement.triggerEventHandler('keydown', arrowDownKeyEvent); inputElement.triggerEventHandler('keydown', enterKeyEvent); tick(); fixture.detectChanges(); verifySelectedItem(selectedItemIndex); })); it('should allow single selection only', fakeAsync(() => { let selectedItemIndex = 5; select.toggle(); tick(); fixture.detectChanges(); selectList.children[selectedItemIndex].nativeElement.click(); tick(); fixture.detectChanges(); verifySelectedItem(selectedItemIndex); selectedItemIndex = 15; select.selectItem(select.items[selectedItemIndex]); tick(); fixture.detectChanges(); verifySelectedItem(selectedItemIndex); selectedItemIndex = 8; select.value = select.items[selectedItemIndex].value.toString(); fixture.detectChanges(); tick(); verifySelectedItem(selectedItemIndex); })); it('should clear selection when value property does not match any item', fakeAsync(() => { const selectedItemIndex = 5; select.value = select.items[selectedItemIndex].value.toString(); fixture.detectChanges(); tick(); verifySelectedItem(selectedItemIndex); select.value = 'Ghost city'; tick(); fixture.detectChanges(); const selectedItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_SELECTED_ITEM)); expect(selectedItems.length).toEqual(0); expect(select.selectedItem).toBeUndefined(); expect(select.input.value).toEqual(''); })); it('should focus first item in dropdown if there is not selected item', fakeAsync(() => { const focusedItemIndex = 0; const selectedItemIndex = 8; select.toggle(); tick(); fixture.detectChanges(); verifyFocusedItem(focusedItemIndex); selectList.children[selectedItemIndex].nativeElement.click(); tick(); fixture.detectChanges(); verifySelectedItem(selectedItemIndex); expect(select.items[focusedItemIndex].focused).toBeFalsy(); // Unselect selected item select.value = ''; fixture.detectChanges(); select.toggle(); tick(); fixture.detectChanges(); verifyFocusedItem(focusedItemIndex); })); it('should populate the input box with the selected item value', fakeAsync(() => { let selectedItemIndex = 5; let selectedItemValue = select.items[selectedItemIndex].value; const checkInputValue = () => { expect(select.selectedItem.value).toEqual(selectedItemValue); expect(select.value).toEqual(selectedItemValue); expect(inputElement.nativeElement.value.toString().trim()).toEqual(selectedItemValue); }; // There is not a selected item initially const selectedItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_SELECTED_ITEM)); expect(selectedItems.length).toEqual(0); expect(select.value).toBeNull(); expect(select.input.value).toEqual(''); expect(inputElement.nativeElement.value).toEqual(''); // Select item - mouse click select.toggle(); tick(); fixture.detectChanges(); selectList.children[selectedItemIndex].nativeElement.click(); tick(); fixture.detectChanges(); checkInputValue(); // Select item - selectItem method selectedItemIndex = 0; selectedItemValue = select.items[selectedItemIndex].value; select.selectItem(select.items[selectedItemIndex]); tick(); fixture.detectChanges(); select.toggle(); tick(); fixture.detectChanges(); checkInputValue(); // Select item - item selected property selectedItemIndex = 12; selectedItemValue = select.items[selectedItemIndex].value; select.items[selectedItemIndex].selected = true; fixture.detectChanges(); tick(); fixture.detectChanges(); checkInputValue(); // Select item - value property selectedItemIndex = 8; selectedItemValue = select.items[selectedItemIndex].value; select.value = select.items[selectedItemIndex].value.toString(); fixture.detectChanges(); tick(); fixture.detectChanges(); checkInputValue(); })); it('should populate the input with the selected item text', fakeAsync(() => { let selectedItemIndex = 0; const checkInputValue = () => { expect(select.selectedItem.text).toEqual(select.input.value); expect(inputElement.nativeElement.value.toString().trim()).toEqual(select.selectedItem.text); }; // There is not a selected item initially const selectedItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_SELECTED_ITEM)); expect(selectedItems.length).toEqual(0); expect(select.value).toBeNull(); expect(select.input.value).toEqual(''); expect(inputElement.nativeElement.value).toEqual(''); // Select item - mouse click select.toggle(); tick(); fixture.detectChanges(); selectList.children[selectedItemIndex].nativeElement.click(); tick(); fixture.detectChanges(); checkInputValue(); // Select item - selectItem method selectedItemIndex = 1; select.selectItem(select.items[selectedItemIndex]); tick(); fixture.detectChanges(); select.toggle(); tick();