angularunit-testingautocompleteangular-materialmat-autocomplete

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?


Solution

  • @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?