@rishavtandon93/ng-select-aria
Version:
Angular ng-select - All in One UI Select, Multiselect and Autocomplete
1,134 lines (952 loc) • 189 kB
text/typescript
import {ComponentFixture, discardPeriodicTasks, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {Component, DebugElement, ErrorHandler, NgZone, Type, ViewChild, ViewEncapsulation} from '@angular/core';
import {ConsoleService} from './console.service';
import {FormsModule} from '@angular/forms';
import {getNgSelectElement, selectOption, TestsErrorHandler, tickAndDetectChanges, triggerKeyDownEvent} from '../testing/helpers';
import {KeyCode, NgOption} from './ng-select.types';
import {MockConsole, MockNgZone} from '../testing/mocks';
import {NgSelectComponent} from '@ng-select/ng-select';
import {NgSelectModule} from './ng-select.module';
import {Subject} from 'rxjs';
import {NgSelectConfig} from './config.service';
describe('NgSelectComponent', () => {
describe('Data source', () => {
it('should set items from primitive numbers array', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="[0, 30, 60, 90, 120, 180, 240]">
</ng-select>`);
tickAndDetectChanges(fixture);
const itemsList = fixture.componentInstance.select.itemsList;
expect(itemsList.items.length).toBe(7);
expect(itemsList.items[0]).toEqual(jasmine.objectContaining({
label: '0',
value: 0
}));
}));
it('should create items from ng-option', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [(ngModel)]="selectedCity">
<ng-option [value]="true">Yes</ng-option>
<ng-option [value]="false">No</ng-option>
</ng-select>`);
tickAndDetectChanges(fixture);
const items = fixture.componentInstance.select.itemsList.items;
expect(items.length).toBe(2);
expect(items[0]).toEqual(jasmine.objectContaining({
label: 'Yes', value: true, disabled: false
}));
expect(items[1]).toEqual(jasmine.objectContaining({
label: 'No', value: false, disabled: false
}));
}));
it('should create empty items list when initialzied with null', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="null">
</ng-select>`);
tickAndDetectChanges(fixture);
const itemsList = fixture.componentInstance.select.itemsList;
expect(itemsList.items.length).toBe(0);
}));
});
describe('Model bindings and data changes', () => {
let select: NgSelectComponent;
it('should update ngModel on value change', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
selectOption(fixture, KeyCode.ArrowDown, 1);
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCity).toEqual(jasmine.objectContaining(fixture.componentInstance.cities[1]));
fixture.componentInstance.select.clearModel();
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCity).toEqual(null);
discardPeriodicTasks();
}));
it('should update internal model on ngModel change', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[0];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([
jasmine.objectContaining({
value: fixture.componentInstance.cities[0]
})
]);
fixture.componentInstance.selectedCity = null;
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([]);
discardPeriodicTasks();
}));
it('should update internal model after it was toggled with *ngIf', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select *ngIf="visible"
[items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
// select first city
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[0];
tickAndDetectChanges(fixture);
// toggle to hide/show
fixture.componentInstance.toggleVisible();
tickAndDetectChanges(fixture);
fixture.componentInstance.toggleVisible();
tickAndDetectChanges(fixture);
fixture.componentInstance.selectedCity = null;
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([]);
}));
it('should set items correctly after ngModel set first when bindValue is used', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
[clearable]="true"
[(ngModel)]="selectedCityId">
</ng-select>`);
fixture.componentInstance.cities = [];
fixture.componentInstance.selectedCityId = 7;
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [{ id: 7, name: 'Pailgis' }];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
expect(select.selectedItems[0]).toBe(select.itemsList.items[0]);
expect(select.selectedItems).toEqual([jasmine.objectContaining({
value: { id: 7, name: 'Pailgis' }
})]);
}));
it('should set items correctly after ngModel set first when bindValue is not used', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.cities = [];
fixture.componentInstance.selectedCity = { id: 7, name: 'Pailgis' };
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [{ id: 7, name: 'Pailgis' }];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
expect(select.selectedItems[0]).toBe(select.itemsList.items[0]);
expect(select.selectedItems).toEqual([jasmine.objectContaining({
value: { id: 7, name: 'Pailgis' }
})]);
}));
it('should set items correctly after ngModel set first when bindValue is used from NgSelectConfig', fakeAsync(() => {
const config = new NgSelectConfig();
config.bindValue = 'id';
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCityId">
</ng-select>`,
config
);
fixture.componentInstance.cities = [];
fixture.componentInstance.selectedCityId = 7;
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [{ id: 7, name: 'Pailgis' }];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
expect(select.selectedItems[0]).toBe(select.itemsList.items[0]);
expect(select.selectedItems).toEqual([jasmine.objectContaining({
value: { id: 7, name: 'Pailgis' }
})]);
}));
it('should not apply global bindValue from NgSelectConfig if bindValue prop explicitly provided in template', fakeAsync(() => {
const config = new NgSelectConfig();
config.bindValue = 'globalbindvalue';
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
[clearable]="true"
[(ngModel)]="selectedCityId">
</ng-select>`,
config
);
fixture.componentInstance.cities = [];
fixture.componentInstance.selectedCityId = 7;
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [{ id: 7, name: 'Pailgis' }];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
expect(select.selectedItems[0]).toBe(select.itemsList.items[0]);
expect(select.selectedItems).toEqual([jasmine.objectContaining({
value: { id: 7, name: 'Pailgis' }
})]);
}));
it('should bind whole object as value when bindValue prop is specified with empty string in template', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue=""
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`
);
fixture.componentInstance.cities = [];
fixture.componentInstance.selectedCity = { id: 7, name: 'Pailgis' };
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [{ id: 7, name: 'Pailgis' }];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
expect(select.selectedItems[0]).toBe(select.itemsList.items[0]);
expect(select.selectedItems).toEqual([jasmine.objectContaining({
value: { id: 7, name: 'Pailgis' }
})]);
}));
it('should map label correctly', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.cities = [{ label: 'Vilnius city', name: 'Vilnius' }];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
expect(select.itemsList.items[0].label).toBe('Vilnius');
}));
it('should escape label', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.cities = [{ label: '<img src="azd" (error)="alert(1)" />', name: 'Vilnius' }];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
select.open();
const options = fixture.debugElement.nativeElement.querySelectorAll('.ng-option');
expect(options[0].innerText).toBe('<img src="azd" (error)="alert(1)" />');
}));
it('should set items correctly after ngModel set first when typeahead and single select is used', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[typeahead]="filter"
placeholder="select value"
[(ngModel)]="selectedCity">
</ng-select>`);
select = fixture.componentInstance.select;
fixture.componentInstance.selectedCity = { id: 1, name: 'Vilnius' };
tickAndDetectChanges(fixture);
expect(select.selectedItems).toEqual([
jasmine.objectContaining({ label: 'Vilnius', value: { id: 1, name: 'Vilnius' } })
]);
fixture.componentInstance.cities = [
{ id: 1, name: 'Vilnius' },
{ id: 2, name: 'Kaunas' },
{ id: 3, name: 'Pabrade' },
];
tickAndDetectChanges(fixture);
const vilnius = select.itemsList.items[0];
expect(select.selectedItems[0]).toBe(select.itemsList.items[0]);
expect(vilnius.selected).toBeTruthy();
}));
it('should set items correctly after ngModel set first when typeahead and multi-select is used', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[multiple]="true"
[typeahead]="filter"
placeholder="select value"
[(ngModel)]="selectedCities">
</ng-select>`);
select = fixture.componentInstance.select;
fixture.componentInstance.selectedCities = [{ id: 1, name: 'Vilnius' }, { id: 2, name: 'Kaunas' }];
tickAndDetectChanges(fixture);
expect(select.selectedItems).toEqual([
jasmine.objectContaining({ label: 'Vilnius', value: { id: 1, name: 'Vilnius' } }),
jasmine.objectContaining({ label: 'Kaunas', value: { id: 2, name: 'Kaunas' } })
]);
fixture.componentInstance.cities = [
{ id: 1, name: 'Vilnius' },
{ id: 2, name: 'Kaunas' },
{ id: 3, name: 'Pabrade' },
];
tickAndDetectChanges(fixture);
const vilnius = select.itemsList.items[0];
const kaunas = select.itemsList.items[1];
expect(select.selectedItems[0]).toBe(vilnius);
expect(vilnius.selected).toBeTruthy();
expect(select.selectedItems[1]).toBe(kaunas);
expect(kaunas.selected).toBeTruthy();
}));
it('should set items correctly if there is no bindLabel', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select
[items]="cities"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
const cities = [{ id: 7, name: 'Pailgis' }];
fixture.componentInstance.selectedCity = { id: 7, name: 'Pailgis' };
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [{ id: 1, name: 'Vilnius' }, { id: 2, name: 'Kaunas' }];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems[0]).toEqual(jasmine.objectContaining({
value: cities[0]
}));
}));
it('should bind ngModel object even if items are empty', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.cities = [];
tickAndDetectChanges(fixture);
fixture.componentInstance.selectedCity = { id: 7, name: 'Pailgis' };
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
value: { id: 7, name: 'Pailgis' },
selected: true
})]);
}));
it('should bind ngModel simple value even if items are empty', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="citiesNames"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.cities = [];
tickAndDetectChanges(fixture);
fixture.componentInstance.selectedCity = <any>'Kaunas';
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
value: 'Kaunas',
label: 'Kaunas',
selected: true
})]);
}));
it('should preserve latest selected value when items are changing', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[0];
tickAndDetectChanges(fixture);
fixture.componentInstance.select.select(fixture.componentInstance.select.itemsList.items[1]);
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [...fixture.componentInstance.cities];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCity).toEqual(fixture.componentInstance.cities[1]);
fixture.componentInstance.select.clearModel();
fixture.componentInstance.cities = [...fixture.componentInstance.cities];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCity).toBeNull();
}));
it('should map selected items with items in dropdown', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
select = fixture.componentInstance.select;
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[0];
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [...fixture.componentInstance.cities];
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [...fixture.componentInstance.cities];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCity).toEqual(fixture.componentInstance.cities[0]);
expect(select.itemsList.filteredItems[0].selected).toBeTruthy();
}));
it('should keep selected item while setting new items and bindValue is incorrect', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue="value"
[clearable]="true"
[(ngModel)]="selectedCityId">
</ng-select>`);
tickAndDetectChanges(fixture); // triggers write value
select = fixture.componentInstance.select;
select.select(select.itemsList.items[1]);
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [...fixture.componentInstance.cities];
tickAndDetectChanges(fixture);
expect(select.selectedItems[0]).toEqual(jasmine.objectContaining({
value: { id: 2, name: 'Kaunas' }
}));
}));
it('should clear previous single select value when setting new model', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[0];
tickAndDetectChanges(fixture);
const lastSelection: any = fixture.componentInstance.select.selectedItems[0];
expect(lastSelection.selected).toBeTruthy();
fixture.componentInstance.selectedCity = null;
tickAndDetectChanges(fixture);
expect(lastSelection.selected).toBeFalsy();
}));
it('should clear disabled selected values when setting new model', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[multiple]="true"
[clearable]="true"
[(ngModel)]="selectedCities">
</ng-select>`);
const disabled = { ...fixture.componentInstance.cities[1], disabled: true };
fixture.componentInstance.selectedCities = <any>[fixture.componentInstance.cities[0], disabled];
tickAndDetectChanges(fixture);
fixture.componentInstance.cities[1].disabled = true;
fixture.componentInstance.cities = [...fixture.componentInstance.cities];
tickAndDetectChanges(fixture);
fixture.componentInstance.selectedCities = [];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([]);
}));
it('should clear previous selected value even if it is disabled', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.cities[0].disabled = true;
fixture.componentInstance.cities = [...fixture.componentInstance.cities];
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[0];
tickAndDetectChanges(fixture);
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[1];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems.length).toBe(1);
}));
it('should clear previous multiple select value when setting new model', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[multiple]="true"
[clearable]="true"
[(ngModel)]="selectedCities">
</ng-select>`);
fixture.componentInstance.selectedCities = [fixture.componentInstance.cities[0]];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
expect(select.selectedItems.length).toBe(1);
fixture.componentInstance.selectedCities = [fixture.componentInstance.cities[1]];
tickAndDetectChanges(fixture);
expect(select.selectedItems.length).toBe(1);
fixture.componentInstance.selectedCities = [];
tickAndDetectChanges(fixture);
expect(select.selectedItems.length).toBe(0);
}));
it('should not add selected items to new items list when [items] are changed', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[multiple]="true"
[clearable]="true"
[(ngModel)]="selectedCities">
</ng-select>`);
fixture.componentInstance.selectedCities = [...fixture.componentInstance.cities.slice(0, 2)];
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [{ id: 1, name: 'New city' }];
tickAndDetectChanges(fixture);
const internalItems = fixture.componentInstance.select.itemsList.items;
expect(internalItems.length).toBe(1);
expect(internalItems[0].value).toEqual(jasmine.objectContaining({ id: 1, name: 'New city' }));
}));
it('should reset marked item when [items] are changed and dropdown is opened', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
select = fixture.componentInstance.select;
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[2];
tickAndDetectChanges(fixture);
triggerKeyDownEvent(getNgSelectElement(fixture), KeyCode.Space);
expect(fixture.componentInstance.select.itemsList.markedItem.value).toEqual({ name: 'Pabrade', id: 3 });
fixture.componentInstance.selectedCity = { name: 'New city', id: 5 };
tickAndDetectChanges(fixture);
fixture.componentInstance.cities = [...fixture.componentInstance.cities];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.itemsList.markedItem.value).toEqual({ name: 'Vilnius', id: 1 });
}));
it('should bind to custom object properties', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
[(ngModel)]="selectedCityId">
</ng-select>`);
selectOption(fixture, KeyCode.ArrowDown, 0);
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCityId).toEqual(1);
fixture.componentInstance.selectedCityId = 2;
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
value: fixture.componentInstance.cities[1]
})]);
}));
it('should bind to nested label property', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="countries"
bindLabel="description.name"
[(ngModel)]="selectedCountry">
</ng-select>`);
selectOption(fixture, KeyCode.ArrowDown, 1);
fixture.detectChanges();
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
label: 'USA',
value: fixture.componentInstance.countries[1]
})]);
fixture.componentInstance.selectedCountry = fixture.componentInstance.countries[0];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
label: 'Lithuania',
value: fixture.componentInstance.countries[0]
})]);
}));
it('should bind to nested value property', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="countries"
bindLabel="description.name"
bindValue="description.id"
[(ngModel)]="selectedCountry">
</ng-select>`);
selectOption(fixture, KeyCode.ArrowDown, 1);
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCountry).toEqual('b');
fixture.componentInstance.selectedCountry = fixture.componentInstance.countries[2].description.id;
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
label: 'Australia',
value: fixture.componentInstance.countries[2]
})]);
selectOption(fixture, KeyCode.ArrowUp, 1);
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCountry).toEqual('b');
}));
it('should bind to simple array', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="citiesNames"
[(ngModel)]="selectedCity">
</ng-select>`);
selectOption(fixture, KeyCode.ArrowDown, 0);
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCity).toBe(<any>'Vilnius');
fixture.componentInstance.selectedCity = <any>'Kaunas';
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems)
.toEqual([jasmine.objectContaining({ label: 'Kaunas', value: 'Kaunas' })]);
}));
it('should bind to object', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[(ngModel)]="selectedCity">
</ng-select>`);
// from component to model
selectOption(fixture, KeyCode.ArrowDown, 0);
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCity).toEqual(fixture.componentInstance.cities[0]);
// from model to component
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[1];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
value: fixture.componentInstance.cities[1]
})]);
discardPeriodicTasks();
}));
it('should use bindLabel from NgSelectConfig when bindLabel is not provided in template', fakeAsync(() => {
const config = new NgSelectConfig();
config.bindLabel = 'name';
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`,
config
);
fixture.componentInstance.cities = [{ id: 1, name: 'Vilnius', label: '' }];
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[0];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
expect(select.selectedItems).toEqual([jasmine.objectContaining({
label: 'Vilnius'
})]);
}));
it('should override bindLabel from NgSelectConfig by template-provided bindLabel property', fakeAsync(() => {
const config = new NgSelectConfig();
config.bindLabel = 'label';
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`,
config
);
fixture.componentInstance.cities = [{ id: 1, name: 'Vilnius', label: 'the capital of Lithuania' }];
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[0];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
expect(select.selectedItems).toEqual([jasmine.objectContaining({
label: 'Vilnius'
})]);
}));
it('should bind option label to "label" property when bindLabel is not provided', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
[clearable]="true"
[(ngModel)]="selectedCity">
</ng-select>`,
);
fixture.componentInstance.cities = [{ id: 1, name: 'Vilnius', label: 'the capital of Lithuania' }];
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[0];
tickAndDetectChanges(fixture);
select = fixture.componentInstance.select;
expect(select.selectedItems).toEqual([jasmine.objectContaining({
label: 'the capital of Lithuania'
})]);
}));
describe('ng-option', () => {
it('should reset to empty array', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [(ngModel)]="selectedCityId">
<ng-option *ngFor="let city of cities" [value]="city.id">{{city.name}}</ng-option>
</ng-select>`);
select = fixture.componentInstance.select;
tickAndDetectChanges(fixture);
expect(select.items.length).toEqual(3);
fixture.componentInstance.cities = [];
tickAndDetectChanges(fixture);
expect(select.items.length).toEqual(0);
}));
it('should bind value', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [(ngModel)]="selectedCityId">
<ng-option [value]="1">A</ng-option>
<ng-option [value]="2">B</ng-option>
</ng-select>`);
// from component to model
selectOption(fixture, KeyCode.ArrowDown, 0);
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCityId).toEqual(1);
// from model to component
fixture.componentInstance.selectedCityId = 2;
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
value: 2,
label: 'B'
})]);
discardPeriodicTasks();
}));
it('should not fail while resolving selected item from object', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [(ngModel)]="selectedCity">
<ng-option [value]="cities[0]">Vilnius</ng-option>
<ng-option [value]="cities[1]">Kaunas</ng-option>
</ng-select>`);
const selected = { name: 'Vilnius', id: 1 };
fixture.componentInstance.selectedCity = selected;
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
value: selected,
label: ''
})]);
}));
});
it('should not set internal model when single select ngModel is not valid', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[multiple]="false"
[(ngModel)]="selectedCity">
</ng-select>`);
const invalidValues = [undefined, null];
for (const v of invalidValues) {
fixture.componentInstance.selectedCity = <any>v;
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems.length).toBe(0);
}
}));
it('should not set internal model when multiselect ngModel is not valid', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[clearable]="true"
[multiple]="true"
[(ngModel)]="selectedCity">
</ng-select>`);
const invalidValues = [{}, '', undefined, 0, 1, 'false', 'true', false];
for (const v of invalidValues) {
fixture.componentInstance.selectedCity = <any>v;
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems.length).toBe(0);
}
}));
describe('Pre-selected model', () => {
describe('single', () => {
it('should select by bindValue when primitive type', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
placeholder="select value"
[(ngModel)]="selectedCityId">
</ng-select>`);
fixture.componentInstance.selectedCityId = 2;
tickAndDetectChanges(fixture);
const result = [jasmine.objectContaining({
value: { id: 2, name: 'Kaunas' },
selected: true
})];
select = fixture.componentInstance.select;
expect(select.selectedItems).toEqual(result);
}));
it('should apply host css classes', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
placeholder="select value"
[(ngModel)]="selectedCityId">
</ng-select>`);
fixture.componentInstance.selectedCityId = 2;
tickAndDetectChanges(fixture);
tickAndDetectChanges(fixture);
const classes = ['ng-select', 'ng-select-single', 'ng-select-searchable'];
const selectEl = fixture.nativeElement.querySelector('ng-select');
for (const c of classes) {
expect(selectEl.classList.contains(c)).toBeTruthy(`expected to contain "${c}" class`);
}
let hasValueEl = fixture.nativeElement.querySelector('.ng-has-value');
expect(hasValueEl).not.toBeNull();
fixture.componentInstance.selectedCityId = null;
tickAndDetectChanges(fixture);
tickAndDetectChanges(fixture);
hasValueEl = fixture.nativeElement.querySelector('.ng-has-value');
expect(hasValueEl).toBeNull();
}));
it('should select by bindValue ', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
placeholder="select value"
[(ngModel)]="selectedCityId">
</ng-select>`);
fixture.componentInstance.cities = [{ id: 0, name: 'Vilnius' }];
fixture.componentInstance.selectedCityId = 0;
tickAndDetectChanges(fixture);
const result = [jasmine.objectContaining({
value: { id: 0, name: 'Vilnius' },
selected: true
})];
expect(fixture.componentInstance.select.selectedItems).toEqual(result);
}));
it('should select by bindLabel when binding to object', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
placeholder="select value"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.selectedCity = { id: 2, name: 'Kaunas' };
tickAndDetectChanges(fixture);
const result = [jasmine.objectContaining({
value: { id: 2, name: 'Kaunas' },
selected: true
})];
expect(fixture.componentInstance.select.selectedItems).toEqual(result);
}));
it('should select by object reference', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
placeholder="select value"
[(ngModel)]="selectedCity">
</ng-select>`);
fixture.componentInstance.selectedCity = fixture.componentInstance.cities[1];
tickAndDetectChanges(fixture);
const result = [jasmine.objectContaining({
value: { id: 2, name: 'Kaunas' },
selected: true
})];
expect(fixture.componentInstance.select.selectedItems).toEqual(result);
}));
it('should select by compareWith function when bindValue is not used', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
placeholder="select value"
[compareWith]="compareWith"
[(ngModel)]="selectedCity">
</ng-select>`);
const city = { name: 'Vilnius', id: 7, district: 'Ozo parkas' };
fixture.componentInstance.cities.push(city);
fixture.componentInstance.cities = [...fixture.componentInstance.cities];
fixture.componentInstance.selectedCity = { name: 'Vilnius', district: 'Ozo parkas' } as any;
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems[0].value).toEqual(city);
}));
it('should select by compareWith function when bindValue is used', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
placeholder="select value"
[compareWith]="compareWith"
[(ngModel)]="selectedCityId">
</ng-select>`);
const cmp = fixture.componentInstance;
cmp.selectedCityId = cmp.cities[1].id.toString();
cmp.compareWith = (city, model: string) => city.id === +model;
tickAndDetectChanges(fixture);
expect(cmp.select.selectedItems[0].value).toEqual(cmp.cities[1]);
}));
it('should select selected when there is no items', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
placeholder="select value"
[(ngModel)]="selectedCityId">
</ng-select>`);
fixture.componentInstance.cities = [];
fixture.componentInstance.selectedCityId = 2;
tickAndDetectChanges(fixture);
const selected = fixture.componentInstance.select.selectedItems[0];
expect(selected.label).toEqual('');
expect(selected.value).toEqual({ name: null, id: 2 });
}));
});
describe('multiple', () => {
const result = [
jasmine.objectContaining({
value: { id: 2, name: 'Kaunas' },
selected: true
}),
jasmine.objectContaining({
value: { id: 3, name: 'Pabrade' },
selected: true
})];
it('should select by bindValue when primitive type', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
multiple="true"
placeholder="select value"
[(ngModel)]="selectedCityIds">
</ng-select>`);
fixture.componentInstance.selectedCityIds = [2, 3];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual(result)
}));
it('should select by bindLabel when binding to object', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
multiple="true"
placeholder="select value"
[(ngModel)]="selectedCities">
</ng-select>`);
fixture.componentInstance.selectedCities = [{ id: 2, name: 'Kaunas' }, { id: 3, name: 'Pabrade' }];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual(result);
}));
});
});
});
describe('Dropdown panel', () => {
it('should set and render items in dropdown panel', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
bindLabel="name"
[(ngModel)]="city">
</ng-select>`);
const select = fixture.componentInstance.select;
select.open();
expect(select.dropdownPanel.items.length).toBe(3);
let options = fixture.debugElement.nativeElement.querySelectorAll('.ng-option');
expect(options.length).toBe(3);
expect(options[0].innerText).toBe('Vilnius');
expect(options[1].innerText).toBe('Kaunas');
expect(options[2].innerText).toBe('Pabrade');
fixture.componentInstance.cities = Array.from(Array(30).keys()).map((_, i) => ({ id: i, name: String.fromCharCode(97 + i) }));
tickAndDetectChanges(fixture);
options = fixture.debugElement.nativeElement.querySelectorAll('.ng-option');
expect(options.length).toBe(30);
expect(options[0].innerText).toBe('a');
}));
it('should always have div #padding with height 0 in dropdown panel when virtua