Angular 2 - 绑定到嵌套自定义表单控件的单元测试
Angular 2 - Unit test binding to nested custom form control
我从带有选择器 app-date-picker
的控件中获得了一个自定义项。它实现了 ControlValueAccessor
。我有一个名为 MyPage
的组件,它通过以下方式包含此自定义表单控件:
<app-date-picker class="address__from-date" [(ngModel)]="fromDate"></app-date-picker>
我正在尝试为 MyPage
编写单元测试来测试绑定的两个方向。我已经为其他表单字段完成了这项工作,例如:
it('should bind zip code', fakeAsync(() => {
const formControl = element.query(By.css('.address__zip-code input')).nativeElement;
// model -> view
component.zipCode = 76777;
fixture.detectChanges();
tick();
expect(formControl.value).toEqual('76777');
// view -> model
formControl.value = '89556';
formControl.dispatchEvent(new Event('input'));
expect(component.zipCode).toEqual(89556);
}));
当我尝试为我的自定义表单控件执行此操作时出现了问题。到目前为止,我只能测试一个方向的绑定,即使这样它也需要使用ng-reflect-model
,这太糟糕了:
it('should bind from-date', fakeAsync(() => {
const formControl = element.query(By.css('.address__from-date app-date-picker')).nativeElement;
// model -> view
component.fromDate = '01/2017';
fixture.detectChanges();
tick();
expect(formControl.attributes['ng-reflect-model'].value).toEqual('01/2017');
// view -> model
// Not sure what to do here either
}));
有没有更好的方法来做到这一点?我想:
- 能够从
MyPage
单元测试中测试绑定的两个方向,这样我就知道我正确地将它连接到表单控件
- 不必使用
ng-reflect-*
其他说明:
MyPage
组件是具有 fromDate
(和 zipCode
)字段的标准组件。
自定义表单字段正确实现 ControlValueAccessor
,有一个 date
字段,并在内部使用 <input>
,它本身通过 ngModel
:[=36 绑定=]
<input [(ngModel)]="date" type="date">
DatePickerComponent
@Component({
selector: 'app-date-picker',
templateUrl: './date-picker.component.html',
styleUrls: ['./date-picker.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DatePickerComponent),
multi: true
}]
})
export class DatePickerComponent implements ControlValueAccessor {
private propagateChange = (_: any) => {};
private _date: string;
get date(): string {
return this._date;
}
@Input()
set date(value: string) {
this._date = value;
this.propagateChange(value);
}
writeValue(newValue: any) {
if (newValue !== undefined) {
this.date = newValue;
}
}
registerOnChange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched(fn: any) {
// Not used
}
}
2017 年 8 月 10 日更新
到目前为止,我已经接近我想要的 @ViewChild
:
@Component(...)
MyPageComponent {
@ViewChild('fromDate') fromDatePicker: DatePickerComponent;
...
}
MyPage
模板变为(注意模板引用变量#fromDate
):
<app-date-picker #fromDate class="address__from-date" [(ngModel)]="fromDate">
</app-date-picker>
那么测试就变成了:
it('should bind from-date', fakeAsync(() => {
// model -> view
component.info.fromDate = '01/2017';
fixture.detectChanges();
tick();
expect(component.fromDatePicker.date).toEqual('01/2017');
// view -> model
component.fromDatePicker.date = '02/2017';
expect(component.info.fromDate).toEqual('02/2017');
}));
有人知道更好的方法吗?虽然这不是最佳选择,但我暂时还可以。
好吧,终于找到了一个令我满意的答案,因为它避免了 ng-reflect-*
和 @ViewChild
。我可以调用 .componentInstance
,而不是调用 .nativeElement
。那么测试就变得简单了:
it('should bind from-date', fakeAsync(() => {
const formControl = element.query(By.css('.address__from-date app-date-picker')).componentInstance;
// model -> view
component.fromDate = '01/2017';
fixture.detectChanges();
tick();
expect(formControl.date).toEqual('01/2017');
// view -> model
formControl.date = '02/2017';
expect(component.fromDate).toEqual('02/2017');
}));
如果有人有更好的解决方案,我仍然持开放态度,但我暂时将其标记为答案。希望它能帮助其他人运行!
我从带有选择器 app-date-picker
的控件中获得了一个自定义项。它实现了 ControlValueAccessor
。我有一个名为 MyPage
的组件,它通过以下方式包含此自定义表单控件:
<app-date-picker class="address__from-date" [(ngModel)]="fromDate"></app-date-picker>
我正在尝试为 MyPage
编写单元测试来测试绑定的两个方向。我已经为其他表单字段完成了这项工作,例如:
it('should bind zip code', fakeAsync(() => {
const formControl = element.query(By.css('.address__zip-code input')).nativeElement;
// model -> view
component.zipCode = 76777;
fixture.detectChanges();
tick();
expect(formControl.value).toEqual('76777');
// view -> model
formControl.value = '89556';
formControl.dispatchEvent(new Event('input'));
expect(component.zipCode).toEqual(89556);
}));
当我尝试为我的自定义表单控件执行此操作时出现了问题。到目前为止,我只能测试一个方向的绑定,即使这样它也需要使用ng-reflect-model
,这太糟糕了:
it('should bind from-date', fakeAsync(() => {
const formControl = element.query(By.css('.address__from-date app-date-picker')).nativeElement;
// model -> view
component.fromDate = '01/2017';
fixture.detectChanges();
tick();
expect(formControl.attributes['ng-reflect-model'].value).toEqual('01/2017');
// view -> model
// Not sure what to do here either
}));
有没有更好的方法来做到这一点?我想:
- 能够从
MyPage
单元测试中测试绑定的两个方向,这样我就知道我正确地将它连接到表单控件 - 不必使用
ng-reflect-*
其他说明:
MyPage
组件是具有 fromDate
(和 zipCode
)字段的标准组件。
自定义表单字段正确实现 ControlValueAccessor
,有一个 date
字段,并在内部使用 <input>
,它本身通过 ngModel
:[=36 绑定=]
<input [(ngModel)]="date" type="date">
DatePickerComponent
@Component({
selector: 'app-date-picker',
templateUrl: './date-picker.component.html',
styleUrls: ['./date-picker.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DatePickerComponent),
multi: true
}]
})
export class DatePickerComponent implements ControlValueAccessor {
private propagateChange = (_: any) => {};
private _date: string;
get date(): string {
return this._date;
}
@Input()
set date(value: string) {
this._date = value;
this.propagateChange(value);
}
writeValue(newValue: any) {
if (newValue !== undefined) {
this.date = newValue;
}
}
registerOnChange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched(fn: any) {
// Not used
}
}
2017 年 8 月 10 日更新
到目前为止,我已经接近我想要的 @ViewChild
:
@Component(...)
MyPageComponent {
@ViewChild('fromDate') fromDatePicker: DatePickerComponent;
...
}
MyPage
模板变为(注意模板引用变量#fromDate
):
<app-date-picker #fromDate class="address__from-date" [(ngModel)]="fromDate">
</app-date-picker>
那么测试就变成了:
it('should bind from-date', fakeAsync(() => {
// model -> view
component.info.fromDate = '01/2017';
fixture.detectChanges();
tick();
expect(component.fromDatePicker.date).toEqual('01/2017');
// view -> model
component.fromDatePicker.date = '02/2017';
expect(component.info.fromDate).toEqual('02/2017');
}));
有人知道更好的方法吗?虽然这不是最佳选择,但我暂时还可以。
好吧,终于找到了一个令我满意的答案,因为它避免了 ng-reflect-*
和 @ViewChild
。我可以调用 .componentInstance
,而不是调用 .nativeElement
。那么测试就变得简单了:
it('should bind from-date', fakeAsync(() => {
const formControl = element.query(By.css('.address__from-date app-date-picker')).componentInstance;
// model -> view
component.fromDate = '01/2017';
fixture.detectChanges();
tick();
expect(formControl.date).toEqual('01/2017');
// view -> model
formControl.date = '02/2017';
expect(component.fromDate).toEqual('02/2017');
}));
如果有人有更好的解决方案,我仍然持开放态度,但我暂时将其标记为答案。希望它能帮助其他人运行!