Ошибка типа не может прочитать свойство 'triggereventhandler' неопределенного в тестировании Карма-Жасмин - PullRequest
0 голосов
/ 23 февраля 2019

В моем приложении angular7 есть модуль «Настройки», в котором используется библиотека Ngrx.Моя цель - протестировать шаблон компонента, который его запускает.При использовании функции MatSlideToggle Angular Material элемент управления вкл / выкл можно чередовать, щелкая модальный компонент.Однако результат теста Karma ~ 3.1.1 и Jasmine ^ 2.99.0 возвращает событие как неопределенное.Я получаю сообщение об ошибке «не могу прочитать свойство« triggereventhandler »из undefined».

Примечание. Я искренне благодарю г-на Томаса Траяна ( @ tomastrajan ) и остальных авторов открытых материалов.project "Angular NgRx Material Starter" .

Заранее благодарю вас за внимание в моем первом вопросе.

Это мой ajustes-cont.component.spec.ts 'file:

import { By } from '@angular/platform-browser';
import { ComponentFixture, async, TestBed } from '@angular/core/testing';
import { Store } from '@ngrx/store';
import { MatSlideToggle } from '@angular/material';

import { MockStore, TestingModule } from '../../../testing/utils';

import { AjustesContComponent } from "./ajustes-cont.component";
import {
  ActionAjustesCambiarNavbarPegado,
  ActionAjustesCambiarTema,
  ActionAjustesCambiarAutoNocheModo,
  ActionAjustesCambiarAnimacionesPagina,
  ActionAjustesCambiarAnimacionesElementos
} from '../ajustes.actions';

describe('AjustesContComponent', () => {
  let component: AjustesContComponent;
  let fixture: ComponentFixture<AjustesContComponent>;
  let store: MockStore<any>;
  let dispatchSpy;

  const getThemeSelectArrow = () =>
    fixture.debugElement.queryAll(By.css('.mat-select-trigger'))[1];
  const getSelectOptions = () =>
    fixture.debugElement.queryAll(By.css('mat-option'));

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [TestingModule],
      declarations: [AjustesContComponent]
    }).compileComponents();

    store = TestBed.get(Store);
    store.setState({
      settings: {
        tema: 'DEFECTO-TEMA', // scss theme
        autoNocheModo: true, // auto night mode
        navbarPegado: true, // sticky Header
        paginaAnimaciones: true, // page animations
        paginaAnimacionesDisabled: false, //// page animations disabled
        elementosAnimaciones: true, // elements animations
        idioma: 'esp' // default language
      }
    });
    fixture = TestBed.createComponent(AjustesContComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  }));

  it('debe disparar el cambio del Navbar Pegado en el conmutador del navbarPegado', () => { // 'should dispatch change sticky header on sticky header toggle'
    dispatchSpy = spyOn(store, 'dispatch');
    const componentDebug = fixture.debugElement;
    const slider = componentDebug.queryAll(By.directive(MatSlideToggle))[0];

    slider.triggerEventHandler('change', { checked: false });
    fixture.detectChanges();

    expect(dispatchSpy).toHaveBeenCalledTimes(1);
    expect(dispatchSpy).toHaveBeenCalledWith(
      new ActionAjustesCambiarNavbarPegado({ navbarPegado: false })
    );
  });

  it('debe disparar el cambio de tema en la selección de tema', () => { // 'should dispatch change theme action on theme selection'
    dispatchSpy = spyOn(store, 'dispatch');
    getThemeSelectArrow().triggerEventHandler('click', {});
    fixture.detectChanges();

    getSelectOptions()[1].triggerEventHandler('click', {});
    fixture.detectChanges();

    expect(dispatchSpy).toHaveBeenCalledTimes(1);
    expect(dispatchSpy).toHaveBeenCalledWith(
      new ActionAjustesCambiarTema({ tema: 'AZUL-TEMA' })
    );
  });

  it('debe disparar el cambio del Auto Noche Modo en el conmutador del autoNocheModo', () => { // 'should dispatch change auto night mode on night mode toggle'
    dispatchSpy = spyOn(store, 'dispatch');
    const componentDebug = fixture.debugElement;
    const slider = componentDebug.queryAll(By.directive(MatSlideToggle))[1];

    slider.triggerEventHandler('change', { checked: false });
    fixture.detectChanges();

    expect(dispatchSpy).toHaveBeenCalledTimes(1);
    expect(dispatchSpy).toHaveBeenCalledWith(
      new ActionAjustesCambiarAutoNocheModo({ autoNocheModo: false })
    );
  });

  it('debe disparar el cambio de las paginaAnimaciones', () => { // 'should dispatch change animations page'
    dispatchSpy = spyOn(store, 'dispatch');
    const componentDebug = fixture.debugElement;
    const slider = componentDebug.queryAll(By.directive(MatSlideToggle))[2];

    slider.triggerEventHandler('change', { checked: false });
    fixture.detectChanges();

    expect(dispatchSpy).toHaveBeenCalledTimes(1);
    expect(dispatchSpy).toHaveBeenCalledWith(
      new ActionAjustesCambiarAnimacionesPagina({ paginaAnimaciones: false })
    );
  });

  it('debe disparar el cambio de los elementosAnimaciones', () => { // 'should dispatch change animations elements'
    dispatchSpy = spyOn(store, 'dispatch');
    const componentDebug = fixture.debugElement;
    const slider = componentDebug.queryAll(By.directive(MatSlideToggle))[3];

    slider.triggerEventHandler('change', { checked: false });
    fixture.detectChanges();

    expect(dispatchSpy).toHaveBeenCalledTimes(1);
    expect(dispatchSpy).toHaveBeenCalledWith(
      new ActionAjustesCambiarAnimacionesElementos({ elementosAnimaciones: false })
    );
  });

  it('debe deshabilitar las paginaAnimaciones cuando se desactiva la configuración', () => { // 'should disable change animations page when disabled is set in state'
    store.setState({
      settings: {
        tema: 'DEFECTO-TEMA',
        autoNocheModo: true,
        paginaAnimaciones: true,
        paginaAnimacionesDisabled: true, // change animations disabled
        elementosAnimaciones: true,
        idioma: 'esp'
      }
    });
    fixture.detectChanges();

    dispatchSpy = spyOn(store, 'dispatch');
    const componentDebug = fixture.debugElement;
    const slider = componentDebug.queryAll(By.directive(MatSlideToggle))[2];

    slider.triggerEventHandler('change', { checked: false });
    fixture.detectChanges();

    expect(dispatchSpy).toHaveBeenCalledTimes(0);
  });

});

