首页 > 解决方案 > 无法解析组件的所有参数: (?, [object Object]) 在 jasmine 组件 UT 上

问题描述

我正在定义一个组件的 UT,该组件具有扩展类并同时使用 i8nService (用于翻译目的)和 ChangeDetectionRef 并且由于以下错误而无法实例化它:

Failed: Can't resolve all parameters for BrandingMultiselectComponent: (?, [object Object]).
Error: Can't resolve all parameters for BrandingMultiselectComponent: (?, [object Object]).
    at syntaxError node_modules/@angular/compiler/fesm5/compiler.js:2426:1)
    at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getDependenciesMetadata node_modules/@angular/compiler/fesm5/compiler.js:18979:1)
    at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getTypeMetadata node_modules/@angular/compiler/fesm5/compiler.js:18872:1)
    at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getNonNormalizedDirectiveMetadata node_modules/@angular/compiler/fesm5/compiler.js:18491:1)
    at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver.loadDirectiveMetadata node_modules/@angular/compiler/fesm5/compiler.js:18353:1)
    at http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/compiler/fesm5/compiler.js:26011:1
    at Array.forEach (<anonymous>)
    at http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/compiler/fesm5/compiler.js:26010:1
    at Array.forEach (<anonymous>)
    at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler._loadModules node_modules/@angular/compiler/fesm5/compiler.js:26007:1),Expected undefined to be defined.
    at UserContext.<anonymous> src/app/wa/components/branding/ddl-multiselect/ddl-multiselect.component.spec.ts:62:23)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:391:1)
    at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:289:1)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:390:1)

这是我目前准备的UT:

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';

import { AngularMultiSelectModule } from 'angular2-multiselect-dropdown';

import { I18nService } from '@core/language/i18n.service';

import { BrandingMultiselectComponent } from '@branding/ddl-multiselect/ddl-multiselect.component';

let component: BrandingMultiselectComponent ;
let fixture: ComponentFixture<BrandingMultiselectComponent >;
let ngOnitComponent_Spy: jasmine.Spy;
let I18nServiceStub: Partial<I18nService>;

describe('branding component - BrandingMultiselectComponent - testing initialization', () => {

  I18nServiceStub = {
    language: 'en-US',
    supportedLanguages: ['en-US', 'es-ES']
  };

  beforeEach(async(() => {
    TestBed.configureTestingModule({
                declarations: [ BrandingMultiselectComponent ],
                imports: [
                  BrowserModule,
                  AngularMultiSelectModule,
                  RouterTestingModule
                ],
                schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
                providers: [ { provide: I18nService, useClass: I18nServiceStub } ]
              })
            .compileComponents();

    fixture = TestBed.createComponent(BrandingMultiselectComponent );
    component = fixture.componentInstance;

    // Spies for component stuff
    ngOnitComponent_Spy = spyOn(component, 'ngOnInit').and.callThrough();
  }));

  it('should component be defined', () => {
    expect(component).toBeDefined();
  });
});

这是组件本身(虽然并非所有内容都已设置)

import { Component, Input, Output, EventEmitter,
         ViewEncapsulation, OnInit, AfterViewChecked,
         ChangeDetectorRef, AfterViewInit, ViewChild } from '@angular/core';

import { ArrayUtil } from '@utils/ArrayUtil/ArrayUtil';
import { ObjectUtil } from '@utils/ObjectUtil/ObjectUtil';

import { I18nService } from '@core/index';

import { BaseTranslation } from '@app/components/base/base.translations';
import { DDLPluginTexts, DDLMultiselectComponent } from '@shared/components/single/ddl-multiselect/ddl-multiselect.component';


@Component({
  selector: 'wa-ddl-multiselect',
  templateUrl: './ddl-multiselect.component.html',
  styleUrls: ['./ddl-multiselect.component.scss'],
  encapsulation: ViewEncapsulation.None
})

export class BrandingMultiselectComponent extends BaseTranslation implements OnInit, AfterViewInit, AfterViewChecked {

  // Required params
  //#region OPTIONS
  sOptions: any[];
  backupRowOpts: any[];
  @Output() optionsChange: EventEmitter<any[]> = new EventEmitter<any[]>();
  @Input()
  get options() {
    return this.sOptions;
  }
  set options(val: any[]) {
    if (!ArrayUtil.AreEqual(this.sOptions, val)) {
      if (!this.hasComponentAlreadyBeenInitialized) {
        this.backupRowOpts = val;
      }

      this.sOptions = val;
      this.optionsChange.emit(this.sOptions);
    }
  }
  //#endregion

  //#region SELECTED OPTIONS
  sSelectedOptions: any[];
  @Output() selectedOptionsChange: EventEmitter<any[]> = new EventEmitter<any[]>();
  @Input()
  get selectedOptions() {
    return ArrayUtil.IsNotEmptyOrNull(this.sSelectedOptions) ? this.applyOptionsTranslation(false, this.sSelectedOptions)
                                                             : this.sSelectedOptions;
  }
  set selectedOptions(val: any[]) {
    if (!ArrayUtil.AreEqual(this.sSelectedOptions, val)) {

      this.sSelectedOptions = val;

      // We want to overwrite text shown with the one expected
      if (this.hasComponentAlreadyBeenInitialized &&
          ArrayUtil.IsNotEmptyOrNull(this.sSelectedOptions) &&
          this.ddlSettings.singleSelection === false &&
          ObjectUtil.IsNotNullOrUndefined(this.translationTexts)) {
        this.defineDdlFooter();
      }

      this.selectedOptionsChange.emit(this.sSelectedOptions);
    }
  }
  //#endregion

