错误:没有DirectiveResolver的提供者!尝试在单元测试

时间:2016-06-28 23:30:44

标签: unit-testing typescript angular

我正在尝试测试我的angular2应用程序。当我向TestComponentBuilder添加导入时,它会抛出一个错误:错误:没有DirectiveResolver的提供者!

这是一个使用Angular2 RC2的打字稿项目。

这是我的规范(测试)文件:

import { Component, provide, EventEmitter} from '@angular/core';
import { HTTP_PROVIDERS }    from '@angular/http';

import { PreliminaryNotice } from '../models/preliminary-notice-model';
import { IndustryCategoryDynamicLookup } from '../models/industry-category-dynamic-lookup-model';
import { PreliminaryNoticeService } from '../preliminary-notice-service/preliminary-notice.service';
import { ExpandedPreliminaryNotice } from './expanded-preliminary-notice';

import { beforeEachProviders,
    beforeEach,
    inject,
    injectAsync,
    fakeAsync,
    it,
    describe,
    expect
} from '@angular/core/testing';

import { TestComponentBuilder } from '@angular/compiler/testing';

class PreliminaryNoticeServiceMock {
    public preliminaryNoticeSaved: EventEmitter<PreliminaryNotice> = new EventEmitter<PreliminaryNotice>(false);

    public savePreliminaryNotice(preliminaryNotice: PreliminaryNotice) {
        this.preliminaryNoticeSaved.next(preliminaryNotice);
    }
}

describe('expandable preliminary notice tests',
() => {
    let builder: TestComponentBuilder;
    let preliminaryNoticeService: PreliminaryNoticeService;

    beforeEachProviders(() => [
        TestComponentBuilder,
        provide(PreliminaryNoticeService, { useClass: PreliminaryNoticeServiceMock })
    ]);

    beforeEach(inject([PreliminaryNoticeService, TestComponentBuilder],
    (pns: PreliminaryNoticeService, tsb: TestComponentBuilder) => {
        preliminaryNoticeService = pns;
        builder = tsb;
    }));

    it('view invoices expanded',
    (done: () => void) =>
    {
        return builder.createAsync(ExpandedPreliminaryNotice)
            .then(fixture => {
                        let nativeElement = fixture.nativeElement;
                        fixture.detectChanges();
                        fixture.nativeElement.querySelector('showOpenInvoicesHeader').click();
                        fixture.detectChanges();
                        expect(nativeElement.querySelector('showOpenInvoicesContent') === null).toBe(false);
                done();
            });
    });

    it('view log expanded',
    () => {
        expect(false).toEqual(true);
    });
});

以下是组件:

import { Component, Injectable, Inject, EventEmitter, OnInit, Output } from '@angular/core';
import { NgClass } from '@angular/common';
import { PreliminaryNotice } from '../models/preliminary-notice-model';
import { IndustryCategoryDynamicLookup } from '../models/industry-category-dynamic-lookup-model';
import { PreliminaryNoticeService } from '../preliminary-notice-service/preliminary-notice.service';

@Component({
    selector: 'expanded-preliminary-notice',
    inputs: ['preliminaryNotice: preliminaryNotice'],
    templateUrl: 'app/grid/expanded-preliminary-notice.html',
    styles: [
        `
.expanded-preliminary-notice {
    padding: 10px;
}

.bold-span {
    font-weight: bold;
    margin-right: 5px;
}

.pad-left {
    margin-left: 90px;
}

.link-div {
    color:  #1F7A74;
    cursor: pointer;
    width: 300px;
    margin-top: 10px;
    margin-bottom: 10px;
}

.align-left {
    text-align: left;
}

.align-right {
    text-align: right;
}

.border-top {
    border-top: 1px solid black;
}

.border-bottom {
    border-bottom: 1px solid black;
}

.bold-gold {
    color: #A95C15;
    font-weight: bold;
}

table {
    border-collapse: collapse;
}

.backgrounded {
    font-family: 'Arial Regular', 'Arial';
    font-weight: 400;
    font-style: normal;
    font-size: 13px;
    color: #333333;
    text-align: left;
    background-color: #D9D1BD;
}

.number-input {
    width: 75px;
}

.h2 {
    font-family: Arial;
    font-weight: Bold;
    font-size: 22px;
    color: #13294b;
}

.h3 {
    font-family: Arial;
    font-weight: Normal;
    font-size: 20px;
    color: #a6601c;
}

.h4 {
    font-family: Arial;
    font-weight: bold;
    font-size: 18px;
    color: #a6601c;
}

#showPreliminaryNoticeStatus {
    margin-top: 5px;
    margin-bottom: 25px;
}

.thick-hr {
    border: none;
    height: 5px;
    color: #746F63;
    background-color: #746F63;
}

`
    ]
})

