Как издеваться над MatChipInput при тестировании с Jasmine - PullRequest
1 голос
/ 08 ноября 2019

Я настроил stackblitz с базовым показом того, в чем проблема.

В основном, когда я пытаюсь вызвать событие в MatFormField, которое содержит MatChipList, который я получаюошибка

 Cannot read property 'stateChanges' of undefined at MatChipInput._onInput

Я попытался переопределить модуль MatChip с помощью замещающего макета для MatInput. Я также попытался переопределить директиву.

У кого-нибудь есть слова мудрости?

Спасибо

HTML

 <h1>Welcome to app!!</h1>

 <div>
  <mat-form-field>
   <mat-chip-list #chipList>
    <mat-chip *ngFor="let contrib of contributors; let idx=index;" [removable]="removable" (removed)="removeContributor(idx)">
     {{contrib.fullName}}
    </mat-chip>
    <input  id="contributor-input"
        placeholder="contributor-input"
        #contributorInput
        [formControl]="contributorCtrl"
        [matAutocomplete]="auto"
        [matChipInputFor]="chipList"
        [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
        [matChipInputAddOnBlur]="addOnBlur">
  </mat-chip-list>
 </mat-form-field>
</div>

TS

import { Component, Input } from '@angular/core';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {

contributors = [{fullName: 'foo bar'}];
removable = true;
addOnBlur = false;
separatorKeysCodes: number[] = [
    ENTER,
    COMMA,
];

contributorCtrl = new FormControl();

filteredPeople: Observable<Array<any>>;

@Input() peopleArr = [];

constructor() {
   this.filteredPeople = this.contributorCtrl.valueChanges.pipe(startWith(''), map((value: any) => 
this.searchPeople(value)));
 }

 searchPeople(searchString: string) {
    const filterValue = String(searchString).toLowerCase();
    const result = this.peopleArr.filter((option) => option.fullName.toLowerCase().includes(filterValue));
    return result;
  }
}

SPEC

import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { 
  BrowserDynamicTestingModule, 
  platformBrowserDynamicTesting 
} from '@angular/platform-browser-dynamic/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {  MatFormFieldModule, 
      MatAutocompleteModule, 
      MatInputModule,
      MatChipsModule } from '@angular/material';
import { FormsModule, ReactiveFormsModule} from '@angular/forms';

describe('AppComponent', () => {

  const mockPeopleArray = [
    { personId: 1,
      email: 'foo1@bar.com',
      department: 'fake1',
      username: 'foo1',
      fullName: 'Foo Johnson'
     },
     { personId: 2,
      email: 'foo2@bar.com',
      department: 'fake1',
      username: 'foo2',
      fullName: 'John Fooson'
     },
     { personId: 3,
      email: 'foo1@bar.com',
      department: 'fake2',
      username: 'foo3',
      fullName: 'Mary Smith'
     }
 ];


 let app: AppComponent;
 let fixture: ComponentFixture<AppComponent>;
 let nativeElement: HTMLElement;

 beforeAll( ()=> {
  TestBed.initTestEnvironment(BrowserDynamicTestingModule, 
  platformBrowserDynamicTesting());
  });
  beforeEach(
   async(() => {
     TestBed.configureTestingModule({
       imports: [
       RouterTestingModule,
       MatFormFieldModule,
       FormsModule,
       ReactiveFormsModule,
       MatAutocompleteModule,
       MatChipsModule,
       MatInputModule,
       NoopAnimationsModule
       ],
       declarations: [AppComponent]
     }).compileComponents();

   fixture = TestBed.createComponent(AppComponent);
   app = fixture.debugElement.componentInstance;
   nativeElement = fixture.nativeElement;
  })
 );
 it(
 'should render title \'Welcome to app!!\' in a h1 tag', async(() => {
  fixture.detectChanges();
  expect(nativeElement.querySelector('h1').textContent).toContain('Welcome to app!!');
})
);

it('searchPeople should trigger and filter', (done) => {
  app.peopleArr = mockPeopleArray;

  const expected = [
    { personId: 3,
      email: 'foo1@bar.com',
      department: 'fake2',
      username: 'foo3',
      fullName: 'Mary Smith'
     }
  ];

  const myInput = <HTMLInputElement> 
  nativeElement.querySelector('#contributor-input');
  expect(myInput).not.toBeNull();
  myInput.value = 'Mar';
  spyOn(app, 'searchPeople').and.callThrough();
  myInput.dispatchEvent(new Event('input'));
    fixture.detectChanges();
    fixture.whenStable().then(() => {
        const myDiv = nativeElement.querySelector('#contrib-div');
        expect(app.searchPeople).toHaveBeenCalledWith('mar');
        app.filteredPeople.subscribe(result => 
        expect(result).toEqual(<any>expected));
        done();
    });
  });
 });

1 Ответ

2 голосов
/ 13 ноября 2019

Вы получаете:

Невозможно прочитать свойство 'stateChanges' неопределенного в MatChipInput._onInput

, поскольку Angular еще не завершил привязки на моментстрельба myInput.dispatchEvent(new Event('input'))

Чтобы исправить это, вы должны сначала вызвать fixture.detectChanges , чтобы Angular выполнял привязку данных.

Тогда вам не нужно делать этот тест асинхроннымпоскольку все действия выполняются синхронно.

Теперь о вашем searchPeople методе. Он будет вызываться дважды, поскольку вы начинаете подписку с начальным значением, используя startWith(''):

this.contributorCtrl.valueChanges.pipe(startWith('')

Поэтому вам нужно пропустить первый вызов и проверить результат вызова после запуска события input.

app.filteredPeople.pipe(skip(1)).subscribe(result => {
  ...
});

spyOn(app, "searchPeople").and.callThrough();

myInput.dispatchEvent(new Event("input"));
expect(app.searchPeople).toHaveBeenCalledWith("Mar");

Весь код теста:

it("searchPeople should trigger and filter", () => {
  app.peopleArr = mockPeopleArray;

  const expected = [
    {
      personId: 3,
      email: "foo1@bar.com",
      department: "fake2",
      username: "foo3",
      fullName: "Mary Smith"
    }
  ];

  fixture.detectChanges();
  const myInput = nativeElement.querySelector<HTMLInputElement>(
    "#contributor-input"
  );
  expect(myInput).not.toBeNull();
  myInput.value = "Mar";

  app.filteredPeople.pipe(skip(1)).subscribe(result => 
    expect(result).toEqual(expected);
  );

  spyOn(app, "searchPeople").and.callThrough();

  myInput.dispatchEvent(new Event("input"));
  expect(app.searchPeople).toHaveBeenCalledWith("Mar");
}); 

Forked Stackblitz

...