  hasComponentAlreadyBeenInitialized = false;
  hasOptionsAlreadyBeenTranslated = false;

  // Optional params
  @Input() doesOptionsRequireTranslations = false;

  //#region TRANSLATION TEXTS
  originalKeyTranslationTexts: DDLPluginTexts = null;
  sTranslationTexts: DDLPluginTexts;
  @Output() translationTextsChange = new EventEmitter<DDLPluginTexts>();

  @Input()
  get translationTexts(): DDLPluginTexts {
    return this.sTranslationTexts;
  }
  set translationTexts(val: DDLPluginTexts) {

    if (val !== null && val !== undefined) {
      if (this.originalKeyTranslationTexts === null) {
        this.originalKeyTranslationTexts = val;
      }
      this.sTranslationTexts = this.applyTranslations();
      this.translationTextsChange.emit(this.sTranslationTexts);
    }
  }
  //#endregion

  @Input() displayProp: string;
  @Input() valueProp: string;
  showToggleAll: boolean;

  // Plugin optional params
  @Input() isEditionMode = false;
  @Input() isFilterActive = false;
  @Input() selectionLimit: string = null;

  // Shared component ref
  @ViewChild(DDLMultiselectComponent) ddlBaseMultiSelect: DDLMultiselectComponent;

  // Events
  @Output() ddlOptionSelectedEvent = new EventEmitter<any>();

  //#region COMPONENT LIFE-CYLCE HOOKS
  constructor(_i8nService: I18nService, private cdRef: ChangeDetectorRef) { super(_i8nService); }

  ngAfterViewInit(): void {
    this.cdRef.detectChanges();

    if (this.isLocalPreference) {
      const currentUserDefaultPrefs = this.ddlBaseMultiSelect.loadDefaultUserPrefs();
      if (ObjectUtil.IsNotNullOrUndefined(currentUserDefaultPrefs)) {
        this.selectedOptions = currentUserDefaultPrefs;
      } else {
        this.selectedOptions = this.options;
      }
    } else {
      if (!ArrayUtil.IsNotEmptyOrNull(this.selectedOptions)) {
        this.selectedOptions = this.options;
      }
    }

    this.hasComponentAlreadyBeenInitialized = true;
  }
  //#endregion

  //#region COMPONENT FUNCS
  applyTranslations(isForLangChange: boolean = false): DDLPluginTexts {

    let resultTranslationsTexts: DDLPluginTexts = null;

    if (this.originalKeyTranslationTexts !== null &&
        this.originalKeyTranslationTexts !== undefined) {
        // Apply translations for input PrimeNG labels
        resultTranslationsTexts = {
          buttonDefaultText: this.i8nService.getInstantTranslation(this.originalKeyTranslationTexts.buttonDefaultText),
          dynamicButtonTextSuffix: '{0} ' + this.i8nService.getInstantTranslation(this.originalKeyTranslationTexts.dynamicButtonTextSuffix),
          filterPlaceHolder: this.i8nService.getInstantTranslation(this.originalKeyTranslationTexts.filterPlaceHolder),
          emptyFilterMessage: this.i8nService.getInstantTranslation(this.originalKeyTranslationTexts.emptyFilterMessage),
          checkAll: this.i8nService.getInstantTranslation(this.originalKeyTranslationTexts.checkAll),
          uncheckAll: this.i8nService.getInstantTranslation(this.originalKeyTranslationTexts.uncheckAll),
          filterSelectAll: this.i8nService.getInstantTranslation(this.originalKeyTranslationTexts.filterSelectAll),
          filterUnSelectAll: this.i8nService.getInstantTranslation(this.originalKeyTranslationTexts.filterUnSelectAll)
        };

        if (isForLangChange) {
          this.translationTexts = resultTranslationsTexts;
        }
    }

    // Apply translations for options (if applies)
    if (this.doesOptionsRequireTranslations &&
        this.doesOptionsRequireTranslations.valueOf() &&
        ArrayUtil.IsNotEmptyOrNull(this.backupRowOpts)) {
        this.hasOptionsAlreadyBeenTranslated = false;
        this.applyOptionsTranslation(true);
    }

    return resultTranslationsTexts;
  }

  applyOptionsTranslation(isForLangChange: boolean = false, optionsToTranslate: any[] = null): any[] {
    // tslint:disable-next-line:prefer-const
    let optionsTranslated =  ArrayUtil.Clone(ArrayUtil.IsNotEmptyOrNull(optionsToTranslate) ? optionsToTranslate
                                                                                            : this.backupRowOpts);
    optionsTranslated.forEach(option => {
      option[this.displayProp] = this.i8nService.getInstantTranslation(option[this.displayProp]);
    });

    if (isForLangChange) {
      this.options = this.backupRowOpts;
    }

    return optionsTranslated;
  }
  //#endregion
}
#endregion
}

我在这里想念什么?有什么要定义的吗?据我了解,它应该与 18nService 有关,但我 100% 确定。

欢迎任何建议

标签: javascriptangulartypescriptkarma-jasmineweb-component

解决方案


使用NO_ERRORS_SCHEMA允许未知元素和属性

TestBed.configureTestingModule({
      declarations: [ BrandingMultiselectComponent ],
      imports: [
        BrowserModule,
        AngularMultiSelectModule,
        RouterTestingModule
      ],
      schemas: [ NO_ERRORS_SCHEMA ], // here
        providers: [ { provide: I18nService, useClass: I18nServiceStub } ]
}).compileComponents();

推荐阅读