web-dev-qa-db-fra.com

Angular Erreur de test - NullInjectorError: aucun fournisseur pour TrimInputDirective

J'ai créé une directive Angular, qui utilise des sélecteurs CSS pour rogner automatiquement les entrées de mon application.

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

Maintenant, étant un bon développeur, je dois corriger quelques tests unitaires ... alors je commence à mettre un fichier ensemble, le voici

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

Maintenant, je souhaite exécuter mes tests pour m'assurer que la configuration du fichier de spécifications est correcte, mais le message d'erreur suivant s'affiche:

HeadlessChrome 0.0.0 (Mac OS X 10.12.6) Directive de finition «avant chaque» hook pour "devrait couper l'entrée" FAILED Uncaught (promis): Erreur: StaticInjectorError (DynamicTestModule) [TrimInputDirective]:
StaticInjectorError (Platform: core) [TrimInputDirective]: NullInjectorError: Pas de fournisseur pour TrimInputDirective! Erreur: StaticInjectorError (DynamicTestModule) [TrimInputDirective]: 

Je ne comprends pas pourquoi je reçois cette erreur, pourquoi dois-je fournir la directive? Je ne pense pas que cela soit nécessaire, même si je dois fournir ce que je fournis. Fournir la directive réelle ne fonctionne pas/ne résout pas l'erreur? Je suis très confus. Si quelqu'un pouvait me dire comment résoudre le problème ou pourquoi je l'obtiens, je vous en serais très reconnaissant.

Veuillez noter qu'il s'agit d'une application héritée Angular qui a été créée avant que le AngularCLI soit disponible. C'est donc un peu peu orthodoxe (par exemple, il n'utilise pas Jasmin).

6
Mark Sandman

1) Vous n'avez pas besoin de fournir votre directive, il vous suffit de la déclarer dans la variable TestingModule. Ensuite, il sera utilisé dans le modèle avec les sélecteurs correspondants.

2) Votre sélecteur ne correspond pas à celui utilisé sur votre entrée. Supprimez formControlName si vous souhaitez qu'il s'applique à toutes les entrées de certains types ou modifie votre test.

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

3) La directive se déclenche sur certains événements. Vous devez simuler ces événements pour voir un effet. Regardez cet exemple simplifié. ( 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();
  }

}

Et le test:

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

Vous devez fournir des directives dans votre TestComponent quelque chose comme ceci

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