Angular4组件测试错误无法解析AutocompleteComponent的所有参数:(?,?,?)

时间:2018-02-21 18:30:29

标签: angular

我采取了新的立场,现在我必须保持angular4自定义组件,而不进行单元测试(我是一个角形菜鸟;)。

我在添加新功能(业力+茉莉花框架)之前正在编写单元测试,我正在绊倒这个错误的问题并且无法解决这个问题(尝试提供参数,更改测试定义,......):

  Failed: Can't resolve all parameters for AutocompleteComponent: (?, ?, ?).
    Error: Can't resolve all parameters for AutocompleteComponent: (?, ?, ?).
        at syntaxError (spec.bundle.js:36817:34)
        at CompileMetadataResolver._getDependenciesMetadata (spec.bundle.js:50893:35)
        at CompileMetadataResolver._getTypeMetadata (spec.bundle.js:50761:26)
        at CompileMetadataResolver.getNonNormalizedDirectiveMetadata (spec.bundle.js:50356:24)
        at CompileMetadataResolver.loadDirectiveMetadata (spec.bundle.js:50220:25)
        at spec.bundle.js:61930:72
        at Array.forEach (<anonymous>)
        at spec.bundle.js:61929:72
        at Array.forEach (<anonymous>)
        at JitCompiler._loadModules (spec.bundle.js:61926:75)
        at JitCompiler._compileModuleAndAllComponents (spec.bundle.js:61909:36)
        at JitCompiler.compileModuleAndAllComponentsAsync (spec.bundle.js:61841:37)
        at TestingCompilerImpl.compileModuleAndAllComponentsAsync (spec.bundle.js:85709:31)
        at TestBed.compileComponents (spec.bundle.js:20190:31)
        at Function.TestBed.compileComponents (spec.bundle.js:20073:67)
        at UserContext.<anonymous> (spec.bundle.js:86201:14)
        at ZoneDelegate.invoke (spec.bundle.js:68195:26)
        at AsyncTestZoneSpec.onInvoke (spec.bundle.js:71078:39)
        at ProxyZoneSpec.onInvoke (spec.bundle.js:70777:39)
        at ZoneDelegate.invoke (spec.bundle.js:68194:32)
        at Zone.runGuarded (spec.bundle.js:67958:47)
        at runInTestZone (spec.bundle.js:19557:25)
        at UserContext.<anonymous> (spec.bundle.js:19496:13)
        at ZoneDelegate.invoke (spec.bundle.js:68195:26)
        at ProxyZoneSpec.onInvoke (spec.bundle.js:70780:39)
        at ZoneDelegate.invoke (spec.bundle.js:68194:32)
        at Zone.run (spec.bundle.js:67945:43)
        at UserContext.<anonymous> (spec.bundle.js:70986:34)
        at ZoneQueueRunner.jasmine.QueueRunner.ZoneQueueRunner.execute (spec.bundle.js:71016:42)
        at ZoneQueueRunner.jasmine.QueueRunner.ZoneQueueRunner.execute (spec.bundle.js:71016:42)
        at spec.bundle.js:71013:130
        at ZoneDelegate.invokeTask (spec.bundle.js:68228:31)
        at Zone.runTask (spec.bundle.js:67995:47)
        at drainMicroTaskQueue (spec.bundle.js:68399:35)
        at <anonymous>

组件:

import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
forwardRef,
Input,
OnChanges,
OnInit,
Output,
Renderer2,
SimpleChanges,
TemplateRef,
ViewChild
} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, NgControl}    from '@angular/forms';
import {Subject} from 'rxjs';
import {AbstractInput} from '../shared/abstract-input';
import {isNullOrUndefined, isString} from 'util';
import {IAutocompleteService} from './autocomplete-service';
import {TypeaheadMatch} from 'ngx-bootstrap';
import {HttpClient} from '@angular/common/http';

export const WH_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => AutocompleteComponent),
    multi: true
};

