3

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.

1 Answer 1

7

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Right, you should also add fixture.detectChanges(); before the triggerEventHandler which should trigger initial value binding, I think that should work.
yes, moving fixture.detectChanges() up, worked for me, Can you please modify your answer, so that I can approve it. Thanks a lot.
Done, happy I helped :)
Can you please tell, What can I write to be expected instead of expect(true).toBe(true);
I've updated my answer with suggested changes and test

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.