Writing unit test for component which uses mat-autocomplete

假如想象 提交于 2019-12-20 20:24:06

问题


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?


回答1:


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...




回答2:


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




回答3:


I am building further upon @David answer here.

Providing component which is being tested has @Output() selectedTimezone = new EventEmitter<string>();, and in component template <mat-autocomplete #auto="matAutocomplete" (optionSelected)="selectTimezone($event.option.value)">, the unit test to capture that appropriate type of event with correct value was emitted is as follows

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


来源:https://stackoverflow.com/questions/50431894/writing-unit-test-for-component-which-uses-mat-autocomplete

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!