diff --git a/demo/app/examples/groups.component.ts b/demo/app/examples/groups.component.ts index 697f44410..6a9d11842 100644 --- a/demo/app/examples/groups.component.ts +++ b/demo/app/examples/groups.component.ts @@ -11,11 +11,14 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; ---html,true + bindLabel="name" + bindValue="name" + groupBy="country" + [multiple]="true" + [(ngModel)]="selectedAccount"> + + {{item.country || 'Unnamed group'}} + ---

@@ -25,11 +28,11 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; ---html,true + bindLabel="name" + bindValue="name" + [groupBy]="groupByFn" + [multiple]="true" + [(ngModel)]="selectedAccount2"> ---

@@ -40,10 +43,13 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; ---html,true + bindLabel="name" + groupBy="country" + [selectableGroup]="true" + [(ngModel)]="selectedAccount3"> + + {{item.country || 'Unnamed group'}} + ---

@@ -58,6 +64,7 @@ export class SelectGroupsComponent { { name: 'Jill', email: 'jill@email.com', age: 15, child: { state: 'Active' } }, { name: 'Henry', email: 'henry@email.com', age: 10, child: { state: 'Active' } }, { name: 'Meg', email: 'meg@email.com', age: 7, country: null, child: { state: 'Active' } }, + { name: 'Homer', email: 'homer@email.com', age: 47, country: '', child: { state: 'Active' } }, { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { state: 'Active' } }, { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { state: 'Active' } }, { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { state: 'Active' } }, diff --git a/src/ng-select/items-list.ts b/src/ng-select/items-list.ts index 87561f7c2..3361a22e9 100644 --- a/src/ng-select/items-list.ts +++ b/src/ng-select/items-list.ts @@ -271,17 +271,17 @@ export class ItemsList { private _groupBy(items: NgOption[], prop: string | Function): OptionGroups { const isFn = isFunction(this._ngSelect.groupBy); - const groups = items.reduce((grouped, item) => { + const groups = new Map(); + for (const item of items) { let key = isFn ? (prop).apply(this, [item.value]) : item.value[prop]; - key = key || undefined; - const group = grouped.get(key); + key = isDefined(key) ? key : undefined; + const group = groups.get(key); if (group) { group.push(item); } else { - grouped.set(key, [item]); + groups.set(key, [item]); } - return grouped; - }, new Map()); + } return groups; } @@ -292,7 +292,7 @@ export class ItemsList { items.push(...withoutGroup); let i = withoutGroup.length; for (const key of Array.from(groups.keys())) { - if (!key) { + if (!isDefined(key)) { continue; } const parent: NgOption = { diff --git a/src/ng-select/ng-select.component.spec.ts b/src/ng-select/ng-select.component.spec.ts index da15177a4..ba9e721e5 100644 --- a/src/ng-select/ng-select.component.spec.ts +++ b/src/ng-select/ng-select.component.spec.ts @@ -424,7 +424,7 @@ describe('NgSelectComponent', function () { fixture.componentInstance.selectedCity = { name: 'New city', id: 5 }; tickAndDetectChanges(fixture); - fixture.componentInstance.cities = [...fixture.componentInstance.cities] + fixture.componentInstance.cities = [...fixture.componentInstance.cities]; tickAndDetectChanges(fixture); expect(fixture.componentInstance.select.itemsList.markedItem.value).toEqual({ name: 'Vilnius', id: 1 }); })); @@ -552,7 +552,7 @@ describe('NgSelectComponent', function () { expect(fixture.componentInstance.selectedCityId).toEqual(1); // from model to component - fixture.componentInstance.selectedCityId = 2 + fixture.componentInstance.selectedCityId = 2; tickAndDetectChanges(fixture); expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({ @@ -741,8 +741,7 @@ describe('NgSelectComponent', function () { `); const cmp = fixture.componentInstance; - const cityId = cmp.cities[1].id.toString(); - cmp.selectedCityId = cityId; + cmp.selectedCityId = cmp.cities[1].id.toString(); cmp.compareWith = (city, model: string) => city.id === +model; @@ -1022,7 +1021,7 @@ describe('NgSelectComponent', function () { it('should stop marked loop if all items disabled', fakeAsync(() => { fixture.componentInstance.cities[0].disabled = true; - fixture.componentInstance.cities = [...fixture.componentInstance.cities] + fixture.componentInstance.cities = [...fixture.componentInstance.cities]; tickAndDetectChanges(fixture); select.filter('vil'); tickAndDetectChanges(fixture); @@ -1119,7 +1118,7 @@ describe('NgSelectComponent', function () { const result = jasmine.objectContaining({ value: fixture.componentInstance.cities[2] }); - expect(fixture.componentInstance.select.itemsList.markedItem).toEqual(result) + expect(fixture.componentInstance.select.itemsList.markedItem).toEqual(result); triggerKeyDownEvent(getNgSelectElement(fixture), KeyCode.Tab); expect(fixture.componentInstance.select.selectedItems).toEqual([result]); })); @@ -1201,7 +1200,7 @@ describe('NgSelectComponent', function () { tick(200); expect(fixture.componentInstance.selectedCity).toBeUndefined(); - expect(select.itemsList.markedItem.label).toBe('Vilnius') + expect(select.itemsList.markedItem.label).toBe('Vilnius'); expect(findByLabel).toHaveBeenCalledWith('vil') })); }); @@ -1215,7 +1214,7 @@ describe('NgSelectComponent', function () { it('should select option and close dropdown', () => { triggerKeyDownEvent(getNgSelectElement(fixture), KeyCode.Space); triggerKeyDownEvent(getNgSelectElement(fixture), KeyCode.Enter); - expect(select.selectedItems[0].value).toEqual(fixture.componentInstance.cities[0]) + expect(select.selectedItems[0].value).toEqual(fixture.componentInstance.cities[0]); expect(select.isOpen).toBe(false); }); }); @@ -1347,7 +1346,7 @@ describe('NgSelectComponent', function () { fixture.detectChanges(); expect(fixture.componentInstance.select.selectedItems.length).toBe(1); - fixture.componentInstance.select.clearItem(fixture.componentInstance.cities[0]) + fixture.componentInstance.select.clearItem(fixture.componentInstance.cities[0]); expect(fixture.componentInstance.select.selectedItems.length).toBe(0); tick(); })); @@ -1367,7 +1366,7 @@ describe('NgSelectComponent', function () { fixture.componentInstance.cities = []; fixture.detectChanges(); - fixture.componentInstance.select.clearItem(selected) + fixture.componentInstance.select.clearItem(selected); expect(fixture.componentInstance.select.selectedItems.length).toBe(0); tick(); })); @@ -1671,9 +1670,9 @@ describe('NgSelectComponent', function () { it('should keep same ordering while unselecting', fakeAsync(() => { fixture.componentInstance.selectedCities = [...fixture.componentInstance.cities.reverse()]; tickAndDetectChanges(fixture); - select.unselect(select.selectedItems[0]) - select.unselect(select.selectedItems[0]) - select.unselect(select.selectedItems[0]) + select.unselect(select.selectedItems[0]); + select.unselect(select.selectedItems[0]); + select.unselect(select.selectedItems[0]); expect(select.selectedItems.length).toBe(0); expect(select.itemsList.filteredItems.length).toBe(3); expect(select.itemsList.filteredItems[0].label).toBe('Vilnius'); @@ -1840,7 +1839,7 @@ describe('NgSelectComponent', function () { tickAndDetectChanges(fixture); tickAndDetectChanges(fixture); const selectEl: HTMLElement = select.elementRef.nativeElement; - const ngControl = selectEl.querySelector('.ng-select-container') + const ngControl = selectEl.querySelector('.ng-select-container'); const placeholder: any = selectEl.querySelector('.ng-placeholder'); expect(ngControl.classList.contains('ng-has-value')).toBeTruthy(); @@ -1855,7 +1854,7 @@ describe('NgSelectComponent', function () { it('should contain .ng-has-value when value was selected', fakeAsync(() => { tickAndDetectChanges(fixture); const selectEl: HTMLElement = fixture.componentInstance.select.elementRef.nativeElement; - const ngControl = selectEl.querySelector('.ng-select-container') + const ngControl = selectEl.querySelector('.ng-select-container'); selectOption(fixture, KeyCode.ArrowDown, 2); tickAndDetectChanges(fixture); expect(ngControl.classList.contains('ng-has-value')).toBeTruthy(); @@ -1963,7 +1962,7 @@ describe('NgSelectComponent', function () { const result = jasmine.objectContaining({ value: fixture.componentInstance.cities[2] }); - expect(fixture.componentInstance.select.itemsList.markedItem).toEqual(result) + expect(fixture.componentInstance.select.itemsList.markedItem).toEqual(result); triggerKeyDownEvent(getNgSelectElement(fixture), KeyCode.Enter); expect(fixture.componentInstance.select.selectedItems).toEqual([result]); })); @@ -1984,7 +1983,7 @@ describe('NgSelectComponent', function () { const result = jasmine.objectContaining({ value: fixture.componentInstance.cities[2] }); - expect(fixture.componentInstance.select.itemsList.markedItem).toEqual(result) + expect(fixture.componentInstance.select.itemsList.markedItem).toEqual(result); triggerKeyDownEvent(getNgSelectElement(fixture), KeyCode.Enter); expect(fixture.componentInstance.select.selectedItems).toEqual([result]); })); @@ -2097,7 +2096,7 @@ describe('NgSelectComponent', function () { })); describe('with typeahead', () => { - let fixture: ComponentFixture + let fixture: ComponentFixture; beforeEach(() => { fixture = createTestingModule( NgSelectTestCmp, @@ -2447,7 +2446,7 @@ describe('NgSelectComponent', function () { tickAndDetectChanges(fixture); tickAndDetectChanges(fixture); triggerMousedown = () => { - const control = fixture.debugElement.query(By.css('.ng-select-container')) + const control = fixture.debugElement.query(By.css('.ng-select-container')); control.triggerEventHandler('mousedown', createEvent({ target: { className: 'ng-control' } })); }; })); @@ -2476,7 +2475,7 @@ describe('NgSelectComponent', function () { tickAndDetectChanges(fixture); tickAndDetectChanges(fixture); triggerMousedown = () => { - const control = fixture.debugElement.query(By.css('.ng-select-container')) + const control = fixture.debugElement.query(By.css('.ng-select-container')); control.triggerEventHandler('mousedown', createEvent({ target: { className: 'ng-clear' } })); }; })); @@ -2528,7 +2527,7 @@ describe('NgSelectComponent', function () { tickAndDetectChanges(fixture); tickAndDetectChanges(fixture); triggerMousedown = () => { - const control = fixture.debugElement.query(By.css('.ng-select-container')) + const control = fixture.debugElement.query(By.css('.ng-select-container')); control.triggerEventHandler('mousedown', createEvent({ target: { className: 'ng-value-icon' } })); }; })); @@ -2559,7 +2558,7 @@ describe('NgSelectComponent', function () { fixture.componentInstance.selectedCity = fixture.componentInstance.cities[0]; tickAndDetectChanges(fixture); triggerMousedown = () => { - const control = fixture.debugElement.query(By.css('.ng-select-container')) + const control = fixture.debugElement.query(By.css('.ng-select-container')); control.triggerEventHandler('mousedown', createEvent({ target: { className: 'ng-arrow' } })); }; })); @@ -2670,16 +2669,20 @@ describe('NgSelectComponent', function () { fixture.componentInstance.accounts.push( { name: 'Henry', email: 'henry@email.com', age: 10 }, { name: 'Meg', email: 'meg@email.com', age: 7, country: null }, + { name: 'Meg', email: 'meg@email.com', age: 7, country: '' }, ); - fixture.componentInstance.accounts = [...fixture.componentInstance.accounts] + fixture.componentInstance.accounts = [...fixture.componentInstance.accounts]; tickAndDetectChanges(fixture); - const items = fixture.componentInstance.select.itemsList.items; - expect(items.length).toBe(16); + const items: NgOption[] = fixture.componentInstance.select.itemsList.items; + expect(items.length).toBe(18); expect(items[0].hasChildren).toBeUndefined(); expect(items[0].parent).toBeUndefined(); expect(items[1].hasChildren).toBeUndefined(); expect(items[1].parent).toBeUndefined(); + expect(items[16].hasChildren).toBeTruthy(); + expect(items[16].label).toBe(''); + expect(items[17].parent).toBeDefined(); })); it('should group by group fn', fakeAsync(() => {