Uma attribute directive modifica o comportamento de um elemento, component ou outra directive. Seu nome reflete a maneira como a directive é aplicada: como um atributo em um elemento host.
Testes da HighlightDirective
A HighlightDirective da aplicação de exemplo define a cor de fundo de um elemento com base em uma cor vinculada por data ou uma cor padrão (lightgray).
Ela também define uma propriedade personalizada do elemento (customProperty) para true por nenhuma outra razão além de mostrar que pode.
app/shared/highlight.directive.ts
import {Directive, ElementRef, inject, 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 bgColor = input('', {alias: 'highlight'}); private el = inject(ElementRef); constructor() { this.el.nativeElement.style.customProperty = true; } ngOnChanges() { this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor; }}
Ela é usada em toda a aplicação, talvez mais simplesmente no AboutComponent:
app/about/about.component.ts
import {Component} from '@angular/core';import {HighlightDirective} from '../shared/highlight.directive';import {TwainComponent} from '../twain/twain.component';@Component({ template: ` <h2 highlight="skyblue">About</h2> <h3>Quote of the day:</h3> <twain-quote /> `, imports: [TwainComponent, HighlightDirective],})export class AboutComponent {}
Testar o uso específico da HighlightDirective dentro do AboutComponent requer apenas as técnicas exploradas na seção "Testes de components aninhados" de Cenários de testes de components.
app/about/about.component.spec.ts
import {provideHttpClient} from '@angular/common/http';import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';import {ComponentFixture, TestBed} from '@angular/core/testing';import {UserService} from '../model';import {TwainService} from '../twain/twain.service';import {AboutComponent} from './about.component';let fixture: ComponentFixture<AboutComponent>;describe('AboutComponent (highlightDirective)', () => { beforeEach(() => { fixture = TestBed.configureTestingModule({ imports: [AboutComponent], providers: [provideHttpClient(), TwainService, UserService], schemas: [CUSTOM_ELEMENTS_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'); });});
No entanto, testar um único caso de uso é improvável de explorar toda a gama de capacidades de uma directive. Encontrar e testar todos os components que usam a directive é tedioso, frágil e quase tão improvável de proporcionar cobertura total.
Testes somente de classe podem ser úteis, mas attribute directives como esta tendem a manipular o DOM. Testes de unidade isolados não tocam o DOM e, portanto, não inspiram confiança na eficácia da directive.
Uma solução melhor é criar um component de teste artificial que demonstre todas as maneiras de aplicar a directive.
app/shared/highlight.directive.spec.ts (TestComponent)
import {Component, DebugElement} from '@angular/core';import {ComponentFixture, TestBed} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {HighlightDirective} from './highlight.directive';@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" />`, imports: [HighlightDirective],})class TestComponent {}describe('HighlightDirective', () => { let fixture: ComponentFixture<TestComponent>; let des: DebugElement[]; // the three elements w/ the directive let bareH2: DebugElement; // the <h2> w/o the directive beforeEach(() => { fixture = TestBed.configureTestingModule({ imports: [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).withContext('initial backgroundColor').toBe('cyan'); input.value = 'green'; // Dispatch a DOM event so that Angular responds to the input value change. input.dispatchEvent(new Event('input')); fixture.detectChanges(); expect(input.style.backgroundColor).withContext('changed backgroundColor').toBe('green'); }); it('bare <h2> should not have a customProperty', () => { expect(bareH2.properties['customProperty']).toBeUndefined(); }); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // // customProperty tests // it('all highlighted elements should have a true customProperty', () => { // const allTrue = des.map(de => !!de.properties['customProperty']).every(v => v === true); // expect(allTrue).toBe(true); // }); // injected directive // attached HighlightDirective can be injected it('can inject `HighlightDirective` in 1st <h2>', () => { const dir = des[0].injector.get(HighlightDirective); expect(dir).toBeTruthy(); }); it('cannot inject `HighlightDirective` in 3rd <h2>', () => { const dir = bareH2.injector.get(HighlightDirective, null); expect(dir).toBe(null); }); // DebugElement.providerTokens // attached HighlightDirective should be listed in the providerTokens it('should have `HighlightDirective` in 1st <h2> providerTokens', () => { expect(des[0].providerTokens).toContain(HighlightDirective); }); it('should not have `HighlightDirective` in 3rd <h2> providerTokens', () => { expect(bareH2.providerTokens).not.toContain(HighlightDirective); });});
ÚTIL: O caso <input> vincula a HighlightDirective ao nome de um valor de cor na caixa de input.
O valor inicial é a palavra "cyan" que deve ser a cor de fundo da caixa de input.
Aqui estão alguns testes deste component:
app/shared/highlight.directive.spec.ts (selected tests)
import {Component, DebugElement} from '@angular/core';import {ComponentFixture, TestBed} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {HighlightDirective} from './highlight.directive';@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" />`, imports: [HighlightDirective],})class TestComponent {}describe('HighlightDirective', () => { let fixture: ComponentFixture<TestComponent>; let des: DebugElement[]; // the three elements w/ the directive let bareH2: DebugElement; // the <h2> w/o the directive beforeEach(() => { fixture = TestBed.configureTestingModule({ imports: [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).withContext('initial backgroundColor').toBe('cyan'); input.value = 'green'; // Dispatch a DOM event so that Angular responds to the input value change. input.dispatchEvent(new Event('input')); fixture.detectChanges(); expect(input.style.backgroundColor).withContext('changed backgroundColor').toBe('green'); }); it('bare <h2> should not have a customProperty', () => { expect(bareH2.properties['customProperty']).toBeUndefined(); }); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // // customProperty tests // it('all highlighted elements should have a true customProperty', () => { // const allTrue = des.map(de => !!de.properties['customProperty']).every(v => v === true); // expect(allTrue).toBe(true); // }); // injected directive // attached HighlightDirective can be injected it('can inject `HighlightDirective` in 1st <h2>', () => { const dir = des[0].injector.get(HighlightDirective); expect(dir).toBeTruthy(); }); it('cannot inject `HighlightDirective` in 3rd <h2>', () => { const dir = bareH2.injector.get(HighlightDirective, null); expect(dir).toBe(null); }); // DebugElement.providerTokens // attached HighlightDirective should be listed in the providerTokens it('should have `HighlightDirective` in 1st <h2> providerTokens', () => { expect(des[0].providerTokens).toContain(HighlightDirective); }); it('should not have `HighlightDirective` in 3rd <h2> providerTokens', () => { expect(bareH2.providerTokens).not.toContain(HighlightDirective); });});
Algumas técnicas são notáveis:
O predicate
By.directiveé uma ótima maneira de obter os elementos que têm esta directive quando seus tipos de elementos são desconhecidosA pseudo-classe
:notemBy.css('h2:not([highlight])')ajuda a encontrar elementos<h2>que não têm a directive.By.css('*:not([highlight])')encontra qualquer elemento que não tenha a directive.DebugElement.stylesproporciona acesso aos estilos do elemento mesmo na ausência de um browser real, graças à abstraçãoDebugElement. Mas sinta-se livre para explorar onativeElementquando isso parecer mais fácil ou mais claro do que a abstração.O Angular adiciona uma directive ao injector do elemento ao qual ela é aplicada. O teste para a cor padrão usa o injector do segundo
<h2>para obter sua instânciaHighlightDirectivee suadefaultColor.DebugElement.propertiesproporciona acesso à propriedade personalizada artificial que é definida pela directive