14 votes

Écriture d'un test unitaire pour le composant qui utilise mat-autocomplete

Je suis nouveau dans Angular, j'essaie de construire un champ de texte avec autocomplétion en utilisant Angular 5.

J'ai trouvé cet exemple dans Documents sur les matériaux angulaires :

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

Je me demandais comment écrire un test unitaire pour tester la fonctionnalité d'autocomplétion. Je fixe une valeur à l'élément input et déclenche un événement 'input'. J'ai essayé de sélectionner les éléments mat-option, mais je constate qu'aucun d'entre eux n'a été créé :

Partie pertinente de mon composant 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>

Dossier spécial :

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

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

Avant cela, je me suis rendu compte que pour que l'objet FormControl définisse la valeur, je dois d'abord faire un inputElement.focus(), c'est quelque chose à faire avec l'utilisation de l'entrée mat du matériel angulaire. Y a-t-il quelque chose que je doive faire pour déclencher l'ouverture du volet des options de mat ?

Comment faire pour que ce test fonctionne ?

12voto

David Points 1029

@Adam Le commentaire de l'auteur de la réponse précédente m'a conduit à l'adresse suivante test propre au composant mat-autocomplete , spécialement aquí . Où l'on peut voir que focusin est l'événement qui ouvre les "options".

Mais ils s'ouvrent en fait dans une superposition en dehors de votre composant, donc dans mon test fixture.nativeElement.querySelectorAll('mat-option').length était 0 mais si je fais une requête sur l'élément document.querySelectorAll('mat-option') J'ai obtenu le nombre d'options prévu.

Pour résumer :

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

Boule supplémentaire : Et si vous voulez cliquer sur une option (je l'ai fait), vous pouvez continuer comme ça :

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

Bien que je n'aie pas réussi à cliquer et à obtenir la valeur dans l'entrée.

9voto

Vous devez ajouter d'autres événements. J'ai eu plus ou moins le même problème que vous et cela ne fonctionnait que lorsque je déclenchais l'événement focusin.

J'utilise ces événements dans mon code. Je ne suis pas sûr que tous soient nécessaires.

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

Vous devez ajouter ceci à votre fonction sendInput...

0voto

Je m'appuie ici sur la réponse de @David.

Fournir le composant qui est testé a @Output() selectedTimezone = new EventEmitter<string>(); , et dans le modèle de composant <mat-autocomplete #auto="matAutocomplete" (optionSelected)="selectTimezone($event.option.value)"> , le test unitaire pour capturer que le type approprié d'événement avec la valeur correcte a été émis est le suivant

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X