export class ExpandedPreliminaryNotice {
    @Output()
    doViewThresholds: EventEmitter<boolean> = new EventEmitter<boolean>(false);

    private preliminaryNotice: PreliminaryNotice;
    private originalPreliminaryNotice: PreliminaryNotice;
    private backgrounColor: string = "transparent";
    private timerToken: any;
    private intervalToken: any;
    private opacity: number = 1;
    protected primaryBusinesses: IndustryCategoryDynamicLookup[] = [
        {
            id: 1,
            industryCategoryName: "Construction",
            industryCategoryDescription: "Construction",
            isLienableFlag: true,
            displaySortOrder: 1
        },
        {
            id: 3,
            industryCategoryName: "Hospitality",
            industryCategoryDescription: "Hospitality",
            isLienableFlag: false,
            displaySortOrder: 2
        },
        {
            id: 2,
            industryCategoryName: "Manufacturing and Logistics",
            industryCategoryDescription: "Manufacturing and Logistics",
            isLienableFlag: false,
            displaySortOrder: 3
        },
        {
            id: 5,
            industryCategoryName: "Retail",
            industryCategoryDescription: "Retail",
            isLienableFlag: false,
            displaySortOrder: 4
        },
        {
            id: 4,
            industryCategoryName: "Transportation",
            industryCategoryDescription: "Transportation",
            isLienableFlag: false,
            displaySortOrder: 5
        },
        {
            id: 6,
            industryCategoryName: "Other",
            industryCategoryDescription: "Other",
            isLienableFlag: false,
            displaySortOrder: 20
        }
    ]; // todo:  pull from service.
    protected noticeStatuses: string[] = ['New', 'Suggested', 'Completed', 'Released']; // todo: pull from service.
    protected doShowStatusLog: boolean = false;
    protected doShowOpenInvoices: boolean = false;
    protected doShowAllOrders: boolean = false;
    protected setThresholdValue: number;

    constructor(private preliminaryNoticeService: PreliminaryNoticeService) {

    }

    ngOnInit() {
        this.preliminaryNoticeService.preliminaryNoticeSaved.subscribe((preliminaryNotice: PreliminaryNotice) => {
            this.preliminaryNotice = preliminaryNotice;
            this.originalPreliminaryNotice = JSON.parse(JSON.stringify(this.preliminaryNotice));
            this.startSaveTimer(2500);
        });
        this.setThresholdValue = this.preliminaryNotice.lienThresholdAmount;
    }

    ngAfterViewInit() {
        //  cheap clone.
        this.originalPreliminaryNotice = JSON.parse(JSON.stringify(this.preliminaryNotice));
    }

    protected getCssClass() {
        return {
            'padding': '10px'
            ,'background-color': this.backgrounColor
        }
    }

    // 1 == stateThreshold, 2 == custom, 3 == don't send.
    protected set noticeSetTo(value: number) {
        this.preliminaryNotice.customerPreliminaryNoticeOption = value;
    }

    protected get noticeSetTo(): number {
        return this.preliminaryNotice.customerPreliminaryNoticeOption;
    }

    protected getInvoiceStyle(isLienable: boolean, textAlign: string) {
        if (isLienable) {
            return {
                "font-weight": "bold",
                "text-align": textAlign,
                "border-bottom": "1px solid lightgray"
            }
        }
        return {
            "font-weight": "normal",
            "text-align": textAlign,
            "border-bottom": "1px solid lightgray"
        }
    }

    public toggleShowPreliminaryNoticeStatusLog() {
        this.doShowStatusLog = !this.doShowStatusLog;
    }

    public toggleShowOpenInvoices() {
        this.doShowOpenInvoices = !this.doShowOpenInvoices;
    }

