Модульный тест Angular Directive с использованием ngModelChange
У меня есть директива PhoneMask, которую я применяю ко входу, и она удаляет нечисловые символы и форматирует число в соответствии с маской, которую я настроил (например, ввод 1234567890
будет отформатирован как (123) 456-7890
). Сама маска работает, но я хотел бы написать для нее модульный тест, и у меня возникли проблемы с выяснением, как это сделать.
Директива PhoneMask:
import {Directive, ElementRef, EventEmitter, Output} from '@angular/core';
import {NgControl} from '@angular/forms';
@Directive({
selector: '[PhoneMask]',
host: {
'(ngModelChange)': 'onInputChange($event)'
}
})
export class PhoneMaskDirective {
@Output() rawChange: EventEmitter<string> = new EventEmitter<string>();
constructor(public model: NgControl, private el: ElementRef) {
}
onInputChange(inputValue) {
const selectionStart = this.el.nativeElement.selectionStart;
const [rawNumber, cursor] = this.getRawNumberAndCursor(inputValue, selectionStart);
const [formattedNumber, formattedCursor] = this.getFormattedNumberAndCursor(rawNumber, cursor);
// Set input to formatted value
this.model.valueAccessor.writeValue(formattedNumber);
// Set cursor in the right spot
this.el.nativeElement.selectionStart = this.el.nativeElement.selectionEnd = formattedCursor;
this.rawChange.emit(rawNumber);
}
getRawNumberAndCursor(text: string, cursorPos: number): [string, number] {
let rawNumber = '';
let rawPos = cursorPos;
for (let i = 0, len = text.length; i < len; i++) {
if (/\d/.test(text[i])) {
rawNumber += text[i];
} else if (i < cursorPos) {
rawPos--;
}
}
return [rawNumber, rawPos];
}
getFormattedNumberAndCursor(numberVal: string, cursorPos: number): [string, number] {
let formattedNumber = '';
if (numberVal.length == 0) {
formattedNumber = '';
} else if (numberVal.length <= 3) {
formattedNumber = numberVal.replace(/^(\d{0,3})/, '($1)');
} else if (numberVal.length <= 6) {
formattedNumber = numberVal.replace(/^(\d{0,3})(\d{0,3})/, '($1) $2');
} else if (numberVal.length <= 10) {
formattedNumber = numberVal.replace(/^(\d{0,3})(\d{0,3})(.*)/, '($1) $2-$3');
} else {
formattedNumber = numberVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,4})(.*)/, '($1) $2-$3 $4');
}
for (let i = 0; i < cursorPos; i++) {
if (/\D/.test(formattedNumber[i])) {
cursorPos++;
}
}
return [formattedNumber, cursorPos];
}
}
Тест PhoneMask / спецификации:
import {PhoneMaskDirective} from './phone-mask.directive';
import {Component} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';
@Component({
template: `
<input PhoneMask [(ngModel)]="phone"/>
`
})
class TestPhoneMaskDirectiveComponent {
}
describe('PhoneMaskDirective', () => {
let fixture;
let theInput;
beforeEach(async () => {
fixture = TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [PhoneMaskDirective, TestPhoneMaskDirectiveComponent],
})
.createComponent(TestPhoneMaskDirectiveComponent);
fixture.detectChanges();
theInput = fixture.debugElement.query(By.directive(PhoneMaskDirective));
});
it('should find one instance of the directive', () => {
expect(theInput).toBeTruthy();
});
it('should handle input of non-numeric characters', async () => {
fixture.detectChanges();
let element = theInput.nativeElement;
element.value = 'bob123';
element.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.componentInstance.phone).toEqual('(123)');
});
});
});
В тесте я ожидаю ввода bob123
привести к значению (123)
, Однако, когда я запускаю тест, я получаю неудавшийся результат Expected 'bob123' to equal '(123)'.
,
Я пробовал различные комбинации async
, fakeAsync
, вместе с detectChanges()
а также tick()
звонки, и даже завершение окончательного ожидания с setTimeout()
, Как получить доступ к значению модели после того, как значение valueAccessor для ввода было обновлено?