1

I have a directive to copy to clip board. When i use it normally it works however when i want to do some tests on it, the @Input property is always undefined

Here is the directive CopyClipboard.directive.ts

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


/**
 * Directive use to copy the content of the event to the clipBoard
 * Source :
 * https://stackoverflow.com/a/52949299/5703228
 *
 * @export
 * @class CopyClipboardDirective
 */
@Directive({
  selector: '[appCopyClipboard]'
})
export class CopyClipboardDirective {

  constructor() {}

  @Input('appCopyClipboard')
  public payload: string;

  @Output() public copied: EventEmitter<string> = new EventEmitter<string>();

  @HostListener('click', ['$event'])
  public onClick(event: MouseEvent): void {

    event.preventDefault();
    if (this.payload == null) {
      return;
    }

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

      this.copied.emit(this.payload);
    };

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

And here is the corresponding test Copyclipboard.directive.spec.ts

/* tslint:disable:no-unused-variable */

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { CopyClipboardDirective } from './CopyClipboard.directive';
import { Component, DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TestUtils } from 'src/app/tests/test-utils';

@Component({
  template: '<button [appCopyClipboard]="getTextToCopy()" (copied)="onCopied($event)">Test button</button>'
})
class TestCopyClipboardDirectiveComponent {

  public textToCopy = 'Test Text';
  public textCopied = '';

  public onCopied(textCopied: string): void {
    this.textCopied = textCopied;
  }

  public getTextToCopy(): string {
    return this.textToCopy;
  }
}

describe('Directive: CopyClipboardDirective', () => {

  let component: TestCopyClipboardDirectiveComponent;
  let fixture: ComponentFixture<TestCopyClipboardDirectiveComponent>;
  let buttonDe: DebugElement;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        CopyClipboardDirective,
        TestCopyClipboardDirectiveComponent
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(TestCopyClipboardDirectiveComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

    buttonDe = fixture.debugElement.query(By.css('button'));
  });

  it('should be created', () => {
    expect(component).toBeTruthy();
    expect(buttonDe).toBeTruthy();
  });

  it('should call callback after copy on click', () => {

    spyOn(component, 'onCopied').and.callThrough();
    const buttonNe: HTMLElement = buttonDe.nativeElement;
    buttonNe.click();
    fixture.detectChanges();

    fixture.whenStable().then(() => {
      expect(component.onCopied).toHaveBeenCalledTimes(1);
      expect(component.onCopied).toHaveBeenCalledWith(component.textToCopy);
      expect(component.textCopied).toBe(component.textToCopy);
    });
  });

  // We cannot access content of ClipBoard as it is a security violation
  // So we cannot test if the data is actually in the clipBoard
  // But we can try to listen for event copy
  it('should emit copy event on document', (done: DoneFn) => {

    spyOn(component, 'onCopied').and.callThrough();
    let success = false;
    const listener = (e: ClipboardEvent) => {

      success = true;
      done();
      document.removeEventListener('copy', listener, false);
    };

    document.addEventListener('copy', listener, false);
    const buttonNe: HTMLElement = buttonDe.nativeElement;
    buttonNe.click();
    fixture.detectChanges();
    /*fixture.whenStable().then(() => {
      expect(success).toBe(true);
    });*/
  });
});

both last tests fail. When i debug the execution i see that payload in the directive is undefined. Why is the directive @Input undefined in test ?

I use it elswhere in a component and it works fine :

<button mat-raised-button color="secondary"
  [appCopyClipboard]="getStringFormattedQuotation()"
  (copied)="notifyCopied($event)">
    <mat-icon>file_copy</mat-icon> Copier
</button>

** Edit ** I tried as suggested to change the test Components template to this :

...[appCopyClipboard]=" \'someText string here\' "...

With that i get the @Input set to this value, however in the directive code, the listener set on document is still never called. I tried to remove the line

document.removeEventListener('copy', listener, false);

to test but it didn't work either. Another thing i noticed is in the console of the browser i get 2 lines of this :

zone.js:3401 Access to XMLHttpRequest at 'ng:///DynamicTestModule/TestCopyClipboardDirectiveComponent.ngfactory.js' from origin 'http://localhost:9876' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

I don't get this error for any of my other tests.

LuftWaffle
  • 187
  • 1
  • 3
  • 19

0 Answers0