Nimish goel
Nimish goel

Reputation: 2761

Unit test angular 4 directive having @input property

I have a directive AllowOnlyNumbers which applied to input type textbox.

<input                         
                    [AllowOnlyNumbers]=true
                    [maxLength]= 'yearlyFieldMaxLength'
                    type="tel"
                    name="totalAnnualIncome"
                    formControlName="totalAnnualIncome"
                    [(ngModel)]="yearlyIncomeAmt"
                    (focus)="onFocusEnableToolTip('TotalAnnualIncome')" 
                    (focusout)="onFocusOutDisableToolTip('TotalAnnualIncome')" 
                     minlength="2"
                     autocomplete="off"/>

A very simple directive restricts users to put only numbers in the textbox.

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

@Directive({
  selector: '[AllowOnlyNumbers]'
})

/**
 * @method AllowOnlyNumbers
 * @desc This directive restricts the keyboard entry to numbers only.
 * Users can enter numbers and can use backspace, tab,enter, escape, end, home, left, right and del keys.
 * Usage: <input type = "text" [AllowOnlyNumbers] = true />
 */

export class AllowOnlyNumbers {

  constructor() { }

  @Input() AllowOnlyNumbers: boolean;
  /**
   * @methodspace AllowOnlyNumbers
   * @method onKeyDown
   * @desc It restricts the keyboard entry to numbers only.
   * @argument event
   * @returns returns only digit
   *
   */
  @HostListener('keydown', ['$event']) onKeyDown(event) {
    const e = event as KeyboardEvent;
    if (this.AllowOnlyNumbers) {
      // Allow: 8=Backspace, 9= Tab, 13=CR, 27=ESC, 35=END, 36=HOME, 37=Left, 39=Right, 46=DEL
      if ([8, 9, 13, 27, 35, 36, 37, 39, 46].indexOf(e.keyCode) !== -1) {
        return;
      }

      // Ensure that it is a number and stop the keypress
      if ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) {
        e.preventDefault();
      }
    }
  }
}

Now, when writing the Unit test case using jasmine, I am not able to set the @Input() AllowOnlyNumbers property as true. This is always undefined. Below is my UT file. (Note: Keydown event is getting fired)

import {ComponentFixture, TestBed} from '@angular/core/testing';
import { AllowOnlyNumbers } from './allow-only-numbers.directive';
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';

@Component({
  template: `<input [AllowOnlyNumbers]= true type="text" name="totalAnnualIncome"  />`
})
// tslint:disable-next-line:no-unnecessary-class
class TestAllowOnlyNumbersComponent {
 //  allowNumbers = true;
}
fdescribe('Directive: AllowOnlyNumbers', () => {
  let component: TestAllowOnlyNumbersComponent;
  let fixture: ComponentFixture<TestAllowOnlyNumbersComponent>;
  let inputEl: DebugElement;
  let linkDes;
  let routerLinks;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [TestAllowOnlyNumbersComponent, AllowOnlyNumbers],
      schemas:      [ NO_ERRORS_SCHEMA ]
    });
    fixture = TestBed.createComponent(TestAllowOnlyNumbersComponent);
    component = fixture.componentInstance;
    inputEl = fixture.debugElement.query(By.css('input[name="totalAnnualIncome"]'));
  });

  it('keydown input', () => {
    inputEl.triggerEventHandler('keydown', {});
    fixture.detectChanges();
    expect(true).toBe(true);
  });

});

I am using this link as reference. I am not able to set the @Input() AllowOnlyNumbers property as true. This is always undefined.

Upvotes: 3

Views: 5312

Answers (1)

Mateusz
Mateusz

Reputation: 232

Answer to your issue:

It should be [AllowOnlyNumbers]="true" not [AllowOnlyNumbers]= true in TestAllowOnlyNumbersComponent.

What you're actually doing is [AllowOnlyNumbers]= which does not assign any value to the input of directive.

Also you should move fixture.detectChanges() before the triggerEventHandler to trigger initial value binding or even better would be to add it at the end of the beforeEach

  beforeEach(() => {
    ...
    fixture.detectChanges();
  });

  it('keydown input', () => {
    inputEl.triggerEventHandler('keydown', {});
    expect(true).toBe(true);
  });

Also additional comment about your directive:

I think you should replace keyCode with key in your directive, as it looks like keyCode is deprecated https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent. I think change should be easy, in your directive you just read that key string and a code based on that value const code = e.key.charCodeAt()

Then I'd create following test, to test one of the keys in this case 'F' key:

  it('keydown input', () => {
    const event = new KeyboardEvent('keydown', { key: 'F' });
    inputEl.nativeElement.dispatchEvent(event);
    expect(event.defaultPrevented).toBe(true);
  });

I guess that might work when making that change in the directive.

Upvotes: 7

Related Questions