    public toggleShowAllOrders() {
        this.doShowAllOrders = !this.doShowAllOrders;
    }

    public viewThresholds() {
        this.doViewThresholds.next(true);
    }

    public getStatusLogToggleText() {
        if (this.doShowStatusLog) {
            return "Hide preliminary notice status change log";
        }
        return "Show preliminary notice status change log";
    }

    public getOpenInvoicesToggleText() {
        if (this.doShowOpenInvoices) {
            return "Hide open invoices";
        }
        return "Show open invoices";
    }

    public getAllOrdersToggleText() {
        if (this.doShowAllOrders) {
            return "Hide orders";
        }
        return "Show orders";
    }

    public save() {
        this.preliminaryNoticeService.savePreliminaryNotice(this.preliminaryNotice);
    }

    public undo() {
        this.preliminaryNotice = JSON.parse(JSON.stringify(this.originalPreliminaryNotice));
        this.setThresholdValue = this.preliminaryNotice.lienThresholdAmount;
    }

    private startSaveTimer(time: number) {
        if (this.timerToken) {
            clearTimeout(this.timerToken);
            this.timerToken = 0;
        }
        if (this.intervalToken) {
            clearInterval(this.intervalToken);
        }
        this.opacity = 1;
        this.timerToken = setTimeout(() => this.backgrounColor = "transparent", time);
        this.intervalToken = setInterval(() => {
            if (this.opacity < 0) return;
            this.opacity -= .02;
            this.backgrounColor = "rgba(154, 212, 113, " + this.opacity + ")";
        }, 10);
    }
}

模板html:

