web-dev-qa-db-fra.com

Écriture d'un test unitaire pour le composant qui utilise la saisie semi-automatique

Je suis nouveau sur Angular, j'essaie de créer un champ de texte avec saisie semi-automatique en utilisant Angular 5.

J'ai trouvé cet exemple dans Angular Material docs :

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

Je me demandais comment écrire un test unitaire pour tester la fonctionnalité de saisie semi-automatique. Je fixe une valeur à l'élément d'entrée et déclenche un événement "entrée" et j'ai essayé de sélectionner les éléments mat-option, mais je vois qu'aucun d'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>

Fichier de spécifications:

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, j'ai réalisé que pour que l'objet FormControl définisse la valeur, je dois d'abord faire un inputElement.focus (), c'est quelque chose à voir avec l'utilisation de l'entrée mat de angular material. Is -il quelque chose que je dois faire pour déclencher l'ouverture du volet mat-options?

Comment faire fonctionner ce test?

12

Vous devez ajouter plus d'événements. J'ai eu plus ou moins le même problème que vous et cela n'a fonctionné que lorsque j'ai déclenché l'événement focusin.

J'utilise ces événements dans mon code. Je ne sais pas si tout est nécessaire.

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

Vous devez l'ajouter à votre fonction sendInput ...

6

@ Adam Le commentaire de la réponse précédente m'a conduit au test du composant mat-autocomplete , spécialement ici . Où vous pouvez 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 recherche sur l'élément document.querySelectorAll('mat-option') j'ai obtenu le nombre attendu d'options .

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

Extra ball: 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'ai pas réussi à cliquer et à obtenir la valeur dans l'entrée. ???? Eh bien, je ne suis pas un testeur expert, mais ce comportement devrait probablement être couvert par les propres tests de mat-autocomplete (Et en fait c'est le cas) et s'appuyer sur lui?

2
David

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

Si le composant testé est @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 le type d'événement approprié 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');
  });
0
Zoryana Tischenko