4

I have a directive which changes background color of an element during mouse over as below.

import {Directive, ElementRef, HostListener, Input} from '@angular/core';

@Directive({
  selector: '[appHighlightme]'
})
export class HighlightmeDirective {

  @Input('appHighlightme') color : string
  constructor(private el:ElementRef) { }

  @HostListener('mouseenter') onMouseEnter(){
    this.highlight(this.color || 'yellow');
  }

  @HostListener('mouseleave') onMouseLeave(){
    this.highlight('inherit');
  }

  highlight(color){
    this.el.nativeElement.style.backgroundColor = color;
  }
}

I was trying to write a unit test case for this directive as below

import { HighlightmeDirective } from './highlightme.directive';
import {ChangeDetectionStrategy, Component, DebugElement, ViewChild} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';

@Component({
  selector: 'my-test-component',
  template: '<a [appHighlightme]="color" >test</a>'
})
export class TestComponent {
  color:string = 'blue';
}

describe('HighlightmeDirective', () => {

  let component: TestComponent;
  let fixture: ComponentFixture<TestComponent>;
  let inputEl: DebugElement;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        TestComponent,
        HighlightmeDirective
      ]
    })

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    inputEl = fixture.debugElement.query(By.directive(HighlightmeDirective));

  });

  it('detect hover changes', () => {
    inputEl.triggerEventHandler('mouseenter', {});
    fixture.detectChanges();
    expect(inputEl.nativeElement.style.backgroundColor).toBe(component.color);
    inputEl.triggerEventHandler('mouseleave', {});
    fixture.detectChanges();
    expect(inputEl.nativeElement.style.backgroundColor).toBe('inherit');
  });

  it('should create an instance', () => {
    const directive = new HighlightmeDirective(inputEl);
    expect(directive).toBeTruthy();
  });
});

The directive is working fine in other components. it accepts a color argument variable defined in the ts file and use it as the bg color when hovering the element. But when I'm trying to unit test the same, the color argument passed form the TestComponent is not getting detected by the directive and the test case is failing with the following error message.

Error: Expected 'yellow' to be 'blue'. - As yellow is set as the default hover color

Dileep TP
  • 135
  • 4
  • 17
  • Just a guess... maybe it takes to change detection cycles so the correct color is in the component. I'm not sure if this makes any sense at all, but your testing code is apparently right. You can try 2 things: (1) make your test asynchronous (fakeAsync) and try to make the time pass; (2) duplicate the first `fixture.detectChanges()`. – julianobrasil Apr 22 '20 at 11:06
  • In the comment above there's a typo: "it takes two change detection cycles"... – julianobrasil Apr 22 '20 at 11:15
  • I have tries fakeAsync. But it is not working it('detect hover changes', fakeAsync(()=> { inputEl.triggerEventHandler('mouseenter', {}); fixture.detectChanges(); tick(2000); expect(inputEl.nativeElement.style.backgroundColor).toBe(component.color); inputEl.triggerEventHandler('mouseleave', {}); fixture.detectChanges(); expect(inputEl.nativeElement.style.backgroundColor).toBe('inherit'); })); – Dileep TP Apr 23 '20 at 09:59
  • duplicating fixture.detectChanges() is also not working – Dileep TP Apr 23 '20 at 10:01

2 Answers2

4

You do not need to track event handlers over the directive. You need to emit an event on the native element (anchor tag in your case) and the directive handlers will be called automatically. This is the actual way of testing a directive.

Let say we have an anchor tag with a class, so we need to create an event on this tag.

@Component({
  selector: 'my-test-component',
  template: '<a class="mytag" [appHighlightme]="color" >test</a>'
})
export class TestComponent {
  color:string = 'blue';
}

describe('HighlightmeDirective', () => {

  let component: TestComponent;
  let fixture: ComponentFixture<TestComponent>;
  let inputEl: DebugElement;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        TestComponent,
        HighlightmeDirective
      ]
    })

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();   // trigger change detection so that UI renders and you can access element in next step.
    inputEl = fixture.debugElement.query(By.css('.mytag'));

  });

  it('detect hover changes', () => {
    inputEl.triggerEventHandler('mouseenter', {});
    fixture.detectChanges();
    expect(inputEl.nativeElement.style.backgroundColor).toBe(component.color);
    inputEl.triggerEventHandler('mouseleave', {});
    fixture.detectChanges();
    expect(inputEl.nativeElement.style.backgroundColor).toBe('inherit');
  });

  it('should create an instance', () => {
    const directive = new HighlightmeDirective(inputEl);
    expect(directive).toBeTruthy();
  });
});

I hope it will help you.

Jasdeep Singh
  • 7,901
  • 1
  • 11
  • 28
2

Before triggering any events you need to make sure that @Input('appHighlightme') color properties is initialized. This initialization happens during first change detection cycle.

So you should trigger change detection and only then trigger mouseenter event. Another observation is that since you're manipulation style directly through this.el.nativeElement.style.backgroundColor you don't even need to use other fixture.detectChanges(); calls.

it('detect hover changes', () => {
  fixture.detectChanges(); // !!! initialize 'color' property

  inputEl.triggerEventHandler('mouseenter', {});  
  // fixture.detectChanges();  <-- useless here
  expect(inputEl.nativeElement.style.backgroundColor).toBe(component.color);

  inputEl.triggerEventHandler('mouseleave', {});
  // fixture.detectChanges();  <-- useless here
  expect(inputEl.nativeElement.style.backgroundColor).toBe('inherit');
});
yurzui
  • 205,937
  • 32
  • 433
  • 399