Help Angular by taking a 1 minute survey!Go to surveyHome

Probando Directivas de Atributo

Una directiva de atributo modifica el comportamiento de un elemento, componente u otra directiva. Su nombre refleja la forma en que se aplica la directiva: como un atributo en un elemento anfitrión.

Para la aplicación de muestra que describen las guías de prueba, visita la aplicación de muestraaplicación de muestra.

Para las funcionalidaddes de las pruebas en las guías de pruebas, visita las pruebaspruebas.

Probando la HighlightDirective

La directiva de muestra HighlightDirective fija el color de fondo de un elemento basado en un color de referencia o en un color predeterminado (lightgray). También establece una propiedad personalizada del elemento (customProperty) a true sin otro motivo más que demostrar que puede.

import { Directive, ElementRef, Input, OnChanges } from '@angular/core'; @Directive({ selector: '[highlight]' }) /** * Set backgroundColor for the attached element to highlight color * and set the element's customProperty to true */ export class HighlightDirective implements OnChanges { defaultColor = 'rgb(211, 211, 211)'; // lightgray @Input('highlight') bgColor: string; constructor(private el: ElementRef) { el.nativeElement.style.customProperty = true; } ngOnChanges() { this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor; } }
app/shared/highlight.directive.ts
      
      import { Directive, ElementRef, Input, OnChanges } from '@angular/core';

@Directive({ selector: '[highlight]' })
/**
 * Set backgroundColor for the attached element to highlight color
 * and set the element's customProperty to true
 */
export class HighlightDirective implements OnChanges {

  defaultColor =  'rgb(211, 211, 211)'; // lightgray

  @Input('highlight') bgColor: string;

  constructor(private el: ElementRef) {
    el.nativeElement.style.customProperty = true;
  }

  ngOnChanges() {
    this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor;
  }
}
    

Se usa a lo largo de la aplicación, quizás más sencillamente en el AboutComponent:

import { Component } from '@angular/core'; @Component({ template: ` <h2 highlight="skyblue">About</h2> <h3>Quote of the day:</h3> <twain-quote></twain-quote> ` }) export class AboutComponent { }
app/about/about.component.ts
      
      import { Component } from '@angular/core';
@Component({
  template: `
  <h2 highlight="skyblue">About</h2>
  <h3>Quote of the day:</h3>
  <twain-quote></twain-quote>
  `
})
export class AboutComponent { }
    

Probar el uso específico de la HighlightDirective dentro del AboutComponent sólo requiere las técnicas exploradas en la sección "Pruebas de componentes anidados" de Escenarios de pruebas de componentes.

beforeEach(() => { fixture = TestBed.configureTestingModule({ declarations: [ AboutComponent, HighlightDirective ], schemas: [ NO_ERRORS_SCHEMA ] }) .createComponent(AboutComponent); fixture.detectChanges(); // initial binding }); it('should have skyblue <h2>', () => { const h2: HTMLElement = fixture.nativeElement.querySelector('h2'); const bgColor = h2.style.backgroundColor; expect(bgColor).toBe('skyblue'); });
app/about/about.component.spec.ts
      
      beforeEach(() => {
  fixture = TestBed.configureTestingModule({
    declarations: [ AboutComponent, HighlightDirective ],
    schemas:      [ NO_ERRORS_SCHEMA ]
  })
  .createComponent(AboutComponent);
  fixture.detectChanges(); // initial binding
});

it('should have skyblue <h2>', () => {
  const h2: HTMLElement = fixture.nativeElement.querySelector('h2');
  const bgColor = h2.style.backgroundColor;
  expect(bgColor).toBe('skyblue');
});
    

Sin embargo, probar un solo caso de uso es poco probable que explore toda la variedad de las posibilidades de una directiva. Encontrar y probar todos los componentes que utilizan la directiva es tedioso, delicado y casi igual de improbable que permita una cobertura completa.

Las Pruebas de clase exclusivas pueden ser de ayuda, pero las directivas de atributo como ésta tienden a manipular el DOM. Las pruebas unitarias aisladas no tocan el DOM y, por lo tanto, no inspiran confianza en la eficacia de la directiva.

Una solución mejor es crear un componente de prueba artificial que demuestre todas las formas de aplicar la directiva.

@Component({ template: ` <h2 highlight="yellow">Something Yellow</h2> <h2 highlight>The Default (Gray)</h2> <h2>No Highlight</h2> <input #box [highlight]="box.value" value="cyan"/>` }) class TestComponent { }
app/shared/highlight.directive.spec.ts (TestComponent)
      
      @Component({
  template: `
  <h2 highlight="yellow">Something Yellow</h2>
  <h2 highlight>The Default (Gray)</h2>
  <h2>No Highlight</h2>
  <input #box [highlight]="box.value" value="cyan"/>`
})
class TestComponent { }
    

El caso de <input> vincula la HighlightDirective al nombre de un valor de color en el campo de entrada. El valor inicial es la palabra "cyan" que debería ser el color de fondo del cuadro de entrada.

Aquí hay algunas pruebas de este componente:

beforeEach(() => { fixture = TestBed.configureTestingModule({ declarations: [ HighlightDirective, TestComponent ] }) .createComponent(TestComponent); fixture.detectChanges(); // initial binding // all elements with an attached HighlightDirective des = fixture.debugElement.queryAll(By.directive(HighlightDirective)); // the h2 without the HighlightDirective bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])')); }); // color tests it('should have three highlighted elements', () => { expect(des.length).toBe(3); }); it('should color 1st <h2> background "yellow"', () => { const bgColor = des[0].nativeElement.style.backgroundColor; expect(bgColor).toBe('yellow'); }); it('should color 2nd <h2> background w/ default color', () => { const dir = des[1].injector.get(HighlightDirective) as HighlightDirective; const bgColor = des[1].nativeElement.style.backgroundColor; expect(bgColor).toBe(dir.defaultColor); }); it('should bind <input> background to value color', () => { // easier to work with nativeElement const input = des[2].nativeElement as HTMLInputElement; expect(input.style.backgroundColor).toBe('cyan', 'initial backgroundColor'); // dispatch a DOM event so that Angular responds to the input value change. input.value = 'green'; input.dispatchEvent(newEvent('input')); fixture.detectChanges(); expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor'); }); it('bare <h2> should not have a customProperty', () => { expect(bareH2.properties.customProperty).toBeUndefined(); });
app/shared/highlight.directive.spec.ts (selected tests)
      
      beforeEach(() => {
  fixture = TestBed.configureTestingModule({
    declarations: [ HighlightDirective, TestComponent ]
  })
  .createComponent(TestComponent);

  fixture.detectChanges(); // initial binding

  // all elements with an attached HighlightDirective
  des = fixture.debugElement.queryAll(By.directive(HighlightDirective));

  // the h2 without the HighlightDirective
  bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])'));
});

// color tests
it('should have three highlighted elements', () => {
  expect(des.length).toBe(3);
});

it('should color 1st <h2> background "yellow"', () => {
  const bgColor = des[0].nativeElement.style.backgroundColor;
  expect(bgColor).toBe('yellow');
});

it('should color 2nd <h2> background w/ default color', () => {
  const dir = des[1].injector.get(HighlightDirective) as HighlightDirective;
  const bgColor = des[1].nativeElement.style.backgroundColor;
  expect(bgColor).toBe(dir.defaultColor);
});

it('should bind <input> background to value color', () => {
  // easier to work with nativeElement
  const input = des[2].nativeElement as HTMLInputElement;
  expect(input.style.backgroundColor).toBe('cyan', 'initial backgroundColor');

  // dispatch a DOM event so that Angular responds to the input value change.
  input.value = 'green';
  input.dispatchEvent(newEvent('input'));
  fixture.detectChanges();

  expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor');
});


it('bare <h2> should not have a customProperty', () => {
  expect(bareH2.properties.customProperty).toBeUndefined();
});
    

Cabe destacar algunas técnicas:

  • El predicado de la By.directive es una buena forma de obtener los elementos que tienen esta directiva cuando sus tipos de elementos son desconocidos.

  • La :not pseudo-clase en By.css('h2:not([highlight])') ayuda a encontrar los elementos <h2> que no tienen la directiva. By.css('*:not([highlight])') encuentra cualquier elemento que no tiene la directiva.

  • DebugElement.styles permite acceder a los estilos de los elementos incluso en ausencia de un navegador real, gracias a la abstracción de DebugElement. Pero siéntete libre de explotar el nativeElement cuando te parezca más fácil o más claro que la abstracción.

  • Angular añade una directiva al inyector del elemento al que se aplica. La prueba para el color por defecto usa el inyector del segundo <h2> para obtener la instancia de su HighlightDirective y su defaultColor.

  • DebugElement.properties permite el acceso a la propiedad artificial personalizada que se establece en la directiva.