Это мой файл' settings-container.component.ts ':

import {
      Component,
      OnInit,
      ChangeDetectionStrategy,
      ChangeDetectorRef
     } from '@angular/core';
    import { Store, select } from '@ngrx/store';
    import { Observable  } from 'rxjs';

    import { ANIMACIONES_RUTA_ELEMENTOS } from '../../nucleo';

    import {
      ActionAjustesCambiarIdioma,
      ActionAjustesCambiarTema,
      ActionAjustesCambiarAutoNocheModo,
      ActionAjustesCambiarNavbarPegado,
      ActionAjustesCambiarAnimacionesPagina,
      ActionAjustesCambiarAnimacionesElementos
    } from '../ajustes.actions';
    import { AjustesState, State } from '../ajustes.model';
    import { selectAjustes } from '../ajustes.selectors';

    @Component({
      selector: 'bab-ajustes-cont',
      templateUrl: './ajustes-cont.component.html',
      styleUrls: ['./ajustes-cont.component.scss'],
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class AjustesContComponent implements OnInit {
      routeAnimationsElements = ANIMACIONES_RUTA_ELEMENTOS;
      ajustes$: Observable<AjustesState>;

      temas = [
        { value: 'DEFECTO-TEMA', label: 'verde' },
        { value: 'AZUL-TEMA', label: 'azul' },
        { value: 'PURPURA-TEMA', label: 'purpura' },
        { value: 'NEGRO-TEMA', label: 'negro' }
      ];

      idiomas = [
        { value: 'esp', label: 'esp' },
        { value: 'val-cat', label: 'val-cat' },
        { value: 'ing', label: 'ing' },
        { value: 'ale', label: 'ale' },
        { value: 'fra', label: 'fra' }
      ];

      constructor(private store: Store<State>) { }

      ngOnInit() {
        this.ajustes$ = this.store.pipe(select(selectAjustes));
      }

      idiomaSelect({ value: idioma }) {
        this.store.dispatch(new ActionAjustesCambiarIdioma({ idioma }));
      }

      temaSelect({ value: tema }) {
        this.store.dispatch(new ActionAjustesCambiarTema({ tema }));
      }

      autoNocheModoToggle({ checked: autoNocheModo }) {
        this.store.dispatch(
          new ActionAjustesCambiarAutoNocheModo({ autoNocheModo })
        );
      }

      navbarPegadoToggle({ checked: navbarPegado }) {
        this.store.dispatch(new ActionAjustesCambiarNavbarPegado({ navbarPegado }));
      }

      paginaAnimacionesToggle({ checked: paginaAnimaciones }) {
        this.store.dispatch(
          new ActionAjustesCambiarAnimacionesPagina({ paginaAnimaciones })
        );
      }

      elementosAnimacionesToggle({ checked: elementosAnimaciones }) {
        this.store.dispatch(
          new ActionAjustesCambiarAnimacionesElementos({ elementosAnimaciones })
        );
      }
    }

Это мой файл' settings-container.component.html 'file:

<div class="container">
  <div class="row">
    <div class="col-sm-12">
      <h1>{{ "bab.ajustes.titulo" | translate }}</h1>
    </div>
  </div>
  <br />

  <ng-container *ngIf="ajustes$ | async as ajustes">
    <div class="row">
      <div class="col-md-6 group">
        <h2>{{ "bab.ajustes.general" | translate }}</h2>
        <div class="icon-form-field">
          <mat-icon color="accent"><fa-icon icon="language" color="accent"></fa-icon></mat-icon>
          <mat-form-field>
            <mat-select
              [placeholder]="'bab.ajustes.general.placeholder' | translate"
              [ngModel]="ajustes.idioma"
              (selectionChange)="idiomaSelect($event)"
              name="language">
              <mat-option *ngFor="let i of idiomas" [value]="i.value">
                {{ "bab.ajustes.general.idioma." + i.label | translate }}
              </mat-option>
            </mat-select>
          </mat-form-field>
        </div>
        <div class="icon-form-field">
          <mat-icon color="accent"><fa-icon icon="bars" color="accent"></fa-icon></mat-icon>
          <mat-placeholder>{{ "bab.ajustes.temas.navbar-pegado" | translate }}</mat-placeholder>
          <mat-slide-toggle
            [checked]="ajustes.navbarPegado"
            (change)="navbarPegadoToggle($event)">
          </mat-slide-toggle>
        </div>
      </div>
    </div>
    <div class="row">
      <div class="col-md-6 group">
        <h2>{{ "bab.ajustes.temas" | translate }}</h2>
        <div class="icon-form-field">
          <mat-icon color="accent"><fa-icon icon="paint-brush" color="accent"></fa-icon></mat-icon>
          <mat-form-field>
            <mat-select
              [placeholder]="'bab.ajustes.temas.placeholder' | translate"
              [ngModel]="ajustes.tema"
              (selectionChange)="temaSelect($event)"
              name="themes">
              <mat-option *ngFor="let t of temas" [value]="t.value">
                {{ "bab.ajustes.temas." + t.label | translate }}
              </mat-option>
            </mat-select>
          </mat-form-field>
        </div>
        <div class="icon-form-field">
          <mat-icon color="accent"><fa-icon icon="lightbulb" color="accent"></fa-icon></mat-icon>
          <mat-placeholder>{{ "bab.ajustes.temas.night-mode" | translate }}</mat-placeholder>
          <mat-slide-toggle
            [checked]="ajustes.autoNocheModo"
            (change)="autoNocheModoToggle($event)">
          </mat-slide-toggle>
        </div>
      </div>
      <div class="col-md-6 group">
        <h2>{{ "bab.ajustes.animaciones" | translate }}</h2>
        <div class="icon-form-field">
          <mat-icon color="accent"><mat-icon color="accent"><fa-icon icon="window-maximize"></fa-icon></mat-icon></mat-icon>
          <mat-placeholder>{{ "bab.ajustes.animaciones.pagina" | translate }}</mat-placeholder>
          <mat-slide-toggle
            matTooltip="Sorry, this feature is disabled in IE, EDGE and Safari"
            matTooltipPosition="before"
            *ngIf="ajustes.paginaAnimacionesDisabled"
            disabled>
          </mat-slide-toggle>
          <mat-slide-toggle
            *ngIf="!ajustes.paginaAnimacionesDisabled"
            [checked]="ajustes.paginaAnimaciones"
            (change)="paginaAnimacionesToggle($event)">
          </mat-slide-toggle>
        </div>
        <div class="icon-form-field">
          <mat-icon color="accent"><fa-icon icon="stream" color="accent"></fa-icon></mat-icon>
          <mat-placeholder>{{ "bab.ajustes.animaciones.elementos" | translate }}</mat-placeholder>
          <mat-slide-toggle
            [checked]="ajustes.elementosAnimaciones"
            (change)="elementosAnimacionesToggle($event)">
          </mat-slide-toggle>
        </div>
      </div>
    </div>
  </ng-container>
</div>

Наконец, это мой файл 'package.json':

{
  "name": "bababo-web",
  "version": "1.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@agm/core": "^1.0.0-beta.5",
    "@angular/animations": "~7.2.0",
    "@angular/cdk": "^7.3.3",
    "@angular/common": "~7.2.0",
    "@angular/compiler": "~7.2.0",
    "@angular/core": "~7.2.0",
    "@angular/fire": "^5.1.1",
    "@angular/forms": "~7.2.0",
    "@angular/material": "^7.3.3",
    "@angular/platform-browser": "~7.2.0",
    "@angular/platform-browser-dynamic": "~7.2.0",
    "@angular/router": "~7.2.0",
    "@fortawesome/angular-fontawesome": "^0.3.0",
    "@fortawesome/fontawesome-svg-core": "^1.2.15",
    "@fortawesome/free-brands-svg-icons": "^5.7.2",
    "@fortawesome/free-solid-svg-icons": "^5.7.2",
    "@ngrx/effects": "^7.2.0",
    "@ngrx/router-store": "^7.2.0",
    "@ngrx/store": "^7.2.0",
    "@ngrx/store-devtools": "^7.2.0",
    "@ngx-translate/core": "^11.0.1",
    "@ngx-translate/http-loader": "^4.0.0",
    "bootstrap": "^4.3.1",
    "browser-detect": "^0.2.28",
    "core-js": "^2.5.4",
    "firebase": "^5.8.4",
    "hammerjs": "^2.0.8",
    "rxjs": "~6.3.3",
    "tslib": "^1.9.0",
    "web-animations-js": "^2.3.1",
    "zone.js": "~0.8.26"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.13.0",
    "@angular/cli": "~7.3.2",
    "@angular/compiler-cli": "~7.2.0",
    "@angular/language-service": "~7.2.0",
    "@types/jasmine": "~2.8.8",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "~8.9.4",
    "codelyzer": "~4.5.0",
    "jasmine": "^2.99.0",
    "jasmine-core": "^2.99.0",
    "jasmine-marbles": "^0.4.1",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~3.1.1",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "^1.1.2",
    "karma-jasmine-html-reporter": "^0.2.2",
    "ngrx-store-freeze": "^0.2.4",
    "protractor": "~5.4.0",
    "puppeteer": "^1.12.2",
    "ts-node": "~7.0.0",
    "tslint": "~5.11.0",
    "typescript": "~3.2.2"
  }
}

Это мой 'TestingModule':

import { NgModule, Injectable } from '@angular/core';
import { ComunModule } from 'src/app/comun';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import {
  Store,
  StateObservable,
  ActionsSubject,
  ReducerManager,
  StoreModule
} from '@ngrx/store';
import { BehaviorSubject } from 'rxjs';
import { RouterTestingModule } from '@angular/router/testing';

@Injectable()
export class MockStore<T> extends Store<T> {
  private stateSubject = new BehaviorSubject<T>({} as T);

  constructor(
    state$: StateObservable,
    actionsObserver: ActionsSubject,
    reducerManager: ReducerManager
  ) {
    super(state$, actionsObserver, reducerManager);
    this.source = this.stateSubject.asObservable();
  }

  setState(nextState: T) {
    this.stateSubject.next(nextState);
  }
}

export function provideMockStore() {
  return {
    provide: Store,
    useClass: MockStore
  };
}

@NgModule({
  imports: [
    NoopAnimationsModule,
    RouterTestingModule,
    ComunModule,
    TranslateModule.forRoot(),
    StoreModule.forRoot({})
  ],
  exports: [
    NoopAnimationsModule,
    RouterTestingModule,
    ComunModule,
    TranslateModule
  ],
  providers: [provideMockStore()]
})
export class TestingModule {
  constructor() {}
}

ЭтоМой 'ComunModule':

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { TranslateModule } from '@ngx-translate/core';

import { MatButtonModule } from '@angular/material/button';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatTabsModule } from '@angular/material/tabs';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatChipsModule } from '@angular/material/chips';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatCardModule } from '@angular/material/card';
import { MatListModule } from '@angular/material/list';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatDividerModule } from '@angular/material/divider';
import { MatSliderModule } from '@angular/material/';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material';