@Component({
    selector: 'cs-autocomplete',
    templateUrl: './autocomplete.component.html',
    styleUrls: ['./autocomplete.component.scss'],
    providers: [WH_AUTOCOMPLETE_VALUE_ACCESSOR],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class AutocompleteComponent extends AbstractInput implements     AfterViewInit, ControlValueAccessor, OnChanges, OnInit {


values: Array<any> = [];
valueFilter: Observable<any>;
haveFocus: boolean;

@Input() width: string = this.DEFAULT_WIDTH;
@Input() editable: boolean = this.DEFAULT_EDITABLE;
@Input() numberOfValues: number = 1;
@Input() removeTag: boolean = false;
@Input() backendUrl: string;
@Input() dataSource: Array<any> = [];
filteredDataSource: Subject<any> = new Subject<any>();
@Input() typeaheadOptionField: string;
@Input() typeaheadOptionsLimit: number;
@Input() typeaheadMinLength: number = 2;
@Input() typeaheadWaitMs: number = 0;
@Input() typeaheadAsync: boolean = false;
@Input() typeaheadLatinize: boolean = true;
@Input() typeaheadSingleWords: boolean = true;
@Input() typeaheadWordDelimitaers: string = ' ';
@Input() typeaheadPhraseDelimiters: string = '\'"';
@Input() typeaheadItemTemplate: TemplateRef<any>;
@Input() nbResult: number = 10;
@Input() searchProperty: string;
@Input() service: IAutocompleteService<any>;
@Input() formControl: FormControl;
@Input() submitted: boolean;
@Input() autoSelectSingleResult: boolean = false;
@Input() params: any;
currentItemTemplate: TemplateRef<any>;
@Input() maxLength: string = 'number';

@Output() typeaheadLoading: EventEmitter<boolean> = new EventEmitter<boolean>();
@Output() typeaheadNoResults: EventEmitter<boolean> = new EventEmitter<boolean>();
@Output() selectionChanged: EventEmitter<any> = new EventEmitter<any>();

@ViewChild('input') input: ElementRef;
@ViewChild('button') button: ElementRef;
@ViewChild(NgControl) ngControl: NgControl;
@ViewChild('defaultItemTemplate') defaultItemTemplate: TemplateRef<any>;

current: any = '';
shouldRemovedTag: boolean = true;

constructor(private http: HttpClient, cdr: ChangeDetectorRef, private renderer: Renderer2) {
    super(cdr);
}

ngOnInit(): void {
    this.currentItemTemplate = this.defaultItemTemplate;
    if (this.typeaheadItemTemplate) {
        this.currentItemTemplate = this.typeaheadItemTemplate;
    }
}

ngOnChanges(changes: SimpleChanges): void {
    super.ngOnChanges(changes);

    if (changes.typeaheadItem) {
        this.currentItemTemplate = this.typeaheadItemTemplate;
    }

    if (changes.dataSource) {
        //console.log('dataSource', this.dataSource);
    }
}

ngAfterViewInit() {
    this.valueFilter = this.ngControl.valueChanges.filter(value => {
        return value.length >= this.typeaheadMinLength;
    });

    if (!this.backendUrl && !this.service) {
        this.valueFilter.subscribe(value => {
            let filteredData = this.dataSource.filter(state => {
                let s: string = state[this.typeaheadOptionField].toLowerCase();
                return !value || s.startsWith(value.toLowerCase());
            });
            this.filteredDataSource.next(filteredData);
            if (this.autoSelectSingleResult && filteredData.length === 1) {
                this.internalSelect(filteredData[0], true);
            }
        });
    } else {
        this.valueFilter
            .debounceTime(500)
            .filter((filter) => {
                return filter !== '' && this.numberOfValues > this.values.length;
            })
            .switchMap(filter => this.callService(filter))
            .subscribe((data: any[]) => {
                this.filteredDataSource.next(data);
                if (this.autoSelectSingleResult && data.length === 1) {
                    this.internalSelect(data[0], true);
                }
            });
    }
}

keyUp(event: KeyboardEvent) {
    if (event.keyCode === 8 && this.current.length === 0 && this.values.length > 0) { // BACK Key
        if (this.shouldRemovedTag) {
            if (this.removeTag) {
                this.values.pop()[this.typeaheadOptionField];
            } else {
                this.values.pop();
                this.current = '';
            }
            this.updateModel();
        }
        this.shouldRemovedTag = !this.shouldRemovedTag;
    } else if (event.keyCode === 32) { // SPACE Key
        this.current = this.current.replace(/ +/g, ' ');
    }
}

keyDown(event: KeyboardEvent) {
    return this.values.length !== this.numberOfValues || event.keyCode === 9;
}

blur() {
    this.current = '';
}

typeaheadOnSelectHandler(selected: TypeaheadMatch): void {
    const selectedItem = (selected && selected.item) ? selected.item : '';
    this.internalSelect(selectedItem, true);
}

typeaheadLoadingHandler($event: any): void {
    this.typeaheadLoading.emit($event);
}

typeaheadNoResultsHandler($event: any): void {
    this.typeaheadNoResults.emit($event);
}

removeTagHandler(tag: any): void {
    let index = this.values.indexOf(tag);
    this.values.splice(index, 1);
    this.shouldRemovedTag = false;
    this.updateModel();
    this.selectionChanged.emit(null);
}

writeValue(values: any): void {
    if (!isNullOrUndefined(values) && Array.isArray(values)) { // cas array non vide
        this.values = values;
    } else if (!isNullOrUndefined(values) && !Array.isArray(values) && !isString(values)) { // cas object non vide
        this.values = [values];
    } else if (!isNullOrUndefined(values) && !Array.isArray(values) && isString(values)) {
        this.current = values;
    } else if (isNullOrUndefined(values)) {
        this.values = [];
    }

    this.cdr.markForCheck();
}

setDisabledState(isDisabled: boolean): void {
    this.renderer.setProperty(this.input.nativeElement, 'disabled', isDisabled);
    if (this.button) {
        this.renderer.setProperty(this.button.nativeElement, 'disabled', isDisabled);
    }

    this.cdr.markForCheck();
}

onFocus() {
    this.haveFocus = true;
}

onFocusOut() {
    this.haveFocus = false;
}

public focus(): void {
    if (!isNullOrUndefined(this.input)) {
        this.input.nativeElement.focus();
    }
}

private updateModel() {
    if (this.numberOfValues > 1) {
        this.onChangeCallback(this.values && this.values.length > 0 ? this.values : null);
    } else {
        this.onChangeCallback(this.values && this.values.length > 0 ? this.values[0] : null);
    }
}

private callService(filter: string): Observable<any> {
    if (this.backendUrl) {
        return this.http.get(`${this.backendUrl}?q=${filter}`);
    }

    if (this.service) {
        const map = new Map();
        for (const p in this.params) {
            map.set(p, this.params[p]);
        }
        return this.service.findAutocomplete(filter, this.searchProperty ? this.searchProperty : this.typeaheadOptionField, this.nbResult, map);
    }
}

private internalSelect(selectedItem: any, emitEvents?: boolean): void {
    if (this.values.indexOf(selectedItem) < 0) {
        this.values.push(selectedItem);
    }
    this.current = '';
    this.input.nativeElement.focus();
    if (emitEvents) {
        this.updateModel();
        this.selectionChanged.emit(selectedItem);
    }
}

}

和测试:

import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ChangeDetectorRef, Renderer2 } from '@angular/core';
import { AutocompleteComponent } from './autocomplete.component';
describe('Component: Autocomplete', () => {
let component: AutocompleteComponent;
let fixture: ComponentFixture<AutocompleteComponent>;

beforeEach(async(() => {
  TestBed.configureTestingModule({
    imports: [HttpClientTestingModule],
    declarations: [AutocompleteComponent]
  })
  .overrideTemplate(AutocompleteComponent, '')
  .compileComponents();
}));

beforeEach(() => {
  fixture = TestBed.createComponent(AutocompleteComponent);
  fixture.detectChanges();
  component = fixture.componentInstance;
});

 fit('should create an instance', () => {
 expect(component).toBeTruthy();
 });

});

有什么建议吗?

0 个答案:

没有答案