Rebecca O'Riordan
Rebecca O'Riordan

Reputation: 883

How to unit test an Angular directive with an Input?

I have the below directive which is added to an element using the copy-to-clipboard attribute, and copies the content of the attribute to the users clipboard on click:

@Directive({
  selector: '[copy-to-clipboard]'
})
export class CopyToClipboardDirective {
  @Input('copy-to-clipboard') public payload: string;

  @HostListener('click', ['$event'])
  onClick(event: MouseEvent): void {
    event.preventDefault();
    if (!this.payload) {
      return;
    }

    const listener = (e: ClipboardEvent) => {
      const clipboard = e.clipboardData;
      clipboard.setData('text', this.payload.toString());
      e.preventDefault();
    };

    document.addEventListener('copy', listener, false);
    document.execCommand('copy');
    document.removeEventListener('copy', listener, false);
  }
}

And my unit test is setup as the following:

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {CopyToClipboardDirective} from './copy-to-clipboard.directive';
import {Component} from '@angular/core';

@Component({
  template: `<input [copy-to-clipboard]="'this is the passed string'" role="button" type="button">`
})
class MockCopyToClipboardComponent {}

fdescribe('Directive: CopyToClipboard', () => {
  let fixture: ComponentFixture<MockCopyToClipboardComponent>;
  let component: MockCopyToClipboardComponent;
  let element;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [CopyToClipboardDirective, MockCopyToClipboardComponent]
    });
    fixture = TestBed.createComponent(MockCopyToClipboardComponent);
    element = fixture.debugElement.nativeElement;
    component = fixture.componentInstance;
  });

  it('should run the copy command', () => {
    spyOn(document, 'execCommand');
    element.querySelector('input').click();
    fixture.detectChanges();
    expect(document.execCommand).toHaveBeenCalled();
  });
});

I'm getting an error back to say that the expected condition never occurs. I'm trying to set up the test to confirm that the document.execCommand has in fact been called, and not sure how I can confirm that the copied value matches that of the input string?

Upvotes: 0

Views: 2141

Answers (1)

uminder
uminder

Reputation: 26150

I run your test and found that the value of CopyToClipboardDirective#payload was never set. You can make this work by placing fixture.detectChanges() at the end of the beforeEach function.

beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [CopyToClipboardDirective, MockCopyToClipboardComponent]
    });
    fixture = TestBed.createComponent(MockCopyToClipboardComponent);
    element = fixture.debugElement.nativeElement;
    component = fixture.componentInstance;
    fixture.detectChanges(); // >>>>>> ADD this line 
});

it('should run the copy command', () => {
    spyOn(document, 'execCommand');
    element.querySelector('input').click();
    // fixture.detectChanges(); // >>>>>> REMOVE this line
    expect(document.execCommand).toHaveBeenCalledWith('copy');
});

To check, what text was copied to the clipboard, you can try to read it back using Clipboard#readText. Since readText returns a Promise, you need to deal with its asynchronous nature. The following example used the done function to do this.

it('should run the copy command', (done) => {
    spyOn(document, 'execCommand');
    element.querySelector('input').click();
    expect(document.execCommand).toHaveBeenCalledWith('copy');

    element.querySelector('input').focus();
    navigator.clipboard.readText()
    .then(t => {
      expect(t).toBe('this is the passed string');
      done();
    })
    .catch(err => {
      fail(err);
    });
});

Upvotes: 2

Related Questions