Angular 2 — привязка модульного теста к вложенному пользовательскому элементу управления формой

У меня есть пользовательский элемент управления с селектором 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
}));

Есть ли лучший способ сделать это? Я бы хотел:

  1. Иметь возможность протестировать оба направления привязки из модульных тестов MyPage, чтобы я знал, что правильно подключил его к элементам управления формы.
  2. Не обязательно использовать ng-reflect-*

Другие примечания:

Компонент MyPage — это стандартный компонент с полем fromDatezipCode).

Пользовательское поле формы правильно реализует ControlValueAccessor, имеет поле date и использует внутреннее <input>, которое само связано через ngModel:

<input [(ngModel)]="date" type="date">

Компонент DatePicker

@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
  }
}

ОБНОВЛЕНИЕ 10 августа 2017 г.

На данный момент я приблизился к тому, чего хочу с @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');
}));

Кто-нибудь знает лучший способ? Это меня пока устраивает, хотя и не оптимально.


person Andrew M.    schedule 09.08.2017    source источник


Ответы (1)


Хорошо, наконец-то нашел ответ, которым я доволен, так как он избегает как ng-reflect-*, так и @ViewChild. Вместо того, чтобы звонить .nativeElement, я могу позвонить .componentInstance. Тогда тест становится простым:

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');
}));

Я все еще открыт, если у кого-то есть лучшее решение, но пока я отмечу это как ответ. Надеюсь, это поможет любому, кто столкнется с этим!

person Andrew M.    schedule 10.08.2017