Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing unit test for component which uses mat-autocomplete

I am new to Angular, I am trying to build a text field with autocomplete using Angular 5.

I found this example in Angular Material docs:

https://stackblitz.com/angular/kopqvokeddbq?file=app%2Fautocomplete-overview-example.ts

I was wondering how to write a unit test for testing the autocomplete functionality. I am setting a value to the input element and triggering an 'input' event and tried selecting the mat-option elements, but see that none of them got created:

Relevant part of my component 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 file:

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();
}

Component 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()));
    }
}

Before this, I realised that for making the FormControl object set the value, I have to do a inputElement.focus() first, this is something to do with using mat input of angular material. Is there something I have to do to trigger opening the mat-options pane?

How do I make this test work?

like image 242
Krishna Chaitanya P Avatar asked May 20 '18 05:05

Krishna Chaitanya P


2 Answers

@Adam's comment to previous answer led me to the mat-autocomplete component's own test, specially here. Where you can see that focusin is the event that opens the "options".

But they actually open in an overlay outside your component, so in my test fixture.nativeElement.querySelectorAll('mat-option').length was 0 but if i query over the element document.querySelectorAll('mat-option') I got the expected number of options.

To sumarize:

    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');

Extra ball: And if you want to click on an option (I did) you can continue like that:

    const optionToClick = matOptions[0] as HTMLElement;
    optionToClick.click();
    fixture.detectChanges();

Although I didn't success on clicking and getting the value into the input. 🤨 Well, I'm not an expert tester, but probably that behaviour should be cover in the own mat-autocomplete's tests (and actually it is) and rely on it?

like image 67
David Avatar answered Nov 20 '22 15:11

David


You need to add more events. I had more or less the same problem as you and it only worked when I triggered the focusin event.

I am using these events in my code. Not sure if all are needed.

inputElement.dispatchEvent(new Event('focus'));
inputElement.dispatchEvent(new Event('focusin'));
inputElement.dispatchEvent(new Event('input'));
inputElement.dispatchEvent(new Event('keydown'));

You need to add this to your sendInput function...

like image 22
Jan Morten Sørensen Avatar answered Nov 20 '22 14:11

Jan Morten Sørensen