<div [ngStyle]="getCssClass()" id="expandablePreliminaryNotice_{{preliminaryNotice.id}}">
    <div class="h2">{{preliminaryNotice.customerName}}</div>

    <div>
        <span class="bold-span" id="expandPrelimNoticeSettingsSpan">Preliminary notice threshold settings for new orders</span>
        <span class="bold-span" style="margin-left: 125px;">Primary business</span>
        <select id="primaryBusinessSelect" [(ngModel)]="preliminaryNotice.customerJobType">
            <option *ngFor="let primaryBusiness of primaryBusinesses" [value]="primaryBusiness.id">{{primaryBusiness.industryCategoryDescription}}</option>
        </select>
    </div>
    <div id="thresholdsSettingsDiv">
        <input type="radio" name="thresholdGroup" value="1" [ngModel]="{checked: noticeSetTo === 1}" (change)="noticeSetTo=$event.target.value"/>Use state threshold of jobsite
        <div id="viewThresholdDiv" style="display: inline-block" class="link-div" (click)="viewThresholds()">View thresholds</div> <br />
        <input type="radio" name="thresholdGroup" value="2" [ngModel]="{checked: noticeSetTo === 2}" (change)="noticeSetTo=$event.target.value" />Set threshold to <input type="number" [(ngModel)]="setThresholdValue" class="align-right number-input" [disabled]="noticeSetTo != 2"/><br />
        <input type="radio" name="thresholdGroup" value="3" [ngModel]="{checked: noticeSetTo === 3}" (change)="noticeSetTo=$event.target.value" />Do not send notices
    </div>
    <hr />
    <div class="h4">{{preliminaryNotice.jobSiteName}} at {{preliminaryNotice.formattedAddress}}</div>
    <!--<div class="h4">{{preliminaryNotice.formattedAddress}}</div>-->
    <div>
        <span class="bold-span">Preliminary notice status</span>
        <select id="noticeStatus">
            <option *ngFor="let status of noticeStatuses" [value]="status">{{status}}</option>
        </select>

        <span class="bold-span align-left pad-left">Lien threshold</span><input type="number" [(ngModel)]="preliminaryNotice.lienThresholdAmount" class="align-right number-input" />

        <span class="bold-span align-left pad-left">Estimated amount</span><input type="number" [(ngModel)]="preliminaryNotice.estimateAmount" class="align-right number-input" />
    </div>
    <div id="showPreliminaryNoticeStatus">
        <div id="showPreliminaryNoticeStatusHeader" (click)="toggleShowPreliminaryNoticeStatusLog()" class="link-div">{{getStatusLogToggleText()}}</div>
        <div id="showPreliminaryNoticeStatusContent" *ngIf="doShowStatusLog" style="width: 450px;">
            <table>
                <thead>
                    <tr>
                        <td class="bold-gold border-bottom" style="width: 120px;">Filing status</td>
                        <td class="bold-gold border-bottom" style="width: 130px;">Date and time</td>
                        <td class="bold-gold border-bottom" style="width: 200px;">Changed by</td>
                    </tr>
                </thead>
                <tbody *ngFor="let log of preliminaryNotice.statusChangeLog">
                    <tr>
                        <td style="border-bottom: 1px solid lightgray">{{log.status}}</td>
                        <td style="border-bottom: 1px solid lightgray">{{log.date | date:"MM/dd/yy"}} {{log.date | date:'shortTime'}}</td>
                        <td style="border-bottom: 1px solid lightgray">{{log.changedBy}}</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
    <div id="invoicesDiv" class="level-one-no-left-margin">
        <table style="width: 200px">
            <thead>
                <tr>
                    <td colspan="2" class="backgrounded">OPEN INVOICES</td>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td class="bold-span align-left">Construction</td>
                    <td class="bold-span align-right">{{preliminaryNotice.openLienableInvoiceAmount | number:'1.2-2'}}</td>
                </tr>
                <tr>
                    <td class="align-left">Non-Construction</td>
                    <td class="align-right">{{preliminaryNotice.openNonLienableInvoiceAmount | number:'1.2-2'}}</td>
                </tr>
                <tr>
                    <td class="align-left border-top">TOTAL</td>
                    <td class="align-right border-top">{{preliminaryNotice.openNonLienableInvoiceAmount + preliminaryNotice.openLienableInvoiceAmount | number:'1.2-2'}}</td>
                </tr>
            </tbody>
        </table>
        <div id="showOpenInvoices">
            <div id="showOpenInvoicesHeader" (click)="toggleShowOpenInvoices()" class="link-div">{{getOpenInvoicesToggleText()}}</div>
            <div id="showOpenInvoicesContent" *ngIf="doShowOpenInvoices" style="width: 275px;">
                <table>
                    <thead>
                        <tr>
                            <td class="bold-gold border-bottom" style="width: 85px;">Number</td>
                            <td class="bold-gold border-bottom" style="width: 90px;">Date</td>
                            <td class="bold-gold border-bottom" style="width: 100px;">Balance</td>
                        </tr>
                    </thead>
                    <tbody *ngFor="let invoice of preliminaryNotice.invoices">
                        <tr>
                            <td [ngStyle]="getInvoiceStyle(invoice.invoiceJobType.isLienableFlag, 'left')">{{invoice.number}}</td>
                            <td [ngStyle]="getInvoiceStyle(invoice.invoiceJobType.isLienableFlag, 'left')">{{invoice.invoiceDate | date:"MM/dd/yyyy"}}</td>
                            <td [ngStyle]="getInvoiceStyle(invoice.invoiceJobType.isLienableFlag, 'right')">{{invoice.balanceAmount | number:'1.2-2'}}</td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
    <div id="ordersDiv" class="level-one-no-left-margin">
        <span class="backgrounded">ALL ORDERS</span><br />
        <div id="showAllOrders">
            <div id="showAllOrdersHeader" (click)="toggleShowAllOrders()" class="link-div">{{getAllOrdersToggleText()}}</div>
            <div id="showAllOrdersContent" *ngIf="doShowAllOrders" style="width: 500px; background-color: #00ced1; height: 100px;">
            </div>
        </div>
    </div>
    <hr class="thick-hr" />
    <div class="buttons-div">
        <button (click)="save()" class="primary-button">Save</button>
        <button (click)="undo()" class="secondary-button">Undo</button>
    </div>
</div>

我尝试为DirectiveResolver添加一个导入,这让我开始不得不手动添加许多其他项目,我猜这不是正确的方法。

已经有一段时间了,因为我在这里发布了一个问题所以如果我遗漏了什么请告诉我,我会添加它。

1 个答案:

答案 0 :(得分:0)

您是否尝试过这些导入:

import {DirectiveResolver} from '@angular/compiler';
import {DirectiveMetadata, Type} from '@angular/core';

var directiveResolver = new DirectiveResolver();