我实现了一个自定义选择控件,它在内部使用本机html选择。
所选选项通过[(ngModel)]
绑定到自定义选择。
在我的单元测试中,我初始化一个带有有效id的变量,以便在select元素中进行预选,但它不起作用。
以下是代码:
MY-select.component.html:
<select
#select
(change)="onChange($event)"
(blur)="onBlur()">
<option *ngFor="let optionItem of options"
[value]="optionItem.id !== undefined ? optionItem.id : optionItem.key"
[selected]="'' + optionItem.id === selectedOption || optionItem.key === selectedOption">
{{ optionItem.value }}
</option>
</select>
MY-select.component.ts
import {
Component, ElementRef, EventEmitter, forwardRef, Input, OnChanges, OnInit, Output, SimpleChanges,
ViewChild
} from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-my-select',
templateUrl: './my-select.component.html',
styleUrls: ['./my-select.component.css'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MySelectComponent),
multi: true
}]
})
export class MySelectComponent implements OnInit, OnChanges {
/**
* Internal select element.
*/
@ViewChild('select') selectElement: ElementRef;
@Input() options: any[];
@Output() select = new EventEmitter<string | number>();
/**
* Currently selected option.
*/
selectedOption?: string;
constructor() {
}
ngOnInit() {
}
/**
* Handling user's select change.
* @param {Event} event
*/
onChange(event: Event) {
const element = <HTMLSelectElement>this.selectElement.nativeElement;
this.selectedOption = element.options[element.selectedIndex].value;
this.propagateAndEmitValue();
}
onBlur() {
this.onTouched();
}
ngOnChanges(changes: SimpleChanges) {
if (changes.options && changes.options.currentValue && changes.options.currentValue.length > 0) {
if (!this.isSelectedOptionAvailableInOptions()) {
this.selectFirstOption();
}
}
}
/**
* Write a new value to the element.
*/
writeValue(obj: any) {
if (obj !== undefined && obj !== null) {
this.selectedOption = '' + obj;
} else {
this.selectFirstOption();
this.propagateAndEmitValue();
}
}
/**
* Set the function to be called when the control receives a change event.
*/
registerOnChange(fn: any) {
this.propagateChange = fn;
}
/**
* Set the function to be called when the control receives a touch event.
*/
registerOnTouched(fn: any) {
this.onTouched = fn;
}
/**
* the method set in registerOnChange, it is just
* a placeholder for a method that takes one parameter,
* we use it to emit changes back to the form
* @param _
*/
private propagateChange = (_: any) => { };
/**
* Property saves angular callback method that has to be called on touch event.
*/
private onTouched = () => {};
/**
* Propagates new value to angular and emits select event.
*/
private propagateAndEmitValue() {
if (this.options) {
const selectValue = parseInt(this.selectedOption || '0', 10);
this.propagateChange(selectValue);
this.select.emit(selectValue);
}
}
/**
* Check if selectedOption is a valid option, i.e. it is available in the options list.
* @returns {boolean} true, if selectedOption is available, otherwise false
*/
private isSelectedOptionAvailableInOptions() {
return this.options.some(option => '' + option.id === this.selectedOption);
}
/**
* Selects first select option.
*/
private selectFirstOption() {
this.selectedOption = '' + this.options[0].id;
}
}
MY-select.component.spec.ts:
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import {MySelectComponent} from './my-select.component';
@Component({
template: `<app-my-select
[options]="options"
[(ngModel)]="selectedOption"
(select)="select($event)"></app-my-select>`
})
class TestComponent {
options: any[];
selectedOption: string;
select() {}
}
describe('MySelectComponent', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
const idSelectOptionsStub = [
{ id: 0, value: '' },
{ id: 1, value: 'First Text' },
{ id: 2, value: 'Second Text' },
{ id: 3, value: 'Third Text' }
];
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [ TestComponent, MySelectComponent ],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
component.options = idSelectOptionsStub;
fixture.detectChanges();
});
it('should init with given id', () => {
component.selectedOption = '2';
const select = fixture.debugElement.query(By.css('select'));
fixture.whenStable().then(() => {
expect(select.nativeElement.selectedIndex).toEqual(2);
expect(select.nativeElement.value).toEqual('2');
expect(component.selectedOption).toEqual('2');
});
});
});
当运行唯一的单元测试&#39;&#39;应该使用给定的id&#39;我得到以下输出:
Chrome 63.0.3239 (Mac OS X 10.13.2) MySelectComponent should init with given id FAILED
Expected 0 to equal 2.
at http://localhost:9876/_karma_webpack_/webpack:/Users/pascalchorus/Development/select-test/src/app/my-select/my-select.component.spec.ts:53:50
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke Users/pascalchorus/Development/select-test/~/zone.js/dist/zone.js:388:1)
at ProxyZoneSpec.webpackJsonp.../../../../zone.js/dist/proxy.js.ProxyZoneSpec.onInvoke Users/pascalchorus/Development/select-test/~/zone.js/dist/proxy.js:79:1)
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke Users/pascalchorus/Development/select-test/~/zone.js/dist/zone.js:387:1)
Expected '0' to equal '2'.
at http://localhost:9876/_karma_webpack_/webpack:/Users/pascalchorus/Development/select-test/src/app/my-select/my-select.component.spec.ts:54:42
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke Users/pascalchorus/Development/select-test/~/zone.js/dist/zone.js:388:1)
at ProxyZoneSpec.webpackJsonp.../../../../zone.js/dist/proxy.js.ProxyZoneSpec.onInvoke Users/pascalchorus/Development/select-test/~/zone.js/dist/proxy.js:79:1)
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke Users/pascalchorus/Development/select-test/~/zone.js/dist/zone.js:387:1)
Expected 0 to equal '2'.
at http://localhost:9876/_karma_webpack_/webpack:/Users/pascalchorus/Development/select-test/src/app/my-select/my-select.component.spec.ts:55:40
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke Users/pascalchorus/Development/select-test/~/zone.js/dist/zone.js:388:1)
at ProxyZoneSpec.webpackJsonp.../../../../zone.js/dist/proxy.js.ProxyZoneSpec.onInvoke Users/pascalchorus/Development/select-test/~/zone.js/dist/proxy.js:79:1)
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke Users/pascalchorus/Development/select-test/~/zone.js/dist/zone.js:387:1)
我的期望是原生选择应该最初选择ID为2的项目,但事实并非如此。
你能解释一下这里有什么问题吗?
您可以在GitHub上找到示例项目: https://github.com/pchorus/select-test