Написание модульного теста для компонента, который использует mat-autocomplete
Я новичок в Angular, я пытаюсь создать текстовое поле с автозаполнением, используя Angular 5.
Я нашел этот пример в документах Angular Material:
https://stackblitz.com/angular/kopqvokeddbq?file=app%2Fautocomplete-overview-example.ts
Мне было интересно, как написать модульный тест для проверки функциональности автозаполнения. Я устанавливаю значение для элемента input и запускаю событие 'input' и пытался выбрать элементы mat-option, но вижу, что ни один из них не был создан:
Соответствующая часть моего компонента HTML:
<form>
<mat-form-field class="input-with-icon">
<div>
<i ngClass="jf jf-search jf-lg md-primary icon"></i>
<input #nameInput matInput class="input-field-with-icon" placeholder="Type name here"
type="search" [matAutocomplete]="auto" [formControl]="userFormControl" [value]="inputField">
</div>
</mat-form-field>
</form>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option.name"
(onSelectionChange)="onNameSelect(option)">
{{ option.name }}
</mat-option>
</mat-autocomplete>
Spec файл:
it('should filter users based on input', fakeAsync(() => {
const hostElement = fixture.nativeElement;
sendInput('john').then(() => {
fixture.detectChanges();
expect(fixture.nativeElement.querySelectorAll('mat-option').length).toBe(1);
expect(hostElement.textContent).toContain('John Rambo');
});
}));
function sendInput(text: string) {
let inputElement: HTMLInputElement;
inputElement = fixture.nativeElement.querySelector('input');
inputElement.focus();
inputElement.value = text;
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
return fixture.whenStable();
}
Компонент HTML:
userFormControl: FormControl = new FormControl();
ngOnInit() {
this.filteredOptions = this.userFormControl.valueChanges
.pipe(
startWith(''),
map(val => this.filter(val))
);
}
filter(val: string): User[] {
if (val.length >= 3) {
console.log(' in filter');
return this.users.filter(user =>
user.name.toLowerCase().includes(val.toLowerCase()));
}
}
Перед этим я понял, что для того, чтобы заставить объект FormControl установить значение, я должен сначала выполнить inputElement.focus(), это как-то связано с использованием матового ввода углового материала. Есть ли что-то, что я должен сделать, чтобы вызвать открытие панели опций мат?
Как заставить этот тест работать?
5 ответов
Adam к предыдущему ответу привел меня к собственному тесту компонента mat-autocomplete, особенно здесь. Где вы можете это увидетьfocusin
это событие, открывающее "опции".
Но на самом деле они открываются в оверлее за пределами вашего компонента, поэтому в моем тесте fixture.nativeElement.querySelectorAll('mat-option').length
был 0
но если я запрашиваю элемент document.querySelectorAll('mat-option')
Получил ожидаемое количество вариантов.
Подводя итог:
fixture.detectChanges();
const inputElement = fixture.debugElement.query(By.css('input')); // Returns DebugElement
inputElement.nativeElement.dispatchEvent(new Event('focusin'));
inputElement.nativeElement.value = text;
inputElement.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
const matOptions = document.querySelectorAll('mat-option');
expect(matOptions.length).toBe(3,
'Expect to have less options after input text and filter');
Дополнительный шар: И если вы хотите нажать на опцию (я сделал), вы можете продолжить так:
const optionToClick = matOptions[0] as HTMLElement;
optionToClick.click();
fixture.detectChanges();
Хотя мне не удалось щелкнуть и получить значение во вход. Что ж, я не опытный тестировщик, но, вероятно, это поведение следует описать отдельно.mat-autocomplete
тесты (а на самом деле это так) и на это полагаться?
Вам нужно добавить больше событий. У меня была более или менее та же проблема, что и у вас, и она сработала только тогда, когда я запустил событие focusin.
Я использую эти события в своем коде. Не уверен, что все нужно.
inputElement.dispatchEvent(new Event('focus'));
inputElement.dispatchEvent(new Event('focusin'));
inputElement.dispatchEvent(new Event('input'));
inputElement.dispatchEvent(new Event('keydown'));
Вам нужно добавить это в вашу функцию sendInput...
Решение, за которое проголосовали выше, отличное. На его основе я нашел решение с помощью MatInputHarness:
const input = await loader.getAllHarnesses(MatInputHarness)
await input[0].setValue('12');
(await input[0].host()).dispatchEvent('change');
fixture.detectChanges();
let matOptions = document.querySelectorAll('mat-option');
expect(matOptions.length).toBe(3,
'Expect to have less options after input text and filter');
matOptions[0].dispatchEvent(new Event('click'));
fixture.detectChanges();
Я основываюсь на ответе @David здесь.
Обеспечение тестируемого компонента @Output() selectedTimezone = new EventEmitter<string>();
, а в шаблоне компонента
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selectTimezone($event.option.value)">
, модульный тест для захвата этого соответствующего типа события с правильным значением был выдан следующим образом
it('should emit selectedTimezone event on optionSelected', async() => {
// Note: 'selectedTimezone' is @Output event type as specified in component's signature
spyOn(component.selectedTimezone, 'emit');
const inputElement = fixture.debugElement.query(By.css('input'));
inputElement.nativeElement.dispatchEvent(new Event('focusin'));
/**
* Note, mat-options in this case set up to have array of ['Africa/Accra (UTC
* +01:00)', 'Africa/Addis_Ababa (UTC +01:00)', 'Africa/Algiers (UTC +01:00)',
* 'Africa/Asmara (UTC +01:00)']. I am setting it up in 'beforeEach'
*/
inputElement.nativeElement.value = 'Africa';
inputElement.nativeElement.dispatchEvent(new Event('input'));
await fixture.whenStable();
const matOptions = document.querySelectorAll('mat-option');
expect(matOptions.length).toBe(4);
const optionToClick = matOptions[0] as HTMLElement;
optionToClick.click();
// With this expect statement we verify both, proper type of event and value in it being emitted
expect(component.selectedTimezone.emit).toHaveBeenCalledWith('Africa/Accra');
});
Благодаря ответу @David я все заработал, пока не выбрал опцию.
Чтобы сделать выбор опций работающим, мне пришлось сделать это;
...
matOptions[0].dispatchEvent(new Event('click'));
...
И вам не нужно использовать тип
matOptions[0]
к
HTMLElement
Примечание ** Я использую Angular 8.0.1, и в новой версии решение может быть другим.