/*
* © 2017 Stratio Big Data Inc., Sucursal en España.
*
* This software is licensed under the Apache License, Version 2.0.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the terms of the License for more details.
*
* SPDX-License-Identifier: Apache-2.0.
*/
import { ChangeDetectionStrategy, Component, DebugElement, NO_ERRORS_SCHEMA, ViewChild } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { StDropdownMenuComponent } from './st-dropdown-menu.component';
import { StPopPlacement } from '../st-pop/st-pop.model';
import { StDropdownMenuModule } from './st-dropdown-menu.module';
import { ARROW_KEY_CODE, StDropDownMenuGroup, StDropDownMenuItem, StDropDownVisualMode } from './st-dropdown-menu.interface';
import { StDropdownMenuItemComponent } from './st-dropdown-menu-item/st-dropdown-menu-item.component';
const simpleItems: StDropDownMenuItem[] = [
{ label: 'example 1', value: 1 },
{ label: 'example 2', value: 2 },
{ label: 'example 3', value: 3 },
{ label: 'example 4', value: 4 },
{ label: 'example 5', value: 5 },
{ label: 'example 6', value: 6 },
{ label: 'example 7', value: 7 },
{ label: 'example 8', value: 8 },
{ label: 'example 9', value: 9 },
{ label: 'example 10', value: 10 },
{ label: 'example 11', value: 11 }
];
const simpleItems2: StDropDownMenuItem[] = [
{ label: 'test 1', value: 11 },
{ label: 'test 2', value: 12 },
{ label: 'test 3', value: 13 },
{ label: 'test 4', value: 14 },
{ label: 'test 5', value: 15 }
];
const groupedItems: StDropDownMenuGroup[] = [
{ title: 'group 1', items: simpleItems },
{ title: 'group 2', items: simpleItems2 }
];
const emptyGroup: StDropDownMenuGroup[] = [
{ title: 'group 1', items: [] },
{ title: 'group 2', items: simpleItems2 }
];
const defaultRowHeight: number = 42;
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
TEST HEADER
TEST FOOTER
`
})
class TestDropdownComponent {
items: StDropDownMenuItem[];
active: boolean = false;
selected: StDropDownMenuItem;
@ViewChild('dropdown', { static: false }) dropdownItem: StDropdownMenuComponent;
dropdownWidth: string = '300px';
visualMode: StDropDownVisualMode = StDropDownVisualMode.OPTION_LIST;
keyBoardMove: boolean = false;
onChange(item: StDropDownMenuItem): void {
this.selected = item;
}
onChangeMenuVisibility(): void {
this.active = !this.active;
}
}
describe('StDropdownMenu', () => {
describe('Component', () => {
let comp: StDropdownMenuComponent;
let fixture: ComponentFixture;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [StDropdownMenuComponent, StDropdownMenuItemComponent],
schemas: [NO_ERRORS_SCHEMA]
})
.overrideComponent(StDropdownMenuItemComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
})
.overrideComponent(StDropdownMenuComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
})
.compileComponents(); // compile template and css
}));
beforeEach(() => {
fixture = TestBed.createComponent(StDropdownMenuComponent);
comp = fixture.componentInstance;
});
it('Should init correctly', () => {
comp.items = simpleItems;
fixture.elementRef.nativeElement.removeAttribute('id');
fixture.detectChanges();
const defaultMaxHeight: number = comp.itemsBeforeScroll * 42; // 42 it's default row height
expect(comp.active).toBeFalsy();
expect(comp.items).toEqual(simpleItems);
expect(comp.placement).toEqual(StPopPlacement.BOTTOM_START);
expect(comp.emptyListMessage).toEqual('');
expect(comp.selectedItem).toBeUndefined();
expect(comp.itemsBeforeScroll).toEqual(8);
expect(comp.moveSelected).toBeTruthy();
expect(comp.styleSelected).toBeTruthy();
expect(comp.offset).toEqual({ x: 0, y: 0 });
expect(comp.componentId).toBeNull();
expect(comp.menuId).toBeNull();
expect(comp.getItemId('test')).toBeNull();
expect(comp.isItemGroup).toBeFalsy();
expect(comp.menuMaxHeight).toEqual(`${defaultMaxHeight}px`);
});
it('Should get max height', () => {
comp.items = simpleItems;
fixture.elementRef.nativeElement.id = null;
fixture.detectChanges();
const defaultMaxHeight: number = comp.itemsBeforeScroll * defaultRowHeight; // 42 it's default row height
expect(comp.menuMaxHeight).toEqual(`${defaultMaxHeight}px`);
comp.itemsBeforeScroll = undefined;
fixture.detectChanges();
expect(comp.menuMaxHeight).toBeNull();
});
it('Should propagate ids', () => {
comp.items = simpleItems;
const id: string = 'test-id';
(fixture.elementRef.nativeElement as HTMLElement).id = id;
fixture.detectChanges();
expect(comp.componentId).toEqual(id);
expect(comp.menuId).toEqual(`${id}-menu`);
expect(comp.getItemId('test')).toEqual(`${id}-option-test`);
});
it('Should propagate ids to menu without label', () => {
comp.items = simpleItems;
const id: string = 'test-id';
(fixture.elementRef.nativeElement as HTMLElement).id = id;
fixture.detectChanges();
expect(comp.componentId).toEqual(id);
expect(comp.menuId).toEqual(`${id}-menu`);
expect(comp.getItemId(undefined)).toBeNull();
});
it('Should show options when active it\'s true', () => {
comp.items = simpleItems;
comp.active = false;
const id: string = 'test-id';
(fixture.elementRef.nativeElement as HTMLElement).id = id;
fixture.detectChanges();
let items: DebugElement[] = fixture.debugElement.queryAll(By.css('.st-dropdown-menu-item'));
expect(items).toBeDefined();
expect(items.length).toBeDefined();
expect(items.length).toEqual(0);
comp.active = true;
fixture.detectChanges();
items = fixture.debugElement.queryAll(By.css('.st-dropdown-menu-item'));
expect(items).toBeDefined();
expect(items.length).toBeDefined();
expect(items.length).toEqual(comp.items.length);
});
it('Should show empty list message', () => {
comp.items = [];
comp.active = false;
comp.emptyListMessage = 'Test message';
const id: string = 'test-id';
(fixture.elementRef.nativeElement as HTMLElement).id = id;
fixture.detectChanges();
let item: DebugElement = fixture.debugElement.query(By.css('.without-results'));
expect(item).toBeNull();
comp.active = true;
fixture.detectChanges();
item = fixture.debugElement.query(By.css('.without-results'));
expect(item).toBeDefined();
expect(item.nativeElement).toBeDefined();
expect((item.nativeElement as HTMLDivElement).innerText).toEqual(comp.emptyListMessage);
});
it('Should show title of group menu', () => {
comp.items = groupedItems;
comp.active = false;
fixture.detectChanges();
let titles: DebugElement[] = fixture.debugElement.queryAll(By.css('h3'));
expect(titles).toBeDefined();
expect(titles.length).toBeDefined();
expect(titles.length).toEqual(0);
comp.active = true;
fixture.detectChanges();
titles = fixture.debugElement.queryAll(By.css('h3'));
expect(titles).toBeDefined();
expect(titles.length).toBeDefined();
expect(titles.length).toEqual(2);
expect((titles[0].nativeElement as HTMLElement).children[0].textContent).toEqual(groupedItems[0].title);
expect((titles[1].nativeElement as HTMLElement).children[0].textContent).toEqual(groupedItems[1].title);
});
it('Should show options when active it\'s true and with a group menu', () => {
comp.items = groupedItems;
comp.active = false;
const id: string = 'test-id';
(fixture.elementRef.nativeElement as HTMLElement).id = id;
const totalItems: number = groupedItems.reduce((count, act) => count + act.items.length, 0);
fixture.detectChanges();
let items: DebugElement[] = fixture.debugElement.queryAll(By.css('.st-dropdown-menu-item'));
expect(items).toBeDefined();
expect(items.length).toBeDefined();
expect(items.length).toEqual(0);
comp.active = true;
fixture.detectChanges();
items = fixture.debugElement.queryAll(By.css('.st-dropdown-menu-item'));
expect(items).toBeDefined();
expect(items.length).toEqual(totalItems);
});
it('Should show empty list message with a group menu', () => {
comp.items = emptyGroup;
comp.active = false;
comp.emptyListMessage = 'Test message';
const id: string = 'test-id';
(fixture.elementRef.nativeElement as HTMLElement).id = id;
fixture.detectChanges();
let item: DebugElement = fixture.debugElement.query(By.css('.without-results'));
expect(item).toBeNull();
comp.active = true;
fixture.detectChanges();
item = fixture.debugElement.query(By.css('.without-results'));
expect(item).toBeDefined();
expect(item.nativeElement).toBeDefined();
expect((item.nativeElement as HTMLDivElement).innerText).toEqual(comp.emptyListMessage);
});
it('Should propagate emit on select', (done) => {
comp.items = simpleItems;
comp.active = true;
(fixture.elementRef.nativeElement as HTMLElement).id = 'test-id';
spyOn(comp.change, 'emit');
fixture.whenStable().then(() => {
comp.onChange(simpleItems[0]);
expect(comp.change.emit).toHaveBeenCalled();
expect(comp.change.emit).toHaveBeenCalledWith(simpleItems[0]);
done();
});
});
describe('Should be able to active keyboard listeners to move between options', () => {
it('If input keyBoardMove is false, listener is not added', () => {
comp.active = true;
comp.items = simpleItems;
comp.keyBoardMove = false;
fixture.detectChanges();
comp.ngOnInit();
spyOn(HTMLLIElement.prototype, 'focus');
const keyboardEvent = new Event('keydown');
( keyboardEvent).key = ARROW_KEY_CODE.ARROW_DOWN;
document.dispatchEvent(keyboardEvent);
expect(HTMLLIElement.prototype.focus).not.toHaveBeenCalled();
});
describe('If input keyBoardMove is true, listener is added', () => {
let options: HTMLLIElement[];
beforeEach(() => {
comp.active = true;
comp.items = simpleItems;
fixture.detectChanges();
comp.keyBoardMove = true;
comp.ngOnInit();
options = fixture.nativeElement.querySelectorAll('.st-dropdown-menu-item');
for (let i = 0; i < options.length; ++i) {
spyOn(options[i], 'focus');
}
});
it('If user presses key down and there isn´t an option selected, first option is focused', () => {
const keyboardEvent = new Event('keydown');
( keyboardEvent).key = ARROW_KEY_CODE.ARROW_DOWN;
document.dispatchEvent(keyboardEvent);
expect(options[0].focus).toHaveBeenCalledTimes(1);
for (let i = 1; i < options.length; ++i) {
expect(options[i].focus).not.toHaveBeenCalled();
}
});
it('If user presses key down and there is an option selected, next option is focused', () => {
comp.ngOnDestroy();
comp.selectedItem = ( comp.items[3]);
comp.ngOnInit();
for (let i = 0; i < options.length; ++i) {
( options[i].focus).calls.reset();
}
const keyboardEvent = new Event('keydown');
( keyboardEvent).key = ARROW_KEY_CODE.ARROW_DOWN;
document.dispatchEvent(keyboardEvent);
expect(options[4].focus).toHaveBeenCalledTimes(1);
for (let i = 0; i < options.length; ++i) {
if (i !== 4) {
expect(options[i].focus).not.toHaveBeenCalled();
}
}
});
it('If user presses key down and current focused options is the last, first option is focused', () => {
comp.ngOnDestroy();
comp.selectedItem = ( comp.items[comp.items.length - 1]);
comp.ngOnInit();
for (let i = 0; i < options.length; ++i) {
( options[i].focus).calls.reset();
}
const keyboardEvent = new Event('keydown');
( keyboardEvent).key = ARROW_KEY_CODE.ARROW_DOWN;
document.dispatchEvent(keyboardEvent);
expect(options[0].focus).toHaveBeenCalledTimes(1);
for (let i = 1; i < options.length; ++i) {
expect(options[i].focus).not.toHaveBeenCalled();
}
});
it('If user presses key up and there isn´t an option selected, first option is focused', () => {
const keyboardEvent = new Event('keydown');
( keyboardEvent).key = ARROW_KEY_CODE.ARROW_UP;
document.dispatchEvent(keyboardEvent);
expect(options[0].focus).toHaveBeenCalledTimes(1);
for (let i = 1; i < options.length; ++i) {
expect(options[i].focus).not.toHaveBeenCalled();
}
});
it('If user presses key up and there is an option selected but no one focused, previous option to the selected is focused', () => {
comp.ngOnDestroy();
comp.selectedItem = ( comp.items[3]);
comp.ngOnInit();
for (let i = 0; i < options.length; ++i) {
( options[i].focus).calls.reset();
}
const keyboardEvent = new Event('keydown');
( keyboardEvent).key = ARROW_KEY_CODE.ARROW_UP;
document.dispatchEvent(keyboardEvent);
expect(options[2].focus).toHaveBeenCalledTimes(1);
for (let i = 0; i < options.length; ++i) {
if (i !== 2) {
expect(options[i].focus).not.toHaveBeenCalled();
}
}
});
it('If user presses key up and there is an option selected and other focused, previous option to the current focused one is focused', () => {
comp.ngOnDestroy();
comp.selectedItem = ( comp.items[1]);
comp.ngOnInit();
const keyboardEvent = new Event('keydown');
( keyboardEvent).key = ARROW_KEY_CODE.ARROW_DOWN;
document.dispatchEvent(keyboardEvent);
for (let i = 0; i < options.length; ++i) {
( options[i].focus).calls.reset();
}
document.dispatchEvent(keyboardEvent); // two under options
expect(options[3].focus).toHaveBeenCalledTimes(1);
for (let i = 0; i < options.length; ++i) {
if (i !== 3) {
expect(options[i].focus).not.toHaveBeenCalled();
}
}
});
it('If user presses key up and current focused options is the first, last option is focused', () => {
comp.ngOnDestroy();
comp.selectedItem = ( comp.items[0]);
comp.ngOnInit();
for (let i = 0; i < options.length; ++i) {
( options[i].focus).calls.reset();
}
const keyboardEvent = new Event('keydown');
( keyboardEvent).key = ARROW_KEY_CODE.ARROW_UP;
document.dispatchEvent(keyboardEvent);
expect(options[comp.items.length - 1].focus).toHaveBeenCalledTimes(1);
for (let i = 0; i < options.length - 1; ++i) {
expect(options[i].focus).not.toHaveBeenCalled();
}
});
it('When user presses enter key when an option is focused, this is selected', () => {
comp.ngOnDestroy();
comp.selectedItem = ( comp.items[2]);
comp.ngOnInit();
const keyboardEvent = new Event('keydown');
( keyboardEvent).key = ARROW_KEY_CODE.ARROW_DOWN;
document.dispatchEvent(keyboardEvent);
spyOn(comp.change, 'emit');
const enterEvent = new Event('keyup');
( enterEvent).key = 'Enter';
options[3].dispatchEvent(enterEvent);
expect(comp.change.emit).toHaveBeenCalledWith( comp.items[3]);
});
});
});
it('When mouse is over an item, event is emitted', () => {
comp.items = simpleItems;
comp.active = true;
fixture.detectChanges();
spyOn(comp.itemMouseEnter, 'emit');
fixture.nativeElement.querySelectorAll('st-dropdown-menu-item')[2].dispatchEvent(new Event('mouseenter'));
expect(comp.itemMouseEnter.emit).toHaveBeenCalledWith(simpleItems[2]);
});
it('When mouse leaves an item, event is emitted', () => {
comp.items = simpleItems;
comp.active = true;
fixture.detectChanges();
spyOn(comp.itemMouseLeave, 'emit');
fixture.nativeElement.querySelectorAll('st-dropdown-menu-item')[3].dispatchEvent(new Event('mouseleave'));
expect(comp.itemMouseLeave.emit).toHaveBeenCalledWith(simpleItems[3]);
});
});
describe('Instance', () => {
let instanceTestFixture: ComponentFixture;
let instanceTestComp: TestDropdownComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [StDropdownMenuModule],
declarations: [TestDropdownComponent]
})
.overrideComponent(StDropdownMenuComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
})
.compileComponents(); // compile template and css
}));
beforeEach(() => {
instanceTestFixture = TestBed.createComponent(TestDropdownComponent);
instanceTestComp = instanceTestFixture.componentInstance;
});
afterEach(() => {
instanceTestFixture.destroy();
});
it('It should be able to add some content before and after the menu', (done) => {
instanceTestComp.active = true;
instanceTestFixture.detectChanges();
instanceTestFixture.whenStable().then(() => {
instanceTestFixture.detectChanges();
expect(instanceTestFixture.nativeElement.querySelector('[dropdown-header]')).not.toBeNull();
expect(instanceTestFixture.nativeElement.querySelector('span[dropdown-header]').innerText).toContain('TEST HEADER');
expect(instanceTestFixture.nativeElement.querySelector('[dropdown-footer]')).not.toBeNull();
expect(instanceTestFixture.nativeElement.querySelector('span[dropdown-footer]').innerText).toContain('TEST FOOTER');
done();
});
});
it('If visual mode is OPTION_LIST, class "menu-list" is not added to the list', () => {
instanceTestComp.active = true;
instanceTestFixture.detectChanges();
expect(instanceTestFixture.nativeElement.querySelector('.st-dropdown-menu').classList).not.toContain('menu-list');
});
it('If visual mode is MENU_LIST, class "menu-list" is added to the list', () => {
instanceTestComp.active = true;
instanceTestComp.visualMode = StDropDownVisualMode.MENU_LIST;
instanceTestFixture.detectChanges();
expect(instanceTestFixture.nativeElement.querySelector('.st-dropdown-menu').classList).toContain('menu-list');
});
it('Should adjust to button width when have button element', (done) => {
instanceTestComp.items = simpleItems;
instanceTestComp.active = false;
instanceTestFixture.detectChanges();
instanceTestFixture.whenStable().then(() => {
expect(instanceTestComp.dropdownItem.widthMenu).toEqual(instanceTestComp.dropdownWidth);
done();
});
});
it('Should have scroll to 0 in menu when without it\'s selected', () => {
instanceTestComp.items = simpleItems;
instanceTestComp.active = true;
instanceTestFixture.detectChanges();
let ul: HTMLInputElement = instanceTestFixture.debugElement.query(By.css('ul')).nativeElement;
expect(ul.scrollTop).toBe(0);
});
it('Should have scroll to 0 in menu', () => {
instanceTestComp.items = simpleItems;
instanceTestComp.active = true;
instanceTestComp.selected = simpleItems[0];
instanceTestFixture.detectChanges();
let ul: HTMLInputElement = instanceTestFixture.debugElement.query(By.css('ul')).nativeElement;
expect(ul.scrollTop).toBe(0);
});
it('Should not add a list container when item list it\'s empty ', () => {
instanceTestComp.items = [];
instanceTestFixture.detectChanges();
expect(instanceTestFixture.debugElement.query(By.css('ul'))).toBeNull();
});
it('Should emit the same object as input on select (Extra parameters)', () => {
const itemsWithExtraParams: StDropDownMenuItem[] = [
{ label: 'example 1', value: 1, extraparam: 'test' },
{ label: 'example 2', value: 2, extraparam: true, extraparam2: 'data' },
{ label: 'example 3', value: 3, extraparam: { test: 'test' } }
];
instanceTestComp.items = itemsWithExtraParams;
spyOn(instanceTestComp, 'onChange');
instanceTestComp.active = true;
instanceTestFixture.detectChanges();
const items: DebugElement[] = instanceTestFixture.debugElement.queryAll(By.css('.st-dropdown-menu-item'));
expect(items).toBeDefined();
expect(items.length).toEqual(3);
(items[0].nativeElement as HTMLElement).click();
instanceTestFixture.detectChanges();
expect(instanceTestComp.onChange).toHaveBeenCalled();
expect(instanceTestComp.onChange).toHaveBeenCalledWith(itemsWithExtraParams[0]);
(items[1].nativeElement as HTMLElement).click();
instanceTestFixture.detectChanges();
expect(instanceTestComp.onChange).toHaveBeenCalled();
expect(instanceTestComp.onChange).toHaveBeenCalledWith(itemsWithExtraParams[1]);
(items[2].nativeElement as HTMLElement).click();
instanceTestFixture.detectChanges();
expect(instanceTestComp.onChange).toHaveBeenCalled();
expect(instanceTestComp.onChange).toHaveBeenCalledWith(itemsWithExtraParams[2]);
});
});
});