import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
import {
  faBars,
  faUserCircle,
  faPowerOff,
  faCog,
  faPlayCircle,
  faRocket,
  faPlus,
  faEdit,
  faTrash,
  faTimes,
  faCaretUp,
  faCaretDown,
  faExclamationTriangle,
  faFilter,
  faTasks,
  faCheck,
  faSquare,
  faLanguage,
  faPaintBrush,
  faLightbulb,
  faWindowMaximize,
  faStream,
  faBook,
  faPhoneVolume,
  faFax,
  faExternalLinkAlt,
  faUniversity,
  faAmbulance,
  faHandRock,
  faVenusMars,
  faUserFriends,
  faFileSignature,
  faHome,
  faPiggyBank,
  faUserSecret,
  faIndustry,
  faFistRaised,
  faTv
} from '@fortawesome/free-solid-svg-icons';
import {
  faGithub,
  faMediumM,
  faTwitter,
  faInstagram,
  faYoutube,
  faAngular,
  faFacebookF,
  faLinkedinIn
} from '@fortawesome/free-brands-svg-icons';

import { InicioComponent } from './inicio/inicio.component';
// import { GranEntradaComponent } from './gran-entrada/gran-entrada.component';
// import { GranEntradaAccionComponent } from './gran-entrada-accion/gran-entrada-accion.component';


