Я пишу тест в Jasmine для моего углового компонента 4 и получаю сообщение об ошибке «Не удается прочитать свойство 'значение' из неопределенного". Где и как мне инициализировать свойство? Сначала я думал об изменении [(ngModel)]="fedExTax.value"
на [ngModel]="fedExTax?.value"
, но мне нужно двустороннее связывание. Может ли кто-нибудь предложить мне решение
Вы можете увидеть [(ngModel)]="fedExTax.value"
в HTML-код ниже
HTML-код
<div class="col-lg-3 col-6 mb-3">
<label class="col-form-label">{{'CAPTIVES.LINES.INCCAPTIVEEXPENSE.' + FedExciseKey|uppercase|translate}}</label>
<div class="input-group">
<input type="text" [readonly]="isReadOnly" class="form-control form-control-sm" [(ngModel)]="fedExTax.value" name="{{FedExciseKey}}" numberFormat="numberPercent:.0-2" (ngModelChange)="change()" [required]="true" tooltip="{{'CAPTIVES.LINES.INCCAPTIVEEXPENSE.' + FedExciseKey + 'TOOLTIP'|uppercase|translate}}"
placement="bottom">
<span class="input-group-addon">%</span>
</div>
</div>
Мой тестовый код
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule, TranslateLoader, TranslateFakeLoader } from '@ngx-translate/core';
import { FormsModule } from '@angular/forms';
import { UniqueIdPipe } from '@wtw/toolkit/src/pipes/unique-id.pipe';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { IncrementalCaptiveAssumptionsComponent } from './incremental-captive-expenses.component';
import { createIncrementalExpenses, createFedExciseExpenses } from '../../../../../../test/models';
import { Component, ViewChild } from '@angular/core';
describe('IncrementalCaptiveAssumptionsComponent', () => {
let harness: HostFormTestComponent;
let sut: IncrementalCaptiveAssumptionsComponent;
let fixture: ComponentFixture<HostFormTestComponent>;
const IncrementalExpenses = createIncrementalExpenses();
const fedExciseTaxExpenses = createFedExciseExpenses();
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
BrowserAnimationsModule,
FormsModule,
TranslateModule.forRoot({
loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
})
],
declarations: [ HostFormTestComponent, IncrementalCaptiveAssumptionsComponent, UniqueIdPipe ],
schemas: [NO_ERRORS_SCHEMA]
});
TestBed.overrideModule(BrowserDynamicTestingModule, {
set: {
entryComponents: [IncrementalCaptiveAssumptionsComponent],
}
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HostFormTestComponent);
harness = fixture.componentInstance;
fixture.detectChanges();
sut = harness.componentUnderTest;
harness.incrementalExpensesInputs = IncrementalExpenses;
harness.fedExciseExpensesInputs = fedExciseTaxExpenses;
fixture.detectChanges();
});
fit('should set fields correctly when the form is displayed', () => {
expect(JSON.stringify(sut.managerFees)).toEqual(JSON.stringify(IncrementalExpenses.find(x => x.fieldInfo.key === 'mgmgtFee')));
expect(JSON.stringify(sut.actuaryFees)).toEqual(JSON.stringify(IncrementalExpenses.find(x => x.fieldInfo.key === 'actuaryFee')));
expect(JSON.stringify(sut.auditorFees)).toEqual(JSON.stringify(IncrementalExpenses.find(x => x.fieldInfo.key === 'auditFee')));
});
@Component({
template: `
<form #pageForm="ngForm">
<incremental-captive-expenses *ngIf="incrementalExpensesInputs"
[(incrementalExpensesInputs)]="incrementalExpensesInputs"
[(fedExciseExpensesInputs)]="fedExciseExpensesInputs"
[currentSelectedCurrency]="currentSelectedCurrency"
[currentSelected953D]= "currentSelected953D"
(isValid)="incrementalExpensesValid($event)"></incremental-captive-expenses>
</form>
`
})
class HostFormTestComponent {
@ViewChild(IncrementalCaptiveAssumptionsComponent)
public componentUnderTest: IncrementalCaptiveAssumptionsComponent;
public incrementalExpensesInputs = createIncrementalExpenses();
public fedExciseExpensesInputs = createFedExciseExpenses();
public currentSelectedCurrency = 'USD';
public currentSelected953D = 0;
constructor() {
}
incrementalExpensesValid(valid) {
}
}
});
Код компонента
import { Component, SimpleChanges, OnInit, OnChanges, Input, Output, ViewChild, EventEmitter } from '@angular/core';
import { NgForm, NgModelGroup, ControlContainer, FormGroup } from '@angular/forms';
import { Base } from '@wtw/toolkit';
import * as BackendDto from '../../../../../api/dtos';
const ManagerFeesFieldKey = 'mgmgtFee';
const ActuaryFeesFieldKey = 'actuaryFee';
const AuditorFeesFieldKey = 'auditFee';
const FedExTaxFieldKey = 'fedExciseTax';
@Component({
// tslint:disable-next-line:component-selector
selector: 'incremental-captive-expenses',
templateUrl: './incremental-captive-expenses.component.html',
viewProviders: [{ provide: ControlContainer, useFactory: Base.DynamicFormFactory, deps: [IncrementalCaptiveAssumptionsComponent] }]
})
export class IncrementalCaptiveAssumptionsComponent extends Base.DynamicFormComponent implements OnInit, OnChanges {
@Input() incrementalExpensesInputs: Array<BackendDto.IncrementalExpense>;
@Input() fedExciseExpensesInputs: Array<BackendDto.FedExciseExpense>;
@Input() currentSelectedCurrency: string;
@Input() currentSelectedCoveragePolicy: number;
@Input() currentSelected953D: number;
@Input() show: boolean;
@Output() isValid = new EventEmitter<boolean>();
public FieldCategory = BackendDto.DynamicFieldCategory;
public FedExciseKey = FedExTaxFieldKey;
public isReadOnly = false;
private _fedExTax: BackendDto.FedExciseExpense = { value: 0, coveragePolicyTypeId: null, is953D: null };
@ViewChild('incrementalExpensesGroup') private incrementalExpensesGroup: NgModelGroup;
constructor(form: NgForm) {
super(form);
}
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges) {
if (changes.show && changes.show.currentValue) {
setTimeout(() => {
this.markFormGroupTouched(<FormGroup>this.formDirective.controls[this.incrementalExpensesGroup.name]);
}, 1);
this.emitFormValidity();
}
}
change() {
this.emitFormValidity();
}
emitFormValidity() {
setTimeout(() => {
this.isValid.emit(this.incrementalExpensesGroup.valid);
}, 1);
}
get managerFees(): BackendDto.IncrementalExpense {
return this.incrementalExpensesInputs.find(x => x.fieldInfo.key === ManagerFeesFieldKey);
}
get actuaryFees(): BackendDto.IncrementalExpense {
return this.incrementalExpensesInputs.find(x => x.fieldInfo.key === ActuaryFeesFieldKey);
}
get auditorFees(): BackendDto.IncrementalExpense {
return this.incrementalExpensesInputs.find(x => x.fieldInfo.key === AuditorFeesFieldKey);
}
get fedExTax(): BackendDto.FedExciseExpense {
if (this.currentSelectedCoveragePolicy !== 0) {
this.isReadOnly = false;
return this.fedExciseExpensesInputs.find(x => x.coveragePolicyTypeId === this.currentSelectedCoveragePolicy && x.is953D === this.currentSelected953D);
} else {
this.isReadOnly = true;
return this._fedExTax;
}
}
private markFormGroupTouched(formGroup: FormGroup) {
if (formGroup && formGroup.controls) {
Object.keys(formGroup.controls).forEach(key => {
const control = formGroup.controls[key];
if (control) {
control.markAsTouched();
}
});
}
}
}
models.ts
export const createFedExciseExpenses = (): Array<BackendDto.FedExciseExpense> => {
return [
{is953D : 1, coveragePolicyTypeId : 1, value : 0},
{is953D : 0, coveragePolicyTypeId : 1, value : 1},
{is953D : 0, coveragePolicyTypeId : 2, value : 4},
{is953D : 1, coveragePolicyTypeId : 2, value : 0}
];
};