Ошибка углового теста - NullInjectorError: Нет поставщика для TrimInputDirective - PullRequest
0 голосов
/ 01 июня 2018

Я создал угловую директиву, которая использует CSS-селекторы для автоматической обрезки входных данных в моем приложении, это выглядит так ...

import { Directive, HostListener, forwardRef } from '@angular/core';
import { DefaultValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

export const TRIM_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => TrimInputDirective),
  multi: true
};

/**
 * The trim accessor for writing trimmed value and listening to changes that is
 * used by the {@link NgModel}, {@link FormControlDirective}, and
 * {@link FormControlName} directives.
 */
/* tslint:disable */
@Directive({
  selector: `
    input
    :not([type=checkbox])
    :not([type=radio])
    :not([type=password])
    :not([readonly])
    :not(.ng-trim-ignore)
    [formControlName],

    input
    :not([type=checkbox])
    :not([type=radio])
    :not([type=password])
    :not([readonly])
    :not(.ng-trim-ignore)
    [formControl],

    input
    :not([type=checkbox])
    :not([type=radio])
    :not([type=password])
    :not([readonly])
    :not(.ng-trim-ignore)
    [ngModel],

    textarea
    :not([readonly])
    :not(.ng-trim-ignore)
    [formControlName],

    textarea
    :not([readonly])
    :not(.ng-trim-ignore)
    [formControl],

    textarea
    :not([readonly])
    :not(.ng-trim-ignore)[ngModel],
    :not([readonly])
    :not(.ng-trim-ignore)
    [ngDefaultControl]'
  `,
  providers: [ TRIM_VALUE_ACCESSOR ]
})
/* tslint:enable */
export class TrimInputDirective extends DefaultValueAccessor {

  protected _onTouched: any;

  /**
   * ngOnChange - Lifecycle hook that is called when any data-bound property of a directive changes.
   * @param {string} val - trim value onChange.
   */
  @HostListener('input', ['$event.target.value'])
  public ngOnChange = (val: string) => {
    this.onChange(val.trim());
  }

  /**
   * applyTrim - trims the passed value
   * @param {string} val - passed value.
   */
  @HostListener('blur', ['$event.target.value'])
  public applyTrim(val: string) {
    this.writeValue(val.trim());
    this._onTouched();
  }

  /**
   * writeValue - trims the passed value
   * @param {any} value - passed  value.
   */
  public writeValue(value: any): void {
    if (typeof value === 'string') {
      value = value.trim();
    }

    super.writeValue(value);
  }

  /**
   * registerOnTouched Registers a callback function that should be called when the control receives a blur event.
   * @param {function} fn - The user information.
   */
  public registerOnTouched(fn: any): void {
    this._onTouched = fn;
  }
}

Теперь, будучи хорошим разработчиком, я должен исправить некоторые модульные тесты ..поэтому я начинаю собирать файл, вот он

import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {TrimInputDirective} from './trim-input.directive';

import {expect} from 'chai';

@Component({
  selector: 'my-directive-test-component',
  template: ''
})
class TestComponent {
}

describe('Trim Directive', () => {
  let fixture: ComponentFixture<TestComponent>;
  let inputDebugElement: any;
  let directive: TrimInputDirective;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        TestComponent,
        TrimInputDirective
      ],
      providers: []
    }).overrideComponent(TestComponent, {
      set: {
        template: '<input type="text">'
      }
    }).compileComponents().then(() => {
      fixture = TestBed.createComponent(TestComponent);
      fixture.detectChanges();
      inputDebugElement = fixture.debugElement.query(By.css('input'));
      directive = inputDebugElement.injector.get(TrimInputDirective);
    });
  }));

  it('should trim the input', () => {
    directive.ngOnChange('     1234.56     ')
    expect('1234.56').to.be('1234.56'); // I know this isn't the correct test... I will amend this
  });
});

Теперь я хочу запустить свои тесты, просто чтобы убедиться, что настройка в файле спецификации правильна, но я получаю следующую ошибку:

HeadlessChrome 0.0.0 (Mac OS X 10.12.6) Директива обрезки "перед каждым" ловушкой для "должна обрезать вход" СБОЙ НЕ УДАЛЕНО (в обещании): Ошибка: StaticInjectorError (DynamicTestModule) [TrimInputDirective]:
StaticInjectorError (Платформа: ядро) [TrimInputDirective]: NullInjectorError: Нет поставщика для TrimInputDirective!Ошибка: StaticInjectorError (DynamicTestModule) [TrimInputDirective]:

Я не понимаю, почему я получаю эту ошибку, почему я должен предоставить директиву?Я не думаю, что это необходимо, даже если я должен предоставить то, что я предоставляю?Предоставление фактической директивы не работает / не устраняет ошибку?Я очень смущен.Если кто-нибудь может сказать мне, как решить проблему или почему я ее получил, я был бы очень признателен.

Обратите внимание, что это устаревшее приложение Angular, которое было создано до того, как AngularCLI был доступен.Так что это немного неортодоксально (например, он не использует Jasmin).

Ответы [ 2 ]

0 голосов
/ 04 июня 2018

1) Вам не нужно указывать свою директиву, вам просто нужно объявить ее в TestingModule.Затем он будет использоваться в шаблоне с соответствующими селекторами.

2) Ваш селектор не соответствует выбранному на вашем входе.Удалите formControlName, если хотите, чтобы он применялся ко всем входам определенных типов или измените ваш тест.

input
    :not([type=checkbox])
    :not([type=radio])
    :not([type=password])
    :not([readonly])
    :not(.ng-trim-ignore)
    [formControlName],
    ^^^^^^^^^^^^^^^^^^ 

3) Директива срабатывает при определенных событиях.Вам нужно смоделировать эти события, чтобы увидеть эффект.Посмотрите на этот упрощенный пример.( Stackblitz )

@Directive({
  selector: `
    input
    :not([type=checkbox])
    :not([type=radio])
    :not([type=password])
    :not([readonly])
    :not(.ng-trim-ignore)
  `
})
export class TrimInputDirective {
  constructor(private el: ElementRef) { }

  @HostListener('blur') onLeave() {
   if (this.el.nativeElement.value)
    this.el.nativeElement.value = this.el.nativeElement.value.trim();
  }

}

И тест:

describe('Trim Directive', () => {
  let fixture: ComponentFixture<TestComponent>;
  let inputDebugElement: any;
  let directive: TrimInputDirective;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        TestComponent,
        TrimInputDirective
      ],
      imports: [FormsModule],
      providers: []
    }).overrideComponent(TestComponent, {
      set: {
        template: '<input type="text">'
      }
    }).compileComponents().then(() => {
      fixture = TestBed.createComponent(TestComponent);
      inputDebugElement = fixture.debugElement.query(By.css('input')).nativeElement;
                                                                      ^^^^^^^^^^^^
    }); 
  }));

  it('should trim the input', () => {
    inputDebugElement.value = '     1234.56     ';
    inputDebugElement.dispatchEvent(new Event('blur'));
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    fixture.detectChanges();
    expect(inputDebugElement.value).toBe('1234.56'); 
  });
});
0 голосов
/ 04 июня 2018

Вы должны предоставить директиву в вашем TestComponent что-то вроде этого

@Component({
  selector: 'my-directive-test-component',
  template: '',
  providers: [ TrimInputDirective ]
})
class TestComponent {
}
...