library.add(
  faBars,
  faUserCircle,
  faPowerOff,
  faCog,
  faRocket,
  faPlayCircle,
  faGithub,
  faMediumM,
  faTwitter,
  faInstagram,
  faYoutube,
  faPlus,
  faEdit,
  faTrash,
  faTimes,
  faCaretUp,
  faCaretDown,
  faExclamationTriangle,
  faFilter,
  faTasks,
  faCheck,
  faSquare,
  faLanguage,
  faPaintBrush,
  faLightbulb,
  faWindowMaximize,
  faStream,
  faBook,
  faAngular,
  faFacebookF,
  faPhoneVolume,
  faFax,
  faLinkedinIn,
  faExternalLinkAlt,
  faUniversity,
  faAmbulance,
  faHandRock,
  faVenusMars,
  faUserFriends,
  faFileSignature,
  faHome,
  faPiggyBank,
  faUserSecret,
  faIndustry,
  faFistRaised,
  faTv
);

@NgModule({
  imports: [
    CommonModule,
    FormsModule,

    TranslateModule,

    MatButtonModule,
    MatToolbarModule,
    MatSelectModule,
    MatTabsModule,
    MatInputModule,
    MatProgressSpinnerModule,
    MatChipsModule,
    MatCardModule,
    MatSidenavModule,
    MatCheckboxModule,
    MatListModule,
    MatMenuModule,
    MatIconModule,
    MatTooltipModule,
    MatSnackBarModule,
    MatSlideToggleModule,
    MatDividerModule,

    FontAwesomeModule
  ],
  declarations: [InicioComponent],
  exports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,

    TranslateModule,

    MatButtonModule,
    MatMenuModule,
    MatTabsModule,
    MatChipsModule,
    MatInputModule,
    MatProgressSpinnerModule,
    MatCheckboxModule,
    MatCardModule,
    MatSidenavModule,
    MatListModule,
    MatSelectModule,
    MatToolbarModule,
    MatIconModule,
    MatTooltipModule,
    MatSnackBarModule,
    MatSlideToggleModule,
    MatDividerModule,
    MatSliderModule,
    MatDatepickerModule,
    MatNativeDateModule,

    FontAwesomeModule,

    InicioComponent,

    // GranEntradaComponent,
    // GranEntradaAccionComponent
  ]
})
export class ComunModule { }

Мой проект работает нормально, но во время выполнения модульного теста появляется эта ошибка, пожалуйста, помогите.

1 Ответ

0 голосов
/ 27 февраля 2019

Проблема в том, что SlideToggle экспортируется не как директива, а как компонент.Следовательно, он недоступен при использовании предиката By.directive.

Вы можете получить доступ к этому компоненту, используя следующий фрагмент:

 const slider = fixture.debugElement.queryAll(By.css('mat-slide-toggle'))[0];
 expect(slider).not.toBeNull();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...